ScolaSync  1.0
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
usbThread.py
Aller à la documentation de ce fichier.
1 # -*- coding: utf-8 -*-
2 # $Id: usbThread.py 47 2011-06-13 10:20:14Z georgesk $
3 
4 licenceEn="""
5  file usbThread.py
6  this file is part of the project scolasync
7 
8  Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>
9 
10  This program is free software: you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation, either version3 of the License, or
13  (at your option) any later version.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  GNU General Public License for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program. If not, see <http://www.gnu.org/licenses/>.
22 """
23 
24 import subprocess, threading, re, os, os.path, shutil, time, glob, shlex
25 from PyQt4.QtCore import *
26 
27 _threadNumber=0
28 
29 ##
30 #
31 # Une classe pour tenir un registre des threads concernant les baladeurs.
32 #
34 
35  ##
36  #
37  # Le constructure met en place un dictionnaire
38  #
39  def __init__(self):
40  self.dico={}
41 
42  def __str__(self):
43  return "ThreadRegister: %s" %self.dico
44 
45  ##
46  #
47  # @param ud un disque
48  # @param thread un thread
49  # Empile un thread pour le baladeur ud
50  #
51  def push(self, ud, thread):
52  if ud.owner not in self.dico.keys():
53  self.dico[ud.owner]=[thread]
54  else:
55  self.dico[ud.owner].append(thread)
56 
57  ##
58  #
59  # @param ud un disque
60  # @param thread un thread
61  # Dépile un thread pour le baladeur ud
62  #
63  def pop(self, ud, thread):
64  self.dico[ud.owner].remove(thread)
65 
66  ##
67  #
68  # Indique si le disque est occupé par des threads
69  # @param owner le propriétaire du disque
70  # @return les données associées par le dictionnaire
71  #
72  def busy(self, owner):
73  if owner in self.dico.keys():
74  return self.dico[owner]
75  return []
76 
77  ##
78  #
79  # renvoie l'ensemble des threads actifs
80  #
81  def threadSet(self):
82  result=set()
83  for o in self.dico.keys():
84  for t in self.dico[o]:
85  result.add(t)
86  return result
87 
88 ##
89 #
90 # Évite d'avoir des <i>slashes</i> dans un nom de thread
91 # @return la fin du nom de chemin, après le dernier <i>slash</i> ;
92 # si le chemin ne finit pas bien, remplace les <i>slashes</i> par
93 # des sous-tirets "_".
94 #
95 def _sanitizePath(path):
96  pattern=re.compile(".*([^/]+)")
97  m=pattern.match(str(path))
98  if m:
99  return m.group(1)
100  else:
101  return str(path).replace('/','_')
102 
103 ##
104 #
105 # fabrique un nom de thread commençant par th_, suivi d'un nombre unique,
106 # suivi d'une chaîne relative à la clé USB
107 # @param ud une instance de uDisk
108 # @return un nom de thread unique
109 #
110 def _threadName(ud):
111  global _threadNumber
112  name="th_%04d_%s" %(_threadNumber,_sanitizePath(ud.path))
113  _threadNumber+=1
114  return name
115 
116 ##
117 #
118 # Renvoie la date et l'heure dans un format court
119 # @return une chaîne donnée par strftime et le format %Y/%m/%d-%H:%M:%S
120 #
121 def _date():
122  return time.strftime("%Y/%m/%d-%H:%M:%S")
123 
124 ##
125 #
126 # Une classe abstraite
127 # Cette classe sert de creuset pour les classe servant aux copies
128 # et aux effacement.
129 #
130 class abstractThreadUSB(threading.Thread):
131  ##
132  #
133  # Constructeur
134  # Crée un thread pour copier une liste de fichiers vers une clé USB.
135  # @param ud l'instance uDisk correspondant à une partition de clé USB
136  # @param fileList la liste des fichiers à traiter
137  # @param subdir un sous-répertoire de la clé USB
138  # @param dest un répertoire de destination si nécessaire, None par défaut
139  # @param logfile un fichier de journalisation, /dev/null par défaut
140  # @param parent un widget qui recevra de signaux en début et en fin
141  # d'exécution
142  #
143  def __init__(self,ud, fileList, subdir, dest=None, logfile="/dev/null",
144  parent=None):
145  threading.Thread.__init__(self,target=self.toDo,
146  args=(ud, fileList, subdir, dest, logfile),
147  name=_threadName(ud))
148  self.cmd=u"echo This is an abstract method, don't call it"
149  self.ud=ud
150  ud.threadRunning=True
151  self.fileList=fileList
152  self.subdir=subdir
153  self.dest=dest
154  self.logfile=logfile
155  self.parent=parent
156 
157  ##
158  #
159  # Écrit un message dans le fichier de journalisation
160  # @param msg le message
161  #
162  def writeToLog(self, msg):
163  open(os.path.expanduser(self.logfile),"a").write(msg+"\n")
164  return
165 
166  ##
167  #
168  # Une version modifiée de shutil.copytree qui accepte que les
169  # repertoires destination soient déjà existants. Cette source dérive
170  # de la documentation fournie avec Python 2.7
171  # @param src un nom de fichier ou de répertoire
172  # @param dst un nom de de répertoire (déjà existant ou à créer)
173  # @param symlinks vrai si on veut recopier les liens tels quels
174  # @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)
175  # @param erase s'il est vrai la source est effacée après copie réussie
176  # @param errors la liste d'erreurs déjà relevées jusque là
177  # @return une liste d'erreurs éventuellement relevées, sinon une liste vide
178  #
179  def copytree(self,src, dst, symlinks=False, ignore=None, erase=False, errors=[]):
180  names = os.listdir(src)
181  if ignore is not None:
182  ignored_names = ignore(src, names)
183  else:
184  ignored_names = set()
185 
186  try:
187  os.makedirs(dst)
188  except OSError, err:
189  pass
190  for name in names:
191  if name in ignored_names:
192  continue
193  srcname = os.path.join(src, name)
194  dstname = os.path.join(dst, name)
195  try:
196  if symlinks and os.path.islink(srcname):
197  linkto = os.readlink(srcname)
198  os.symlink(linkto, dstname)
199  if not errors and erase:
200  os.unlink(srcname)
201  elif os.path.isdir(srcname):
202  errors=self.copytree(srcname, dstname,
203  symlinks=symlinks, ignore=ignore,
204  erase=erase, errors=errors)
205  if not errors and erase:
206  os.rmdir(srcname)
207  else:
208  shutil.copy2(srcname, dstname)
209  if not errors and erase:
210  os.unlink(srcname)
211  # XXX What about devices, sockets etc.?
212  except (IOError, os.error), why:
213  errors.append((srcname, dstname, str(why)))
214  # catch the Error from the recursive copytree so that we can
215  # continue with other files
216  except Exception, err:
217  errors.extend(err.args[0])
218  return errors
219 
220  ##
221  #
222  # Renvoie une chaîne informative sur le thread
223  # @return une chaine donnant des informations sur ce qui va
224  # se passer dans le thread qui a été créé.
225  #
226  def __str__(self):
227  result="%s(\n" %self.threadType()
228  result+=" ud = %s\n" %self.ud
229  result+=" fileList = %s\n" %self.fileList
230  result+=" subdir = %s\n" %self.subdir
231  result+=" dest = %s\n" %self.dest
232  result+=" logfile = %s\n" %self.logfile
233  result+=" cmd = %s\n" %self.cmd
234  result+="\n"
235  return result
236 
237  ##
238  #
239  # @return une chaîne courte qui informe sur le type de thread
240  #
241  def threadType(self):
242  return "abstractThreadUSB"
243 
244  ##
245  #
246  # La fonction abstraite pour les choses à faire
247  # @param ud l'instance uDisk correspondant à une partition de clé USB
248  # @param fileList la liste des fichiers à traiter
249  # @param subdir un sous-répertoire de la clé USB
250  # @param dest un répertoire de destination
251  # @param logfile un fichier de journalisation
252  #
253  def toDo(self, ud, fileList, subdir, dest, logfile):
254  # ça ne fait rien du tout pour un thread abstrait
255  pass
256 
257 ##
258 #
259 # Classe pour les threads copiant vers les clés USB
260 #
261 class threadCopyToUSB(abstractThreadUSB):
262  ##
263  #
264  # Constructeur
265  # Crée un thread pour copier une liste de fichiers vers une clé USB.
266  # @param ud l'instance uDisk correspondant à une partition de clé USB
267  # @param fileList la liste des fichiers à copier
268  # @param subdir le sous-répertoire de la clé USB où faire la copie
269  # @param logfile un fichier de journalisation, /dev/null par défaut
270  # @param parent un widget qui recevra de signaux en début et en fin
271  # d'exécution
272  #
273  def __init__(self,ud, fileList, subdir, logfile="/dev/null",
274  parent=None):
275  abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=None, logfile=logfile, parent=parent)
276  self.cmd=u'mkdir -p "{toDir}"; cp -R {fromFile} "{toDir}"'
277 
278  ##
279  #
280  # @return une chaîne courte qui informe sur le type de thread
281  #
282  def threadType(self):
283  return "threadCopyToUSB"
284 
285  ##
286  #
287  # Copie une liste de fichiers vers une clé USB sous un répertoire donné.
288  # Ce répertoire est composé de ud.visibleDir() joint au
289  # sous-répertoire subdir.
290  # À chaque fichier ou répertoire copié, une ligne est journalisée dans le
291  # fichier de journal de l'application.
292  # @param ud l'instance uDisk correspondant à une partition de clé USB
293  # @param fileList la liste des fichiers à copier
294  # @param logfile un fichier de journalisation
295  # @param subdir le sous-répertoire de la clé USB où faire la copie
296  #
297  def toDo(self, ud, fileList, subdir, dest, logfile):
298  while subdir[0]=='/':
299  subdir=subdir[1:]
300  destpath=os.path.join(ud.ensureMounted(),ud.visibleDir(),subdir)
301  for f in fileList:
302  cmd="copying %s to %s" %(f, destpath)
303  if self.parent:
304  self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
305  destpath1=os.path.join(destpath, os.path.basename(f))
306  if os.path.isdir(f):
307  errors=self.copytree(f, destpath1)
308  else:
309  errors=[]
310  try:
311  shutil.copy2(f, destpath1)
312  except Exceptio, err:
313  errors.extend((f, destpath1, str(err)))
314 
315  msg="[%s] " %_date()
316  if not errors:
317  msg += "Success: "
318  else:
319  msg += "Error: "
320  msg += cmd
321  for e in errors:
322  msg += " <%s>" %e
323  if self.parent:
324  self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
325  self.writeToLog(msg)
326 
327 ##
328 #
329 # Classe pour les threads copiant depuis les clés USB
330 #
332  ##
333  #
334  # Constructeur
335  # Crée un thread pour copier une liste de fichiers depuis une clé USB
336  # vers un répertoire de disque.
337  # @param ud l'instance uDisk correspondant à une partition de clé USB
338  # @param fileList la liste des fichiers à copier
339  # @param subdir le sous-répertoire de la clé USB d'où faire la copie
340  # @param dest un répertoire de destination
341  # @param logfile un fichier de journalisation, /dev/null par défaut
342  # @param parent un widget qui recevra de signaux en début et en fin
343  # d'exécution
344  #
345  def __init__(self,ud, fileList, subdir=".", dest="/tmp",
346  rootPath="/", logfile="/dev/null", parent=None):
347  abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=dest,
348  logfile=logfile, parent=parent)
349  self.rootPath=rootPath
350  self.cmd=u'mkdir -p "{toPath}"; cp -R {fromPath} "{toPath}"'
351 
352  ##
353  #
354  # Copie une liste de fichiers d'une clé USB sous un répertoire donné.
355  # À chaque fichier ou répertoire copié, une ligne est journalisée
356  # dans le fichier de journal de l'application.
357  # @param ud l'instance uDisk correspondant à une partition de clé USB
358  # @param fileList la liste des fichiers à copier, qui peut contenir des jokers
359  # @param dest un répertoire de destination
360  # @param logfile un fichier de journalisation
361  # @param subdir le sous-répertoire de la clé USB où faire la copie
362  #
363  def toDo(self, ud, fileList, subdir, dest, logfile):
364 
365  for f in fileList:
366  ## prend le fichier ou le répertoire sur le disque courant
367  fromPath=os.path.join(ud.ensureMounted(), f)
368  owner=ud.ownerByDb()
369  ## personnalise le nom de la destination
370  newName=u"%s_%s" %(owner,os.path.dirname(f))
371  ## calcule le point de copie et le répertoire à créer s'il le faut
372  toPath=os.path.join(dest,newName)
373  cmd="copying %s to %s" %(fromPath, toPath)
374  if self.parent:
375  self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
376  destpath1=os.path.join(toPath, os.path.basename(f))
377  if os.path.isdir(fromPath):
378  errors=self.copytree(fromPath, destpath1)
379  else:
380  errors=[]
381  try:
382  shutil.copy2(fromPath, destpath1)
383  except Exception, err:
384  errors.extend((fromPath, destpath1, str(err)))
385 
386  msg="[%s] " %_date()
387  if not errors:
388  msg += "Success: "
389  else:
390  msg += "Error: "
391  msg += cmd
392  for e in errors:
393  msg += " <%s>" %e
394  if self.parent:
395  self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
396  self.writeToLog(msg)
397 
398 ##
399 #
400 # Classe pour les threads déplaçant des fichiers depuis les clés USB
401 #
403  ##
404  #
405  # Constructeur
406  # Crée un thread pour déplacer une liste de fichiers depuis une clé USB
407  # vers un répertoire de disque.
408  # @param ud l'instance uDisk correspondant à une partition de clé USB
409  # @param fileList la liste des fichiers à copier
410  # @param subdir le sous-répertoire de la clé USB d'où faire la copie
411  # @param dest un répertoire de destination
412  # @param logfile un fichier de journalisation, /dev/null par défaut
413  # @param parent un widget qui recevra de signaux en début et en fin
414  # d'exécution
415  #
416  def __init__(self,ud, fileList, subdir=".", dest="/tmp",
417  rootPath="/", logfile="/dev/null", parent=None):
418  abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=dest,
419  logfile=logfile, parent=parent)
420  self.rootPath=rootPath
421  self.cmd=u'mkdir -p "{toPath}"; cp -R {fromPath} "{toPath}" && rm -rf {fromPath}'
422 
423  ##
424  #
425  # Copie une liste de fichiers d'une clé USB sous un répertoire donné.
426  # Après chaque copie réussie la source est effacée.
427  # À chaque fichier ou répertoire copié, une ligne est journalisée
428  # dans le fichier de journal de l'application.
429  # @param ud l'instance uDisk correspondant à une partition de clé USB
430  # @param fileList la liste des fichiers à copier
431  # @param dest un répertoire de destination
432  # @param logfile un fichier de journalisation
433  # @param subdir le sous-répertoire de la clé USB où faire la copie
434  #
435  def toDo(self, ud, fileList, subdir, dest, logfile):
436  for f in fileList:
437  ## prend le fichier ou le répertoire sur le disque courant
438  fromPath=os.path.join(ud.ensureMounted(), f)
439  owner=ud.ownerByDb()
440  ## personnalise le nom de la destination
441  newName=u"%s_%s" %(owner,os.path.dirname(f))
442  ## calcule le point de copie et le répertoire à créer s'il le faut
443  toPath=os.path.join(dest,newName)
444  cmd="copying %s to %s" %(fromPath, toPath)
445  if self.parent:
446  self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
447  destpath1=os.path.join(toPath, os.path.basename(f))
448  if os.path.isdir(fromPath):
449  errors=self.copytree(fromPath, destpath1, erase=True)
450  try:
451  os.rmdir(fromPath)
452  except Exception, err:
453  errors.extend((fromPath, destpath1, str(err)))
454  else:
455  errors=[]
456  try:
457  shutil.copy2(fromPath, destpath1)
458  os.unlink(fromPath)
459  except Exception, err:
460  errors.extend((fromPath, destpath1, str(err)))
461 
462  msg="[%s] " %_date()
463  if not errors:
464  msg += "Success: "
465  else:
466  msg += "Error: "
467  msg += cmd
468  for e in errors:
469  msg += " <%s>" %e
470  if self.parent:
471  self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
472  self.writeToLog(msg)
473 
474 ##
475 #
476 # Classe pour les threads effaçant des sous-arbres dans les clés USB
477 #
479  ##
480  #
481  # Constructeur
482  # Crée un thread pour supprimer une liste de fichiers dans une clé USB.
483  # @param ud l'instance uDisk correspondant à une partition de clé USB
484  # @param fileList la liste des fichiers à supprimer
485  # @param subdir le sous-répertoire de la clé USB où faire les suppressions
486  # @param logfile un fichier de journalisation, /dev/null par défaut
487  # @param parent un widget qui recevra de signaux en début et en fin
488  # d'exécution
489  #
490  def __init__(self,ud, fileList, subdir, logfile="/dev/null",
491  parent=None):
492  abstractThreadUSB.__init__(self,ud, fileList, subdir, dest=None,
493  logfile=logfile, parent=parent)
494  self.cmd=u'rm -rf {toDel}'
495 
496  ##
497  #
498  # Supprime une liste de fichiers dans une clé USB.
499  # La liste est prise sous un répertoire donné. Le répertoire visible
500  # qui dépend du constructuer d ela clé est pris en compte.
501  # À chaque fichier ou répertoire supprimé, une ligne est
502  # journalisée dans le fichier de journal de l'application.
503  # @param l'instance uDisk correspondant à une partition de clé USB
504  # @param fileList la liste des fichiers à copier
505  # @param dest un répertoire de destination
506  # @param logfile un fichier de journalisation
507  # @param subdir le sous-répertoire de la clé USB où faire la copie
508  #
509  def toDo(self, ud, fileList, subdir, dest, logfile):
510  for f in fileList:
511  toDel=os.path.join(ud.ensureMounted(), f)
512  cmd="Deleting %s" %toDel
513  errors=[]
514  if self.parent:
515  self.parent.emit(SIGNAL("pushCmd(QString, QString)"), ud.owner, cmd)
516  if os.path.isdir(toDel):
517  try:
518  for root, dirs, files in os.walk(toDel, topdown=False):
519  for name in files:
520  os.remove(os.path.join(root, name))
521  for name in dirs:
522  os.rmdir(os.path.join(root, name))
523  os.rmdir(toDel)
524  except Exception, err:
525  errors.expand((toDel,str(err)))
526  else:
527  try:
528  os.unlink(toDel)
529  except Exception, err:
530  errors.expand((toDel,str(err)))
531  msg="[%s] " %_date()
532  if not errors:
533  msg += "Success: "
534  else:
535  msg += "Error: "
536  msg += cmd
537  for e in errors:
538  msg += " <%s>" %e
539  if self.parent:
540  self.parent.emit(SIGNAL("popCmd(QString, QString)"), ud.owner, msg)
541  self.writeToLog(msg)
542