releasing version 1.1.1
[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 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
12 CONFIG_FILE = '/etc/auf-refer/auf-refer.conf'
13
14 DIR_BASE = '/var/lib/auf-refer'
15 URL_BASE = 'http://intranet.auf/auf-refer'
16 AVAILABLE_LIST = 'auf-refer.json'
17
18 lines = filter(lambda l: not l.startswith('#'), file(CONFIG_FILE))
19 config = dict(map(lambda l: map(lambda s: s.strip(), l.split('=')), lines))
20
21 DIR_BASE = config.get('DIR_BASE', DIR_BASE).rstrip('/')
22 URL_BASE = config.get('URL_BASE', URL_BASE).rstrip('/')
23 AVAILABLE_LIST = config.get('AVAILABLE_LIST', AVAILABLE_LIST)
24
25 __all__ = ( 'DIR_BASE', 'URL_BASE', 'AVAILABLE_LIST' )
26
27 TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
28
29 from os import listdir, utime, unlink
30 from os.path import join, exists, getmtime
31 from time import gmtime, strftime
32 from calendar import timegm
33 from urllib2 import Request, urlopen, HTTPError, URLError
34 from cStringIO import StringIO
35 from gzip import GzipFile
36 from simplejson import loads
37
38 def path(referentiel):
39 return join(DIR_BASE, referentiel)
40
41 def _update(referentiel, force=False):
42 headers = {}
43 headers['Accept-Encoding'] = 'gzip,x-gzip'
44 if force:
45 headers['Pragma'] = 'no-cache'
46 filename = join(DIR_BASE, referentiel)
47 if exists(filename):
48 # n'effectuer le chargement qu'en cas de nouvelle version
49 mtime = gmtime(getmtime(filename))
50 headers['If-Modified-Since'] = strftime(TIME_FORMAT, mtime)
51 else:
52 # fichier vide à date très ancienne pour déclencher la synchro
53 try:
54 file(filename, 'a').close()
55 except IOError, e:
56 raise RuntimeError, \
57 "La création du référentiel '%s' a été refusée :\n %s" \
58 % (referentiel, e)
59 utime(filename, (0, 0))
60 url = URL_BASE + '/' + referentiel
61 req = Request(url, None, headers)
62 try:
63 u = urlopen(req)
64 except HTTPError, e:
65 if e.code == 304:
66 return
67 raise RuntimeError, \
68 "L'URL suivante renvoie un code d'erreur %s :\n %s" \
69 % (e.code, url)
70 except URLError:
71 raise RuntimeError, "L'URL suivante est inaccessible :\n %s" % url
72 i = u.info()
73 if referentiel.endswith('.json') and i.type != 'application/json':
74 u.close()
75 raise RuntimeError, \
76 "Le type des données chargées n'est pas JSON mais '%s'.\n" \
77 "URL concernée : %s" % (i.type, url)
78 data = u.read()
79 if i.get('content-encoding') in ('gzip','x-gzip'):
80 data = GzipFile('', 'r', 0, StringIO(data)).read()
81 u.close()
82 if referentiel.endswith('.json'):
83 try:
84 loads(data, encoding='utf-8')
85 except ValueError:
86 raise RuntimeError, "Les données ne sont pas au format JSON.\n" \
87 "URL concernée : %s" % url
88 # si on est arrivé jusqu'ici c'est que tout va bien... on enregistre !
89 try:
90 f = file(filename, 'wb')
91 except IOError, e:
92 raise RuntimeError, \
93 "L'écriture du référentiel '%s' a été refusée :\n %s" \
94 % (referentiel, e)
95 f.write(data)
96 f.close()
97 # on fixe la date donnée par le serveur, le cas échéant
98 mtime = i.getdate('last-modified')
99 if mtime:
100 mtime = timegm(mtime)
101 utime(filename, (mtime, mtime))
102
103 def get(referentiel, force=False):
104 filename = join(DIR_BASE, referentiel)
105 if not exists(filename):
106 _update(referentiel, force)
107 try:
108 f = open(filename, 'rb')
109 except IOError:
110 raise RuntimeError, "Le référentiel '%s' est indisponible." \
111 % referentiel
112 data = f.read()
113 f.close()
114 if referentiel.endswith('.json'):
115 try:
116 data = loads(data, encoding='utf-8')
117 except ValueError:
118 raise RuntimeError, \
119 "Les données du référentiel '%s' ne sont pas au format JSON.\n" \
120 "Le fichier est peut-être corrompu, essayez de forcer une mise à jour." % referentiel
121 # XXX: voir comment faire : data.last_modified = gmtime(getmtime(filename))
122 return data
123
124 def add(referentiel, force=False):
125 filename = join(DIR_BASE, referentiel)
126 if exists(filename):
127 raise RuntimeError, \
128 "Le référentiel '%s' avait déjà été ajouté." % referentiel
129 _update(referentiel, force)
130
131 def remove(referentiel):
132 filename = join(DIR_BASE, referentiel)
133 if not exists(filename):
134 raise RuntimeError, "Le référentiel '%s' est absent." % referentiel
135 try:
136 unlink(filename)
137 except (IOError, OSError), e:
138 raise RuntimeError, \
139 "La suppression du référentiel '%s' a été refusée :\n %s" \
140 % (referentiel, e)
141
142 def update(referentiel=False, force=False):
143 if referentiel:
144 _update(referentiel, force)
145 return
146 error_messages = []
147 for referentiel in listdir(DIR_BASE):
148 try:
149 _update(referentiel, force)
150 except Exception, e:
151 error_messages.append(str(e))
152 if error_messages:
153 raise RuntimeError, '\n'.join(error_messages)
154
155 def list():
156 return listdir(DIR_BASE)
157
158 def list_available(force=False):
159 return get(AVAILABLE_LIST, force)
160
161 def add_available(force=False):
162 missing = set(list_available(force)) - set(listdir(DIR_BASE))
163 for referentiel in missing:
164 _update(referentiel, force)
165