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. ]