auf-refer-photos : ajout des options -v et -f dans l'aide
[auf-refer.git] / aufrefer.py
old mode 100755 (executable)
new mode 100644 (file)
index 70babad..6d5ff71
@@ -2,12 +2,20 @@
 # -*- coding: utf-8 -*-
 """Librairie de copie et mise à jour des référentiels AuF.
 
-Copyright ©2009  Agence universitaire de la Francophonie
+Copyright ©2009-2011  Agence universitaire de la Francophonie
 Licence : LGPL version 3
 Auteur : Progfou <jean-christophe.andre@auf.org>
 
 Dépendances Debian : python >= 2.5, python-simplejson
 """
+from os import listdir, utime, unlink, umask
+from os.path import join, exists, getmtime
+from time import gmtime, strftime
+from calendar import timegm
+from urllib2 import Request, urlopen, HTTPError, URLError
+from cStringIO import StringIO
+from gzip import GzipFile
+from simplejson import loads
 
 CONFIG_FILE = '/etc/auf-refer/auf-refer.conf'
 
@@ -15,8 +23,12 @@ DIR_BASE = '/var/lib/auf-refer'
 URL_BASE = 'http://intranet.auf/auf-refer'
 AVAILABLE_LIST = 'auf-refer.json'
 
-lines = filter(lambda l: not l.startswith('#'), file(CONFIG_FILE))
-config = dict(map(lambda l: map(lambda s: s.strip(), l.split('=')), lines))
+try:
+    lines = filter(lambda l: not l.startswith('#'), file(CONFIG_FILE))
+    config = dict(map(lambda l: map(lambda s: s.strip(), l.split('=')), lines))
+except IOError:
+    print "Fichier de configuration '%s' indisponible." % CONFIG_FILE
+    config = {}
 
 DIR_BASE = config.get('DIR_BASE', DIR_BASE).rstrip('/')
 URL_BASE = config.get('URL_BASE', URL_BASE).rstrip('/')
@@ -26,129 +38,153 @@ __all__ = ( 'DIR_BASE', 'URL_BASE', 'AVAILABLE_LIST' )
 
 TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
 
-from os import listdir, utime, unlink
-from os.path import join, exists, getmtime
-from time import gmtime, strftime
-from calendar import timegm
-from urllib2 import Request, urlopen, HTTPError, URLError
-from cStringIO import StringIO
-from gzip import GzipFile
-from simplejson import loads
+def path(referentiel):
+    return join(DIR_BASE, referentiel)
 
-def update_referentiel(referentiel, force=False):
+def retrieve(url, filename, force=False, mimetype=None, ignore404=False):
     headers = {}
-    headers['Accept-Encoding'] = 'x-gzip'
+    headers['Accept-Encoding'] = 'gzip,x-gzip'
+    # ajout d'un champ pour forcer le cache au besoin
     if force:
         headers['Pragma'] = 'no-cache'
-    filename = join(DIR_BASE, referentiel)
+    # ajout d'un champ indiquant la date du fichier déjà présent
     if exists(filename):
         # n'effectuer le chargement qu'en cas de nouvelle version
         mtime = gmtime(getmtime(filename))
         headers['If-Modified-Since'] = strftime(TIME_FORMAT, mtime)
-    else:
-        # fichier vide à date très ancienne pour déclencher la synchro
-        try:
-            file(filename, 'a').close()
-        except IOError, msg:
-            raise RuntimeError, \
-                u"La création du référentiel '%s' a été refusée :\n  %s" \
-                                                    % (referentiel, msg)
-        utime(filename, (0, 0))
-    url = URL_BASE + '/' + referentiel
+    # requête de données à l'URL demandée
     req = Request(url, None, headers)
     try:
         u = urlopen(req)
     except HTTPError, e:
         if e.code == 304:
-            return
+            return None
+        if ignore404 and e.code == 404:
+            return False
         raise RuntimeError, \
-            u"L'URL suivante renvoie un code d'erreur %s :\n  %s" \
+            "L'URL suivante renvoie un code d'erreur %s :\n  %s" \
                                                         % (e.code, url)
     except URLError:
-        raise RuntimeError, u"L'URL suivante est inaccessible :\n  %s" % url
+        raise RuntimeError, "L'URL suivante est inaccessible :\n  %s" % url
+    # infos sur les données
     i = u.info()
-    if referentiel.endswith('.json') and i.type != 'application/json':
+    # vérification du type MIME
+    if mimetype and i.type != mimetype:
         u.close()
-        raise RuntimeError, \
-            u"Le type des données chargées n'est pas JSON mais '%s'.\n" \
-            u"URL concernée : %s" % (i.type, url)
+        raise ValueError, \
+            "Le type de données n'est pas '%s' mais '%s'.\n" \
+            "URL concernée : %s" % (mimetype, i.type, url)
     data = u.read()
-    if i.get('content-encoding') == 'x-gzip':
+    # décompression à la volée
+    if i.get('content-encoding') in ('gzip','x-gzip'):
         data = GzipFile('', 'r', 0, StringIO(data)).read()
     u.close()
-    if referentiel.endswith('.json'):
+    # test de validité des données en cas de JSON
+    if mimetype == 'application/json':
         try:
             loads(data, encoding='utf-8')
         except ValueError:
-            raise RuntimeError, u"Les données ne sont pas au format JSON.\n" \
-                                u"URL concernée : %s" % url
+            raise ValueError, "Les données ne sont pas au format JSON.\n" \
+                                "URL concernée : %s" % url
     # si on est arrivé jusqu'ici c'est que tout va bien... on enregistre !
     try:
+        old_umask = umask(022)
         f = file(filename, 'wb')
-    except IOError, msg:
-        raise RuntimeError, \
-            u"L'écriture du référentiel '%s' a été refusée :\n  %s" \
-                                                    % (referentiel, msg)
+        umask(old_umask)
+    except IOError, e:
+        raise IOError, \
+            "L'écriture du fichier '%s' a été refusée :\n  %s" \
+                                                        % (filename, e)
     f.write(data)
     f.close()
-    # on fixe la date donnée par le serveur, le cas échéant
+    # on fixe la date indiquée par le serveur, le cas échéant
     mtime = i.getdate('last-modified')
     if mtime:
         mtime = timegm(mtime)
         utime(filename, (mtime, mtime))
 
+    return True
+
+def _update(referentiel, force=False):
+    url = URL_BASE + '/' + referentiel
+    filename = join(DIR_BASE, referentiel)
+    # vérification de la présence d'une copie
+    if not exists(filename):
+        # fichier vide à date très ancienne pour déclencher la synchro
+        try:
+            file(filename, 'a').close()
+            utime(filename, (0, 0))
+        except IOError, e:
+            raise RuntimeError, \
+                "La création du référentiel '%s' a été refusée :\n  %s" \
+                                                        % (referentiel, e)
+    # type MIME attendu
+    if referentiel.endswith('.json'):
+        mimetype = 'application/json'
+    else:
+        mimetype = None
+    return retrieve(url, filename, mimetype=mimetype, force=force)
+
+def get(referentiel, force=False):
+    filename = join(DIR_BASE, referentiel)
+    if not exists(filename):
+        _update(referentiel, force)
+    try:
+        f = open(filename, 'rb')
+    except IOError:
+        raise RuntimeError, "Le référentiel '%s' est indisponible." \
+                                                            % referentiel
+    data = f.read()
+    f.close()
+    if referentiel.endswith('.json'):
+        try:
+            data = loads(data, encoding='utf-8')
+        except ValueError:
+            raise RuntimeError, \
+                "Les données du référentiel '%s' ne sont pas au format JSON.\n" \
+                "Le fichier est peut-être corrompu, essayez de forcer une mise à jour." % referentiel
+    # XXX: voir comment faire : data.last_modified = gmtime(getmtime(filename))
+    return data
+
 def add(referentiel, force=False):
-    if referentiel in listdir(DIR_BASE):
+    filename = join(DIR_BASE, referentiel)
+    if exists(filename):
         raise RuntimeError, \
-            u"Le référentiel '%s' avait déjà été ajouté." % referentiel
-    update_referentiel(referentiel, force)
+            "Le référentiel '%s' avait déjà été ajouté." % referentiel
+    _update(referentiel, force)
 
 def remove(referentiel):
-    if not referentiel in listdir(DIR_BASE):
-        raise RuntimeError, u"Le référentiel '%s' est absent." % referentiel
+    filename = join(DIR_BASE, referentiel)
+    if not exists(filename):
+        raise RuntimeError, "Le référentiel '%s' est absent." % referentiel
     try:
-        unlink(join(DIR_BASE, referentiel))
-    except (IOError, OSError), msg:
+        unlink(filename)
+    except (IOError, OSError), e:
         raise RuntimeError, \
-            u"La suppression du référentiel '%s' a été refusée :\n  %s" \
-                                                    % (referentiel, msg)
+            "La suppression du référentiel '%s' a été refusée :\n  %s" \
+                                                        % (referentiel, e)
 
 def update(referentiel=False, force=False):
     if referentiel:
-        update_referentiel(referentiel, force)
+        _update(referentiel, force)
         return
     error_messages = []
-    for referentiel in listdir(DIR_BASE):
+    for referentiel in list_present():
         try:
-            update_referentiel(referentiel, force)
-        except Exception, msg:
-            error_messages.append(unicode(msg))
+            _update(referentiel, force)
+        except Exception, e:
+            error_messages.append(str(e))
     if error_messages:
-        raise RuntimeError, u'\n'.join(error_messages)
+        raise RuntimeError, '\n'.join(error_messages)
 
-def list():
-    return listdir(DIR_BASE)
+def list_present():
+    return filter(lambda n: not n.startswith('.'), listdir(DIR_BASE))
 
 def list_available(force=False):
-    filename = join(DIR_BASE, AVAILABLE_LIST)
-    if not exists(filename):
-        update_referentiel(AVAILABLE_LIST, force)
-    try:
-        f = open(filename, 'rb')
-    except IOError:
-        raise RuntimeError, u"La liste des référentiels est indisponible."
-    try:
-        referentiels = loads(f.read(), encoding='utf-8')
-    except ValueError:
-        raise RuntimeError, \
-            u"La liste des référentiels n'est pas au format JSON.\n" \
-            u"Essayez de forcer une mise à jour."
-    f.close()
-    return referentiels
+    return get(AVAILABLE_LIST, force)
 
 def add_available(force=False):
-    referentiels = list_available(force)
-    referentiels = set(referentiels) - set(listdir(DIR_BASE))
-    for referentiel in referentiels:
-        update_referentiel(referentiel, force)
+    missing = set(list_available(force)) - set(list_present())
+    for referentiel in missing:
+        _update(referentiel, force)