source: sage/server/notebook/twist.py @ 4975:ac16237f99af

Revision 4975:ac16237f99af, 23.8 KB checked in by Bobby Moretti <moretti@…>, 6 years ago (diff)

Removed an unnecessary line from twist.py

Line 
1"""
2SAGE Notebook (Twisted Version)
3"""
4import os
5
6from twisted.web2 import server, http, resource, channel
7from twisted.web2 import static, http_headers, responsecode
8   
9import css, js, keyboards
10
11from sage.misc.misc import SAGE_EXTCODE, DOT_SAGE, walltime
12
13p = os.path.join
14css_path        = p(SAGE_EXTCODE, "notebook/css")
15image_path      = p(SAGE_EXTCODE, "notebook/images")
16javascript_path = p(SAGE_EXTCODE, "notebook/javascript")
17conf_path       = p(DOT_SAGE, 'notebook')
18
19_cols = None
20def word_wrap_cols():
21    global _cols
22    if _cols is None:
23        _cols = notebook.defaults()['word_wrap_cols']
24    return _cols
25
26############################
27# Encoding data to go between the server and client
28############################
29SEP = '___S_A_G_E___'
30
31def encode_list(v):
32    return SEP.join([str(x) for x in v])
33
34   
35
36############################
37# Notebook autosave.
38############################
39# save if make a change to notebook and at least some seconds have elapsed since last save.
40save_interval = 10 
41last_time = walltime()
42
43def notebook_save_check():
44    global last_time
45    t = walltime()
46    if t > last_time + save_interval:
47        notebook.save()
48        last_time = t
49
50
51######################################################################################
52# RESOURCES
53######################################################################################
54
55############################
56# Create a SAGE worksheet from a latex2html'd file
57############################
58from docHTMLProcessor import DocHTMLProcessor
59
60def doc_worksheet():
61    wnames = notebook.worksheet_names()
62    name = 'doc_browser_0'
63    if name in wnames:
64        W = notebook.get_worksheet_with_name(name)
65        W.restart_sage()
66        W.clear()
67        return W
68    else:
69        return notebook.create_new_worksheet(name)
70   
71
72class WorksheetFile(resource.Resource):
73    addSlash = False
74   
75    def __init__(self, path):
76        self.docpath = path
77       
78    def render(self, ctx=None):
79        # Create a live SAGE worksheet out of self.path and render it.
80        doc_page_html = open(self.docpath).read()
81        directory = os.path.split(self.docpath)[0]
82        doc_page, css_href = DocHTMLProcessor().process_doc_html(DOC,
83                               directory, doc_page_html)
84        if css_href:
85            css_href = DOC + directory + css_href
86        W = doc_worksheet()
87        W.edit_save(doc_page)
88        s = notebook.html(worksheet_id = W.name())
89        return http.Response(stream=s)
90       
91    def childFactory(self, request, name):
92        path = self.docpath + '/' + name
93        if name.endswith('.html'):
94            return WorksheetFile(path)
95        else:
96            return static.File(path)
97
98############################
99# The documentation browsers
100############################
101
102DOC = os.path.abspath(os.environ['SAGE_ROOT'] + '/doc/')
103class DocStatic(resource.Resource):
104    addSlash = True   
105    def render(self, ctx):
106        return static.File('%s/index.html'%DOC)
107   
108    def childFactory(self, request, name):
109        return static.File('%s/%s'%(DOC,name))
110
111class DocLive(resource.Resource):
112    addSlash = True
113   
114    def render(self, ctx):
115        return static.File('%s/index.html'%DOC)
116   
117    def childFactory(self, request, name):
118        return WorksheetFile('%s/%s'%(DOC,name))
119
120class Doc(resource.Resource):
121    addSlash = True
122    child_static = DocStatic()
123    child_live = DocLive()
124   
125    def render(self, ctx):
126        s = """
127        <h1><font color="darkred">SAGE Documentation</font></h1>
128        <br><br><br>
129        <font size=+3>
130        <a href="static">Static Documentation</a><br><br>
131        <a href="live">Interactive Live Documentation</a><br>
132        </font>
133        """
134        return http.Response(stream=s)
135   
136
137############################
138# A resource attached to a given worksheet.
139#
140# This has the name of the worksheet and the
141# worksheet object itself set as attributes.
142# It's much better to do it once-for-all here
143# instead of doing it in the derived classes
144# over and over again.
145############################
146class WorksheetResource:
147    def __init__(self, name):
148        self.name = name
149        self.worksheet = notebook.get_worksheet_with_id(name)
150
151    def id(self, ctx):
152        return int(ctx.args['id'][0])
153
154###############################################
155# Worksheet data -- a file that
156# is associated with a cell in some worksheet.
157# The file is stored on the filesystem.
158#      /ws/worksheet_name/data/cell_number/filename
159##############################################
160class CellData(resource.Resource):
161    def __init__(self, worksheet, number):
162        self.worksheet = worksheet
163        self.number = number
164       
165    def childFactory(self, request, name):
166        dir = self.worksheet.directory()
167        path = '%s/cells/%s/%s'%(dir, self.number, name)
168        return static.File(path)
169   
170class Worksheet_data(WorksheetResource, resource.Resource):
171    def childFactory(self, request, number):
172        return CellData(self.worksheet, number)
173
174########################################################
175# Use this to wrap a worksheet operation in a confirmation
176# request.  See WorksheetDelete and WorksheetAdd for
177# examples.
178########################################################
179class FastRedirect(resource.Resource):
180    def __init__(self, dest):
181        self.dest = dest
182    def render(self, ctx):
183        s = '<html><head><meta http-equiv="REFRESH" content="0; URL=%s"></head></html>'%self.dest
184        return http.Response(stream = s)
185
186class FastRedirectWithEffect(FastRedirect):
187    def __init__(self, dest, effect):
188        self.dest = dest
189        if not effect is None:
190            effect()
191
192class YesNo(resource.Resource):
193    addSlash = True
194   
195    def __init__(self, mesg, yes_path, no_path, yes_effect=None, no_effect=None):
196        self.mesg = mesg
197        self.yes_path = yes_path
198        self.no_path  = no_path
199        self.yes_effect = yes_effect
200        self.no_effect = no_effect
201
202    def render(self, ctx):
203        s = '<html><body>%s<br>'%self.mesg
204        s += '<a href="yes">Yes</a> or <a href="no">No</a></body></html>'
205        return http.Response(stream = s)
206
207    def childFactory(self, request, op):
208        if op == 'yes':
209            return FastRedirectWithEffect(self.yes_path, self.yes_effect)
210        elif op == 'no':
211            return FastRedirectWithEffect(self.no_path, self.no_effect)
212
213
214########################################################
215# Completely delete the worksheet from the notebook
216# server.  It is assumed that the javascript has already
217# done all relevant confirmation, and of course in the
218# future we'll check that the users is authenticated to
219# touch the worksheet.  Delete should also, in the
220# future, really just put the result in a trash can.
221########################################################
222
223def Worksheet_delete(name):
224    def do_delete():
225        notebook.delete_worksheet(name)
226    return YesNo('Do you want to delete the worksheet "%s"?'%name,
227                 '/', '..', yes_effect=do_delete)
228
229########################################################
230# Create a new worksheet.
231########################################################
232
233def Worksheet_create(name):
234    def do_create():
235        notebook.create_new_worksheet(name)
236    return YesNo('Do you want to create the worksheet "%s"?'%name,
237                 '.', '/', yes_effect=do_create)
238
239#Toplevel(), Worksheet(name))
240## class WorksheetCreate(WorksheetResource, resource.Resource):
241##     def render(self, ctx):
242##         notebook.create_new_worksheet(name)
243##         s = "The worksheet '%s' has been created.  <a href='..'>Continue</a>"%self.name
244##         return http.Response(stream = s)
245
246## Worksheet_create = worksheet_confirm(WorksheetCreate, worksheet_create_msg)
247
248########################################################
249# Cell introspection
250########################################################
251class Worksheet_introspect(WorksheetResource, resource.PostableResource):
252    """
253    Cell introspection.  This is called when the user presses the tab
254    key in the browser in order to introspect.
255    """
256    def render(self, ctx):
257        try:
258            id = int(ctx.args['id'][0])
259        except (KeyError,TypeError):
260            return http.Response(stream = 'Error in introspection -- invalid cell id.')
261        try:
262            before_cursor = ctx.args['before_cursor'][0]
263        except KeyError:
264            before_cursor = ''
265        try:
266            after_cursor = ctx.args['after_cursor'][0]
267        except KeyError:
268            after_cursor = ''
269        C = self.worksheet.get_cell_with_id(id)
270        C.evaluate(introspect=[before_cursor, after_cursor])
271        return http.Response(stream = encode_list([C.next_id(),'no_new_cell',id]))
272
273########################################################
274# Edit the entire worksheet
275########################################################
276class Worksheet_edit(WorksheetResource, resource.Resource):
277    """
278    Return a window that allows the user to edit the text of the
279    worksheet with the given filename.
280    """
281    def render(self, ctx):
282        return http.Response(stream = notebook.edit_window(self.worksheet))
283
284
285########################################################
286# Save a worksheet
287########################################################
288class Worksheet_save(WorksheetResource, resource.PostableResource):
289    """
290    Save the contents of a worksheet after editing it in plain-text edit mode.
291    """
292    def render(self, ctx):
293        if ctx.args.has_key('button_save'):
294            self.worksheet.edit_save(ctx.args['textfield'][0])
295        s = notebook.html(worksheet_id = self.name)           
296        return http.Response(stream=s)
297
298
299########################################################
300# Set output type of a cell
301########################################################
302class Worksheet_set_cell_output_type(WorksheetResource, resource.PostableResource):
303    """
304    Set the output type of the cell.
305
306    This enables the type of output cell, such as to allowing wrapping
307    for output that is very long.
308    """
309    def render(self, ctx):
310        id = self.id(ctx)       
311        typ = ctx.args['type'][0]
312        W = self.worksheet
313        W.get_cell_with_id(id).set_cell_output_type(typ)
314        return http.Response(stream = '')
315
316########################################################
317# The new cell command: /ws/worksheet/new_cell?id=number
318########################################################
319class Worksheet_new_cell(WorksheetResource, resource.PostableResource):
320    """
321    Adds a new cell before a given cell.
322    """
323    def render(self, ctx):
324        id = self.id(ctx)       
325        cell = self.worksheet.new_cell_before(id)
326        s = encode_list([cell.id(), cell.html(div_wrap=False), id])
327        return http.Response(stream = s)
328   
329
330########################################################
331# The delete cell command: /ws/worksheet/delete_cell?id=number
332########################################################
333class Worksheet_delete_cell(WorksheetResource, resource.PostableResource):
334    """
335    Deletes a notebook cell.
336
337    If there is only one cell left in a given worksheet, the request
338    to delete that cell is ignored because there must be a least one
339    cell at all times in a worksheet.  (This requirement exists so
340    other functions that evaluate relative to existing cells will
341    still work, and so one can add new cells.)
342    """
343    def render(self, ctx):
344        id = self.id(ctx)
345        W = self.worksheet
346        if len(W) <= 1:
347            s = 'ignore'
348        else:
349            prev_id = W.delete_cell_with_id(id)
350            s = encode_list(['delete', id, prev_id, W.cell_id_list()])
351        return http.Response(stream = s)
352   
353
354############################
355# Get the latest update on output appearing
356# in a given output cell.
357############################
358class Worksheet_cell_update(WorksheetResource, resource.PostableResource):
359    def render(self, ctx):
360        id = self.id(ctx)
361
362        worksheet = self.worksheet
363
364        # update the computation one "step".
365        worksheet.check_comp()
366       
367        # now get latest status on our cell
368        status, cell = worksheet.check_cell(id)
369       
370        if status == 'd':
371            new_input = cell.changed_input_text()
372            out_html = cell.output_html()
373        else:
374            new_input = ''
375            out_html = ''
376           
377        if cell.interrupted():
378            inter = 'true'
379        else:
380            inter = 'false'
381       
382        raw = cell.output_text(raw=True).split("\n")
383        if "Unhandled SIGSEGV" in raw:
384            inter = 'restart'
385            print "Segmentation fault detected in output!"
386
387        msg = '%s%s %s'%(status, cell.id(),
388                       encode_list([cell.output_text(html=True),
389                                    cell.output_text(word_wrap_cols(), html=True),
390                                    out_html,
391                                    new_input,
392                                    inter,
393                                    cell.introspect_html()]))
394
395        # There may be more computations left to do, so start one if there is one.
396        worksheet.start_next_comp()
397       
398        return http.Response(stream=msg)
399   
400
401class Worksheet_eval(WorksheetResource, resource.PostableResource):
402    """
403    Evaluate a worksheet cell.
404
405    If the request is not authorized, (the requester did not enter the
406    correct password for the given worksheet), then the request to
407    evaluate or introspect the cell is ignored.
408
409    If the cell contains either 1 or 2 question marks at the end (not
410    on a comment line), then this is interpreted as a request for
411    either introspection to the documentation of the function, or the
412    documentation of the function and the source code of the function
413    respectively.
414    """
415    def render(self, ctx):
416        newcell = int(ctx.args['newcell'][0])  # whether to insert a new cell or not
417        id = self.id(ctx)
418        if not ctx.args.has_key('input'):
419            input_text = ''
420        else:
421            input_text = ctx.args['input'][0]
422            input_text = input_text.replace('\r\n', '\n')   # DOS
423
424        W = self.worksheet
425        cell = W.get_cell_with_id(id)
426        cell.set_input_text(input_text)
427        cell.evaluate()
428
429        if cell.is_last():
430            new_cell = W.append_new_cell()
431            s = encode_list([new_cell.id(), 'append_new_cell', new_cell.html(div_wrap=False)])
432        elif newcell:
433            new_cell = W.new_cell_after(id)
434            s = encode_list([new_cell.id(), 'insert_cell', new_cell.html(div_wrap=False), str(id)])
435        else:
436            s = encode_list([cell.next_id(), 'no_new_cell', str(id)])
437
438        notebook_save_check()
439        return http.Response(stream=s)
440
441
442class Worksheet_restart_sage(WorksheetResource, resource.Resource):
443    def render(self, ctx):
444        # TODO -- this must not block long (!)
445        self.worksheet.restart_sage()
446        return http.Response(stream='done')
447
448class Worksheet_interrupt(WorksheetResource, resource.Resource):
449    def render(self, ctx):
450        # TODO -- this must not block long (!)
451        s = self.worksheet.interrupt()
452        return http.Response(stream='ok' if s else 'failed')
453
454
455class Worksheet_plain(WorksheetResource, resource.Resource):
456    def render(self, ctx):
457        s = notebook.plain_text_worksheet_html(self.name)
458        return http.Response(stream=s)
459
460class Worksheet_print(WorksheetResource, resource.Resource):
461    def render(self, ctx):
462        s = notebook.worksheet_html(self.name)
463        return http.Response(stream=s)
464
465
466class NotImplementedWorksheetOp(resource.Resource):
467    def __init__(self, op):
468        self.op = op
469
470    def render(self, ctx):
471        return http.Response(stream = 'The worksheet operation "%s" is not implemented.'%self.op)
472   
473
474class Worksheet(WorksheetResource, resource.Resource):
475    addSlash = True
476
477    def render(self, ctx):
478        s = notebook.html(worksheet_id = self.name)
479        self.worksheet.sage()
480        return http.Response(stream=s)
481
482    def childFactory(self, request, op):
483        notebook_save_check()       
484        try:
485            R = globals()['Worksheet_%s'%op]
486            return R(self.name)
487        except KeyError:
488            return NotImplementedWorksheetOp(op)
489
490class Worksheets(resource.Resource):
491    def render(self, ctx):
492        return http.Response(stream = "Please request a specific worksheet")
493
494    def childFactory(self, request, name):
495        try:
496            return Worksheet(name)
497        except KeyError:
498            return Worksheet_create(name)
499
500############################
501# Adding a new worksheet
502############################
503
504class AddWorksheet(resource.Resource):
505    def render(self, ctx):
506        name = ctx.args['name'][0]
507        W = notebook.create_new_worksheet(name)
508        v = notebook.worksheet_list_html(W.name())
509        return http.Response(stream = encode_list([v, W.name()]))
510
511class Notebook(resource.Resource):
512    child_add_worksheet = AddWorksheet()
513
514
515############################
516
517class Help(resource.Resource):
518    def render(self, ctx):
519        try:
520            s = self._cache
521        except AttributeError:
522            s = notebook.help_window()
523            self._cache = s
524        return http.Response(stream=s)
525
526
527############################
528
529############################
530
531class History(resource.Resource):
532    def render(self, ctx):
533        s = notebook.history_html()
534        return http.Response(stream=s)
535
536
537############################
538
539class Main_css(resource.Resource):
540    def render(self, ctx):
541        s = css.css()
542        return http.Response(stream=s)
543   
544class CSS(resource.Resource):
545    def childFactory(self, request, name):
546        return static.File(css_path + "/" + name)
547
548setattr(CSS, 'child_main.css', Main_css())
549
550############################
551
552
553############################
554# Javascript resources
555############################
556
557class Main_js(resource.Resource):
558    def render(self, ctx):
559        s = js.javascript()
560        return http.Response(stream=s)
561
562class Keyboard_js_specific(resource.Resource):
563    def __init__(self, browser_os):
564        self.s = keyboards.get_keyboard(browser_os)
565
566    def render(self, ctx):
567        return http.Response(stream = self.s)
568   
569
570class Keyboard_js(resource.Resource):
571    def childFactory(self, request, browser_os):
572        return Keyboard_js_specific(browser_os)
573
574class Javascript(resource.Resource):
575    child_keyboard = Keyboard_js()
576   
577    def childFactory(self, request, name):
578        return static.File(javascript_path + "/" + name)
579
580setattr(Javascript, 'child_main.js', Main_js())
581
582############################
583# Image resource
584############################
585
586class Images(resource.Resource):
587    def childFactory(self, request, name):
588        return static.File(image_path + "/" + name)
589
590############################
591
592# class Toplevel(resource.Resource):
593class Toplevel(resource.PostableResource):
594    addSlash = True
595
596    child_images = Images()
597    child_javascript = Javascript()
598    child_css = CSS()
599    child_ws = Worksheets()
600    child_notebook = Notebook()
601    child_doc = Doc()
602   
603    def __init__(self, cookie):
604        self.cookie = cookie
605       
606    def render(self, ctx):
607        from twisted.web2 import responsecode, http_headers
608        s = notebook.html()
609        return http.Response(responsecode.OK, 
610                             {'content-type': http_headers.MimeType('text',
611                                                                    'html'),
612                             'set-cookie':[http_headers.Cookie("sid",
613                                                            self.cookie)]},
614                             stream=s)
615
616    def childFactory(self, request, name):
617        print request, name
618
619setattr(Toplevel, 'child_help.html', Help())
620setattr(Toplevel, 'child_history.html', History())
621
622# site = server.Site(Toplevel())
623notebook = None  # this gets set on startup.
624
625
626
627
628
629
630##########################################################
631# This actually serves up the notebook.
632##########################################################
633
634from   sage.server.misc import print_open_msg
635import os, shutil, socket
636
637private_pem = conf_path + '/private.pem'
638public_pem = conf_path + '/public.pem'
639
640def notebook_setup(self=None):
641    if not os.path.exists(conf_path):
642        os.makedirs(conf_path)
643    print "Using dsage certificates."
644    dsage = os.path.join(DOT_SAGE, 'dsage')
645    if not os.path.exists(dsage + '/cacert.pem'):
646        import sage.dsage.all
647        sage.dsage.all.dsage.setup()
648    if not os.path.exists(dsage + '/cacert.pem'):
649        print "Error configuring."
650        return
651    shutil.copyfile(dsage + '/cacert.pem', private_pem)
652    shutil.copyfile(dsage + '/pubcert.pem', public_pem)
653    print "Successfully configured notebook."
654
655def notebook_twisted(self,
656             directory   = 'sage_notebook',
657             port        = 8000,
658             address     = 'localhost',
659             port_tries  = 1,
660             secure      = True,
661             multisession= True,
662             jsmath      = True):
663    r"""
664    Experimental twisted version of the SAGE Notebook.
665    """
666    if not os.path.exists(directory):
667        os.makedirs(directory)
668    port = int(port)
669    conf = '%s/twistedconf.py'%directory
670
671    def run(port):
672        ## Create the config file
673        if secure:
674            if not os.path.exists(private_pem) or not os.path.exists(public_pem):
675                print "In order to use an SECURE encrypted notebook, you must first run notebook.setup()."
676                print "Now running notebook.setup()"
677                notebook_setup()
678            if not os.path.exists(private_pem) or not os.path.exists(public_pem):
679                print "Failed to setup notebook.  Please try notebook.setup() again manually."
680            strport = 'tls:%s:privateKey=%s:certKey=%s'%(port, private_pem, public_pem)
681        else:
682            strport = 'tcp:%s'%port
683
684        config = open(conf, 'w')
685        config.write("""
686import sage.server.notebook.notebook
687sage.server.notebook.notebook.JSMATH=%s
688import sage.server.notebook.notebook as notebook
689import sage.server.notebook.twist as twist
690twist.notebook = notebook.load_notebook('%s')
691import sage.server.notebook.worksheet as worksheet
692worksheet.init_sage_prestart()
693worksheet.multisession = %s
694
695import signal, sys
696def my_sigint(x, n):
697    twist.notebook.save()
698    signal.signal(signal.SIGINT, signal.SIG_DFL)
699    print "(Notebook cleanly saved. Press control-C again to exit.)"
700   
701signal.signal(signal.SIGINT, my_sigint)
702
703## Use Knoboo's authentication framework
704from twisted.web2 import log, server, channel
705from twisted.cred import portal, checkers, credentials
706import sage.server.notebook.guard as guard
707import sage.server.notebook.avatars as avatars
708
709from twisted.cred import portal
710
711password_dict = {'alex':'alex', 'yqiang@gmail.com':'yqiang'}
712realm = avatars.LoginSystem(password_dict)
713p = portal.Portal(realm)
714# p.registerChecker(avatars.PasswordDataBaseChecker(DBCONNECTION))
715p.registerChecker(avatars.PasswordDictChecker(password_dict))
716# p.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous)
717p.registerChecker(checkers.AllowAnonymousAccess())
718rsrc = guard.MySessionWrapper(p)
719log.DefaultCommonAccessLoggingObserver().start()
720site = server.Site(rsrc)
721factory = channel.HTTPFactory(site)
722
723from twisted.web2 import channel
724from twisted.application import service, strports
725application = service.Application("SAGE Notebook")
726s = strports.service('%s', factory)
727s.setServiceParent(application)
728"""%(jsmath, os.path.abspath(directory), multisession, strport))
729
730
731        config.close()                     
732
733        ## Start up twisted
734        print_open_msg(address, port, secure=secure)
735        e = os.system('cd "%s" && sage -twistd -ny twistedconf.py'%directory)
736        if e == 256:
737            raise socket.error
738                     
739
740    for i in range(int(port_tries)):
741        try:
742            run(port + i)
743        except socket.error:
744            print "Port %s is already in use.  Trying next port..."%port
745        else:
746            break
747
748    return True
Note: See TracBrowser for help on using the repository browser.