]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/status.py
webish: show storage sizelimit, abbreviate current usage
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / status.py
1
2 import time
3 from twisted.internet import defer
4 from nevow import rend, tags as T
5 from allmydata.util import base32, idlib
6 from allmydata.web.common import IClient, getxmlfile, abbreviate_time, \
7      abbreviate_rate
8 from allmydata.interfaces import IUploadStatus, IDownloadStatus, \
9      IPublishStatus, IRetrieveStatus
10
11 def plural(sequence_or_length):
12     if isinstance(sequence_or_length, int):
13         length = sequence_or_length
14     else:
15         length = len(sequence_or_length)
16     if length == 1:
17         return ""
18     return "s"
19
20 class RateAndTimeMixin:
21
22     def render_time(self, ctx, data):
23         return abbreviate_time(data)
24
25     def render_rate(self, ctx, data):
26         return abbreviate_rate(data)
27
28 class UploadResultsRendererMixin(RateAndTimeMixin):
29     # this requires a method named 'upload_results'
30
31     def render_pushed_shares(self, ctx, data):
32         d = self.upload_results()
33         d.addCallback(lambda res: res.pushed_shares)
34         return d
35
36     def render_preexisting_shares(self, ctx, data):
37         d = self.upload_results()
38         d.addCallback(lambda res: res.preexisting_shares)
39         return d
40
41     def render_sharemap(self, ctx, data):
42         d = self.upload_results()
43         d.addCallback(lambda res: res.sharemap)
44         def _render(sharemap):
45             if sharemap is None:
46                 return "None"
47             l = T.ul()
48             for shnum in sorted(sharemap.keys()):
49                 l[T.li["%d -> %s" % (shnum, sharemap[shnum])]]
50             return l
51         d.addCallback(_render)
52         return d
53
54     def render_servermap(self, ctx, data):
55         d = self.upload_results()
56         d.addCallback(lambda res: res.servermap)
57         def _render(servermap):
58             if servermap is None:
59                 return "None"
60             l = T.ul()
61             for peerid in sorted(servermap.keys()):
62                 peerid_s = idlib.shortnodeid_b2a(peerid)
63                 shares_s = ",".join(["#%d" % shnum
64                                      for shnum in servermap[peerid]])
65                 l[T.li["[%s] got share%s: %s" % (peerid_s,
66                                                  plural(servermap[peerid]),
67                                                  shares_s)]]
68             return l
69         d.addCallback(_render)
70         return d
71
72     def data_file_size(self, ctx, data):
73         d = self.upload_results()
74         d.addCallback(lambda res: res.file_size)
75         return d
76
77     def _get_time(self, name):
78         d = self.upload_results()
79         d.addCallback(lambda res: res.timings.get(name))
80         return d
81
82     def data_time_total(self, ctx, data):
83         return self._get_time("total")
84
85     def data_time_storage_index(self, ctx, data):
86         return self._get_time("storage_index")
87
88     def data_time_contacting_helper(self, ctx, data):
89         return self._get_time("contacting_helper")
90
91     def data_time_existence_check(self, ctx, data):
92         return self._get_time("existence_check")
93
94     def data_time_cumulative_fetch(self, ctx, data):
95         return self._get_time("cumulative_fetch")
96
97     def data_time_helper_total(self, ctx, data):
98         return self._get_time("helper_total")
99
100     def data_time_peer_selection(self, ctx, data):
101         return self._get_time("peer_selection")
102
103     def data_time_total_encode_and_push(self, ctx, data):
104         return self._get_time("total_encode_and_push")
105
106     def data_time_cumulative_encoding(self, ctx, data):
107         return self._get_time("cumulative_encoding")
108
109     def data_time_cumulative_sending(self, ctx, data):
110         return self._get_time("cumulative_sending")
111
112     def data_time_hashes_and_close(self, ctx, data):
113         return self._get_time("hashes_and_close")
114
115     def _get_rate(self, name):
116         d = self.upload_results()
117         def _convert(r):
118             file_size = r.file_size
119             time = r.timings.get(name)
120             if time is None:
121                 return None
122             try:
123                 return 1.0 * file_size / time
124             except ZeroDivisionError:
125                 return None
126         d.addCallback(_convert)
127         return d
128
129     def data_rate_total(self, ctx, data):
130         return self._get_rate("total")
131
132     def data_rate_storage_index(self, ctx, data):
133         return self._get_rate("storage_index")
134
135     def data_rate_encode(self, ctx, data):
136         return self._get_rate("cumulative_encoding")
137
138     def data_rate_push(self, ctx, data):
139         return self._get_rate("cumulative_sending")
140
141     def data_rate_encode_and_push(self, ctx, data):
142         d = self.upload_results()
143         def _convert(r):
144             file_size = r.file_size
145             if file_size is None:
146                 return None
147             time1 = r.timings.get("cumulative_encoding")
148             if time1 is None:
149                 return None
150             time2 = r.timings.get("cumulative_sending")
151             if time2 is None:
152                 return None
153             try:
154                 return 1.0 * file_size / (time1+time2)
155             except ZeroDivisionError:
156                 return None
157         d.addCallback(_convert)
158         return d
159
160     def data_rate_ciphertext_fetch(self, ctx, data):
161         d = self.upload_results()
162         def _convert(r):
163             fetch_size = r.ciphertext_fetched
164             if fetch_size is None:
165                 return None
166             time = r.timings.get("cumulative_fetch")
167             if time is None:
168                 return None
169             try:
170                 return 1.0 * fetch_size / time
171             except ZeroDivisionError:
172                 return None
173         d.addCallback(_convert)
174         return d
175
176 class UploadStatusPage(UploadResultsRendererMixin, rend.Page):
177     docFactory = getxmlfile("upload-status.xhtml")
178
179     def __init__(self, data):
180         rend.Page.__init__(self, data)
181         self.upload_status = data
182
183     def upload_results(self):
184         return defer.maybeDeferred(self.upload_status.get_results)
185
186     def render_results(self, ctx, data):
187         d = self.upload_results()
188         def _got_results(results):
189             if results:
190                 return ctx.tag
191             return ""
192         d.addCallback(_got_results)
193         return d
194
195     def render_started(self, ctx, data):
196         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
197         started_s = time.strftime(TIME_FORMAT,
198                                   time.localtime(data.get_started()))
199         return started_s
200
201     def render_si(self, ctx, data):
202         si_s = base32.b2a_or_none(data.get_storage_index())
203         if si_s is None:
204             si_s = "(None)"
205         return si_s
206
207     def render_helper(self, ctx, data):
208         return {True: "Yes",
209                 False: "No"}[data.using_helper()]
210
211     def render_total_size(self, ctx, data):
212         size = data.get_size()
213         if size is None:
214             size = "(unknown)"
215         return size
216
217     def render_progress_hash(self, ctx, data):
218         progress = data.get_progress()[0]
219         # TODO: make an ascii-art bar
220         return "%.1f%%" % (100.0 * progress)
221
222     def render_progress_ciphertext(self, ctx, data):
223         progress = data.get_progress()[1]
224         # TODO: make an ascii-art bar
225         return "%.1f%%" % (100.0 * progress)
226
227     def render_progress_encode_push(self, ctx, data):
228         progress = data.get_progress()[2]
229         # TODO: make an ascii-art bar
230         return "%.1f%%" % (100.0 * progress)
231
232     def render_status(self, ctx, data):
233         return data.get_status()
234
235 class DownloadResultsRendererMixin(RateAndTimeMixin):
236     # this requires a method named 'download_results'
237
238     def render_servermap(self, ctx, data):
239         d = self.download_results()
240         d.addCallback(lambda res: res.servermap)
241         def _render(servermap):
242             if servermap is None:
243                 return "None"
244             l = T.ul()
245             for peerid in sorted(servermap.keys()):
246                 peerid_s = idlib.shortnodeid_b2a(peerid)
247                 shares_s = ",".join(["#%d" % shnum
248                                      for shnum in servermap[peerid]])
249                 l[T.li["[%s] has share%s: %s" % (peerid_s,
250                                                  plural(servermap[peerid]),
251                                                  shares_s)]]
252             return l
253         d.addCallback(_render)
254         return d
255
256     def render_servers_used(self, ctx, data):
257         d = self.download_results()
258         d.addCallback(lambda res: res.servers_used)
259         def _got(servers_used):
260             if not servers_used:
261                 return ""
262             peerids_s = ", ".join(["[%s]" % idlib.shortnodeid_b2a(peerid)
263                                    for peerid in servers_used])
264             return T.li["Servers Used: ", peerids_s]
265         d.addCallback(_got)
266         return d
267
268     def render_problems(self, ctx, data):
269         d = self.download_results()
270         d.addCallback(lambda res: res.server_problems)
271         def _got(server_problems):
272             if not server_problems:
273                 return ""
274             l = T.ul()
275             for peerid in sorted(server_problems.keys()):
276                 peerid_s = idlib.shortnodeid_b2a(peerid)
277                 l[T.li["[%s]: %s" % (peerid_s, server_problems[peerid])]]
278             return T.li["Server Problems:", l]
279         d.addCallback(_got)
280         return d
281
282     def data_file_size(self, ctx, data):
283         d = self.download_results()
284         d.addCallback(lambda res: res.file_size)
285         return d
286
287     def _get_time(self, name):
288         d = self.download_results()
289         d.addCallback(lambda res: res.timings.get(name))
290         return d
291
292     def data_time_total(self, ctx, data):
293         return self._get_time("total")
294
295     def data_time_peer_selection(self, ctx, data):
296         return self._get_time("peer_selection")
297
298     def data_time_uri_extension(self, ctx, data):
299         return self._get_time("uri_extension")
300
301     def data_time_hashtrees(self, ctx, data):
302         return self._get_time("hashtrees")
303
304     def data_time_segments(self, ctx, data):
305         return self._get_time("segments")
306
307     def data_time_cumulative_fetch(self, ctx, data):
308         return self._get_time("cumulative_fetch")
309
310     def data_time_cumulative_decode(self, ctx, data):
311         return self._get_time("cumulative_decode")
312
313     def data_time_cumulative_decrypt(self, ctx, data):
314         return self._get_time("cumulative_decrypt")
315
316     def _get_rate(self, name):
317         d = self.download_results()
318         def _convert(r):
319             file_size = r.file_size
320             time = r.timings.get(name)
321             if time is None:
322                 return None
323             try:
324                 return 1.0 * file_size / time
325             except ZeroDivisionError:
326                 return None
327         d.addCallback(_convert)
328         return d
329
330     def data_rate_total(self, ctx, data):
331         return self._get_rate("total")
332
333     def data_rate_segments(self, ctx, data):
334         return self._get_rate("segments")
335
336     def data_rate_fetch(self, ctx, data):
337         return self._get_rate("cumulative_fetch")
338
339     def data_rate_decode(self, ctx, data):
340         return self._get_rate("cumulative_decode")
341
342     def data_rate_decrypt(self, ctx, data):
343         return self._get_rate("cumulative_decrypt")
344
345     def render_server_timings(self, ctx, data):
346         d = self.download_results()
347         d.addCallback(lambda res: res.timings.get("fetch_per_server"))
348         def _render(per_server):
349             if per_server is None:
350                 return ""
351             l = T.ul()
352             for peerid in sorted(per_server.keys()):
353                 peerid_s = idlib.shortnodeid_b2a(peerid)
354                 times_s = ", ".join([self.render_time(None, t)
355                                      for t in per_server[peerid]])
356                 l[T.li["[%s]: %s" % (peerid_s, times_s)]]
357             return T.li["Per-Server Segment Fetch Response Times: ", l]
358         d.addCallback(_render)
359         return d
360
361 class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page):
362     docFactory = getxmlfile("download-status.xhtml")
363
364     def __init__(self, data):
365         rend.Page.__init__(self, data)
366         self.download_status = data
367
368     def download_results(self):
369         return defer.maybeDeferred(self.download_status.get_results)
370
371     def render_results(self, ctx, data):
372         d = self.download_results()
373         def _got_results(results):
374             if results:
375                 return ctx.tag
376             return ""
377         d.addCallback(_got_results)
378         return d
379
380     def render_started(self, ctx, data):
381         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
382         started_s = time.strftime(TIME_FORMAT,
383                                   time.localtime(data.get_started()))
384         return started_s
385
386     def render_si(self, ctx, data):
387         si_s = base32.b2a_or_none(data.get_storage_index())
388         if si_s is None:
389             si_s = "(None)"
390         return si_s
391
392     def render_helper(self, ctx, data):
393         return {True: "Yes",
394                 False: "No"}[data.using_helper()]
395
396     def render_total_size(self, ctx, data):
397         size = data.get_size()
398         if size is None:
399             size = "(unknown)"
400         return size
401
402     def render_progress(self, ctx, data):
403         progress = data.get_progress()
404         # TODO: make an ascii-art bar
405         return "%.1f%%" % (100.0 * progress)
406
407     def render_status(self, ctx, data):
408         return data.get_status()
409
410 class RetrieveStatusPage(rend.Page, RateAndTimeMixin):
411     docFactory = getxmlfile("retrieve-status.xhtml")
412
413     def __init__(self, data):
414         rend.Page.__init__(self, data)
415         self.retrieve_status = data
416
417     def render_started(self, ctx, data):
418         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
419         started_s = time.strftime(TIME_FORMAT,
420                                   time.localtime(data.get_started()))
421         return started_s
422
423     def render_si(self, ctx, data):
424         si_s = base32.b2a_or_none(data.get_storage_index())
425         if si_s is None:
426             si_s = "(None)"
427         return si_s
428
429     def render_helper(self, ctx, data):
430         return {True: "Yes",
431                 False: "No"}[data.using_helper()]
432
433     def render_current_size(self, ctx, data):
434         size = data.get_size()
435         if size is None:
436             size = "(unknown)"
437         return size
438
439     def render_progress(self, ctx, data):
440         progress = data.get_progress()
441         # TODO: make an ascii-art bar
442         return "%.1f%%" % (100.0 * progress)
443
444     def render_status(self, ctx, data):
445         return data.get_status()
446
447     def render_encoding(self, ctx, data):
448         k, n = data.get_encoding()
449         return ctx.tag["Encoding: %s of %s" % (k, n)]
450
451     def render_search_distance(self, ctx, data):
452         d = data.get_search_distance()
453         return ctx.tag["Search Distance: %s peer%s" % (d, plural(d))]
454
455     def render_problems(self, ctx, data):
456         problems = data.problems
457         if not problems:
458             return ""
459         l = T.ul()
460         for peerid in sorted(problems.keys()):
461             peerid_s = idlib.shortnodeid_b2a(peerid)
462             l[T.li["[%s]: %s" % (peerid_s, problems[peerid])]]
463         return ctx.tag["Server Problems:", l]
464
465     def _get_rate(self, data, name):
466         file_size = self.retrieve_status.get_size()
467         time = self.retrieve_status.timings.get(name)
468         if time is None:
469             return None
470         try:
471             return 1.0 * file_size / time
472         except ZeroDivisionError:
473             return None
474
475     def data_time_total(self, ctx, data):
476         return self.retrieve_status.timings.get("total")
477     def data_rate_total(self, ctx, data):
478         return self._get_rate(data, "total")
479
480     def data_time_peer_selection(self, ctx, data):
481         return self.retrieve_status.timings.get("peer_selection")
482
483     def data_time_fetch(self, ctx, data):
484         return self.retrieve_status.timings.get("fetch")
485     def data_rate_fetch(self, ctx, data):
486         return self._get_rate(data, "fetch")
487
488     def data_time_cumulative_verify(self, ctx, data):
489         return self.retrieve_status.timings.get("cumulative_verify")
490
491     def data_time_decode(self, ctx, data):
492         return self.retrieve_status.timings.get("decode")
493     def data_rate_decode(self, ctx, data):
494         return self._get_rate(data, "decode")
495
496     def data_time_decrypt(self, ctx, data):
497         return self.retrieve_status.timings.get("decrypt")
498     def data_rate_decrypt(self, ctx, data):
499         return self._get_rate(data, "decrypt")
500
501     def render_server_timings(self, ctx, data):
502         per_server = self.retrieve_status.timings.get("fetch_per_server")
503         if not per_server:
504             return ""
505         l = T.ul()
506         for peerid in sorted(per_server.keys()):
507             peerid_s = idlib.shortnodeid_b2a(peerid)
508             times_s = ", ".join([self.render_time(None, t)
509                                  for t in per_server[peerid]])
510             l[T.li["[%s]: %s" % (peerid_s, times_s)]]
511         return T.li["Per-Server Fetch Response Times: ", l]
512
513
514 class PublishStatusPage(rend.Page, RateAndTimeMixin):
515     docFactory = getxmlfile("publish-status.xhtml")
516
517     def __init__(self, data):
518         rend.Page.__init__(self, data)
519         self.publish_status = data
520
521     def render_started(self, ctx, data):
522         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
523         started_s = time.strftime(TIME_FORMAT,
524                                   time.localtime(data.get_started()))
525         return started_s
526
527     def render_si(self, ctx, data):
528         si_s = base32.b2a_or_none(data.get_storage_index())
529         if si_s is None:
530             si_s = "(None)"
531         return si_s
532
533     def render_helper(self, ctx, data):
534         return {True: "Yes",
535                 False: "No"}[data.using_helper()]
536
537     def render_current_size(self, ctx, data):
538         size = data.get_size()
539         if size is None:
540             size = "(unknown)"
541         return size
542
543     def render_progress(self, ctx, data):
544         progress = data.get_progress()
545         # TODO: make an ascii-art bar
546         return "%.1f%%" % (100.0 * progress)
547
548     def render_status(self, ctx, data):
549         return data.get_status()
550
551     def render_encoding(self, ctx, data):
552         k, n = data.get_encoding()
553         return ctx.tag["Encoding: %s of %s" % (k, n)]
554
555     def render_peers_queried(self, ctx, data):
556         return ctx.tag["Peers Queried: ", data.peers_queried]
557
558     def render_sharemap(self, ctx, data):
559         sharemap = data.sharemap
560         if sharemap is None:
561             return ctx.tag["None"]
562         l = T.ul()
563         for shnum in sorted(sharemap.keys()):
564             l[T.li["%d -> Placed on " % shnum,
565                    ", ".join(["[%s]" % idlib.shortnodeid_b2a(peerid)
566                               for (peerid,seqnum,root_hash)
567                               in sharemap[shnum]])]]
568         return ctx.tag["Sharemap:", l]
569
570     def render_problems(self, ctx, data):
571         problems = data.problems
572         if not problems:
573             return ""
574         l = T.ul()
575         for peerid in sorted(problems.keys()):
576             peerid_s = idlib.shortnodeid_b2a(peerid)
577             l[T.li["[%s]: %s" % (peerid_s, problems[peerid])]]
578         return ctx.tag["Server Problems:", l]
579
580     def _get_rate(self, data, name):
581         file_size = self.publish_status.get_size()
582         time = self.publish_status.timings.get(name)
583         if time is None:
584             return None
585         try:
586             return 1.0 * file_size / time
587         except ZeroDivisionError:
588             return None
589
590     def data_time_total(self, ctx, data):
591         return self.publish_status.timings.get("total")
592     def data_rate_total(self, ctx, data):
593         return self._get_rate(data, "total")
594
595     def data_time_setup(self, ctx, data):
596         return self.publish_status.timings.get("setup")
597
598     def data_time_query(self, ctx, data):
599         return self.publish_status.timings.get("query")
600
601     def data_time_privkey(self, ctx, data):
602         return self.publish_status.timings.get("privkey")
603
604     def data_time_privkey_fetch(self, ctx, data):
605         return self.publish_status.timings.get("privkey_fetch")
606     def render_privkey_from(self, ctx, data):
607         peerid = data.privkey_from
608         if peerid:
609             return " (got from [%s])" % idlib.shortnodeid_b2a(peerid)
610         else:
611             return ""
612
613     def data_time_encrypt(self, ctx, data):
614         return self.publish_status.timings.get("encrypt")
615     def data_rate_encrypt(self, ctx, data):
616         return self._get_rate(data, "encrypt")
617
618     def data_time_encode(self, ctx, data):
619         return self.publish_status.timings.get("encode")
620     def data_rate_encode(self, ctx, data):
621         return self._get_rate(data, "encode")
622
623     def data_time_pack(self, ctx, data):
624         return self.publish_status.timings.get("pack")
625     def data_rate_pack(self, ctx, data):
626         return self._get_rate(data, "pack")
627     def data_time_sign(self, ctx, data):
628         return self.publish_status.timings.get("sign")
629
630     def data_time_push(self, ctx, data):
631         return self.publish_status.timings.get("push")
632     def data_rate_push(self, ctx, data):
633         return self._get_rate(data, "push")
634
635     def data_initial_read_size(self, ctx, data):
636         return self.publish_status.initial_read_size
637
638     def render_server_timings(self, ctx, data):
639         per_server = self.publish_status.timings.get("per_server")
640         if not per_server:
641             return ""
642         l = T.ul()
643         for peerid in sorted(per_server.keys()):
644             peerid_s = idlib.shortnodeid_b2a(peerid)
645             times = []
646             for op,t in per_server[peerid]:
647                 if op == "read":
648                     times.append( "(" + self.render_time(None, t) + ")" )
649                 else:
650                     times.append( self.render_time(None, t) )
651             times_s = ", ".join(times)
652             l[T.li["[%s]: %s" % (peerid_s, times_s)]]
653         return T.li["Per-Server Response Times: ", l]
654
655
656 class Status(rend.Page):
657     docFactory = getxmlfile("status.xhtml")
658     addSlash = True
659
660     def data_active_operations(self, ctx, data):
661         active =  (IClient(ctx).list_active_uploads() +
662                    IClient(ctx).list_active_downloads() +
663                    IClient(ctx).list_active_publish() +
664                    IClient(ctx).list_active_retrieve())
665         return active
666
667     def data_recent_operations(self, ctx, data):
668         recent = [o for o in (IClient(ctx).list_recent_uploads() +
669                               IClient(ctx).list_recent_downloads() +
670                               IClient(ctx).list_recent_publish() +
671                               IClient(ctx).list_recent_retrieve())
672                   if not o.get_active()]
673         recent.sort(lambda a,b: cmp(a.get_started(), b.get_started()))
674         recent.reverse()
675         return recent
676
677     def render_row(self, ctx, data):
678         s = data
679
680         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
681         started_s = time.strftime(TIME_FORMAT,
682                                   time.localtime(s.get_started()))
683         ctx.fillSlots("started", started_s)
684
685         si_s = base32.b2a_or_none(s.get_storage_index())
686         if si_s is None:
687             si_s = "(None)"
688         ctx.fillSlots("si", si_s)
689         ctx.fillSlots("helper", {True: "Yes",
690                                  False: "No"}[s.using_helper()])
691
692         size = s.get_size()
693         if size is None:
694             size = "(unknown)"
695         ctx.fillSlots("total_size", size)
696
697         progress = data.get_progress()
698         if IUploadStatus.providedBy(data):
699             link = "up-%d" % data.get_counter()
700             ctx.fillSlots("type", "upload")
701             # TODO: make an ascii-art bar
702             (chk, ciphertext, encandpush) = progress
703             progress_s = ("hash: %.1f%%, ciphertext: %.1f%%, encode: %.1f%%" %
704                           ( (100.0 * chk),
705                             (100.0 * ciphertext),
706                             (100.0 * encandpush) ))
707             ctx.fillSlots("progress", progress_s)
708         elif IDownloadStatus.providedBy(data):
709             link = "down-%d" % data.get_counter()
710             ctx.fillSlots("type", "download")
711             ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress))
712         elif IPublishStatus.providedBy(data):
713             link = "publish-%d" % data.get_counter()
714             ctx.fillSlots("type", "publish")
715             ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress))
716         else:
717             assert IRetrieveStatus.providedBy(data)
718             ctx.fillSlots("type", "retrieve")
719             link = "retrieve-%d" % data.get_counter()
720             ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress))
721         ctx.fillSlots("status", T.a(href=link)[s.get_status()])
722         return ctx.tag
723
724     def childFactory(self, ctx, name):
725         client = IClient(ctx)
726         stype,count_s = name.split("-")
727         count = int(count_s)
728         if stype == "up":
729             for s in client.list_recent_uploads():
730                 if s.get_counter() == count:
731                     return UploadStatusPage(s)
732             for s in client.list_all_uploads():
733                 if s.get_counter() == count:
734                     return UploadStatusPage(s)
735         if stype == "down":
736             for s in client.list_recent_downloads():
737                 if s.get_counter() == count:
738                     return DownloadStatusPage(s)
739             for s in client.list_all_downloads():
740                 if s.get_counter() == count:
741                     return DownloadStatusPage(s)
742         if stype == "publish":
743             for s in client.list_recent_publish():
744                 if s.get_counter() == count:
745                     return PublishStatusPage(s)
746             for s in client.list_all_publish():
747                 if s.get_counter() == count:
748                     return PublishStatusPage(s)
749         if stype == "retrieve":
750             for s in client.list_recent_retrieve():
751                 if s.get_counter() == count:
752                     return RetrieveStatusPage(s)
753             for s in client.list_all_retrieve():
754                 if s.get_counter() == count:
755                     return RetrieveStatusPage(s)
756
757