]> arthur.barton.de Git - bup.git/blob - cmd/web-cmd.py
vfs: try_lresolve() was a bad idea. Create try_resolve() instead.
[bup.git] / cmd / web-cmd.py
1 #!/usr/bin/env python
2 import sys, stat, cgi, shutil, urllib, mimetypes, posixpath, time
3 import tornado.httpserver
4 import tornado.ioloop
5 import tornado.template
6 import tornado.web
7 from bup import options, git, vfs
8 from bup.helpers import *
9
10 handle_ctrl_c()
11
12
13 def _compute_breadcrumbs(path):
14     """Returns a list of breadcrumb objects for a path."""
15     breadcrumbs = []
16     breadcrumbs.append(('[root]', '/'))
17     path_parts = path.split('/')[1:-1]
18     full_path = '/'
19     for part in path_parts:
20         full_path += part + '/'
21         breadcrumbs.append((part, full_path))
22     return breadcrumbs
23
24
25 def _compute_dir_contents(n):
26     """Given a vfs node, returns an iterator for display info of all subs."""
27     contents = []
28     for sub in n:
29         display = link = sub.name
30
31         # link should be based on fully resolved type to avoid extra
32         # HTTP redirect.
33         if stat.S_ISDIR(sub.try_resolve().mode):
34             link = sub.name + "/"
35
36         size = None
37         if stat.S_ISDIR(sub.mode):
38             display = sub.name + '/'
39         elif stat.S_ISLNK(sub.mode):
40             display = sub.name + '@'
41         else:
42             size = sub.size()
43
44         yield (display, link, size)
45
46
47 class BupRequestHandler(tornado.web.RequestHandler):
48     def get(self, path):
49         return self._process_request(path)
50
51     def head(self, path):
52         return self._process_request(path)
53
54     def _process_request(self, path):
55         path = urllib.unquote(path)
56         print 'Handling request for %s' % path
57         try:
58             n = top.resolve(path)
59         except vfs.NoSuchFile:
60             self.send_error(404)
61             return
62         f = None
63         if stat.S_ISDIR(n.mode):
64             self._list_directory(path, n)
65         else:
66             self._get_file(path, n)
67
68     def _list_directory(self, path, n):
69         """Helper to produce a directory listing.
70
71         Return value is either a file object, or None (indicating an
72         error).  In either case, the headers are sent.
73         """
74         if not path.endswith('/') and len(path) > 0:
75             print 'Redirecting from %s to %s' % (path, path + '/')
76             return self.redirect(path + '/', permanent=True)
77
78         self.render(
79             'list-directory.html',
80             path=path,
81             breadcrumbs=_compute_breadcrumbs(path),
82             dir_contents=_compute_dir_contents(n),
83             # We need the standard url_escape so we don't escape /
84             url_escape=urllib.quote)
85
86     def _get_file(self, path, n):
87         """Process a request on a file.
88
89         Return value is either a file object, or None (indicating an error).
90         In either case, the headers are sent.
91         """
92         ctype = self._guess_type(path)
93
94         self.set_header("Last-Modified", self.date_time_string(n.mtime))
95         self.set_header("Content-Type", ctype)
96         size = n.size()
97         self.set_header("Content-Length", str(size))
98
99         if self.request.method != 'HEAD':
100             f = n.open()
101             for blob in chunkyreader(f):
102                 self.write(blob)
103             f.close()
104
105     def _guess_type(self, path):
106         """Guess the type of a file.
107
108         Argument is a PATH (a filename).
109
110         Return value is a string of the form type/subtype,
111         usable for a MIME Content-type header.
112
113         The default implementation looks the file's extension
114         up in the table self.extensions_map, using application/octet-stream
115         as a default; however it would be permissible (if
116         slow) to look inside the data to make a better guess.
117         """
118         base, ext = posixpath.splitext(path)
119         if ext in self.extensions_map:
120             return self.extensions_map[ext]
121         ext = ext.lower()
122         if ext in self.extensions_map:
123             return self.extensions_map[ext]
124         else:
125             return self.extensions_map['']
126
127     if not mimetypes.inited:
128         mimetypes.init() # try to read system mime.types
129     extensions_map = mimetypes.types_map.copy()
130     extensions_map.update({
131         '': 'text/plain', # Default
132         '.py': 'text/plain',
133         '.c': 'text/plain',
134         '.h': 'text/plain',
135         })
136
137     def date_time_string(self, t):
138         return time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime(t))
139
140
141 optspec = """
142 bup web [[hostname]:port]
143 --
144 """
145 o = options.Options('bup web', optspec)
146 (opt, flags, extra) = o.parse(sys.argv[1:])
147
148 if len(extra) > 1:
149     o.fatal("at most one argument expected")
150
151 address = ('127.0.0.1', 8080)
152 if len(extra) > 0:
153     addressl = extra[0].split(':', 1)
154     addressl[1] = int(addressl[1])
155     address = tuple(addressl)
156
157 git.check_repo_or_die()
158 top = vfs.RefList(None)
159
160 settings = dict(
161     debug = 1,
162     template_path = resource_path('web'),
163 )
164
165 # Disable buffering on stdout, for debug messages
166 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
167
168 application = tornado.web.Application([
169     (r"(/.*)", BupRequestHandler),
170 ], **settings)
171
172 if __name__ == "__main__":
173     http_server = tornado.httpserver.HTTPServer(application)
174     http_server.listen(address[1], address=address[0])
175
176     print "Serving HTTP on %s:%d..." % http_server._socket.getsockname()
177     loop = tornado.ioloop.IOLoop.instance()
178     loop.start()
179