Package advene :: Package server :: Module webcherry
[hide private]
[frames] | no frames]

Source Code for Module advene.server.webcherry

  1  # 
  2  # Advene: Annotate Digital Videos, Exchange on the NEt 
  3  # Copyright (C) 2008 Olivier Aubert <olivier.aubert@liris.cnrs.fr> 
  4  # 
  5  # Advene is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Advene is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with Advene; if not, write to the Free Software 
 17  # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 18  # 
 19  """Advene http server. 
 20   
 21  This module defines a number of classes that describe the structure of 
 22  the Advene webserver. The L{AdveneWebServer} class is the main server 
 23  class. 
 24   
 25  URL syntax 
 26  ========== 
 27   
 28    The URL syntax is described in each decription class. 
 29   
 30    The server can run standalone or embedded in another application 
 31    (typically the C{advene} GUI).  In all cases, Webserver depends on 
 32    AdveneController. 
 33  """ 
 34   
 35  import advene.core.config as config 
 36  import advene.core.version 
 37   
 38  import sys 
 39  import os 
 40  import re 
 41  import urllib 
 42  import cgi 
 43  import socket 
 44  import imghdr 
 45   
 46  from gettext import gettext as _ 
 47   
 48  import cherrypy 
 49   
 50  if int(cherrypy.__version__.split('.')[0]) < 3: 
 51      raise _("The webserver requires version 3.0 of CherryPy at least.") 
 52   
 53  import advene.util.helper as helper 
 54  from advene.model.cam.package import Package 
 55  from advene.model.tales import AdveneContext 
 56  from advene.model.exceptions import NoSuchElementError, UnreachableImportError 
 57  import advene.util.session 
 58   
 59  from simpletal.simpleTALES import PathNotFoundException 
 60  from simpletal.simpleTAL import TemplateParseException 
 61   
62 -def remove_tales_prefix(name):
63 if name.startswith("_tales_"): 64 return name[7:] 65 else: 66 return name
67 68 DEBUG=True
69 -class Common:
70 """Common functionalities for all cherrypy nodes. 71 """
72 - def __init__(self, controller=None):
73 self.controller=controller
74
75 - def _cpOnError(self):
76 """Error message handling. 77 """ 78 err = sys.exc_info() 79 if DEBUG: 80 print "Error handling" 81 import traceback, StringIO 82 bodyFile = StringIO.StringIO() 83 traceback.print_exc(file = bodyFile) 84 cherrypy.response.status=500 85 cherrypy.response.headers['Content-type']='text/html' 86 cherrypy.response.body = ["""<html><head><title>Advene - Error</title></head><body>Sorry, an error occured. <pre>%s</pre></body></html>""" % bodyFile.getvalue() ] 87 bodyFile.close() 88 89 else: 90 # Do something else here. 91 cherrypy.response.body = ['Error: ' + str(err[0])]
92
93 - def location_bar (self):
94 """Returns a string representing the active location bar. 95 96 This method will use the current URL path, and return an HTML string 97 where the different components of the path are linked to their URL. 98 99 @return: a HTML string 100 @rtype: string 101 """ 102 s = urllib.splittag( 103 urllib.splitquery(cherrypy.request.path_info)[0] 104 )[0] 105 path = s.split('/')[1:] 106 return """<a href="/">/</a>""" + "/".join( 107 ["""<a href="%s">%s</a>""" 108 % (uri, name) 109 for (name, uri) in zip(path, 110 ["/"+"/".join(path[:i+1]) 111 for i in range(len(path))])] 112 )
113
114 - def no_cache (self):
115 """Write the cache-control headers in the response. 116 117 This method sends cache-control headers to ensure that the 118 browser does not cache the response, as it may vary from one 119 call to another. 120 121 @return: nothing 122 """ 123 cherrypy.response.headers['Pragma']='no-cache' 124 cherrypy.response.headers['Cache-Control']='max-age=0'
125
126 - def start_html (self, title="", headers=None, head_section=None, body_attributes="", 127 mode=None, mimetype=None, duplicate_title=False, cache=False):
128 """Starts writing a HTML response (header + common body start). 129 130 @param title: Title of the HTML document (default : "") 131 @type title: string 132 @param headers: additional headers to add 133 @type headers: a list of tuples (keyword, value) 134 @param head_section: additional HTML code to add in the <head></head> section 135 @type head_section: string 136 @param body_attributes: attributes added to the body tag 137 @type body_attributes: string 138 139 @param mode: presentation mode (navigation, raw). Default: navigation 140 @type mode: string 141 @param duplicate_title: duplicate title as HTML header (h1) 142 @type duplicate_title: boolean 143 @param cache: make the document cacheable. Default: False 144 @type cache: boolean 145 @return: nothing 146 """ 147 if mode is None: 148 mode = config.data.webserver['displaymode'] 149 cherrypy.response.status=200 150 if mode == 'navigation' or mimetype is None or mimetype == 'text/html': 151 mimetype='text/html; charset=utf-8' 152 # Enforce utf-8 encoding on all text resources 153 if mimetype.startswith('text/') and 'charset' not in mimetype: 154 mimetype += '; charset=utf-8' 155 cherrypy.response.headers['Content-type']=mimetype 156 if headers is not None: 157 for h in headers: 158 cherrypy.request.headers[h[0]]=h[1] 159 if not cache: 160 self.no_cache () 161 162 res=[] 163 if mode == "navigation": 164 res.append("""<html><head><title>%s</title><link rel="stylesheet" type="text/css" href="/data/advene.css" />""" % title) 165 if head_section is not None: 166 res.append(head_section) 167 168 res.append("</head><body %s>" % body_attributes) 169 170 res.append(_(""" 171 <p> 172 <a href="/admin">Server administration</a> | 173 <a href="/media">Media control</a> | 174 <a href="%(path)s?mode=raw">Raw view</a> 175 </p> 176 Location: %(locationbar)s 177 <hr> 178 """) % { 'locationbar': self.location_bar (), 179 'path': cherrypy.request.path_info } ) 180 181 if duplicate_title and mode == 'navigation': 182 res.append("<h1>%s</h1>\n" % title) 183 184 return "".join(res)
185
186 - def send_no_content(self):
187 """Sends a No Content (204) response. 188 189 Mainly used in /media handling. 190 """ 191 cherrypy.response.status=204 192 self.no_cache () 193 return ""
194
195 - def send_redirect (self, location):
196 """Sends a redirect response. 197 198 As this method generates headers, it must be called before other 199 headers or content. 200 201 @param location: the URL to redirect to. 202 @type location: string (URL) 203 """ 204 raise cherrypy.HTTPRedirect(location)
205
206 - def send_error (self, status=404, message=None):
207 """Sends an error response. 208 209 @param message: the error message 210 @type message: string 211 """ 212 if message is None: 213 message=_("Unspecified Error") 214 raise cherrypy.HTTPError(status, _(""" 215 <h1>Error</h1> 216 <p>An error occurred:</p> 217 %s 218 """) % message)
219
220 - def image_type (self, o):
221 """Return the image type (mime) of the object. 222 223 This method examines the content of the given object, and 224 returns either a mime-type if it is an image, I{None} if it is 225 not. 226 227 @param o: an object 228 @type o: any 229 @return: the content-type if o is an image, else None 230 @rtype: string 231 """ 232 res=imghdr.what (None, str(o)) 233 if res is not None: 234 return "image/%s" % res 235 else: 236 return None
237
238 -class Admin(Common):
239 """Handles the X{/admin} requests. 240 241 The C{/admin} folder contains the following elements for the 242 administration of the server: 243 244 - C{/admin/list} : display the list of currently loaded packages 245 - C{/admin/load} : load a new package 246 - C{/admin/save/alias} : save a package 247 - C{/admin/delete/alias} : remove a loaded package 248 - C{/admin/status} : display current status 249 - C{/admin/display} : display or set the default webserver display mode 250 - C{/admin/halt} : halt the webserver 251 252 Accessing the C{/admin} folder itself displays the summary 253 (equivalent to the root document). 254 255 Loading, saving or deleting packages is done by specifying the 256 C{alias} parameter. In the case of the C{/admin/load} action, 257 the C{uri} parameter must provide the appropriate URI. 258 259 Setting the X{display mode} 260 =========================== 261 262 Accessing the C{/admin/display} element updates the server 263 display mode. The data should be available as a parameter 264 named C{mode}, which is either C{default} or C{raw}, or as 265 last element in the URI, for instance 266 C{/admin/display/navigation} 267 268 @param l: the access path as a list of elements, 269 with the initial one (C{access}) omitted 270 @type l: list 271 @param query: the options given in the URL 272 @type query: dict 273 """
274 - def index(self):
275 """Display the main administration page. 276 277 This method displays the administration page of the server, which 278 should link to all functionalities. 279 """ 280 res=[ self.start_html (_("Server Administration"), duplicate_title=True, mode='navigation') ] 281 if self.controller.server.displaymode == 'raw': 282 switch='navigation' 283 else: 284 switch='raw' 285 mode_sw="""%(mode)s (<a href="/admin/display/%(switch)s">switch to %(switch)s</a>)""" % { 286 'mode': self.controller.server.displaymode, 'switch': switch } 287 288 res.append(_(""" 289 <p><a href="/admin/reset">Reset the server</a></p> 290 <p><a href="/admin/halt">Halt the server</a></p> 291 <p><a href="/admin/list">List available files</a></p> 292 <p><a href="/packages">List loaded packages</a> (%(packagelist)s)</p> 293 <p>Display mode : %(displaymode)s</p> 294 <hr> 295 <p>Load a package : 296 <form action="/admin/load" method="GET"> 297 Alias: <input type="text" name="alias" /><br> 298 URI: <input type="text" name="uri" /><br> 299 <input type="submit" value="Load" /> 300 </form> 301 </body></html> 302 """) % { 'packagelist': " | ".join( ['<a href="/packages/%s">%s</a>' % (alias, alias) 303 for alias in self.controller.packages.keys() ] ), 304 'displaymode': mode_sw }) 305 return "".join(res)
306 index.exposed=True 307
308 - def list(self):
309 """Display available Advene files. 310 311 This method displays the data files in the data directory. 312 Maybe it should be removed when the server runs embedded. 313 """ 314 res=[ self.start_html (_("Available files"), duplicate_title=True, mode='navigation') ] 315 res.append ("<ul>") 316 317 l=[ os.path.join(self.controller.server.packages_directory, n) 318 for n in os.listdir(self.controller.server.packages_directory) 319 if n.lower().endswith('.czp') ] 320 l.sort(lambda a, b: cmp(a.lower(), b.lower())) 321 for uri in l: 322 name, ext = os.path.splitext(uri) 323 alias = re.sub('[^a-zA-Z0-9_]', '_', os.path.basename(name)) 324 res.append (""" 325 <li><a href="/admin/load?alias=%(alias)s&uri=%(uri)s">%(uri)s</a></li> 326 """ % {'alias':alias, 'uri':uri}) 327 res.append (""" 328 </ul> 329 """) 330 return "".join(res)
331 list.exposed=True 332
333 - def load(self, *args, **query):
334 """Load the specified URI with the given alias. 335 """ 336 try: 337 alias = query['alias'] 338 except KeyError: 339 return self.send_error (501, 340 _("""You should specify an alias""")) 341 try: 342 uri = query['uri'] 343 except KeyError: 344 return self.send_error (501, 345 _("""You should specify an uri""")) 346 try: 347 # FIXME: potential problem: this method executes in the webserver thread, which may 348 # cause problems with the GUI 349 self.controller.load_package (uri=uri, alias=alias) 350 return "".join( ( 351 self.start_html (_("Package %s loaded") % alias, duplicate_title=True, mode='navigation'), 352 _("""<p>Go to the <a href="/packages/%(alias)s">%(alias)s</a> package, or to the <a href="/packages">package list</a>.""") % { 'alias': alias } 353 )) 354 except Exception, e: 355 return self.send_error(501, _("""<p>Cannot load package %(alias)s : %(error)s</p>""" % { 356 'alias': alias, 357 'error': str(e) }))
358 load.exposed=True 359
360 - def delete(self, alias):
361 """Unload a package. 362 """ 363 try: 364 self.controller.unregister_package (alias) 365 return "".join(( 366 self.start_html (_("Package %s deleted") % alias, duplicate_title=True, mode='navigation'), 367 _("""<p>Go to the <a href="/packages">package list</a>.""") 368 )) 369 except Exception, e: 370 return self.send_error(501, _("""<p>Cannot delete package %(alias)s : %(error)s</p>""" % { 371 'alias': alias, 372 'error': str(e) } 373 ))
374 delete.exposed = True 375
376 - def save(self, alias=None):
377 """Save a package. 378 """ 379 try: 380 if alias is not None: 381 # Save a specific package 382 self.controller.save_package(alias=alias) 383 else: 384 self.controller.save_package() 385 alias='default' 386 return "".join(( 387 self.start_html (_("Package %s saved") % alias, duplicate_title=True, mode='navigation'), 388 _("""<p>Go to the <a href="/packages/%(alias)s">%(alias)s</a> package, or to the <a href="/packages">package list</a>.""") % { 'alias': alias } 389 )) 390 except Exception, e: 391 return self.send_error(501, _("""<p>Cannot save package %(alias)s : %(error)s</p>""" % { 392 'alias': alias, 393 'error': str(e) } 394 ))
395 save.exposed=True 396
397 - def reset(self):
398 """Reset packages list. 399 """ 400 self.controller.reset() 401 return self.start_html (_('Server reset'), duplicate_title=True, mode='navigation')
402 reset.exposed=True 403
404 - def halt(self):
405 """Halt server. 406 """ 407 self.controller.server.stop() 408 raise SystemExit(0)
409 halt.exposed=True 410
411 - def display(self, mode=None):
412 """Set display mode. 413 """ 414 if mode: 415 # Set default display mode 416 if mode in ('raw', 'navigation'): 417 self.controller.server.displaymode=mode 418 ref=cherrypy.request.headers.get('Referer', "/") 419 return self.send_redirect(ref) 420 else: 421 # Display status 422 cherrypy.response.status=200 423 cherrypy.response.headers['Content-type']='text/plain' 424 self.no_cache () 425 return self.controller.server.displaymode
426 display.exposed=True
427
428 -class Packages(Common):
429 """Node for packages access. 430 """
431 - def index(self):
432 """Display currently available (loaded) packages. 433 434 This method displays the list of currently loaded 435 packages. The generated list provides links for accessing, 436 reloading, saving or removing the package. 437 """ 438 res=[ self.start_html (_("Loaded package(s)"), mode='navigation') ] 439 440 res.append (_(""" 441 <h1>Loaded package(s)</h1> 442 <table border="1" width="50%"> 443 <tr> 444 <th>Alias</th> 445 <th>Action</th> 446 <th>URI</th> 447 <th>Annotations</th> 448 </tr> 449 """)) 450 for alias in self.controller.packages.keys(): 451 p = self.controller.packages[alias] 452 res.append (_("""<tr> 453 <td><a href="/packages/%(alias)s">%(alias)s</a></td> 454 <td align="center"><a href="/admin/load?alias=%(alias)s&uri=%(uri)s">Reload</a>|<a href="/admin/delete?alias=%(alias)s">Drop</a>|<a href="/admin/save?alias=%(alias)s">Save</a></td> 455 <td>%(uri)s</td> 456 <td>%(size)d</td> 457 </tr> 458 """) % { 'alias':alias, 'uri':p.uri, 'size':len(list(p.own.annotations)) }) 459 res.append (""" 460 </ul> 461 """) 462 return "".join(res)
463 index.exposed=True 464
465 - def display_package_element (self, p, tales, query=None):
466 """Display a view for a TALES expression. 467 468 This method displays the view for the element defined by the 469 expression C{tales} wrt to package C{p}, with facultative 470 parameters in C{query}. It handles the X{/packages} folder. 471 472 The query can hold an optional parameter, named C{mode}, which 473 indicates a specific display mode for the resulting 474 object. Valid modes are: 475 476 - C{image} : the object is an image, and we generate the 477 correct headers. 478 - C{raw} : do not display the navigation interface, but only the 479 object's view 480 - C{content} : return the content data of an object, with 481 its specific mime-type 482 - C{default} : default mode, with navigation interface 483 484 The C{display mode} has a default value at the server level, 485 wich can be set through the C{/admin} folder. 486 487 An autodetect feature will force the display mode to C{image} 488 if the object is an image (depending on the return value of 489 L{image_type}). 490 491 All other other parameters given on the URL path are kepts in 492 the C{query} dictionary, which is available in TALES 493 expressions through the C{request/} root element. 494 495 @param p: the package in which the expression should be evaluated 496 @type p: advene.model.Package 497 @param tales: a TALES expression 498 @type tales: string 499 @param query: options used in TAL/TALES processing 500 @type query: dict 501 """ 502 res=[] 503 alias = self.controller.aliases[p] 504 505 if query is None: 506 query={} 507 508 if tales == "": 509 expr = "here" 510 elif tales.startswith ('options'): 511 expr = tales 512 else: 513 expr = "here/%s" % tales 514 515 # Define the package as root package for the model layer 516 advene.util.session.session.package=self.controller.package 517 518 context = self.controller.build_context (here=p, alias=alias) 519 context.pushLocals() 520 context.setLocal('request', query) 521 # FIXME: the following line is a hack for having qname-keys work 522 # It is a hack because obviously, p is not a "view" 523 context.setLocal (u'view', p) 524 525 try: 526 objet = context.evaluate(expr) 527 except PathNotFoundException, e: 528 self.start_html (_("Error"), duplicate_title=True, mode='navigation') 529 res.append (_("""The TALES expression %s is not valid.""") % tales) 530 res.append (unicode(e.args).encode('utf-8')) 531 return "".join(res) 532 except NoSuchElementError, e: 533 self.start_html (_("Error"), duplicate_title=True) 534 res.append (_("""The element %s cannot be found.""") % unicode(e)) 535 return "".join(res) 536 except UnreachableImportError, e: 537 self.start_html (_("Error"), duplicate_title=True) 538 res.append (_("""The element %s is in a package which could not be imported.""") % unicode(e)) 539 return "".join(res) 540 541 # FIXME: 542 # Principe: si l'objet est un viewable, on appelle la 543 # methode view pour en obtenir une vue par defaut. 544 #if isinstance(objet, advene.model.viewable.Viewable): 545 # # It is a viewable, so display it using the default view 546 # objet.view(context=context) 547 548 displaymode = self.controller.server.displaymode 549 # Hack to automatically switch to an image view for image objects. 550 if query.has_key('mode'): 551 displaymode = query['mode'] 552 del (query['mode']) 553 554 if displaymode != "raw": 555 displaymode = "navigation" 556 557 # Display content 558 if hasattr (objet, 'view') and callable (objet.view): 559 560 context = self.controller.build_context(here=objet, alias=alias) 561 context.pushLocals() 562 context.setLocal('request', query) 563 # FIXME: should be default view 564 context.setLocal(u'view', objet) 565 try: 566 v=objet.view (context=context) 567 if v.contenttype.startswith('text'): 568 res.append( self.start_html(mimetype=v.contenttype) ) 569 res.append (unicode(v).encode('utf-8')) 570 else: 571 res.append( self.start_html(mimetype=v.contenttype, mode='raw') ) 572 res.append(v) 573 except PathNotFoundException, e: 574 res.append( self.start_html(_("Error")) ) 575 res.append(_("<h1>Error</h1>")) 576 res.append(_("""<p>There was an error in the TALES expression.</p> 577 <pre>%s</pre>""") % cgi.escape(unicode(e.args[0]).encode('utf-8'))) 578 return "".join(res) 579 else: 580 mimetype=None 581 try: 582 mimetype = objet.mimetype 583 except AttributeError: 584 try: 585 mimetype = objet.contenttype 586 except AttributeError: 587 pass 588 try: 589 if mimetype and mimetype.startswith('text'): 590 res.append( self.start_html(mimetype=mimetype) ) 591 res.append (unicode(objet).encode('utf-8')) 592 else: 593 res.append( self.start_html(mimetype=mimetype, mode='raw' ) ) 594 res.append(str(objet)) 595 return res 596 except PathNotFoundException, e: 597 res.append(_("<h1>Error</h1>")) 598 res.append(_("""<p>There was an error.</p> 599 <pre>%s</pre>""") % cgi.escape(unicode(e.args[0]).encode('utf-8'))) 600 except TemplateParseException, e: 601 res.append(_("<h1>Error</h1>")) 602 res.append(_("""<p>There was an error in the template code.</p> 603 <p>Tag name: <strong>%(tagname)s</strong></p> 604 <p>Error message: <em>%(message)s</em></p>""") % { 605 'tagname': cgi.escape(e.location), 606 'message': e.errorDescription}) 607 608 # Generating navigation footer 609 if displaymode == 'navigation' and 'html' in cherrypy.response.headers['Content-type']: 610 uri=cherrypy.request.path_info 611 levelup = uri[:uri.rindex("/")] 612 auto_components = [ c 613 for c in helper.get_valid_members (objet) 614 if not c.startswith('----') ] 615 auto_components.sort() 616 try: 617 auto_views = objet.validViews 618 auto_views.sort() 619 except AttributeError: 620 auto_views = [] 621 622 res.append (_(""" 623 <hr> 624 <p> 625 Location: %(location)s<br> 626 <form name="navigation" method="GET"> 627 <a href="%(levelup)s">Up one level</a> | 628 Next level : 629 <select name="path" onchange="submit()"> 630 <option selected></option>" 631 """) % { 632 'location': self.location_bar (), 633 'levelup': levelup}) 634 635 if hasattr (objet, 'view'): 636 res.append ("<option>view</option>") 637 638 res.append ("\n".join( 639 ["""<option>%s</option>""" % c for c in auto_components])) 640 641 res.append (_(""" 642 </select> View: <select name="view" onchange="submit()"> 643 <option selected></option> 644 """)) 645 646 res.append ("\n".join( 647 ["""<option value="%s">%s</option>""" % 648 ("/".join((cherrypy.url(), "view", c)), c) 649 for c in auto_views])) 650 651 res.append (""" 652 </select> 653 <input type="submit" value="go"> 654 </form> 655 <form name="entry" method="GET"> 656 <input size="50" type="text" name="path" accesskey="p"> 657 <input type="submit" value="go"></form> 658 """) 659 res.append (_("""<hr> 660 <p>Evaluating expression "<strong>%(expression)s</strong>" on package %(uri)s returns %(value)s</p> 661 """) % { 662 'expression': tales , 663 'uri': p.uri, 664 'value': cgi.escape(str(type(objet)))}) 665 return "".join(res)
666
667 - def default(self, *args, **query):
668 """Access a specific package. 669 670 URL handling 671 ============ 672 673 The URL is first split in components, and some parameters may 674 modify the interpretation of the path. 675 676 The C{path} parameter is used to interactively modifying the 677 path (through a form). 678 679 Else, the C{path} value will be appended to the current path 680 of the URL, and the browser is redirected to this new location. 681 682 The C{view} parameter is a shortcut for rapidly accessing an 683 element view. Its value is in fact the URL displaying the 684 correct view, and the browser is redirected. 685 686 """ 687 if not args: 688 return self.index() 689 690 pkgid = args[0] 691 692 try: 693 p = self.controller.packages[pkgid] 694 except KeyError: 695 return self.send_error (501, _("<p>Package <strong>%s</strong> not loaded</p>") 696 % pkgid) 697 698 # Handle form parameters (from the navigation interface) 699 if query.has_key('view'): 700 if query['view'] != '': 701 # The 'view' parameter is in fact the new path 702 # that we should redirect to 703 return self.send_redirect (query['view']) 704 else: 705 # If the submit was done automatically on "path" modification, 706 # the view field will exist but be empty, so delete it 707 del(query['view']) 708 709 if query.has_key('path'): 710 # Append the given path to the current URL 711 p="/".join( (cherrypy.url(), query['path']) ) 712 if query['path'].find ('..') != -1: 713 p = os.path.normpath (p) 714 del(query['path']) 715 if len(query) == 0: 716 location = p 717 else: 718 location = "%s?%s" % (p, 719 "&".join (["%s=%s" % (k,urllib.quote(query[k])) 720 for k in query.keys()])) 721 return self.send_redirect (location) 722 723 tales = "/".join (args[1:]) 724 725 if cherrypy.request.method == 'PUT': 726 return self.handle_put_request(*args, **query) 727 elif cherrypy.request.method == 'POST': 728 return self.handle_post_request(*args, **query) 729 elif cherrypy.request.method != 'GET': 730 return self.send_error(400, 'Unknown method: %s' % cherrypy.request.method) 731 732 try: 733 return self.display_package_element (p , tales, query) 734 except TemplateParseException, e: 735 res=[ self.start_html(_("Error")) ] 736 res.append(_("<h1>Error</h1>")) 737 res.append(_("""<p>There was an error in the template code.</p> 738 <p>Tag name: <strong>%(tagname)s</strong></p> 739 <p>Error message: <em>%(message)s</em></p>""") % { 740 'tagname': cgi.escape(e.location), 741 'message': e.errorDescription }) 742 except PathNotFoundException, e: 743 res=[ self.start_html(_("Error")) ] 744 res.append(_("<h1>Error</h1>")) 745 res.append(_("""<p>There was an error in the expression.</p> 746 <pre>%s</pre>""") % cgi.escape(unicode(e.args[0]).encode('utf-8'))) 747 except: 748 # FIXME: should use standard Cherrypy error handling. 749 t, v, tr = sys.exc_info() 750 import code 751 res=[ self.start_html(_("Error")) ] 752 res.append(_("<h1>Error</h1>")) 753 res.append(_("""<p>Cannot resolve TALES expression %(expr)s on package %(package)s<p><pre> 754 %(type)s 755 %(value)s 756 %(traceback)s</pre>""") % { 757 'expr': tales, 758 'package': pkgid, 759 'type': unicode(tales), 760 'value': unicode(v), 761 'traceback': "\n".join(code.traceback.format_tb (tr)) }) 762 763 return "".join(res)
764 default.exposed=True
765 766
767 -class Root(Common):
768 """Common methods for all web resources. 769 770 URL syntax 771 ========== 772 773 The virtual tree served by this server has the following entry points : 774 775 - C{/admin} : administrate the webserver 776 - C{/packages} : access to packages 777 - C{/media} : control the player 778 - C{/action} : list and invoke Advene actions 779 - C{/application} : control the application 780 """
781 - def __init__(self, controller=None):
782 self.controller=controller 783 self.admin=Admin(controller) 784 self.packages=Packages(controller)
785
786 - def index(self):
787 """Display the server root document. 788 """ 789 res=[ self.start_html (_("Advene webserver"), duplicate_title=True, mode='navigation') ] 790 res.append(_("""<p>Welcome on the <a href="http://liris.cnrs.fr/advene/">Advene</a> webserver run by %(userid)s on %(serveraddress)s.</p>""") % 791 { 792 'userid': config.data.userid, 793 'serveraddress': cherrypy.request.base, 794 }) 795 796 if len(self.controller.packages) == 0: 797 res.append(_(""" <p>No package is loaded. You can access the <a href="/admin">server administration page</a>.<p>""")) 798 else: 799 # It must be 2, since we always have a 'advene' key. but 800 # there could be some strange case where the advene key is 801 # not present? 802 if len(self.controller.packages) <= 2: 803 alias='advene' 804 p=self.controller.packages['advene'] 805 mes=_("""the <a href="/packages/%s">loaded package's data</a>""") % alias 806 res.append(_(""" <p>You can either access %s or the <a href="/admin">server administration page</a>.<p>""") % mes) 807 808 #res.append(_("""<hr><p align="right"><em>Document generated by <a href="http://liris.cnrs.fr/advene/">Advene</a> v. %s.</em></p>""") % (advene.core.version.version)) 809 return "".join(res)
810 index.exposed=True
811
812 -class AdveneWebServer:
813 """Embedded HTTP server for the Advene framework. 814 815 This is an embedded HTTP Server dedicated to serving Advene 816 packages content, and interacting with a media player. 817 818 @ivar controller: the controller 819 @type controller: advene.core.controller.Controller 820 821 @ivar urlbase: the base URL for this server 822 @type urlbase: string 823 @ivar displaymode: the default display-mode 824 @type displaymode: string 825 @ivar authorized_hosts: the list of authorized hosts 826 @type authorized_hosts: dict 827 """
828 - def __init__(self, controller=None, port=1234):
829 """HTTP Server initialization. 830 831 When running embedded, the server should be provided a 832 C{controller} parameter, responsible for handling package 833 loading and player interaction. 834 835 @param controller: the controller 836 @type controller: advene.core.controller.Controller 837 @param port: the port number the server should bind to 838 @type port: int 839 """ 840 self.controller=controller 841 self.urlbase = u"http://localhost:%d/" % port 842 self.displaymode = config.data.webserver['displaymode'] 843 844 settings = { 845 'global': { 846 'server.socket_port' : port, 847 #'server.socket_queue_size': 5, 848 #'server.protocol_version': "HTTP/1.0", 849 'log.screen': False, 850 'log.access_file': config.data.advenefile('webserver.log', 'settings'), 851 'log.error_file': config.data.advenefile('webserver-error.log', 'settings'), 852 'server.reverse_dns': False, 853 'server.thread_pool': 10, 854 'engine.autoreload_on': False, 855 #'server.environment': "development", 856 'server.environment': "production", 857 }, 858 # '/admin': { 859 # 'session_authenticate_filter.on' :True 860 # }, 861 } 862 cherrypy.config.update(settings) 863 864 865 # Not used for the moment. 866 self.authorized_hosts = {'127.0.0.1': 'localhost'} 867 868 app_config={ 869 '/favicon.ico': { 870 'tools.staticfile.on': True, 871 'tools.staticfile.filename': config.data.advenefile( ( 'pixmaps', 'advene.ico' ) ), 872 }, 873 '/data': { 874 'tools.staticdir.on': True, 875 'tools.staticdir.dir': config.data.path['web'], 876 }, 877 } 878 cherrypy.tree.mount(Root(controller), config=app_config) 879 880 try: 881 # server.quickstart *must* be started from the main thread. 882 cherrypy.server.quickstart() 883 except Exception, e: 884 self.controller.log(_("Cannot start HTTP server: %s") % unicode(e))
885
886 - def start(self):
887 """Start the webserver. 888 """ 889 self.controller.queue_action(cherrypy.engine.start, False) 890 return True
891
892 - def stop(self):
893 """Stop the webserver. 894 """ 895 cherrypy.engine.stop() 896 cherrypy.server.stop()
897
898 -class BasicController:
899 - def __init__(self):
900 self.packages={} 901 self.aliases={} 902 self.server=None
903
904 - def load_main_package(self, fname):
905 p=Package(fname) 906 self.packages['advene']=p 907 self.aliases[p]='advene'
908
909 - def log(self, *p):
910 print p
911
912 - def load_package(self, uri=None, alias=None):
913 print "FIXME"
914
915 - def save_package(self, alias=None):
916 print "FIXME"
917
918 - def unregister_package(self, alias=None):
919 print "FIXME"
920
921 - def reset(self):
922 self.packages={} 923 self.aliases={}
924
925 - def build_context(self, here=None, alias=None):
926 c=AdveneContext(here) 927 c.addGlobal("package", here) 928 return c
929
930 - def queue_action(self, action, *p, **kw):
931 action(*p, **kw)
932 933 if __name__ == '__main__': 934 controller=BasicController() 935 controller.load_main_package('file:' + sys.argv[1]) 936 controller.server=AdveneWebServer(controller) 937 938 controller.server.start() 939 cherrypy.engine.block() 940