]> arthur.barton.de Git - bup.git/blobdiff - cmd/web-cmd.py
test-restore-map-owner: accommodate python 3 and test there
[bup.git] / cmd / web-cmd.py
index 4add17c1ca20237d10854ce0482ce7ff5d39aa39..eeb9107d3e93e19e98c2c1bb61a7cf68d571d82b 100755 (executable)
@@ -5,12 +5,16 @@ exec "$bup_python" "$0" ${1+"$@"}
 """
 # end of bup preamble
 
 """
 # end of bup preamble
 
+from __future__ import absolute_import, print_function
 from collections import namedtuple
 import mimetypes, os, posixpath, signal, stat, sys, time, urllib, webbrowser
 
 from bup import options, git, vfs
 from collections import namedtuple
 import mimetypes, os, posixpath, signal, stat, sys, time, urllib, webbrowser
 
 from bup import options, git, vfs
-from bup.helpers import (chunkyreader, debug1, handle_ctrl_c, log,
-                         resource_path, saved_errors)
+from bup.helpers import (chunkyreader, debug1, format_filesize, handle_ctrl_c,
+                         log, saved_errors)
+from bup.metadata import Metadata
+from bup.path import resource_path
+from bup.repo import LocalRepo
 
 try:
     from tornado import gen
 
 try:
     from tornado import gen
@@ -23,9 +27,16 @@ except ImportError:
     sys.exit(1)
 
 
     sys.exit(1)
 
 
+# FIXME: right now the way hidden files are handled causes every
+# directory to be traversed twice.
+
 handle_ctrl_c()
 
 
 handle_ctrl_c()
 
 
+def http_date_from_utc_ns(utc_ns):
+    return time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime(utc_ns / 10**9))
+
+
 def _compute_breadcrumbs(path, show_hidden=False):
     """Returns a list of breadcrumb objects for a path."""
     breadcrumbs = []
 def _compute_breadcrumbs(path, show_hidden=False):
     """Returns a list of breadcrumb objects for a path."""
     breadcrumbs = []
@@ -41,50 +52,69 @@ def _compute_breadcrumbs(path, show_hidden=False):
     return breadcrumbs
 
 
     return breadcrumbs
 
 
-def _contains_hidden_files(n):
-    """Return True if n contains files starting with a '.', False otherwise."""
-    for sub in n:
-        name = sub.name
-        if len(name)>1 and name.startswith('.'):
-            return True
+def _contains_hidden_files(repo, dir_item):
+    """Return true if the directory contains items with names other than
+    '.' and '..' that begin with '.'
 
 
+    """
+    for name, item in vfs.contents(repo, dir_item, want_meta=False):
+        if name in ('.', '..'):
+            continue
+        if name.startswith('.'):
+            return True
     return False
 
 
     return False
 
 
-def _compute_dir_contents(n, path, show_hidden=False):
-    """Given a vfs node, returns an iterator for display info of all subs."""
-    url_append = ""
-    if show_hidden:
-        url_append = "?hidden=1"
+def _dir_contents(repo, resolution, show_hidden=False):
+    """Yield the display information for the contents of dir_item."""
 
 
-    if path != "/":
-        yield('..', '../' + url_append, '')
-    for sub in n:
-        display = sub.name
-        link = urllib.quote(sub.name)
+    url_query = '?hidden=1' if show_hidden else ''
 
 
+    def display_info(name, item, resolved_item, display_name=None):
         # link should be based on fully resolved type to avoid extra
         # HTTP redirect.
         # link should be based on fully resolved type to avoid extra
         # HTTP redirect.
-        if stat.S_ISDIR(sub.try_resolve().mode):
-            link += "/"
-
-        if not show_hidden and len(display)>1 and display.startswith('.'):
-            continue
-
-        size = None
-        if stat.S_ISDIR(sub.mode):
-            display += '/'
-        elif stat.S_ISLNK(sub.mode):
-            display += '@'
+        if stat.S_ISDIR(vfs.item_mode(resolved_item)):
+            link = urllib.quote(name) + '/'
         else:
         else:
-            size = sub.size()
-            size = (opt.human_readable and format_filesize(size)) or size
+            link = urllib.quote(name)
 
 
-        yield (display, link + url_append, size)
+        size = vfs.item_size(repo, item)
+        if opt.human_readable:
+            display_size = format_filesize(size)
+        else:
+            display_size = size
+
+        if not display_name:
+            mode = vfs.item_mode(item)
+            if stat.S_ISDIR(mode):
+                display_name = name + '/'
+            elif stat.S_ISLNK(mode):
+                display_name = name + '@'
+            else:
+                display_name = name
+
+        return display_name, link + url_query, display_size
+
+    dir_item = resolution[-1][1]    
+    for name, item in vfs.contents(repo, dir_item):
+        if not show_hidden:
+            if (name not in ('.', '..')) and name.startswith('.'):
+                continue
+        if name == '.':
+            yield display_info(name, item, item, '.')
+            parent_item = resolution[-2][1] if len(resolution) > 1 else dir_item
+            yield display_info('..', parent_item, parent_item, '..')
+            continue
+        res = vfs.try_resolve(repo, name, parent=resolution, want_meta=False)
+        res_name, res_item = res[-1]
+        yield display_info(name, item, res_item)
 
 
 class BupRequestHandler(tornado.web.RequestHandler):
 
 
 
 class BupRequestHandler(tornado.web.RequestHandler):
 
+    def initialize(self, repo=None):
+        self.repo = repo
+
     def decode_argument(self, value, name=None):
         if name == 'path':
             return value
     def decode_argument(self, value, name=None):
         if name == 'path':
             return value
@@ -98,30 +128,33 @@ class BupRequestHandler(tornado.web.RequestHandler):
     
     def _process_request(self, path):
         path = urllib.unquote(path)
     
     def _process_request(self, path):
         path = urllib.unquote(path)
-        print 'Handling request for %s' % path
-        try:
-            n = top.resolve(path)
-        except vfs.NoSuchFile:
+        print('Handling request for %s' % path)
+        # Set want_meta because dir metadata won't be fetched, and if
+        # it's not a dir, then we're going to want the metadata.
+        res = vfs.resolve(self.repo, path, want_meta=True)
+        leaf_name, leaf_item = res[-1]
+        if not leaf_item:
             self.send_error(404)
             return
             self.send_error(404)
             return
-        f = None
-        if stat.S_ISDIR(n.mode):
-            self._list_directory(path, n)
+        mode = vfs.item_mode(leaf_item)
+        if stat.S_ISDIR(mode):
+            self._list_directory(path, res)
         else:
         else:
-            self._get_file(path, n)
+            self._get_file(self.repo, path, res)
 
 
-    def _list_directory(self, path, n):
+    def _list_directory(self, path, resolution):
         """Helper to produce a directory listing.
 
         Return value is either a file object, or None (indicating an
         error).  In either case, the headers are sent.
         """
         if not path.endswith('/') and len(path) > 0:
         """Helper to produce a directory listing.
 
         Return value is either a file object, or None (indicating an
         error).  In either case, the headers are sent.
         """
         if not path.endswith('/') and len(path) > 0:
-            print 'Redirecting from %s to %s' % (path, path + '/')
+            print('Redirecting from %s to %s' % (path, path + '/'))
             return self.redirect(path + '/', permanent=True)
 
             return self.redirect(path + '/', permanent=True)
 
+        hidden_arg = self.request.arguments.get('hidden', [0])[-1]
         try:
         try:
-            show_hidden = int(self.request.arguments.get('hidden', [0])[-1])
+            show_hidden = int(hidden_arg)
         except ValueError as e:
             show_hidden = False
 
         except ValueError as e:
             show_hidden = False
 
@@ -129,32 +162,33 @@ class BupRequestHandler(tornado.web.RequestHandler):
             'list-directory.html',
             path=path,
             breadcrumbs=_compute_breadcrumbs(path, show_hidden),
             'list-directory.html',
             path=path,
             breadcrumbs=_compute_breadcrumbs(path, show_hidden),
-            files_hidden=_contains_hidden_files(n),
+            files_hidden=_contains_hidden_files(self.repo, resolution[-1][1]),
             hidden_shown=show_hidden,
             hidden_shown=show_hidden,
-            dir_contents=_compute_dir_contents(n, path, show_hidden))
+            dir_contents=_dir_contents(self.repo, resolution,
+                                       show_hidden=show_hidden))
 
     @gen.coroutine
 
     @gen.coroutine
-    def _get_file(self, path, n):
+    def _get_file(self, repo, path, resolved):
         """Process a request on a file.
 
         Return value is either a file object, or None (indicating an error).
         In either case, the headers are sent.
         """
         """Process a request on a file.
 
         Return value is either a file object, or None (indicating an error).
         In either case, the headers are sent.
         """
+        file_item = resolved[-1][1]
+        file_item = vfs.augment_item_meta(repo, file_item, include_size=True)
+        meta = file_item.meta
         ctype = self._guess_type(path)
         ctype = self._guess_type(path)
-        self.set_header("Last-Modified", self.date_time_string(n.mtime))
+        self.set_header("Last-Modified", http_date_from_utc_ns(meta.mtime))
         self.set_header("Content-Type", ctype)
         self.set_header("Content-Type", ctype)
-        size = n.size()
-        self.set_header("Content-Length", str(size))
-        assert(len(n.hash) == 20)
-        self.set_header("Etag", n.hash.encode('hex'))
+        
+        self.set_header("Content-Length", str(meta.size))
+        assert len(file_item.oid) == 20
+        self.set_header("Etag", file_item.oid.encode('hex'))
         if self.request.method != 'HEAD':
         if self.request.method != 'HEAD':
-            f = n.open()
-            try:
+            with vfs.fopen(self.repo, file_item) as f:
                 it = chunkyreader(f)
                 for blob in chunkyreader(f):
                     self.write(blob)
                 it = chunkyreader(f)
                 for blob in chunkyreader(f):
                     self.write(blob)
-            finally:
-                f.close()
         raise gen.Return()
 
     def _guess_type(self, path):
         raise gen.Return()
 
     def _guess_type(self, path):
@@ -189,9 +223,6 @@ class BupRequestHandler(tornado.web.RequestHandler):
         '.h': 'text/plain',
         })
 
         '.h': 'text/plain',
         })
 
-    def date_time_string(self, t):
-        return time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime(t))
-
 
 io_loop = None
 
 
 io_loop = None
 
@@ -242,7 +273,6 @@ else:
         address = InetAddress(host=host, port=port)
 
 git.check_repo_or_die()
         address = InetAddress(host=host, port=port)
 
 git.check_repo_or_die()
-top = vfs.RefList(None)
 
 settings = dict(
     debug = 1,
 
 settings = dict(
     debug = 1,
@@ -254,7 +284,7 @@ settings = dict(
 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
 
 application = tornado.web.Application([
 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
 
 application = tornado.web.Application([
-    (r"(?P<path>/.*)", BupRequestHandler),
+    (r"(?P<path>/.*)", BupRequestHandler, dict(repo=LocalRepo())),
 ], **settings)
 
 http_server = HTTPServer(application)
 ], **settings)
 
 http_server = HTTPServer(application)
@@ -266,14 +296,14 @@ if isinstance(address, InetAddress):
         sock = http_server._socket # tornado < 2.0
     except AttributeError as e:
         sock = http_server._sockets.values()[0]
         sock = http_server._socket # tornado < 2.0
     except AttributeError as e:
         sock = http_server._sockets.values()[0]
-    print "Serving HTTP on %s:%d..." % sock.getsockname()
+    print('Serving HTTP on %s:%d...' % sock.getsockname())
     if opt.browser:
         browser_addr = 'http://' + address[0] + ':' + str(address[1])
         io_loop_pending.add_callback(lambda : webbrowser.open(browser_addr))
 elif isinstance(address, UnixAddress):
     unix_socket = bind_unix_socket(address.path)
     http_server.add_socket(unix_socket)
     if opt.browser:
         browser_addr = 'http://' + address[0] + ':' + str(address[1])
         io_loop_pending.add_callback(lambda : webbrowser.open(browser_addr))
 elif isinstance(address, UnixAddress):
     unix_socket = bind_unix_socket(address.path)
     http_server.add_socket(unix_socket)
-    print "Serving HTTP on filesystem socket %r" % address.path
+    print('Serving HTTP on filesystem socket %r' % address.path)
 else:
     log('error: unexpected address %r', address)
     sys.exit(1)
 else:
     log('error: unexpected address %r', address)
     sys.exit(1)