source file: /home/buildslave/tahoe/edgy/build/src/allmydata/web/operations.py
file stats: 102 lines, 101 executed: 99.0% covered
coverage versus previous test: 0 lines added, 0 lines removed
    1. 
    2. import time
    3. from zope.interface import implements
    4. from nevow import rend, url, tags as T
    5. from nevow.inevow import IRequest
    6. from twisted.python.failure import Failure
    7. from twisted.internet import reactor, defer
    8. from twisted.web.http import NOT_FOUND
    9. from twisted.web.html import escape
   10. from twisted.application import service
   11. 
   12. from allmydata.web.common import IOpHandleTable, WebError, \
   13.      get_root, get_arg, boolean_of_arg
   14. 
   15. MINUTE = 60
   16. HOUR = 60*MINUTE
   17. 
   18. (MONITOR, RENDERER, WHEN_ADDED) = range(3)
   19. 
   20. class OphandleTable(rend.Page, service.Service):
   21.     implements(IOpHandleTable)
   22. 
   23.     UNCOLLECTED_HANDLE_LIFETIME = 1*HOUR
   24.     COLLECTED_HANDLE_LIFETIME = 10*MINUTE
   25. 
   26.     def __init__(self):
   27.         # both of these are indexed by ophandle
   28.         self.handles = {} # tuple of (monitor, renderer, when_added)
   29.         self.timers = {}
   30. 
   31.     def stopService(self):
   32.         for t in self.timers.values():
   33.             if t.active():
   34.                 t.cancel()
   35.         del self.handles # this is not restartable
   36.         del self.timers
   37.         return service.Service.stopService(self)
   38. 
   39.     def add_monitor(self, ctx, monitor, renderer):
   40.         ophandle = get_arg(ctx, "ophandle")
   41.         assert ophandle
   42.         now = time.time()
   43.         self.handles[ophandle] = (monitor, renderer, now)
   44.         retain_for = get_arg(ctx, "retain-for", None)
   45.         if retain_for is not None:
   46.             self._set_timer(ophandle, int(retain_for))
   47.         monitor.when_done().addBoth(self._operation_complete, ophandle)
   48. 
   49.     def _operation_complete(self, res, ophandle):
   50.         if ophandle in self.handles:
   51.             if ophandle not in self.timers:
   52.                 # the client has not provided a retain-for= value for this
   53.                 # handle, so we set our own.
   54.                 now = time.time()
   55.                 added = self.handles[ophandle][WHEN_ADDED]
   56.                 when = max(self.UNCOLLECTED_HANDLE_LIFETIME, now - added)
   57.                 self._set_timer(ophandle, when)
   58.             # if we already have a timer, the client must have provided the
   59.             # retain-for= value, so don't touch it.
   60. 
   61.     def redirect_to(self, ctx):
   62.         ophandle = get_arg(ctx, "ophandle")
   63.         assert ophandle
   64.         target = get_root(ctx) + "/operations/" + ophandle
   65.         output = get_arg(ctx, "output")
   66.         if output:
   67.             target = target + "?output=%s" % output
   68.         return url.URL.fromString(target)
   69. 
   70.     def childFactory(self, ctx, name):
   71.         ophandle = name
   72.         if ophandle not in self.handles:
   73.             raise WebError("unknown/expired handle '%s'" % escape(ophandle),
   74.                            NOT_FOUND)
   75.         (monitor, renderer, when_added) = self.handles[ophandle]
   76. 
   77.         request = IRequest(ctx)
   78.         t = get_arg(ctx, "t", "status")
   79.         if t == "cancel" and request.method == "POST":
   80.             monitor.cancel()
   81.             # return the status anyways, but release the handle
   82.             self._release_ophandle(ophandle)
   83. 
   84.         else:
   85.             retain_for = get_arg(ctx, "retain-for", None)
   86.             if retain_for is not None:
   87.                 self._set_timer(ophandle, int(retain_for))
   88. 
   89.             if monitor.is_finished():
   90.                 if boolean_of_arg(get_arg(ctx, "release-after-complete", "false")):
   91.                     self._release_ophandle(ophandle)
   92.                 if retain_for is None:
   93.                     # this GET is collecting the ophandle, so change its timer
   94.                     self._set_timer(ophandle, self.COLLECTED_HANDLE_LIFETIME)
   95. 
   96.         status = monitor.get_status()
   97.         if isinstance(status, Failure):
   98.             return defer.fail(status)
   99. 
  100.         return renderer
  101. 
  102.     def _set_timer(self, ophandle, when):
  103.         if ophandle in self.timers and self.timers[ophandle].active():
  104.             self.timers[ophandle].cancel()
  105.         t = reactor.callLater(when, self._release_ophandle, ophandle)
  106.         self.timers[ophandle] = t
  107. 
  108.     def _release_ophandle(self, ophandle):
  109.         if ophandle in self.timers and self.timers[ophandle].active():
  110.             self.timers[ophandle].cancel()
  111.         self.timers.pop(ophandle, None)
  112.         self.handles.pop(ophandle, None)
  113. 
  114. class ReloadMixin:
  115.     REFRESH_TIME = 1*MINUTE
  116. 
  117.     def render_refresh(self, ctx, data):
  118.         if self.monitor.is_finished():
  119.             return ""
  120.         # dreid suggests ctx.tag(**dict([("http-equiv", "refresh")]))
  121.         # but I can't tell if he's joking or not
  122.         ctx.tag.attributes["http-equiv"] = "refresh"
  123.         ctx.tag.attributes["content"] = str(self.REFRESH_TIME)
  124.         return ctx.tag
  125. 
  126.     def render_reload(self, ctx, data):
  127.         if self.monitor.is_finished():
  128.             return ""
  129.         req = IRequest(ctx)
  130.         # url.gethere would break a proxy, so the correct thing to do is
  131.         # req.path[-1] + queryargs
  132.         ophandle = req.prepath[-1]
  133.         reload_target = ophandle + "?output=html"
  134.         cancel_target = ophandle + "?t=cancel"
  135.         cancel_button = T.form(action=cancel_target, method="POST",
  136.                                enctype="multipart/form-data")[
  137.             T.input(type="submit", value="Cancel"),
  138.             ]
  139. 
  140.         return [T.h2["Operation still running: ",
  141.                      T.a(href=reload_target)["Reload"],
  142.                      ],
  143.                 cancel_button,
  144.                 ]