auf-refer-photos : ajout des options -v et -f dans l'aide
[auf-refer.git] / aufrefer.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 """Librairie de copie et mise à jour des référentiels AuF.
4
5 Copyright ©2009-2011 Agence universitaire de la Francophonie
6 Licence : LGPL version 3
7 Auteur : Progfou <jean-christophe.andre@auf.org>
8
9 Dépendances Debian : python >= 2.5, python-simplejson
10 """
11 from os import listdir, utime, unlink, umask
12 from os.path import join, exists, getmtime
13 from time import gmtime, strftime
14 from calendar import timegm
15 from urllib2 import Request, urlopen, HTTPError, URLError
16 from cStringIO import StringIO
17 from gzip import GzipFile
18 from simplejson import loads
19
20 CONFIG_FILE = '/etc/auf-refer/auf-refer.conf'
21
22 DIR_BASE = '/var/lib/auf-refer'
23 URL_BASE = 'http://intranet.auf/auf-refer'
24 AVAILABLE_LIST = 'auf-refer.json'
25
26 try:
27 lines = filter(lambda l: not l.startswith('#'), file(CONFIG_FILE))
28 config = dict(map(lambda l: map(lambda s: s.strip(), l.split('=')), lines))
29 except IOError:
30 print "Fichier de configuration '%s' indisponible." % CONFIG_FILE
31 config = {}
32
33 DIR_BASE = config.get('DIR_BASE', DIR_BASE).rstrip('/')
34 URL_BASE = config.get('URL_BASE', URL_BASE).rstrip('/')
35 AVAILABLE_LIST = config.get('AVAILABLE_LIST', AVAILABLE_LIST)
36
37 __all__ = ( 'DIR_BASE', 'URL_BASE', 'AVAILABLE_LIST' )
38
39 TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
40
41 def path(referentiel):
42 return join(DIR_BASE, referentiel)
43
44 def retrieve(url, filename, force=False, mimetype=None, ignore404=False):
45 headers = {}
46 headers['Accept-Encoding'] = 'gzip,x-gzip'
47 # ajout d'un champ pour forcer le cache au besoin
48 if force:
49 headers['Pragma'] = 'no-cache'
50 # ajout d'un champ indiquant la date du fichier déjà présent
51 if exists(filename):
52 # n'effectuer le chargement qu'en cas de nouvelle version
53 mtime = gmtime(getmtime(filename))
54 headers['If-Modified-Since'] = strftime(TIME_FORMAT, mtime)
55 # requête de données à l'URL demandée
56 req = Request(url, None, headers)
57 try:
58 u = urlopen(req)
59 except HTTPError, e:
60 if e.code == 304:
61 return None
62 if ignore404 and e.code == 404:
63 return False
64 raise RuntimeError, \
65 "L'URL suivante renvoie un code d'erreur %s :\n %s" \
66 % (e.code, url)
67 except URLError:
68 raise RuntimeError, "L'URL suivante est inaccessible :\n %s" % url
69 # infos sur les données
70 i = u.info()
71 # vérification du type MIME
72 if mimetype and i.type != mimetype:
73 u.close()
74 raise ValueError, \
75 "Le type de données n'est pas '%s' mais '%s'.\n" \
76 "URL concernée : %s" % (mimetype, i.type, url)
77 data = u.read()
78 # décompression à la volée
79 if i.get('content-encoding') in ('gzip','x-gzip'):
80 data = GzipFile('', 'r', 0, StringIO(data)).read()
81 u.close()
82 # test de validité des données en cas de JSON
83 if mimetype == 'application/json':
84 try:
85 loads(data, encoding='utf-8')
86 except ValueError:
87 raise ValueError, "Les données ne sont pas au format JSON.\n" \
88 "URL concernée : %s" % url
89 # si on est arrivé jusqu'ici c'est que tout va bien... on enregistre !
90 try:
91 old_umask = umask(022)
92 f = file(filename, 'wb')
93 umask(old_umask)
94 except IOError, e:
95 raise IOError, \
96 "L'écriture du fichier '%s' a été refusée :\n %s" \
97 % (filename, e)
98 f.write(data)
99 f.close()
100 # on fixe la date indiquée par le serveur, le cas échéant
101 mtime = i.getdate('last-modified')
102 if mtime:
103 mtime = timegm(mtime)
104 utime(filename, (mtime, mtime))
105
106 return True
107
108 def _update(referentiel, force=False):
109 url = URL_BASE + '/' + referentiel
110 filename = join(DIR_BASE, referentiel)
111 # vérification de la présence d'une copie
112 if not exists(filename):
113 # fichier vide à date très ancienne pour déclencher la synchro
114 try:
115 file(filename, 'a').close()
116 utime(filename, (0, 0))
117 except IOError, e:
118 raise RuntimeError, \
119 "La création du référentiel '%s' a été refusée :\n %s" \
120 % (referentiel, e)
121 # type MIME attendu
122 if referentiel.endswith('.json'):
123 mimetype = 'application/json'
124 else:
125 mimetype = None
126 return retrieve(url, filename, mimetype=mimetype, force=force)
127
128 def get(referentiel, force=False):
129 filename = join(DIR_BASE, referentiel)
130 if not exists(filename):
131 _update(referentiel, force)
132 try:
133 f = open(filename, 'rb')
134 except IOError:
135 raise RuntimeError, "Le référentiel '%s' est indisponible." \
136 % referentiel
137 data = f.read()
138 f.close()
139 if referentiel.endswith('.json'):
140 try:
141 data = loads(data, encoding='utf-8')
142 except ValueError:
143 raise RuntimeError, \
144 "Les données du référentiel '%s' ne sont pas au format JSON.\n" \
145 "Le fichier est peut-être corrompu, essayez de forcer une mise à jour." % referentiel
146 # XXX: voir comment faire : data.last_modified = gmtime(getmtime(filename))
147 return data
148
149 def add(referentiel, force=False):
150 filename = join(DIR_BASE, referentiel)
151 if exists(filename):
152 raise RuntimeError, \
153 "Le référentiel '%s' avait déjà été ajouté." % referentiel
154 _update(referentiel, force)
155
156 def remove(referentiel):
157 filename = join(DIR_BASE, referentiel)
158 if not exists(filename):
159 raise RuntimeError, "Le référentiel '%s' est absent." % referentiel
160 try:
161 unlink(filename)
162 except (IOError, OSError), e:
163 raise RuntimeError, \
164 "La suppression du référentiel '%s' a été refusée :\n %s" \
165 % (referentiel, e)
166
167 def update(referentiel=False, force=False):
168 if referentiel:
169 _update(referentiel, force)
170 return
171 error_messages = []
172 for referentiel in list_present():
173 try:
174 _update(referentiel, force)
175 except Exception, e:
176 error_messages.append(str(e))
177 if error_messages:
178 raise RuntimeError, '\n'.join(error_messages)
179
180 def list_present():
181 return filter(lambda n: not n.startswith('.'), listdir(DIR_BASE))
182
183 def list_available(force=False):
184 return get(AVAILABLE_LIST, force)
185
186 def add_available(force=False):
187 missing = set(list_available(force)) - set(list_present())
188 for referentiel in missing:
189 _update(referentiel, force)
190