releasing version 1.1.1
[auf-refer.git] / aufrefer.py
CommitLineData
5b826c54
P
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3"""Librairie de copie et mise à jour des référentiels AuF.
4
5Copyright ©2009 Agence universitaire de la Francophonie
6Licence : LGPL version 3
7Auteur : Progfou <jean-christophe.andre@auf.org>
8
9Dépendances Debian : python >= 2.5, python-simplejson
10"""
11
12CONFIG_FILE = '/etc/auf-refer/auf-refer.conf'
13
14DIR_BASE = '/var/lib/auf-refer'
15URL_BASE = 'http://intranet.auf/auf-refer'
16AVAILABLE_LIST = 'auf-refer.json'
17
18lines = filter(lambda l: not l.startswith('#'), file(CONFIG_FILE))
19config = dict(map(lambda l: map(lambda s: s.strip(), l.split('=')), lines))
20
21DIR_BASE = config.get('DIR_BASE', DIR_BASE).rstrip('/')
22URL_BASE = config.get('URL_BASE', URL_BASE).rstrip('/')
23AVAILABLE_LIST = config.get('AVAILABLE_LIST', AVAILABLE_LIST)
24
25__all__ = ( 'DIR_BASE', 'URL_BASE', 'AVAILABLE_LIST' )
26
27TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
28
29from os import listdir, utime, unlink
30from os.path import join, exists, getmtime
31from time import gmtime, strftime
32from calendar import timegm
33from urllib2 import Request, urlopen, HTTPError, URLError
34from cStringIO import StringIO
35from gzip import GzipFile
36from simplejson import loads
37
dbd7f4a1
P
38def path(referentiel):
39 return join(DIR_BASE, referentiel)
40
41def _update(referentiel, force=False):
5b826c54 42 headers = {}
dbd7f4a1 43 headers['Accept-Encoding'] = 'gzip,x-gzip'
5b826c54
P
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()
dbd7f4a1 55 except IOError, e:
5b826c54 56 raise RuntimeError, \
dbd7f4a1
P
57 "La création du référentiel '%s' a été refusée :\n %s" \
58 % (referentiel, e)
5b826c54
P
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, \
dbd7f4a1 68 "L'URL suivante renvoie un code d'erreur %s :\n %s" \
5b826c54
P
69 % (e.code, url)
70 except URLError:
dbd7f4a1 71 raise RuntimeError, "L'URL suivante est inaccessible :\n %s" % url
5b826c54
P
72 i = u.info()
73 if referentiel.endswith('.json') and i.type != 'application/json':
74 u.close()
75 raise RuntimeError, \
dbd7f4a1
P
76 "Le type des données chargées n'est pas JSON mais '%s'.\n" \
77 "URL concernée : %s" % (i.type, url)
5b826c54 78 data = u.read()
dbd7f4a1 79 if i.get('content-encoding') in ('gzip','x-gzip'):
5b826c54
P
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:
dbd7f4a1
P
86 raise RuntimeError, "Les données ne sont pas au format JSON.\n" \
87 "URL concernée : %s" % url
5b826c54
P
88 # si on est arrivé jusqu'ici c'est que tout va bien... on enregistre !
89 try:
90 f = file(filename, 'wb')
dbd7f4a1 91 except IOError, e:
5b826c54 92 raise RuntimeError, \
dbd7f4a1
P
93 "L'écriture du référentiel '%s' a été refusée :\n %s" \
94 % (referentiel, e)
5b826c54
P
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
dbd7f4a1
P
103def 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
5b826c54 124def add(referentiel, force=False):
dbd7f4a1
P
125 filename = join(DIR_BASE, referentiel)
126 if exists(filename):
5b826c54 127 raise RuntimeError, \
dbd7f4a1
P
128 "Le référentiel '%s' avait déjà été ajouté." % referentiel
129 _update(referentiel, force)
5b826c54
P
130
131def remove(referentiel):
dbd7f4a1
P
132 filename = join(DIR_BASE, referentiel)
133 if not exists(filename):
134 raise RuntimeError, "Le référentiel '%s' est absent." % referentiel
5b826c54 135 try:
dbd7f4a1
P
136 unlink(filename)
137 except (IOError, OSError), e:
5b826c54 138 raise RuntimeError, \
dbd7f4a1
P
139 "La suppression du référentiel '%s' a été refusée :\n %s" \
140 % (referentiel, e)
5b826c54
P
141
142def update(referentiel=False, force=False):
143 if referentiel:
dbd7f4a1 144 _update(referentiel, force)
5b826c54
P
145 return
146 error_messages = []
147 for referentiel in listdir(DIR_BASE):
148 try:
dbd7f4a1
P
149 _update(referentiel, force)
150 except Exception, e:
151 error_messages.append(str(e))
5b826c54 152 if error_messages:
dbd7f4a1 153 raise RuntimeError, '\n'.join(error_messages)
5b826c54
P
154
155def list():
156 return listdir(DIR_BASE)
157
158def list_available(force=False):
dbd7f4a1 159 return get(AVAILABLE_LIST, force)
5b826c54
P
160
161def add_available(force=False):
dbd7f4a1
P
162 missing = set(list_available(force)) - set(listdir(DIR_BASE))
163 for referentiel in missing:
164 _update(referentiel, force)
5b826c54 165