partageweb2 : pré-calcul de la taille de l'archive tar transmise.
[progfou.git] / partageweb2 / partage-compta.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Outil de partage de fichiers.
5
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
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):
43 if size <= 1: return "%s octet" % size
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):
55 m1 = re.match('[0-9]{1,8}', name1)
56 m2 = re.match('[0-9]{1,8}', name2)
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 def find_files(path):
87 files_list = []
88 for root, dirs, files in os.walk(path):
89 for a_file in files:
90 files_list.append(os.path.join(root, a_file))
91 return files_list
92
93
94 id_number = 0
95
96 def dir_content(prefix, root, level=0):
97 dirs, files = my_listdir(root)
98 if not len(dirs) and not len(files):
99 return [], 0
100 space = u" " * level
101 content = []
102 file_size_total = 0
103 for d in dirs:
104 path = root.rstrip('/') + '/' + d
105 sub_dir_content, sub_dir_size = dir_content(prefix, path, level + 1)
106 file_size_total += sub_dir_size
107 if sub_dir_content:
108 global id_number
109 id_number += 1
110 name = d.replace('_',' ')
111 try:
112 name = name.decode('utf-8')
113 except UnicodeDecodeError:
114 name = INVALID_NAME % name.decode('iso-8859-1')
115 desc_file = '.' + path + '/' + dir_description_filename
116 if os.path.isfile(desc_file):
117 desc = file(desc_file).read().strip().replace('\n',' ')
118 desc = dir_description_template % desc.decode('utf-8')
119 else:
120 desc = ''
121 archive_path = (prefix + path + '.tar').decode('utf-8')
122 archive_size = human_size(sub_dir_size)
123 content.append(dir_link_template % \
124 (space, id_number, name, archive_size, archive_path, desc))
125 content.append(begin_content_template % (space, id_number))
126 content.extend(sub_dir_content)
127 content.append(end_content_template % space)
128 for f in files:
129 path = root.rstrip('/') + '/' + f
130 file_size = os.path.getsize('.' + path)
131 file_size_total += file_size
132 if file_size > 0 and f not in EXCLUDED_FILES:
133 name = f.replace('_',' ')
134 try:
135 name = name.decode('utf-8')
136 except UnicodeDecodeError:
137 name = INVALID_NAME % name.decode('iso-8859-1')
138 content.append( file_link_template % (space,
139 urllib.quote(prefix + path), name, human_size(file_size)) )
140 return content, file_size_total
141
142 class FileBuffer():
143 def __init__(self):
144 self.reset()
145 def reset(self):
146 self._data = []
147 def write(self, data):
148 self._data.append(data)
149 def read(self):
150 return ''.join(self._data)
151 def length(self):
152 return sum([len(x) for x in self._data])
153
154 def tar_size(files, mode='w|', bufsize=65536):
155 size = 0
156 buffer = FileBuffer()
157 tar = tarfile.open(mode=mode, fileobj=buffer, bufsize=bufsize)
158 for a_file in files:
159 tar.add(a_file, recursive=False)
160 size += buffer.length()
161 buffer.reset()
162 tar.close()
163 size += buffer.length()
164 return size
165
166 def tar_generator(files, mode='w|', bufsize=65536):
167 buffer = FileBuffer()
168 tar = tarfile.open(mode=mode, fileobj=buffer, bufsize=bufsize)
169 for a_file in files:
170 tar.add(a_file, recursive=False)
171 yield buffer.read()
172 buffer.reset()
173 tar.close()
174 yield buffer.read()
175
176 def application(environ, start_response):
177 global id_number
178 id_number = 0
179 os.chdir(ROOT_PATH)
180 if False and not environ.get('HTTP_USER'):
181 headers = [('Content-Type', 'text/plain; charset=utf-8'), ]
182 start_response('401 Authorization Required', headers)
183 return ['Authorization Required']
184 path = environ['PATH_INFO']
185 if not valid_path(os.path.dirname(path)):
186 headers = [('Content-Type', 'text/plain; charset=utf-8'), ]
187 start_response('404 Not Found', headers)
188 return ['Not Found']
189 if os.path.isfile('.' + path):
190 if path.endswith('.txt'):
191 mime_type = 'text/plain; charset=utf-8'
192 elif path.endswith('.pdf'):
193 mime_type = 'application/pdf'
194 else:
195 mime_type = 'application/octet-stream'
196 length = str(os.path.getsize('.' + path))
197 headers = [('Content-Type', mime_type), ('Content-Length', length), ]
198 start_response('200 OK', headers)
199 return file('.' + path)
200 if path.endswith('.tar') and os.path.isdir('.' + path[:-4]):
201 files = find_files('.' + path[:-4])
202 headers = [('Content-Type', 'application/octet-stream'),
203 ('Content-Length', str(tar_size(files)) ), ]
204 start_response('200 OK', headers)
205 return tar_generator(files)
206 content, size = dir_content(environ['SCRIPT_NAME'], environ['PATH_INFO'])
207 environ.update({
208 'page_title': PAGE_TITLE % human_size(size),
209 'background_url': BACKGROUND_URL,
210 'content': '<ul>\n' + '\n'.join(content) + '\n</ul>',
211 })
212 html_template = file(html_template_filename).read().decode('utf-8')
213 content = (html_template % environ).encode('utf-8')
214 headers = [('Content-Type', 'text/html; charset=utf-8'), ]
215 start_response('200 OK', headers)
216 return [content]
217
218 if __name__ == '__main__':
219 # this runs when script is started directly from commandline
220 try:
221 # create a simple WSGI server and run the application
222 from wsgiref import simple_server
223 print "Running test application - point your browser at http://localhost:8000/ ..."
224 httpd = simple_server.WSGIServer(('', 8000), simple_server.WSGIRequestHandler)
225 httpd.set_app(application)
226 httpd.serve_forever()
227 except ImportError:
228 # wsgiref not installed, just output html to stdout
229 for content in application({}, lambda status, headers: None):
230 print content
231