ScolaSync  1.0
usbThread.py
Aller à la documentation de ce fichier.
00001 # -*- coding: utf-8 -*-    
00002 # $Id: usbThread.py 47 2011-06-13 10:20:14Z georgesk $  
00003 
00004 licenceEn="""
00005     file usbThread.py
00006     this file is part of the project scolasync
00007     
00008     Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>
00009 
00010     This program is free software: you can redistribute it and/or modify
00011     it under the terms of the GNU General Public License as published by
00012     the Free Software Foundation, either version3 of the License, or
00013     (at your option) any later version.
00014 
00015     This program is distributed in the hope that it will be useful,
00016     but WITHOUT ANY WARRANTY; without even the implied warranty of
00017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018     GNU General Public License for more details.
00019 
00020     You should have received a copy of the GNU General Public License
00021     along with this program.  If not, see <http://www.gnu.org/licenses/>.
00022 """
00023 
00024 import subprocess, threading, re, os, os.path, shutil, time, glob, shlex
00025 from PyQt4.QtCore import *
00026 
00027 _threadNumber=0
00028 
00029 ##
00030 # 
00031 #     Une classe pour tenir un registre des threads concernant les baladeurs.
00032 #     
00033 class ThreadRegister:
00034 
00035     ##
00036     # 
00037     #         Le constructure met en place un dictionnaire
00038     #         
00039     def __init__(self):
00040         self.dico={}
00041 
00042     def __str__(self):
00043         return "ThreadRegister: %s" %self.dico
00044         
00045     ##
00046     # 
00047     #         @param ud un disque
00048     #         @param thread un thread
00049     #         Empile un thread pour le baladeur ud
00050     #         
00051     def push(self, ud, thread):
00052         if ud.owner not in self.dico.keys():
00053             self.dico[ud.owner]=[thread]
00054         else:
00055             self.dico[ud.owner].append(thread)
00056 
00057     ##
00058     # 
00059     #         @param ud un disque
00060     #         @param thread un thread
00061     #         Dépile un thread pour le baladeur ud
00062     #         
00063     def pop(self, ud, thread):
00064         self.dico[ud.owner].remove(thread)
00065 
00066     ##
00067     # 
00068     #         Indique si le disque est occupé par des threads
00069     #         @param owner le propriétaire du disque
00070     #         @return les données associées par le dictionnaire
00071     #         
00072     def busy(self, owner):
00073         if owner in self.dico.keys():
00074             return self.dico[owner]
00075         return []
00076 
00077     ##
00078     # 
00079     #         renvoie l'ensemble des threads actifs
00080     #         
00081     def threadSet(self):
00082         result=set()
00083         for o in self.dico.keys():
00084             for t in self.dico[o]:
00085                 result.add(t)
00086         return result
00087             
00088 ##
00089 # 
00090 #     Évite d'avoir des <i>slashes</i> dans un nom de thread
00091 #     @return la fin du nom de chemin, après le dernier <i>slash</i> ;
00092 #     si le chemin ne finit pas bien, remplace les <i>slashes</i> par
00093 #     des sous-tirets "_".
00094 #     
00095 def _sanitizePath(path):
00096     pattern=re.compile(".*([^/]+)")
00097     m=pattern.match(str(path))
00098     if m:
00099         return m.group(1)
00100     else:
00101         return str(path).replace('/','_')
00102 
00103 ##
00104 # 
00105 #     fabrique un nom de thread commençant par th_, suivi d'un nombre unique,
00106 #     suivi d'une chaîne relative à la clé USB
00107 #     @param ud une instance de uDisk
00108 #     @return un nom de thread unique
00109 #     
00110 def _threadName(ud):
00111     global _threadNumber
00112     name="th_%04d_%s" %(_threadNumber,_sanitizePath(ud.path))
00113     _threadNumber+=1
00114     return name
00115 
00116 ##
00117 # 
00118 #     Renvoie la date et l'heure dans un format court
00119 #     @return une chaîne donnée par strftime et le format %Y/%m/%d-%H:%M:%S
00120 #     
00121 def _date():
00122     return time.strftime("%Y/%m/%d-%H:%M:%S")
00123 
00124 ##
00125 # 
00126 #     Une classe abstraite
00127 #      Cette classe sert de creuset pour les classe servant aux copies
00128 #      et aux effacement.
00129 #     
00130 class abstractThreadUSB(threading.Thread):
00131     ##
00132     # 
00133     #         Constructeur
00134     #         Crée un thread pour copier une liste de fichiers vers une clé USB.
00135     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00136     #         @param fileList la liste des fichiers à traiter
00137     #         @param subdir un sous-répertoire de la clé USB
00138     #         @param dest un répertoire de destination si nécessaire, None par défaut
00139     #         @param logfile un fichier de journalisation, /dev/null par défaut
00140     #         @param parent un widget qui recevra de signaux en début et en fin
00141     #           d'exécution
00142     #         
00143     def __init__(self,ud, fileList, subdir, dest=None, logfile="/dev/null",
00144                  parent=None):
00145         threading.Thread.__init__(self,target=self.toDo,
00146                                   args=(ud, fileList, subdir, dest, logfile),
00147                                   name=_threadName(ud))        
00148         self.cmd=u"echo This is an abstract method, don't call it"
00149         self.ud=ud
00150         ud.threadRunning=True
00151         self.fileList=fileList
00152         self.subdir=subdir
00153         self.dest=dest
00154         self.logfile=logfile
00155         self.parent=parent
00156 
00157     ##
00158     # 
00159     #         Écrit un message dans le fichier de journalisation
00160     #         @param msg le message
00161     #         
00162     def writeToLog(self, msg):
00163         open(os.path.expanduser(self.logfile),"a").write(msg+"\n")
00164         return
00165 
00166     ##
00167     # 
00168     #         Une version modifiée de shutil.copytree qui accepte que les
00169     #         repertoires destination soient déjà existants. Cette source dérive
00170     #         de la documentation fournie avec Python 2.7
00171     #         @param src un nom de fichier ou de répertoire
00172     #         @param dst un nom de de répertoire (déjà existant ou à créer)
00173     #         @param symlinks vrai si on veut recopier les liens tels quels
00174     #         @param ignore une fonction qui construit une liste de fichiers à ignorer (profil : répertoire, liste de noms de fichiers -> liste de noms de fichiers à ignorer)
00175     #         @param erase s'il est vrai la source est effacée après copie réussie
00176     #         @param errors la liste d'erreurs déjà relevées jusque là
00177     #         @return une liste d'erreurs éventuellement relevées, sinon une liste vide
00178     #         
00179     def copytree(self,src, dst, symlinks=False, ignore=None, erase=False, errors=[]):
00180         names = os.listdir(src)
00181         if ignore is not None:
00182             ignored_names = ignore(src, names)
00183         else:
00184             ignored_names = set()
00185 
00186         try:
00187             os.makedirs(dst)
00188         except OSError, err:
00189             pass
00190         for name in names:
00191             if name in ignored_names:
00192                 continue
00193             srcname = os.path.join(src, name)
00194             dstname = os.path.join(dst, name)
00195             try:
00196                 if symlinks and os.path.islink(srcname):
00197                     linkto = os.readlink(srcname)
00198                     os.symlink(linkto, dstname)
00199                     if not errors and erase:
00200                         os.unlink(srcname)
00201                 elif os.path.isdir(srcname):
00202                     errors=self.copytree(srcname, dstname,
00203                                          symlinks=symlinks, ignore=ignore,
00204                                          erase=erase, errors=errors)
00205                     if not errors and erase:
00206                         os.rmdir(srcname)
00207                 else:
00208                     shutil.copy2(srcname, dstname)
00209                     if not errors and erase:
00210                         os.unlink(srcname)
00211                 # XXX What about devices, sockets etc.?
00212             except (IOError, os.error), why:
00213                 errors.append((srcname, dstname, str(why)))
00214             # catch the Error from the recursive copytree so that we can
00215             # continue with other files
00216             except Exception, err:
00217                 errors.extend(err.args[0])
00218         return errors
00219 
00220     ##
00221     # 
00222     #         Renvoie une chaîne informative sur le thread
00223     #         @return une chaine donnant des informations sur ce qui va
00224     #         se passer dans le thread qui a été créé.
00225     #         
00226     def __str__(self):
00227         result="%s(\n" %self.threadType()
00228         result+="  ud       = %s\n" %self.ud
00229         result+="  fileList = %s\n" %self.fileList
00230         result+="  subdir   = %s\n" %self.subdir
00231         result+="  dest     = %s\n" %self.dest
00232         result+="  logfile  = %s\n" %self.logfile
00233         result+="  cmd      = %s\n" %self.cmd
00234         result+="\n"
00235         return result
00236 
00237     ##
00238     # 
00239     #         @return une chaîne courte qui informe sur le type de thread
00240     #         
00241     def threadType(self):
00242         return "abstractThreadUSB"
00243 
00244     ##
00245     # 
00246     #         La fonction abstraite pour les choses à faire
00247     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00248     #         @param fileList la liste des fichiers à traiter
00249     #         @param subdir un sous-répertoire de la clé USB
00250     #         @param dest un répertoire de destination
00251     #         @param logfile un fichier de journalisation
00252     #         
00253     def toDo(self, ud, fileList, subdir, dest, logfile):
00254         # ça ne fait rien du tout pour un thread abstrait
00255         pass
00256 
00257 ##
00258 # 
00259 #     Classe pour les threads copiant vers les clés USB
00260 #     
00261 class threadCopyToUSB(abstractThreadUSB):
00262     ##
00263     # 
00264     #         Constructeur
00265     #         Crée un thread pour copier une liste de fichiers vers une clé USB.
00266     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00267     #         @param fileList la liste des fichiers à copier
00268     #         @param subdir le sous-répertoire de la clé USB où faire la copie
00269     #         @param logfile un fichier de journalisation, /dev/null par défaut
00270     #         @param parent un widget qui recevra de signaux en début et en fin
00271     #           d'exécution
00272     #         
00273     def __init__(self,ud, fileList, subdir, logfile="/dev/null",
00274                  parent=None):
00275         abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=None, logfile=logfile, parent=parent)
00276         self.cmd=u'mkdir -p "{toDir}"; cp -R {fromFile} "{toDir}"'
00277 
00278     ##
00279     # 
00280     #         @return une chaîne courte qui informe sur le type de thread
00281     #         
00282     def threadType(self):
00283         return "threadCopyToUSB"
00284 
00285     ##
00286     # 
00287     #         Copie une liste de fichiers vers une clé USB sous un répertoire donné.
00288     #          Ce répertoire est composé de ud.visibleDir() joint au
00289     #          sous-répertoire subdir.
00290     #          À chaque fichier ou répertoire copié, une ligne est journalisée dans le
00291     #          fichier de journal de l'application.
00292     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00293     #         @param fileList la liste des fichiers à copier
00294     #         @param logfile un fichier de journalisation
00295     #         @param subdir le sous-répertoire de la clé USB où faire la copie
00296     #         
00297     def toDo(self, ud, fileList, subdir, dest, logfile):
00298         while subdir[0]=='/':
00299             subdir=subdir[1:]
00300         destpath=os.path.join(ud.ensureMounted(),ud.visibleDir(),subdir)
00301         for f in fileList:
00302             cmd="copying %s to %s" %(f, destpath)
00303             if self.parent:
00304                 self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
00305             destpath1=os.path.join(destpath, os.path.basename(f))
00306             if os.path.isdir(f):
00307                 errors=self.copytree(f, destpath1)
00308             else:
00309                 errors=[]
00310                 try:
00311                     shutil.copy2(f, destpath1)
00312                 except Exceptio, err:
00313                     errors.extend((f, destpath1, str(err)))
00314                     
00315             msg="[%s] " %_date()
00316             if not errors:
00317                 msg += "Success: "
00318             else:
00319                 msg += "Error:   "
00320             msg += cmd
00321             for e in errors:
00322                 msg += " <%s>" %e
00323             if self.parent:
00324                 self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
00325             self.writeToLog(msg)
00326             
00327 ##
00328 # 
00329 #     Classe pour les threads copiant depuis les clés USB
00330 #     
00331 class threadCopyFromUSB(abstractThreadUSB):
00332     ##
00333     # 
00334     #         Constructeur
00335     #         Crée un thread pour copier une liste de fichiers depuis une clé USB
00336     #         vers un répertoire de disque.
00337     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00338     #         @param fileList la liste des fichiers à copier
00339     #         @param subdir le sous-répertoire de la clé USB d'où faire la copie
00340     #         @param dest un répertoire de destination
00341     #         @param logfile un fichier de journalisation, /dev/null par défaut
00342     #         @param parent un widget qui recevra de signaux en début et en fin
00343     #           d'exécution
00344     #         
00345     def __init__(self,ud, fileList, subdir=".", dest="/tmp",
00346                  rootPath="/", logfile="/dev/null", parent=None):
00347         abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=dest,
00348                                    logfile=logfile, parent=parent)
00349         self.rootPath=rootPath
00350         self.cmd=u'mkdir -p "{toPath}"; cp -R {fromPath} "{toPath}"'
00351 
00352     ##
00353     # 
00354     #         Copie une liste de fichiers d'une clé USB sous un répertoire donné.
00355     #          À chaque fichier ou répertoire copié, une ligne est journalisée
00356     #          dans le fichier de journal de l'application.
00357     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00358     #         @param fileList la liste des fichiers à copier, qui peut contenir des jokers
00359     #         @param dest un répertoire de destination
00360     #         @param logfile un fichier de journalisation
00361     #         @param subdir le sous-répertoire de la clé USB où faire la copie
00362     #         
00363     def toDo(self, ud, fileList, subdir, dest, logfile):
00364         
00365         for f in fileList:
00366             ## prend le fichier ou le répertoire sur le disque courant
00367             fromPath=os.path.join(ud.ensureMounted(), f)
00368             owner=ud.ownerByDb()
00369             ## personnalise le nom de la destination
00370             newName=u"%s_%s" %(owner,os.path.dirname(f))
00371             ## calcule le point de copie et le répertoire à créer s'il le faut
00372             toPath=os.path.join(dest,newName)
00373             cmd="copying %s to %s" %(fromPath, toPath)
00374             if self.parent:
00375                 self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
00376             destpath1=os.path.join(toPath, os.path.basename(f))
00377             if os.path.isdir(fromPath):
00378                 errors=self.copytree(fromPath, destpath1)
00379             else:
00380                 errors=[]
00381                 try:
00382                     shutil.copy2(fromPath, destpath1)
00383                 except Exception, err:
00384                     errors.extend((fromPath, destpath1, str(err)))
00385                     
00386             msg="[%s] " %_date()
00387             if not errors:
00388                 msg += "Success: "
00389             else:
00390                 msg += "Error:   "
00391             msg += cmd
00392             for e in errors:
00393                 msg += " <%s>" %e
00394             if self.parent:
00395                 self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
00396             self.writeToLog(msg)
00397 
00398 ##
00399 # 
00400 #     Classe pour les threads déplaçant des fichiers depuis les clés USB
00401 #     
00402 class threadMoveFromUSB(abstractThreadUSB):
00403     ##
00404     # 
00405     #         Constructeur
00406     #         Crée un thread pour déplacer une liste de fichiers depuis une clé USB
00407     #         vers un répertoire de disque.
00408     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00409     #         @param fileList la liste des fichiers à copier
00410     #         @param subdir le sous-répertoire de la clé USB d'où faire la copie
00411     #         @param dest un répertoire de destination
00412     #         @param logfile un fichier de journalisation, /dev/null par défaut
00413     #         @param parent un widget qui recevra de signaux en début et en fin
00414     #           d'exécution
00415     #         
00416     def __init__(self,ud, fileList, subdir=".", dest="/tmp",
00417                  rootPath="/", logfile="/dev/null", parent=None):
00418         abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=dest,
00419                                    logfile=logfile, parent=parent)
00420         self.rootPath=rootPath
00421         self.cmd=u'mkdir -p "{toPath}"; cp -R {fromPath} "{toPath}" && rm -rf {fromPath}'
00422 
00423     ##
00424     # 
00425     #         Copie une liste de fichiers d'une clé USB sous un répertoire donné.
00426     #          Après chaque copie réussie la source est effacée.
00427     #          À chaque fichier ou répertoire copié, une ligne est journalisée
00428     #          dans le fichier de journal de l'application.
00429     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00430     #         @param fileList la liste des fichiers à copier
00431     #         @param dest un répertoire de destination
00432     #         @param logfile un fichier de journalisation
00433     #         @param subdir le sous-répertoire de la clé USB où faire la copie
00434     #         
00435     def toDo(self, ud, fileList, subdir, dest, logfile):
00436         for f in fileList:
00437             ## prend le fichier ou le répertoire sur le disque courant
00438             fromPath=os.path.join(ud.ensureMounted(), f)
00439             owner=ud.ownerByDb()
00440             ## personnalise le nom de la destination
00441             newName=u"%s_%s" %(owner,os.path.dirname(f))
00442             ## calcule le point de copie et le répertoire à créer s'il le faut
00443             toPath=os.path.join(dest,newName)
00444             cmd="copying %s to %s" %(fromPath, toPath)
00445             if self.parent:
00446                 self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
00447             destpath1=os.path.join(toPath, os.path.basename(f))
00448             if os.path.isdir(fromPath):
00449                 errors=self.copytree(fromPath, destpath1, erase=True)
00450                 try:
00451                     os.rmdir(fromPath)
00452                 except Exception, err:
00453                     errors.extend((fromPath, destpath1, str(err)))
00454             else:
00455                 errors=[]
00456                 try:
00457                     shutil.copy2(fromPath, destpath1)
00458                     os.unlink(fromPath)
00459                 except Exception, err:
00460                     errors.extend((fromPath, destpath1, str(err)))
00461                     
00462             msg="[%s] " %_date()
00463             if not errors:
00464                 msg += "Success: "
00465             else:
00466                 msg += "Error:   "
00467             msg += cmd
00468             for e in errors:
00469                 msg += " <%s>" %e
00470             if self.parent:
00471                 self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
00472             self.writeToLog(msg)
00473             
00474 ##
00475 # 
00476 #     Classe pour les threads effaçant des sous-arbres dans les clés USB
00477 #     
00478 class threadDeleteInUSB(abstractThreadUSB):
00479     ##
00480     # 
00481     #         Constructeur
00482     #          Crée un thread pour supprimer une liste de fichiers dans une clé USB.
00483     #         @param ud l'instance uDisk correspondant à une partition de clé USB
00484     #         @param fileList la liste des fichiers à supprimer
00485     #         @param subdir le sous-répertoire de la clé USB où faire les suppressions
00486     #         @param logfile un fichier de journalisation, /dev/null par défaut
00487     #         @param parent un widget qui recevra de signaux en début et en fin
00488     #           d'exécution
00489     #         
00490     def __init__(self,ud, fileList, subdir, logfile="/dev/null",
00491                  parent=None):
00492         abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=None,
00493                                    logfile=logfile, parent=parent)
00494         self.cmd=u'rm -rf {toDel}'
00495 
00496     ##
00497     # 
00498     #         Supprime une liste de fichiers dans une clé USB.
00499     #          La liste est prise sous un répertoire donné. Le répertoire visible
00500     #          qui dépend du constructuer d ela clé est pris en compte.
00501     #          À chaque fichier ou répertoire supprimé, une ligne est
00502     #          journalisée dans le fichier de journal de l'application.
00503     #         @param l'instance uDisk correspondant à une partition de clé USB
00504     #         @param fileList la liste des fichiers à copier
00505     #         @param dest un répertoire de destination
00506     #         @param logfile un fichier de journalisation
00507     #         @param subdir le sous-répertoire de la clé USB où faire la copie
00508     #         
00509     def toDo(self, ud, fileList, subdir, dest, logfile):
00510         for f in fileList:
00511             toDel=os.path.join(ud.ensureMounted(), f)
00512             cmd="Deleting %s" %toDel
00513             errors=[]
00514             if self.parent:
00515                 self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
00516             if os.path.isdir(toDel):
00517                 try:
00518                     for root, dirs, files in os.walk(toDel, topdown=False):
00519                         for name in files:
00520                             os.remove(os.path.join(root, name))
00521                         for name in dirs:
00522                             os.rmdir(os.path.join(root, name))
00523                     os.rmdir(toDel)
00524                 except Exception, err:
00525                     errors.expand((toDel,str(err)))
00526             else:
00527                 try:
00528                     os.unlink(toDel)
00529                 except Exception, err:
00530                     errors.expand((toDel,str(err)))
00531             msg="[%s] " %_date()
00532             if not errors:
00533                 msg += "Success: "
00534             else:
00535                 msg += "Error:   "
00536             msg += cmd
00537             for e in errors:
00538                 msg += " <%s>" %e
00539             if self.parent:
00540                 self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
00541             self.writeToLog(msg)
00542 
 Tout Classes Espaces de nommage Fichiers Fonctions Variables