2 # -*- coding: utf-8 -*-
3 """Librairie de copie et mise à jour des référentiels AuF.
5 Copyright ©2009 Agence universitaire de la Francophonie
6 Licence : LGPL version 3
7 Auteur : Progfou <jean-christophe.andre@auf.org>
9 Dépendances Debian : python >= 2.5, python-simplejson
12 CONFIG_FILE
= '/etc/auf-refer/auf-refer.conf'
14 DIR_BASE
= '/var/lib/auf-refer'
15 URL_BASE
= 'http://intranet.auf/auf-refer'
16 AVAILABLE_LIST
= 'auf-refer.json'
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
))
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
)
25 __all__
= ( 'DIR_BASE', 'URL_BASE', 'AVAILABLE_LIST' )
27 TIME_FORMAT
= '%a, %d %b %Y %H:%M:%S GMT'
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
38 def path(referentiel
):
39 return join(DIR_BASE
, referentiel
)
41 def _update(referentiel
, force
=False):
43 headers
['Accept-Encoding'] = 'gzip,x-gzip'
45 headers
['Pragma'] = 'no-cache'
46 filename
= join(DIR_BASE
, referentiel
)
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
)
52 # fichier vide à date très ancienne pour déclencher la synchro
54 file(filename
, 'a').close()
57 "La création du référentiel '%s' a été refusée :\n %s" \
59 utime(filename
, (0, 0))
60 url
= URL_BASE
+ '/' + referentiel
61 req
= Request(url
, None, headers
)
68 "L'URL suivante renvoie un code d'erreur %s :\n %s" \
71 raise RuntimeError, "L'URL suivante est inaccessible :\n %s" % url
73 if referentiel
.endswith('.json') and i
.type != 'application/json':
76 "Le type des données chargées n'est pas JSON mais '%s'.\n" \
77 "URL concernée : %s" % (i
.type, url
)
79 if i
.get('content-encoding') in ('gzip','x-gzip'):
80 data
= GzipFile('', 'r', 0, StringIO(data
)).read()
82 if referentiel
.endswith('.json'):
84 loads(data
, encoding
='utf-8')
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 !
90 f
= file(filename
, 'wb')
93 "L'écriture du référentiel '%s' a été refusée :\n %s" \
97 # on fixe la date donnée par le serveur, le cas échéant
98 mtime
= i
.getdate('last-modified')
100 mtime
= timegm(mtime
)
101 utime(filename
, (mtime
, mtime
))
103 def get(referentiel
, force
=False):
104 filename
= join(DIR_BASE
, referentiel
)
105 if not exists(filename
):
106 _update(referentiel
, force
)
108 f
= open(filename
, 'rb')
110 raise RuntimeError, "Le référentiel '%s' est indisponible." \
114 if referentiel
.endswith('.json'):
116 data
= loads(data
, encoding
='utf-8')
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))
124 def add(referentiel
, force
=False):
125 filename
= join(DIR_BASE
, referentiel
)
127 raise RuntimeError, \
128 "Le référentiel '%s' avait déjà été ajouté." % referentiel
129 _update(referentiel
, force
)
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
137 except (IOError, OSError), e
:
138 raise RuntimeError, \
139 "La suppression du référentiel '%s' a été refusée :\n %s" \
142 def update(referentiel
=False, force
=False):
144 _update(referentiel
, force
)
147 for referentiel
in listdir(DIR_BASE
):
149 _update(referentiel
, force
)
151 error_messages
.append(str(e
))
153 raise RuntimeError, '\n'.join(error_messages
)
156 return listdir(DIR_BASE
)
158 def list_available(force
=False):
159 return get(AVAILABLE_LIST
, force
)
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
)