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
12 from allmydata.web.common import IOpHandleTable, WebError, \
13 get_root, get_arg, boolean_of_arg
19 (MONITOR, RENDERER, WHEN_ADDED) = range(3)
21 class OphandleTable(rend.Page, service.Service):
22 implements(IOpHandleTable)
24 UNCOLLECTED_HANDLE_LIFETIME = 4*DAY
25 COLLECTED_HANDLE_LIFETIME = 1*DAY
28 # both of these are indexed by ophandle
29 self.handles = {} # tuple of (monitor, renderer, when_added)
32 def stopService(self):
33 for t in self.timers.values():
36 del self.handles # this is not restartable
38 return service.Service.stopService(self)
40 def add_monitor(self, ctx, monitor, renderer):
41 ophandle = get_arg(ctx, "ophandle")
44 self.handles[ophandle] = (monitor, renderer, now)
45 retain_for = get_arg(ctx, "retain-for", None)
46 if retain_for is not None:
47 self._set_timer(ophandle, int(retain_for))
48 monitor.when_done().addBoth(self._operation_complete, ophandle)
50 def _operation_complete(self, res, ophandle):
51 if ophandle in self.handles:
52 if ophandle not in self.timers:
53 # the client has not provided a retain-for= value for this
54 # handle, so we set our own.
56 added = self.handles[ophandle][WHEN_ADDED]
57 when = max(self.UNCOLLECTED_HANDLE_LIFETIME, now - added)
58 self._set_timer(ophandle, when)
59 # if we already have a timer, the client must have provided the
60 # retain-for= value, so don't touch it.
62 def redirect_to(self, ctx):
63 ophandle = get_arg(ctx, "ophandle")
65 target = get_root(ctx) + "/operations/" + ophandle
66 output = get_arg(ctx, "output")
68 target = target + "?output=%s" % output
69 return url.URL.fromString(target)
71 def childFactory(self, ctx, name):
73 if ophandle not in self.handles:
74 raise WebError("unknown/expired handle '%s'" % escape(ophandle),
76 (monitor, renderer, when_added) = self.handles[ophandle]
78 request = IRequest(ctx)
79 t = get_arg(ctx, "t", "status")
80 if t == "cancel" and request.method == "POST":
82 # return the status anyways, but release the handle
83 self._release_ophandle(ophandle)
86 retain_for = get_arg(ctx, "retain-for", None)
87 if retain_for is not None:
88 self._set_timer(ophandle, int(retain_for))
90 if monitor.is_finished():
91 if boolean_of_arg(get_arg(ctx, "release-after-complete", "false")):
92 self._release_ophandle(ophandle)
93 if retain_for is None:
94 # this GET is collecting the ophandle, so change its timer
95 self._set_timer(ophandle, self.COLLECTED_HANDLE_LIFETIME)
97 status = monitor.get_status()
98 if isinstance(status, Failure):
99 return defer.fail(status)
103 def _set_timer(self, ophandle, when):
104 if ophandle in self.timers and self.timers[ophandle].active():
105 self.timers[ophandle].cancel()
106 t = reactor.callLater(when, self._release_ophandle, ophandle)
107 self.timers[ophandle] = t
109 def _release_ophandle(self, ophandle):
110 if ophandle in self.timers and self.timers[ophandle].active():
111 self.timers[ophandle].cancel()
112 self.timers.pop(ophandle, None)
113 self.handles.pop(ophandle, None)
116 REFRESH_TIME = 1*MINUTE
118 def render_refresh(self, ctx, data):
119 if self.monitor.is_finished():
121 # dreid suggests ctx.tag(**dict([("http-equiv", "refresh")]))
122 # but I can't tell if he's joking or not
123 ctx.tag.attributes["http-equiv"] = "refresh"
124 ctx.tag.attributes["content"] = str(self.REFRESH_TIME)
127 def render_reload(self, ctx, data):
128 if self.monitor.is_finished():
131 # url.gethere would break a proxy, so the correct thing to do is
132 # req.path[-1] + queryargs
133 ophandle = req.prepath[-1]
134 reload_target = ophandle + "?output=html"
135 cancel_target = ophandle + "?t=cancel"
136 cancel_button = T.form(action=cancel_target, method="POST",
137 enctype="multipart/form-data")[
138 T.input(type="submit", value="Cancel"),
141 return [T.h2["Operation still running: ",
142 T.a(href=reload_target)["Reload"],