source file: /home/buildslave/tahoe/edgy/build/src/allmydata/web/root.py
file stats: 198 lines, 191 executed: 96.5% covered
1.
2. import time
3.
4. from twisted.internet import address
5. from twisted.web import http
6. from nevow import rend, url, tags as T
7. from nevow.inevow import IRequest
8. from nevow.static import File as nevow_File # TODO: merge with static.File?
9. from nevow.util import resource_filename
10. from formless import webform
11.
12. import allmydata # to display import path
13. from allmydata import get_package_versions_string
14. from allmydata import provisioning
15. from allmydata.util import idlib, log
16. from allmydata.interfaces import IFileNode
17. from allmydata.web import filenode, directory, unlinked, status
18. from allmydata.web.common import abbreviate_size, IClient, getxmlfile, \
19. WebError, get_arg, RenderMixin
20.
21.
22.
23. class URIHandler(RenderMixin, rend.Page):
24. # I live at /uri . There are several operations defined on /uri itself,
25. # mostly involved with creation of unlinked files and directories.
26.
27. def render_GET(self, ctx):
28. req = IRequest(ctx)
29. uri = get_arg(req, "uri", None)
30. if uri is None:
31. raise WebError("GET /uri requires uri=")
32. there = url.URL.fromContext(ctx)
33. there = there.clear("uri")
34. # I thought about escaping the childcap that we attach to the URL
35. # here, but it seems that nevow does that for us.
36. there = there.child(uri)
37. return there
38.
39. def render_PUT(self, ctx):
40. req = IRequest(ctx)
41. # either "PUT /uri" to create an unlinked file, or
42. # "PUT /uri?t=mkdir" to create an unlinked directory
43. t = get_arg(req, "t", "").strip()
44. if t == "":
45. mutable = bool(get_arg(req, "mutable", "").strip())
46. if mutable:
47. return unlinked.PUTUnlinkedSSK(ctx)
48. else:
49. return unlinked.PUTUnlinkedCHK(ctx)
50. if t == "mkdir":
51. return unlinked.PUTUnlinkedCreateDirectory(ctx)
52. errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
53. "and POST?t=mkdir")
54. raise WebError(errmsg, http.BAD_REQUEST)
55.
56. def render_POST(self, ctx):
57. # "POST /uri?t=upload&file=newfile" to upload an
58. # unlinked file or "POST /uri?t=mkdir" to create a
59. # new directory
60. req = IRequest(ctx)
61. t = get_arg(req, "t", "").strip()
62. if t in ("", "upload"):
63. mutable = bool(get_arg(req, "mutable", "").strip())
64. if mutable:
65. return unlinked.POSTUnlinkedSSK(ctx)
66. else:
67. return unlinked.POSTUnlinkedCHK(ctx)
68. if t == "mkdir":
69. return unlinked.POSTUnlinkedCreateDirectory(ctx)
70. errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
71. "and POST?t=mkdir")
72. raise WebError(errmsg, http.BAD_REQUEST)
73.
74. def childFactory(self, ctx, name):
75. # 'name' is expected to be a URI
76. client = IClient(ctx)
77. try:
78. node = client.create_node_from_uri(name)
79. return directory.make_handler_for(node)
80. except (TypeError, AssertionError):
81. raise WebError("'%s' is not a valid file- or directory- cap"
82. % name)
83.
84. class FileHandler(rend.Page):
85. # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
86. # file can be downloaded correctly by tools like "wget".
87.
88. def childFactory(self, ctx, name):
89. req = IRequest(ctx)
90. if req.method not in ("GET", "HEAD"):
91. raise WebError("/file can only be used with GET or HEAD")
92. # 'name' must be a file URI
93. client = IClient(ctx)
94. try:
95. node = client.create_node_from_uri(name)
96. except (TypeError, AssertionError):
97. raise WebError("'%s' is not a valid file- or directory- cap"
98. % name)
99. if not IFileNode.providedBy(node):
100. raise WebError("'%s' is not a file-cap" % name)
101. return filenode.FileNodeDownloadHandler(node)
102.
103. def renderHTTP(self, ctx):
104. raise WebError("/file must be followed by a file-cap and a name",
105. http.NOT_FOUND)
106.
107. class IncidentReporter(RenderMixin, rend.Page):
108. def render_POST(self, ctx):
109. req = IRequest(ctx)
110. log.msg(format="User reports incident through web page: %(details)s",
111. details=get_arg(req, "details", ""),
112. level=log.WEIRD, umid="LkD9Pw")
113. req.setHeader("content-type", "text/plain")
114. return "Thank you for your report!"
115.
116. class Root(rend.Page):
117.
118. addSlash = True
119. docFactory = getxmlfile("welcome.xhtml")
120.
121. child_uri = URIHandler()
122. child_cap = URIHandler()
123. child_file = FileHandler()
124. child_named = FileHandler()
125.
126. child_webform_css = webform.defaultCSS
127. child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
128.
129. child_provisioning = provisioning.ProvisioningTool()
130. child_status = status.Status()
131. child_helper_status = status.HelperStatus()
132. child_statistics = status.Statistics()
133.
134. child_report_incident = IncidentReporter()
135.
136. def data_version(self, ctx, data):
137. return get_package_versions_string()
138. def data_import_path(self, ctx, data):
139. return str(allmydata)
140. def data_my_nodeid(self, ctx, data):
141. return idlib.nodeid_b2a(IClient(ctx).nodeid)
142. def data_my_nickname(self, ctx, data):
143. return IClient(ctx).nickname
144.
145. def render_services(self, ctx, data):
146. ul = T.ul()
147. client = IClient(ctx)
148. try:
149. ss = client.getServiceNamed("storage")
150. allocated_s = abbreviate_size(ss.allocated_size())
151. allocated = "about %s allocated" % allocated_s
152. sizelimit = "no size limit"
153. if ss.sizelimit is not None:
154. sizelimit = "size limit is %s" % abbreviate_size(ss.sizelimit)
155. ul[T.li["Storage Server: %s, %s" % (allocated, sizelimit)]]
156. except KeyError:
157. ul[T.li["Not running storage server"]]
158.
159. try:
160. h = client.getServiceNamed("helper")
161. stats = h.get_stats()
162. active_uploads = stats["chk_upload_helper.active_uploads"]
163. ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
164. except KeyError:
165. ul[T.li["Not running helper"]]
166.
167. return ctx.tag[ul]
168.
169. def data_introducer_furl(self, ctx, data):
170. return IClient(ctx).introducer_furl
171. def data_connected_to_introducer(self, ctx, data):
172. if IClient(ctx).connected_to_introducer():
173. return "yes"
174. return "no"
175.
176. def data_helper_furl(self, ctx, data):
177. try:
178. uploader = IClient(ctx).getServiceNamed("uploader")
179. except KeyError:
180. return None
181. furl, connected = uploader.get_helper_info()
182. return furl
183. def data_connected_to_helper(self, ctx, data):
184. try:
185. uploader = IClient(ctx).getServiceNamed("uploader")
186. except KeyError:
187. return "no" # we don't even have an Uploader
188. furl, connected = uploader.get_helper_info()
189. if connected:
190. return "yes"
191. return "no"
192.
193. def data_known_storage_servers(self, ctx, data):
194. ic = IClient(ctx).introducer_client
195. servers = [c
196. for c in ic.get_all_connectors().values()
197. if c.service_name == "storage"]
198. return len(servers)
199.
200. def data_connected_storage_servers(self, ctx, data):
201. ic = IClient(ctx).introducer_client
202. return len(ic.get_all_connections_for("storage"))
203.
204. def data_services(self, ctx, data):
205. ic = IClient(ctx).introducer_client
206. c = [ (service_name, nodeid, rsc)
207. for (nodeid, service_name), rsc
208. in ic.get_all_connectors().items() ]
209. c.sort()
210. return c
211.
212. def render_service_row(self, ctx, data):
213. (service_name, nodeid, rsc) = data
214. ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
215. ctx.fillSlots("nickname", rsc.nickname)
216. if rsc.rref:
217. rhost = rsc.remote_host
218. if nodeid == IClient(ctx).nodeid:
219. rhost_s = "(loopback)"
220. elif isinstance(rhost, address.IPv4Address):
221. rhost_s = "%s:%d" % (rhost.host, rhost.port)
222. else:
223. rhost_s = str(rhost)
224. connected = "Yes: to " + rhost_s
225. since = rsc.last_connect_time
226. else:
227. connected = "No"
228. since = rsc.last_loss_time
229.
230. TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
231. ctx.fillSlots("connected", connected)
232. ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
233. ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
234. time.localtime(rsc.announcement_time)))
235. ctx.fillSlots("version", rsc.version)
236. ctx.fillSlots("service_name", rsc.service_name)
237.
238. return ctx.tag
239.
240. def render_download_form(self, ctx, data):
241. # this is a form where users can download files by URI
242. form = T.form(action="uri", method="get",
243. enctype="multipart/form-data")[
244. T.fieldset[
245. T.legend(class_="freeform-form-label")["Download a file"],
246. "URI to download: ",
247. T.input(type="text", name="uri"), " ",
248. "Filename to download as: ",
249. T.input(type="text", name="filename"), " ",
250. T.input(type="submit", value="Download!"),
251. ]]
252. return T.div[form]
253.
254. def render_view_form(self, ctx, data):
255. # this is a form where users can download files by URI, or jump to a
256. # named directory
257. form = T.form(action="uri", method="get",
258. enctype="multipart/form-data")[
259. T.fieldset[
260. T.legend(class_="freeform-form-label")["View a file or directory"],
261. "URI to view: ",
262. T.input(type="text", name="uri"), " ",
263. T.input(type="submit", value="View!"),
264. ]]
265. return T.div[form]
266.
267. def render_upload_form(self, ctx, data):
268. # this is a form where users can upload unlinked files
269. form = T.form(action="uri", method="post",
270. enctype="multipart/form-data")[
271. T.fieldset[
272. T.legend(class_="freeform-form-label")["Upload a file"],
273. "Choose a file: ",
274. T.input(type="file", name="file", class_="freeform-input-file"),
275. T.input(type="hidden", name="t", value="upload"),
276. " Mutable?:", T.input(type="checkbox", name="mutable"),
277. T.input(type="submit", value="Upload!"),
278. ]]
279. return T.div[form]
280.
281. def render_mkdir_form(self, ctx, data):
282. # this is a form where users can create new directories
283. form = T.form(action="uri", method="post",
284. enctype="multipart/form-data")[
285. T.fieldset[
286. T.legend(class_="freeform-form-label")["Create a directory"],
287. T.input(type="hidden", name="t", value="mkdir"),
288. T.input(type="hidden", name="redirect_to_result", value="true"),
289. T.input(type="submit", value="Create Directory!"),
290. ]]
291. return T.div[form]
292.
293. def render_incident_button(self, ctx, data):
294. # this button triggers a foolscap-logging "incident"
295. form = T.form(action="report_incident", method="post",
296. enctype="multipart/form-data")[
297. T.fieldset[
298. T.legend(class_="freeform-form-label")["Report an Incident"],
299. T.input(type="hidden", name="t", value="report-incident"),
300. "What went wrong?: ",
301. T.input(type="text", name="details"), " ",
302. T.input(type="submit", value="Report!"),
303. ]]
304. return T.div[form]