partageweb2 : pré-calcul de la taille de l'archive tar transmise.
[progfou.git] / partageweb2 / partage-compta.py
CommitLineData
63a46810
P
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""
4Outil de partage de fichiers.
5
b816959a
P
6Copyright : Agence universitaire de la Francophonie — www.auf.org
7Licence : GNU General Public Licence, version 2
8Auteur : Jean Christophe André
9Date de création : 26 août 2010
10
63a46810
P
11Depends: libapache2-mod-wsgi
12
13Attention : le code n'est pas encore “thread-safe”...
14"""
15import os
16import re
17import urllib
18import tarfile
19
20ROOT_PATH = '/srv/www/compta'
21PAGE_TITLE = u'Rapport financier du BAP <em>(%s)</em>'
22BACKGROUND_URL = 'Bamboo2.png'
23EXCLUDED_FILES = [ 'description.txt' ]
24INVALID_NAME = u'%s <span class="alert">(NOM INVALIDE)</span>'
25
26html_template_filename = os.path.abspath(__file__.rstrip('.py') + '.tpl')
27dir_description_filename = 'description.txt'
28
29dir_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>"""
30dir_description_template = u""" <em>%s</em>"""
31begin_content_template = u"""%s<div id="l%s" class="hidden"><ul>"""
32end_content_template = u"""%s</ul></div>"""
33file_link_template = u"""%s<li><a href="%s">%s</a> <em>(%s)</em></li>"""
34
35def 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
42def 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
54def 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
67def 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
30c419c6
P
86def 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
63a46810
P
93
94id_number = 0
95
96def 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:
b6eb652d 114 name = INVALID_NAME % name.decode('iso-8859-1')
63a46810
P
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:
b6eb652d 137 name = INVALID_NAME % name.decode('iso-8859-1')
63a46810
P
138 content.append( file_link_template % (space,
139 urllib.quote(prefix + path), name, human_size(file_size)) )
140 return content, file_size_total
141
142class 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)
30c419c6
P
151 def length(self):
152 return sum([len(x) for x in self._data])
63a46810 153
30c419c6
P
154def tar_size(files, mode='w|', bufsize=65536):
155 size = 0
63a46810 156 buffer = FileBuffer()
63a46810 157 tar = tarfile.open(mode=mode, fileobj=buffer, bufsize=bufsize)
30c419c6
P
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
166def 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()
63a46810 173 tar.close()
63a46810
P
174 yield buffer.read()
175
176def 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]):
30c419c6
P
201 files = find_files('.' + path[:-4])
202 headers = [('Content-Type', 'application/octet-stream'),
203 ('Content-Length', str(tar_size(files)) ), ]
63a46810 204 start_response('200 OK', headers)
30c419c6 205 return tar_generator(files)
63a46810
P
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
218if __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