Commit | Line | Data |
---|---|---|
5b826c54 P |
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 | ||
dbd7f4a1 P |
38 | def path(referentiel): |
39 | return join(DIR_BASE, referentiel) | |
40 | ||
41 | def _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 |
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 | ||
5b826c54 | 124 | def 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 | |
131 | def 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 | |
142 | def 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 | |
155 | def list(): | |
156 | return listdir(DIR_BASE) | |
157 | ||
158 | def list_available(force=False): | |
dbd7f4a1 | 159 | return get(AVAILABLE_LIST, force) |
5b826c54 P |
160 | |
161 | def 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 |