Commit | Line | Data |
---|---|---|
63a46810 P |
1 | #!/usr/bin/env python |
2 | # -*- coding: utf-8 -*- | |
3 | """ | |
4 | Outil de partage de fichiers. | |
5 | ||
b816959a P |
6 | Copyright : Agence universitaire de la Francophonie — www.auf.org |
7 | Licence : GNU General Public Licence, version 2 | |
8 | Auteur : Jean Christophe André | |
9 | Date de création : 26 août 2010 | |
10 | ||
63a46810 P |
11 | Depends: libapache2-mod-wsgi |
12 | ||
13 | Attention : le code n'est pas encore “thread-safe”... | |
14 | """ | |
15 | import os | |
16 | import re | |
17 | import urllib | |
18 | import tarfile | |
19 | ||
20 | ROOT_PATH = '/srv/www/compta' | |
21 | PAGE_TITLE = u'Rapport financier du BAP <em>(%s)</em>' | |
22 | BACKGROUND_URL = 'Bamboo2.png' | |
23 | EXCLUDED_FILES = [ 'description.txt' ] | |
24 | INVALID_NAME = u'%s <span class="alert">(NOM INVALIDE)</span>' | |
25 | ||
26 | html_template_filename = os.path.abspath(__file__.rstrip('.py') + '.tpl') | |
27 | dir_description_filename = 'description.txt' | |
28 | ||
29 | dir_link_template = u"""%s<li class="dir"><a href="javascript:void(0)" onclick="sw('l%s')">%s</a> <em>(%s) <a href="%s">archive du dossier</a></em> %s</li>""" | |
30 | dir_description_template = u""" <em>%s</em>""" | |
31 | begin_content_template = u"""%s<div id="l%s" class="hidden"><ul>""" | |
32 | end_content_template = u"""%s</ul></div>""" | |
33 | file_link_template = u"""%s<li><a href="%s">%s</a> <em>(%s)</em></li>""" | |
34 | ||
35 | def valid_path(path): | |
36 | if os.path.islink('.' + path): | |
37 | return False | |
38 | if not os.path.isdir('.' + path): | |
39 | return False | |
40 | return True | |
41 | ||
42 | def human_size(size): | |
b6eb652d | 43 | if size <= 1: return "%s octet" % size |
63a46810 P |
44 | if size < 1024: return "%s octets" % size |
45 | size /= 1024 | |
46 | if size < 1024: return "%s Kio" % size | |
47 | size /= 1024 | |
48 | if size < 1024: return "%s Mio" % size | |
49 | size /= 1024 | |
50 | if size < 1024: return "%s Gio" % size | |
51 | size /= 1024 | |
52 | return "%s Tio" % size | |
53 | ||
54 | def my_cmp(name1, name2): | |
b6eb652d P |
55 | m1 = re.match('[0-9]{1,8}', name1) |
56 | m2 = re.match('[0-9]{1,8}', name2) | |
63a46810 P |
57 | if not m1 or not m2: |
58 | return cmp(name1, name2) | |
59 | n1 = int(m1.group()) | |
60 | n2 = int(m2.group()) | |
61 | if n1 == n2: | |
62 | return cmp(name1[m1.end():], name2[m2.end():]) | |
63 | if n1 < n2: | |
64 | return -1 | |
65 | return 1 | |
66 | ||
67 | def my_listdir(path): | |
68 | path = '.' + path | |
69 | try: | |
70 | names = os.listdir(path) | |
71 | except OSError: | |
72 | return [], [] | |
73 | dirs = [] | |
74 | files = [] | |
75 | for n in names: | |
76 | p = path + '/' + n | |
77 | if not os.path.islink(p): | |
78 | if os.path.isdir(p): | |
79 | dirs.append(n) | |
80 | if os.path.isfile(p): | |
81 | files.append(n) | |
82 | dirs.sort(cmp=my_cmp) | |
83 | files.sort(cmp=my_cmp) | |
84 | return dirs, files | |
85 | ||
86 | ||
87 | id_number = 0 | |
88 | ||
89 | def dir_content(prefix, root, level=0): | |
90 | dirs, files = my_listdir(root) | |
91 | if not len(dirs) and not len(files): | |
92 | return [], 0 | |
93 | space = u" " * level | |
94 | content = [] | |
95 | file_size_total = 0 | |
96 | for d in dirs: | |
97 | path = root.rstrip('/') + '/' + d | |
98 | sub_dir_content, sub_dir_size = dir_content(prefix, path, level + 1) | |
99 | file_size_total += sub_dir_size | |
100 | if sub_dir_content: | |
101 | global id_number | |
102 | id_number += 1 | |
103 | name = d.replace('_',' ') | |
104 | try: | |
105 | name = name.decode('utf-8') | |
106 | except UnicodeDecodeError: | |
b6eb652d | 107 | name = INVALID_NAME % name.decode('iso-8859-1') |
63a46810 P |
108 | desc_file = '.' + path + '/' + dir_description_filename |
109 | if os.path.isfile(desc_file): | |
110 | desc = file(desc_file).read().strip().replace('\n',' ') | |
111 | desc = dir_description_template % desc.decode('utf-8') | |
112 | else: | |
113 | desc = '' | |
114 | archive_path = (prefix + path + '.tar').decode('utf-8') | |
115 | archive_size = human_size(sub_dir_size) | |
116 | content.append(dir_link_template % \ | |
117 | (space, id_number, name, archive_size, archive_path, desc)) | |
118 | content.append(begin_content_template % (space, id_number)) | |
119 | content.extend(sub_dir_content) | |
120 | content.append(end_content_template % space) | |
121 | for f in files: | |
122 | path = root.rstrip('/') + '/' + f | |
123 | file_size = os.path.getsize('.' + path) | |
124 | file_size_total += file_size | |
125 | if file_size > 0 and f not in EXCLUDED_FILES: | |
126 | name = f.replace('_',' ') | |
127 | try: | |
128 | name = name.decode('utf-8') | |
129 | except UnicodeDecodeError: | |
b6eb652d | 130 | name = INVALID_NAME % name.decode('iso-8859-1') |
63a46810 P |
131 | content.append( file_link_template % (space, |
132 | urllib.quote(prefix + path), name, human_size(file_size)) ) | |
133 | return content, file_size_total | |
134 | ||
135 | class FileBuffer(): | |
136 | def __init__(self): | |
137 | self.reset() | |
138 | def reset(self): | |
139 | self._data = [] | |
140 | def write(self, data): | |
141 | self._data.append(data) | |
142 | def read(self): | |
143 | return ''.join(self._data) | |
144 | ||
145 | def tar_generator(path, mode='w|', bufsize=65536): | |
146 | buffer = FileBuffer() | |
147 | #yield 'open()\n' | |
148 | tar = tarfile.open(mode=mode, fileobj=buffer, bufsize=bufsize) | |
149 | for root, dirs, files in os.walk(path): | |
150 | for name in files: | |
151 | tar.add(os.path.join(root, name), recursive=False) | |
152 | #yield '+ file: %s\n' % os.path.join(root, name) | |
153 | yield buffer.read() | |
154 | buffer.reset() | |
155 | tar.close() | |
156 | #yield 'close()\n' | |
157 | yield buffer.read() | |
158 | ||
159 | def application(environ, start_response): | |
160 | global id_number | |
161 | id_number = 0 | |
162 | os.chdir(ROOT_PATH) | |
163 | if False and not environ.get('HTTP_USER'): | |
164 | headers = [('Content-Type', 'text/plain; charset=utf-8'), ] | |
165 | start_response('401 Authorization Required', headers) | |
166 | return ['Authorization Required'] | |
167 | path = environ['PATH_INFO'] | |
168 | if not valid_path(os.path.dirname(path)): | |
169 | headers = [('Content-Type', 'text/plain; charset=utf-8'), ] | |
170 | start_response('404 Not Found', headers) | |
171 | return ['Not Found'] | |
172 | if os.path.isfile('.' + path): | |
173 | if path.endswith('.txt'): | |
174 | mime_type = 'text/plain; charset=utf-8' | |
175 | elif path.endswith('.pdf'): | |
176 | mime_type = 'application/pdf' | |
177 | else: | |
178 | mime_type = 'application/octet-stream' | |
179 | length = str(os.path.getsize('.' + path)) | |
180 | headers = [('Content-Type', mime_type), ('Content-Length', length), ] | |
181 | start_response('200 OK', headers) | |
182 | return file('.' + path) | |
183 | if path.endswith('.tar') and os.path.isdir('.' + path[:-4]): | |
184 | #headers = [('Content-Type', 'text/plain; charset=utf-8'), ] | |
185 | headers = [('Content-Type', 'application/octet-stream'), ] | |
186 | start_response('200 OK', headers) | |
187 | return tar_generator('.' + path[:-4]) | |
188 | content, size = dir_content(environ['SCRIPT_NAME'], environ['PATH_INFO']) | |
189 | environ.update({ | |
190 | 'page_title': PAGE_TITLE % human_size(size), | |
191 | 'background_url': BACKGROUND_URL, | |
192 | 'content': '<ul>\n' + '\n'.join(content) + '\n</ul>', | |
193 | }) | |
194 | html_template = file(html_template_filename).read().decode('utf-8') | |
195 | content = (html_template % environ).encode('utf-8') | |
196 | headers = [('Content-Type', 'text/html; charset=utf-8'), ] | |
197 | start_response('200 OK', headers) | |
198 | return [content] | |
199 | ||
200 | if __name__ == '__main__': | |
201 | # this runs when script is started directly from commandline | |
202 | try: | |
203 | # create a simple WSGI server and run the application | |
204 | from wsgiref import simple_server | |
205 | print "Running test application - point your browser at http://localhost:8000/ ..." | |
206 | httpd = simple_server.WSGIServer(('', 8000), simple_server.WSGIRequestHandler) | |
207 | httpd.set_app(application) | |
208 | httpd.serve_forever() | |
209 | except ImportError: | |
210 | # wsgiref not installed, just output html to stdout | |
211 | for content in application({}, lambda status, headers: None): | |
212 | print content | |
213 |