web: add /status/?t=json, with active upload/download ops. Addresses #493.
authorBrian Warner <warner@allmydata.com>
Sat, 26 Jul 2008 00:41:10 +0000 (17:41 -0700)
committerBrian Warner <warner@allmydata.com>
Sat, 26 Jul 2008 00:41:10 +0000 (17:41 -0700)
docs/webapi.txt
src/allmydata/test/test_web.py
src/allmydata/web/status.py

index ea5a4ecaf30126ce86e7de723745bf0d4949c47c..44d16087bf0c29f557cb39fd29b9c0163b867f8a 100644 (file)
@@ -754,6 +754,45 @@ GET /status/
  that describes file sizes, servers that were involved, and the time consumed
  in each phase of the operation.
 
+ A GET of /status/?t=json will contain a machine-readable subset of the same
+ data. It returns a JSON-encoded dictionary. The only key defined at this
+ time is "active", with a value that is a list of operation dictionaries, one
+ for each active operation. Once an operation is completed, it will no longer
+ appear in data["active"] .
+
+ Each op-dict contains a "type" key, one of "upload", "download",
+ "mapupdate", "publish", or "retrieve" (the first two are for immutable
+ files, while the latter three are for mutable files and directories).
+
+ The "upload" op-dict will contain the following keys:
+
+   type (string): "upload"
+   storage-index-string (string): a base32-encoded storage index
+   total-size (int): total size of the file
+   status (string): current status of the operation
+   progress-hash (float): 1.0 when the file has been hashed
+   progress-ciphertext (float): 1.0 when the file has been encrypted.
+   progress-encode-push (float): 1.0 when the file has been encoded and
+                                 pushed to the storage servers. For helper
+                                 uploads, the ciphertext value climbs to 1.0
+                                 first, then encoding starts. For unassisted
+                                 uploads, ciphertext and encode-push progress
+                                 will climb at the same pace.
+
+ The "download" op-dict will contain the following keys:
+
+   type (string): "download"
+   storage-index-string (string): a base32-encoded storage index
+   total-size (int): total size of the file
+   status (string): current status of the operation
+   progress (float): 1.0 when the file has been fully downloaded
+
+ Front-ends which want to report progress information are advised to simply
+ average together all the progress-* indicators. A slightly more accurate
+ value can be found by ignoring the progress-hash value (since the current
+ implementation hashes synchronously, so clients will probably never see
+ progress-hash!=1.0).
+
 GET /provisioning/
 
  This page provides a basic tool to predict the likely storage and bandwidth
index da1b5ec85d79b1fe5ee57f794eed57d4e7950321..b794f8d4dbc9dbb0b84d3a9a6bddae4f6f2da997 100644 (file)
@@ -438,6 +438,15 @@ class Web(WebMixin, unittest.TestCase):
             self.failUnless('"publish-%d"' % pub_num in res, res)
             self.failUnless('"retrieve-%d"' % ret_num in res, res)
         d.addCallback(_check)
+        d.addCallback(lambda res: self.GET("/status/?t=json"))
+        def _check_json(res):
+            data = simplejson.loads(res)
+            self.failUnless(isinstance(data, dict))
+            active = data["active"]
+            # TODO: test more. We need a way to fake an active operation
+            # here.
+        d.addCallback(_check_json)
+
         d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
         def _check_dl(res):
             self.failUnless("File Download Status" in res, res)
index c2d429efc9cce652a400d3e0c65970c05abc353d..2c9bafaac91d6541b05bea1180739ab8fbeb5a8a 100644 (file)
@@ -772,6 +772,41 @@ class Status(rend.Page):
     docFactory = getxmlfile("status.xhtml")
     addSlash = True
 
+    def renderHTTP(self, ctx):
+        t = get_arg(inevow.IRequest(ctx), "t")
+        if t == "json":
+            return self.json(ctx)
+        return rend.Page.renderHTTP(self, ctx)
+
+    def json(self, ctx):
+        inevow.IRequest(ctx).setHeader("content-type", "text/plain")
+        client = IClient(ctx)
+        data = {}
+        data["active"] = active = []
+        for s in self.data_active_operations(ctx, None):
+            si_s = base32.b2a_or_none(s.get_storage_index())
+            size = s.get_size()
+            status = s.get_status()
+            if IUploadStatus.providedBy(s):
+                h,c,e = s.get_progress()
+                active.append({"type": "upload",
+                               "storage-index-string": si_s,
+                               "total-size": size,
+                               "status": status,
+                               "progress-hash": h,
+                               "progress-ciphertext": c,
+                               "progress-encode-push": e,
+                               })
+            elif IDownloadStatus.providedBy(s):
+                active.append({"type": "download",
+                               "storage-index-string": si_s,
+                               "total-size": size,
+                               "status": status,
+                               "progress": s.get_progress(),
+                               })
+
+        return simplejson.dumps(data, indent=1)
+
     def _get_all_statuses(self, client):
         return itertools.chain(client.list_all_upload_statuses(),
                                client.list_all_download_statuses(),