2008-10-22 00:03:07 +00:00
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
import time
|
2008-10-22 00:03:07 +00:00
|
|
|
from zope.interface import implements
|
|
|
|
from nevow import rend, url, tags as T
|
|
|
|
from nevow.inevow import IRequest
|
2008-11-07 04:53:09 +00:00
|
|
|
from twisted.python.failure import Failure
|
|
|
|
from twisted.internet import reactor, defer
|
2008-10-22 05:13:54 +00:00
|
|
|
from twisted.web.http import NOT_FOUND
|
|
|
|
from twisted.web.html import escape
|
|
|
|
from twisted.application import service
|
2008-10-22 00:03:07 +00:00
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
from allmydata.web.common import IOpHandleTable, WebError, \
|
|
|
|
get_root, get_arg, boolean_of_arg
|
2008-10-22 00:03:07 +00:00
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
MINUTE = 60
|
|
|
|
HOUR = 60*MINUTE
|
2010-02-21 01:05:12 +00:00
|
|
|
DAY = 24*HOUR
|
2008-10-22 05:13:54 +00:00
|
|
|
|
|
|
|
(MONITOR, RENDERER, WHEN_ADDED) = range(3)
|
|
|
|
|
|
|
|
class OphandleTable(rend.Page, service.Service):
|
2008-10-22 00:03:07 +00:00
|
|
|
implements(IOpHandleTable)
|
|
|
|
|
2010-02-21 01:05:12 +00:00
|
|
|
UNCOLLECTED_HANDLE_LIFETIME = 4*DAY
|
|
|
|
COLLECTED_HANDLE_LIFETIME = 1*DAY
|
2008-10-22 05:13:54 +00:00
|
|
|
|
2008-10-22 00:03:07 +00:00
|
|
|
def __init__(self):
|
2008-10-22 05:13:54 +00:00
|
|
|
# both of these are indexed by ophandle
|
|
|
|
self.handles = {} # tuple of (monitor, renderer, when_added)
|
|
|
|
self.timers = {}
|
|
|
|
|
|
|
|
def stopService(self):
|
|
|
|
for t in self.timers.values():
|
|
|
|
if t.active():
|
|
|
|
t.cancel()
|
|
|
|
del self.handles # this is not restartable
|
|
|
|
del self.timers
|
|
|
|
return service.Service.stopService(self)
|
|
|
|
|
|
|
|
def add_monitor(self, ctx, monitor, renderer):
|
|
|
|
ophandle = get_arg(ctx, "ophandle")
|
|
|
|
assert ophandle
|
|
|
|
now = time.time()
|
|
|
|
self.handles[ophandle] = (monitor, renderer, now)
|
|
|
|
retain_for = get_arg(ctx, "retain-for", None)
|
|
|
|
if retain_for is not None:
|
|
|
|
self._set_timer(ophandle, int(retain_for))
|
|
|
|
monitor.when_done().addBoth(self._operation_complete, ophandle)
|
2008-10-22 00:03:07 +00:00
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
def _operation_complete(self, res, ophandle):
|
|
|
|
if ophandle in self.handles:
|
|
|
|
if ophandle not in self.timers:
|
|
|
|
# the client has not provided a retain-for= value for this
|
|
|
|
# handle, so we set our own.
|
|
|
|
now = time.time()
|
|
|
|
added = self.handles[ophandle][WHEN_ADDED]
|
|
|
|
when = max(self.UNCOLLECTED_HANDLE_LIFETIME, now - added)
|
|
|
|
self._set_timer(ophandle, when)
|
|
|
|
# if we already have a timer, the client must have provided the
|
|
|
|
# retain-for= value, so don't touch it.
|
2008-10-22 00:03:07 +00:00
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
def redirect_to(self, ctx):
|
|
|
|
ophandle = get_arg(ctx, "ophandle")
|
|
|
|
assert ophandle
|
2008-10-23 22:56:58 +00:00
|
|
|
target = get_root(ctx) + "/operations/" + ophandle
|
2008-10-22 00:03:07 +00:00
|
|
|
output = get_arg(ctx, "output")
|
|
|
|
if output:
|
2008-10-23 22:56:58 +00:00
|
|
|
target = target + "?output=%s" % output
|
2008-10-22 00:03:07 +00:00
|
|
|
return url.URL.fromString(target)
|
|
|
|
|
|
|
|
def childFactory(self, ctx, name):
|
|
|
|
ophandle = name
|
|
|
|
if ophandle not in self.handles:
|
2008-10-22 05:13:54 +00:00
|
|
|
raise WebError("unknown/expired handle '%s'" % escape(ophandle),
|
|
|
|
NOT_FOUND)
|
|
|
|
(monitor, renderer, when_added) = self.handles[ophandle]
|
|
|
|
|
2008-10-23 22:56:58 +00:00
|
|
|
request = IRequest(ctx)
|
2008-10-22 00:03:07 +00:00
|
|
|
t = get_arg(ctx, "t", "status")
|
2008-10-23 22:56:58 +00:00
|
|
|
if t == "cancel" and request.method == "POST":
|
2008-10-22 00:03:07 +00:00
|
|
|
monitor.cancel()
|
2008-10-22 05:13:54 +00:00
|
|
|
# return the status anyways, but release the handle
|
|
|
|
self._release_ophandle(ophandle)
|
|
|
|
|
|
|
|
else:
|
|
|
|
retain_for = get_arg(ctx, "retain-for", None)
|
|
|
|
if retain_for is not None:
|
|
|
|
self._set_timer(ophandle, int(retain_for))
|
|
|
|
|
|
|
|
if monitor.is_finished():
|
|
|
|
if boolean_of_arg(get_arg(ctx, "release-after-complete", "false")):
|
|
|
|
self._release_ophandle(ophandle)
|
|
|
|
if retain_for is None:
|
|
|
|
# this GET is collecting the ophandle, so change its timer
|
|
|
|
self._set_timer(ophandle, self.COLLECTED_HANDLE_LIFETIME)
|
|
|
|
|
2008-11-07 04:53:09 +00:00
|
|
|
status = monitor.get_status()
|
|
|
|
if isinstance(status, Failure):
|
|
|
|
return defer.fail(status)
|
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
return renderer
|
|
|
|
|
|
|
|
def _set_timer(self, ophandle, when):
|
|
|
|
if ophandle in self.timers and self.timers[ophandle].active():
|
|
|
|
self.timers[ophandle].cancel()
|
|
|
|
t = reactor.callLater(when, self._release_ophandle, ophandle)
|
|
|
|
self.timers[ophandle] = t
|
2008-10-22 00:03:07 +00:00
|
|
|
|
2008-10-22 05:13:54 +00:00
|
|
|
def _release_ophandle(self, ophandle):
|
|
|
|
if ophandle in self.timers and self.timers[ophandle].active():
|
|
|
|
self.timers[ophandle].cancel()
|
|
|
|
self.timers.pop(ophandle, None)
|
2009-01-15 02:14:59 +00:00
|
|
|
self.handles.pop(ophandle, None)
|
2008-10-22 00:03:07 +00:00
|
|
|
|
|
|
|
class ReloadMixin:
|
2008-10-22 16:48:42 +00:00
|
|
|
REFRESH_TIME = 1*MINUTE
|
|
|
|
|
|
|
|
def render_refresh(self, ctx, data):
|
|
|
|
if self.monitor.is_finished():
|
|
|
|
return ""
|
|
|
|
# dreid suggests ctx.tag(**dict([("http-equiv", "refresh")]))
|
|
|
|
# but I can't tell if he's joking or not
|
|
|
|
ctx.tag.attributes["http-equiv"] = "refresh"
|
|
|
|
ctx.tag.attributes["content"] = str(self.REFRESH_TIME)
|
|
|
|
return ctx.tag
|
2008-10-22 00:03:07 +00:00
|
|
|
|
|
|
|
def render_reload(self, ctx, data):
|
|
|
|
if self.monitor.is_finished():
|
|
|
|
return ""
|
|
|
|
req = IRequest(ctx)
|
|
|
|
# url.gethere would break a proxy, so the correct thing to do is
|
|
|
|
# req.path[-1] + queryargs
|
|
|
|
ophandle = req.prepath[-1]
|
2008-10-23 22:56:58 +00:00
|
|
|
reload_target = ophandle + "?output=html"
|
2008-10-22 00:03:07 +00:00
|
|
|
cancel_target = ophandle + "?t=cancel"
|
|
|
|
cancel_button = T.form(action=cancel_target, method="POST",
|
|
|
|
enctype="multipart/form-data")[
|
|
|
|
T.input(type="submit", value="Cancel"),
|
|
|
|
]
|
|
|
|
|
|
|
|
return [T.h2["Operation still running: ",
|
|
|
|
T.a(href=reload_target)["Reload"],
|
|
|
|
],
|
|
|
|
cancel_button,
|
|
|
|
]
|