From a8a768ef9dbd2ffa433ac204c0343368a20dbe2b Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Sun, 27 Dec 2009 17:54:43 -0500
Subject: [PATCH] Fix 'tahoe ls' on files (#771). Patch adapted from Kevan
 Carstensen.

web/filenode.py: also serve edge metadata when using t=json on a
                 DIRCAP/childname object.
tahoe_ls.py: list file objects as if we were listing one-entry directories.
             Show edge metadata if we have it, which will be true when doing
             'tahoe ls DIRCAP/filename' and false when doing 'tahoe ls
             FILECAP'
---
 src/allmydata/scripts/tahoe_ls.py |  2 +-
 src/allmydata/test/test_cli.py    |  5 +++++
 src/allmydata/test/test_web.py    | 15 ++++++++++++++-
 src/allmydata/web/filenode.py     | 11 +++++++++--
 4 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/src/allmydata/scripts/tahoe_ls.py b/src/allmydata/scripts/tahoe_ls.py
index 1552da42..88bbde96 100644
--- a/src/allmydata/scripts/tahoe_ls.py
+++ b/src/allmydata/scripts/tahoe_ls.py
@@ -52,7 +52,7 @@ def list(options):
         children = d['children']
     elif nodetype == "filenode":
         childname = path.split("/")[-1]
-        children = {childname: d}
+        children = {childname: (nodetype, d)}
     childnames = sorted(children.keys())
     now = time.time()
 
diff --git a/src/allmydata/test/test_cli.py b/src/allmydata/test/test_cli.py
index fd12128c..3c5829f9 100644
--- a/src/allmydata/test/test_cli.py
+++ b/src/allmydata/test/test_cli.py
@@ -785,6 +785,11 @@ class List(GridTestMixin, CLITestMixin, unittest.TestCase):
         d.addCallback(_check3)
         d.addCallback(lambda ign: self.do_cli("ls", "0share"))
         d.addCallback(_check3)
+        def _check4((rc, out, err)):
+            self.failUnlessEqual(rc, 0)
+            self.failUnlessIn("good", out)
+        d.addCallback(lambda ign: self.do_cli("ls", "good"))
+        d.addCallback(_check4)
         return d
 
 class Mv(GridTestMixin, CLITestMixin, unittest.TestCase):
diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
index fd5e9942..529afe76 100644
--- a/src/allmydata/test/test_web.py
+++ b/src/allmydata/test/test_web.py
@@ -860,13 +860,26 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
         d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
         return d
 
+    def failUnlessHasBarDotTxtMetadata(self, res):
+        data = simplejson.loads(res)
+        self.failUnless(isinstance(data, list))
+        self.failUnless(data[1].has_key("metadata"))
+        self.failUnless(data[1]["metadata"].has_key("ctime"))
+        self.failUnless(data[1]["metadata"].has_key("mtime"))
+        self.failUnlessEqual(data[1]["metadata"]["ctime"],
+                             self._bar_txt_metadata["ctime"])
+
     def test_GET_FILEURL_json(self):
         # twisted.web.http.parse_qs ignores any query args without an '=', so
         # I can't do "GET /path?json", I have to do "GET /path/t=json"
         # instead. This may make it tricky to emulate the S3 interface
         # completely.
         d = self.GET(self.public_url + "/foo/bar.txt?t=json")
-        d.addCallback(self.failUnlessIsBarJSON)
+        def _check1(data):
+            self.failUnlessIsBarJSON(data)
+            self.failUnlessHasBarDotTxtMetadata(data)
+            return
+        d.addCallback(_check1)
         return d
 
     def test_GET_FILEURL_json_missing(self):
diff --git a/src/allmydata/web/filenode.py b/src/allmydata/web/filenode.py
index daf3d43f..b6a9a790 100644
--- a/src/allmydata/web/filenode.py
+++ b/src/allmydata/web/filenode.py
@@ -186,7 +186,12 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
             d.addCallback(lambda dn: FileDownloader(dn, filename))
             return d
         if t == "json":
-            return FileJSONMetadata(ctx, self.node)
+            if self.parentnode and self.name:
+                d = self.parentnode.get_metadata_for(self.name)
+            else:
+                d = defer.succeed(None)
+            d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md))
+            return d
         if t == "info":
             return MoreInfo(self.node)
         if t == "uri":
@@ -421,7 +426,7 @@ class FileDownloader(rend.Page):
         return req.deferred
 
 
-def FileJSONMetadata(ctx, filenode):
+def FileJSONMetadata(ctx, filenode, edge_metadata=None):
     if filenode.is_readonly():
         rw_uri = None
         ro_uri = filenode.get_uri()
@@ -438,6 +443,8 @@ def FileJSONMetadata(ctx, filenode):
     if verifycap:
         data[1]['verify_uri'] = verifycap.to_string()
     data[1]['mutable'] = filenode.is_mutable()
+    if edge_metadata:
+        data[1]["metadata"] = edge_metadata
     return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
 
 def FileURI(ctx, filenode):
-- 
2.45.2