releasing version 1.0.0
[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 update_referentiel(referentiel, force=False):
39 headers = {}
40 headers['Accept-Encoding'] = 'x-gzip'
41 if force:
42 headers['Pragma'] = 'no-cache'
43 filename = join(DIR_BASE, referentiel)
44 if exists(filename):
45 # n'effectuer le chargement qu'en cas de nouvelle version
46 mtime = gmtime(getmtime(filename))
47 headers['If-Modified-Since'] = strftime(TIME_FORMAT, mtime)
48 else:
49 # fichier vide à date très ancienne pour déclencher la synchro
50 try:
51 file(filename, 'a').close()
52 except IOError, msg:
53 raise RuntimeError, \
54 u"La création du référentiel '%s' a été refusée :\n %s" \
55 % (referentiel, msg)
56 utime(filename, (0, 0))
57 url = URL_BASE + '/' + referentiel
58 req = Request(url, None, headers)
59 try:
60 u = urlopen(req)
61 except HTTPError, e:
62 if e.code == 304:
63 return
64 raise RuntimeError, \
65 u"L'URL suivante renvoie un code d'erreur %s :\n %s" \
66 % (e.code, url)
67 except URLError:
68 raise RuntimeError, u"L'URL suivante est inaccessible :\n %s" % url
69 i = u.info()
70 if referentiel.endswith('.json') and i.type != 'application/json':
71 u.close()
72 raise RuntimeError, \
73 u"Le type des données chargées n'est pas JSON mais '%s'.\n" \
74 u"URL concernée : %s" % (i.type, url)
75 data = u.read()
76 if i.get('content-encoding') == 'x-gzip':
77 data = GzipFile('', 'r', 0, StringIO(data)).read()
78 u.close()
79 if referentiel.endswith('.json'):
80 try:
81 loads(data, encoding='utf-8')
82 except ValueError:
83 raise RuntimeError, u"Les données ne sont pas au format JSON.\n" \
84 u"URL concernée : %s" % url
85 # si on est arrivé jusqu'ici c'est que tout va bien... on enregistre !
86 try:
87 f = file(filename, 'wb')
88 except IOError, msg:
89 raise RuntimeError, \
90 u"L'écriture du référentiel '%s' a été refusée :\n %s" \
91 % (referentiel, msg)
92 f.write(data)
93 f.close()
94 # on fixe la date donnée par le serveur, le cas échéant
95 mtime = i.getdate('last-modified')
96 if mtime:
97 mtime = timegm(mtime)
98 utime(filename, (mtime, mtime))
99
100 def add(referentiel, force=False):
101 if referentiel in listdir(DIR_BASE):
102 raise RuntimeError, \
103 u"Le référentiel '%s' avait déjà été ajouté." % referentiel
104 update_referentiel(referentiel, force)
105
106 def remove(referentiel):
107 if not referentiel in listdir(DIR_BASE):
108 raise RuntimeError, u"Le référentiel '%s' est absent." % referentiel
109 try:
110 unlink(join(DIR_BASE, referentiel))
111 except (IOError, OSError), msg:
112 raise RuntimeError, \
113 u"La suppression du référentiel '%s' a été refusée :\n %s" \
114 % (referentiel, msg)
115
116 def update(referentiel=False, force=False):
117 if referentiel:
118 update_referentiel(referentiel, force)
119 return
120 error_messages = []
121 for referentiel in listdir(DIR_BASE):
122 try:
123 update_referentiel(referentiel, force)
124 except Exception, msg:
125 error_messages.append(unicode(msg))
126 if error_messages:
127 raise RuntimeError, u'\n'.join(error_messages)
128
129 def list():
130 return listdir(DIR_BASE)
131
132 def list_available(force=False):
133 filename = join(DIR_BASE, AVAILABLE_LIST)
134 if not exists(filename):
135 update_referentiel(AVAILABLE_LIST, force)
136 try:
137 f = open(filename, 'rb')
138 except IOError:
139 raise RuntimeError, u"La liste des référentiels est indisponible."
140 try:
141 referentiels = loads(f.read(), encoding='utf-8')
142 except ValueError:
143 raise RuntimeError, \
144 u"La liste des référentiels n'est pas au format JSON.\n" \
145 u"Essayez de forcer une mise à jour."
146 f.close()
147 return referentiels
148
149 def add_available(force=False):
150 referentiels = list_available(force)
151 referentiels = set(referentiels) - set(listdir(DIR_BASE))
152 for referentiel in referentiels:
153 update_referentiel(referentiel, force)
154