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