2 import sys, stat, cgi, shutil, urllib, mimetypes, posixpath, time
3 import tornado.httpserver
6 from bup import options, git, vfs
7 from bup.helpers import *
11 class BupRequestHandler(tornado.web.RequestHandler):
13 return self._process_request(path)
16 return self._process_request(path)
18 def _process_request(self, path):
19 path = urllib.unquote(path)
20 print 'Handling request for %s' % path
23 except vfs.NoSuchFile:
27 if stat.S_ISDIR(n.mode):
28 self._list_directory(path, n)
30 self._get_file(path, n)
32 def _list_directory(self, path, n):
33 """Helper to produce a directory listing.
35 Return value is either a file object, or None (indicating an
36 error). In either case, the headers are sent.
38 if not path.endswith('/') and len(path) > 0:
39 print 'Redirecting from %s to %s' % (path, path + '/')
40 return self.redirect(path + '/', permanent=True)
42 self.set_header("Content-Type", "text/html")
44 displaypath = cgi.escape(path)
45 self.write("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
48 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
49 <title>Directory listing for %(displaypath)s</title>
50 <style type="text/css">
51 body, table { font-family: sans-serif }
52 #breadcrumb { margin: 10px 0; }
53 .dir-name { text-align: left }
54 .dir-size { text-align: right }
59 """ % { 'displaypath': displaypath })
61 self.write("""<strong>[root]</strong>""")
63 self.write("""<a href="/">[root]</a> """)
64 path_parts = path.split("/")
65 path_parts_cleaned = path_parts[1:-1]
66 for index, value in enumerate(path_parts_cleaned[0:-1]):
67 self.write("""/ <a href="/%(path)s/">%(element)s</a> """ % { 'path' : "/".join(path_parts_cleaned[0:(index + 1)]) , 'element' : value})
68 self.write("""/ <strong>%s</strong>""" % path_parts_cleaned[-1])
73 <th class="dir-name">Name</th>
74 <th class="dir-size">Size</th>
78 displayname = linkname = sub.name
79 # Append / for directories or @ for symbolic links
80 size = str(sub.size())
81 if stat.S_ISDIR(sub.mode):
82 displayname = sub.name + "/"
83 linkname = sub.name + "/"
85 if stat.S_ISLNK(sub.mode):
86 displayname = sub.name + "@"
87 # Note: a link to a directory displays with @ and links with /
90 <td class="dir-name"><a href="%s">%s</a></td>
91 <td class="dir-size">%s</td>
92 </tr>""" % (urllib.quote(linkname), cgi.escape(displayname), size))
98 def _get_file(self, path, n):
99 """Process a request on a file.
101 Return value is either a file object, or None (indicating an error).
102 In either case, the headers are sent.
104 ctype = self._guess_type(path)
106 self.set_header("Last-Modified", self.date_time_string(n.mtime))
107 self.set_header("Content-Type", ctype)
109 self.set_header("Content-Length", str(size))
111 if self.request.method != 'HEAD':
113 for blob in chunkyreader(f):
117 def _guess_type(self, path):
118 """Guess the type of a file.
120 Argument is a PATH (a filename).
122 Return value is a string of the form type/subtype,
123 usable for a MIME Content-type header.
125 The default implementation looks the file's extension
126 up in the table self.extensions_map, using application/octet-stream
127 as a default; however it would be permissible (if
128 slow) to look inside the data to make a better guess.
130 base, ext = posixpath.splitext(path)
131 if ext in self.extensions_map:
132 return self.extensions_map[ext]
134 if ext in self.extensions_map:
135 return self.extensions_map[ext]
137 return self.extensions_map['']
139 if not mimetypes.inited:
140 mimetypes.init() # try to read system mime.types
141 extensions_map = mimetypes.types_map.copy()
142 extensions_map.update({
143 '': 'text/plain', # Default
149 def date_time_string(self, t):
150 return time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime(t))
154 bup web [[hostname]:port]
157 o = options.Options('bup web', optspec)
158 (opt, flags, extra) = o.parse(sys.argv[1:])
161 o.fatal("at most one argument expected")
165 addressl = extra[0].split(':', 1)
166 addressl[1] = int(addressl[1])
167 address = tuple(addressl)
169 git.check_repo_or_die()
170 top = vfs.RefList(None)
172 (pwd,junk) = os.path.split(sys.argv[0])
178 # Disable buffering on stdout, for debug messages
179 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
181 application = tornado.web.Application([
182 (r"(/.*)", BupRequestHandler),
185 if __name__ == "__main__":
186 http_server = tornado.httpserver.HTTPServer(application)
187 http_server.listen(address[1], address=address[0])
189 print "Listening on port %s" % http_server._socket.getsockname()[1]
190 loop = tornado.ioloop.IOLoop.instance()