ScolaSync  4.0
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
usbDisk.py
Aller à la documentation de ce fichier.
1 # -*- coding: utf-8 -*-
2 # $Id: usbDisk.py 36 2011-01-15 19:37:27Z georgesk $
3 
4 licence={}
5 licence_en="""
6  file usbDisk.py
7  this file is part of the project scolasync
8 
9  Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>
10 
11  This program is free software: you can redistribute it and/or modify
12  it under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version3 of the License, or
14  (at your option) any later version.
15 
16  This program is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  GNU General Public License for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program. If not, see <http://www.gnu.org/licenses/>.
23 """
24 
25 licence['en']=licence_en
26 dependences="python3-dbus python3-dbus.mainloop.qt"
27 python3safe="True"
28 
29 import dbus, subprocess, os, os.path, re, time, threading
30 from PyQt4.QtGui import *
31 
32 
33 ##
34 #
35 # une classe pour représenter un disque ou une partition.
36 #
37 # les attributs publics sont :
38 # - \b path le chemin dans le système dbus
39 # - \b device l'objet dbus qui correspond à l'instance
40 # - \b device_prop un proxy pour questionner cet objet dbus
41 # - \b selected booléen vrai si on doit considérer cette instance comme sélectionnée. Vrai à l'initialisation
42 # - \b rlock un verrou récursif permettant de réserver l'usage du media pour un seul thread
43 #
44 class uDisk:
45 
46  ##
47  #
48  # Le constructeur
49  # @param path un chemin dans le système dbus
50  # @param bus un objet dbus.BusSystem
51  #
52  def __init__(self, path, bus):
53  self.path=path
54  self.mp=None # a variable to cache the result of self.mountPoint()
55  self.device = bus.get_object("org.freedesktop.UDisks", self.path)
56  self.device_prop = dbus.Interface(self.device, "org.freedesktop.DBus.Properties")
57  self.selected=True
58  self.rlock=threading.RLock()
59  self.stickid=self.getProp("drive-serial")
60  self.uuid=self.getProp("id-uuid")
61  self.fatuuid=None # pour l'uuid de la première partion vfat
62  self.firstFat=None # poignée de la première partition vfat
63  p=self.file()
64  # self.devStuff is the name of device which is usable to umount safely this object
65  if p:
66  self.devStuff=os.path.abspath(os.path.join(os.path.dirname(p), os.readlink(p)))
67  else:
68  self.devStuff=None
69  #
70 
71 
72  _itemNames={
73  "1device-mount-paths":QApplication.translate("uDisk","point de montage",None, QApplication.UnicodeUTF8),
74  "2device-size":QApplication.translate("uDisk","taille",None, QApplication.UnicodeUTF8),
75  "3drive-vendor":QApplication.translate("uDisk","marque",None, QApplication.UnicodeUTF8),
76  "4drive-model":QApplication.translate("uDisk","modèle de disque",None, QApplication.UnicodeUTF8),
77  "5drive-serial":QApplication.translate("uDisk","numéro de série",None, QApplication.UnicodeUTF8),
78  }
79 
80  _specialItems={"0Check":QApplication.translate("uDisk","cocher",None, QApplication.UnicodeUTF8)}
81 
82  _ItemPattern=re.compile("[0-9]?(.*)")
83 
84  ##
85  #
86  # renvoie l'uuid de la première partition FAT après que celle-ci aura été
87  # identifiée (utile pour les disques partitionnés)
88  # @return un uuid
89  #
90  def getFatUuid(self):
91  return "%s" %self.fatuuid
92 
93  ##
94  #
95  # renvoie un identifiant unique. Dans cette classe, cette fonction
96  # est synonyme de file()
97  # @return un identifiant unique, garanti par le système de fichiers
98  #
99  def uniqueId(self):
100  return self.file()
101 
102  ##
103  #
104  # Méthode statique, pour avoir des titres de colonne.
105  # renvoie des titres pour les items obtenus par __getitem__.
106  # @param locale la locale, pour traduire les titres éventuellement.
107  # Valeur par défaut : "C"
108  # @return une liste de titres de colonnes
109  #
110  def headers(locale="C"):
111  result= list(uDisk._specialItems.keys())+ list(uDisk._itemNames.keys())
112  return sorted(result)
113 
114  headers = staticmethod(headers)
115 
116  ##
117  #
118  # renvoie un proxy vers un navigateur de propriétés
119  # @param bus une instace de dbus.SystemBus
120  # @return l'objet proxy
121  #
122  def devicePropProxy(self, bus):
123  return self.device_prop
124 
125  ##
126  #
127  # Renvoie la valeur de vérité d'une propriété
128  # @param prop une propriété
129  # @param value
130  # @return vrai si la propriété est vraie (cas où value==None) ou vrai si la propriété a exactement la valeur value.
131  #
132  def isTrue(self,prop, value=None):
133  if value==None:
134  return bool(self.getProp(prop))
135  else:
136  return self.getProp(prop)==value
137 
138  ##
139  #
140  # Facilite le réprage des disques USB USB
141  # @return vrai dans le cas d'un disque USB
142  #
143  def isUsbDisk(self):
144  return self.isTrue("device-is-removable") and self.isTrue("drive-connection-interface","usb") and self.isTrue("device-size")
145 
146  ##
147  #
148  # Fournit une représentation imprimable
149  # @return une représentation imprimable de l'instance
150  #
151  def __str__(self):
152  return self.title()+self.valuableProperties()
153 
154  ##
155  #
156  # Permet d'obtenir un identifiant unique de disque
157  # @return le chemin dbus de l'instance
158  #
159  def title(self):
160  return self.path
161 
162  ##
163  #
164  # Permet d'accèder à l'instance par un nom de fichier
165  # @return un nom valide dans le système de fichiers, pour accéder
166  # à l'instance.
167  #
168  def file(self):
169  fileByPath=self.getProp("device-file-by-path")
170  if isinstance(fileByPath, dbus.Array) and len(fileByPath)>0:
171  fileByPath=fileByPath[0]
172  return fileByPath
173  else:
174  return None
175 
176  ##
177  #
178  # Permet d'accèder à l'instance par un point de montage
179  # @return un point de montage, s'il en existe, sinon None
180  #
181  def mountPoint(self):
182  if self.mp==None:
183  paths=self.getProp("device-mount-paths")
184  if isinstance(paths, dbus.Array) and len(paths)>0:
185  self.mp=paths[0]
186  return paths[0]
187  else:
188  return None
189  else:
190  return self.mp
191 
192  ##
193  #
194  # Facilite l'accès aux propriétés à l'aide des mots clés du module udisks
195  # @param name le nom d'une propriété
196  # @return une propriété dbus du disque ou de la partition, sinon None si le nom name est illégal
197  #
198  def getProp(self, name):
199  try:
200  return self.device_prop.Get("org.freedesktop.UDisks", name)
201  except:
202  return None
203 
204  ##
205  #
206  # Permet de reconnaitre les partitions DOS-FAT
207  # @return True dans le cas d'une partition FAT16 ou FAT32
208  #
209  def isDosFat(self):
210  return self.getProp("id-type")=="vfat"
211 
212  ##
213  #
214  # @return True si le disque ou la partion est montée
215  #
216  def isMounted(self):
217  return bool(self.getProp("device-is-mounted"))
218 
219  ##
220  #
221  # Facilite l'accès aux propriétés intéressantes d'une instance
222  # @return une chaîne indentée avec les propriétés intéressantes, une par ligne
223  #
224  def valuableProperties(self,indent=4):
225  prefix="\n"+" "*indent
226  r=""
227  props=["device-file-by-id",
228  "device-file-by-path",
229  "device-mount-paths",
230  "device-is-partition-table",
231  "partition-table-count",
232  "device-is-read-only",
233  "device-is-drive",
234  "device-is-optical-disc",
235  "device-is-mounted",
236  "drive-vendor",
237  "drive-model",
238  "drive-serial",
239  "id-uuid",
240  "partition-slave",
241  "partition-type",
242  "device-size",
243  "id-type"]
244  for prop in props:
245  p=self.getProp(prop)
246  if isinstance(p,dbus.Array):
247  if len(p)>0:
248  r+=prefix+"%s = array:" %(prop)
249  for s in p:
250  r+=prefix+" "*indent+s
251  elif isinstance(p,dbus.Boolean):
252  r+=prefix+"%s = %s" %(prop, bool(p))
253  elif isinstance(p,dbus.Int16) or isinstance(p,dbus.Int32) or isinstance(p,dbus.Int64) or isinstance(p,dbus.UInt16) or isinstance(p,dbus.UInt32) or isinstance(p,dbus.UInt64) or isinstance(p,int):
254  if p < 10*1024:
255  r+=prefix+"%s = %s" %(prop,p)
256  elif p < 10*1024*1024:
257  r+=prefix+"%s = %s k" %(prop,p/1024)
258  elif p < 10*1024*1024*1024:
259  r+=prefix+"%s = %s M" %(prop,p/1024/1024)
260  else:
261  r+=prefix+"%s = %s G" %(prop,p/1024/1024/1024)
262  else:
263  r+=prefix+"%s = %s" %(prop,p)
264  r+=prefix+"%s = %s" %('devStuff', self.devStuff)
265  return r
266 
267  ##
268  #
269  # renvoie le chemin du disque, dans le cas où self est une partition
270  # @return le chemin dbus du disque maître, sinon "/"
271  #
272  def master(self):
273  return self.getProp("partition-slave")
274 
275  ##
276  #
277  # retire le numéro des en-têtes pour en faire un nom de propriété
278  # valide pour interroger dbus
279  # @param n un numéro de propriété qui se réfère aux headers
280  # @return une propriété renvoyée par dbus, dans un format imprimable
281  #
282  def unNumberProp(self,n):
283  m=uDisk._ItemPattern.match(self.headers()[n])
284  try:
285  prop=m.group(1)
286  result=self.showableProp(prop)
287  return result
288  except:
289  return ""
290 
291  ##
292  #
293  # Renvoie un élément de listage de données internes au disque
294  # @param n un nombre
295  # @return un élément si n>0, et le drapeau self.selected si n==0.
296  # Les noms des éléments sont dans la liste itemNames utilisée dans
297  # la fonction statique headers
298  #
299  def __getitem__(self,n):
300  propListe=self.headers()
301  if n==0:
302  return self.selected
303  elif n <= len(propListe):
304  return self.unNumberProp(n-1)
305 
306  ##
307  #
308  # Renvoie une propriété dans un type "montrable" par QT.
309  # les propriétés que renvoie dbus ont des types inconnus de Qt4,
310  # cette fonction les transtype pour que QVariant arrive à les
311  # prendre en compte.
312  # @param name le nom de la propriété
313  # @return une nombre ou une chaîne selon le type de propriété
314  #
315  def showableProp(self, name):
316  p=self.getProp(name)
317  if isinstance(p,dbus.Array):
318  if len(p)>0: return str(p[0])
319  else: return ""
320  elif isinstance(p,dbus.Boolean):
321  return "%s" %bool(p)
322  elif isinstance(p,dbus.Int16) or isinstance(p,dbus.Int32) or isinstance(p,dbus.Int64) or isinstance(p,dbus.UInt16) or isinstance(p,dbus.UInt32) or isinstance(p,dbus.UInt64) or isinstance(p,int):
323  return int(p)
324  else:
325  return "%s" %p
326 
327  ##
328  #
329  # Renvoie la première partition VFAT
330  # @result la première partition VFAT ou None s'il n'y en a pas
331  #
332  def getFirstFat(self):
333  if self.isDosFat(): return self
334  return self.firstFat
335 
336  ##
337  #
338  # Permet de s'assurer qu'une partition ou un disque sera bien monté
339  # @result le chemin du point de montage
340  #
341  def ensureMounted(self):
342  mount_paths=self.getProp("device-mount-paths")
343  if mount_paths==None: # le cas où la notion de montage est hors-sujet
344  return ""
345  leftTries=5
346  while len(mount_paths)==0 and leftTries >0:
347  leftTries = leftTries - 1
348  path=self.getProp("device-file-by-path")
349  if isinstance(path,dbus.Array) and len(path)>0:
350  path=path[0]
351  subprocess.call("udisks --mount %s > /dev/null" %path,shell=True)
352  paths=self.getProp("device-mount-paths")
353  if paths:
354  return self.getProp("device-mount-paths")[0]
355  else:
356  time.sleep(0.5)
357  else:
358  time.sleep(0.5)
359  if leftTries==0:
360  raise Exception ("Could not mount the VFAT after 5 tries.")
361  else:
362  return mount_paths[0]
363 
364 
365 
366 ##
367 #
368 # une classe pour représenter la collection des disques USB connectés
369 #
370 # les attributs publics sont :
371 # - \b access le type d'accès qu'on veut pour les items
372 # - \b bus une instance de dbus.SystemBus
373 # - \b disks la collection de disques USB, organisée en un dictionnaire
374 # de disques : les clés sont les disques, qui renvoient à un ensemble
375 # de partitions du disque
376 # - \b enumdev une liste de chemins dbus vers les disques trouvés
377 # - \b firstFats une liste composée de la première partion DOS-FAT de chaque disque USB.
378 #
379 class Available:
380 
381  ##
382  #
383  # Le constructeur
384  # @param access définit le type d'accès souhaité. Par défaut, c'est "disk"
385  # c'est à dire qu'on veut la liste des disques USB. Autres valeurs
386  # possibles : "firstFat" pour les premières partitions vfat.
387  # @param diskClass la classe de disques à créer
388  # @param diskDict un dictionnaire des disque maintenu par deviceListener
389  #
390  def __init__(self,access="disk", diskClass=uDisk, diskDict=None):
391  self.access=access
392  self.bus = dbus.SystemBus()
393  proxy = self.bus.get_object("org.freedesktop.UDisks",
394  "/org/freedesktop/UDisks")
395  iface = dbus.Interface(proxy, "org.freedesktop.UDisks")
396  self.disks={}
397  self.enumDev=iface.EnumerateDevices()
398  ### récupération des disques usb dans le dictionnaire self.disks
399  for path in self.enumDev:
400  ud=diskClass(path, self.bus)
401  if ud.isUsbDisk():
402  self.disks[ud]=[]
403  # cas des disques sans partitions
404  if bool(ud.getProp("device-is-partition-table")) == False:
405  # la propriété "device-is-partition-table" est fausse,
406  # probablement qu'il y a un système de fichiers
407  self.disks[ud].append(ud)
408  ### une deuxième passe pour récupérer et associer les partitions
409  for path in self.enumDev:
410  ud=diskClass(path, self.bus)
411  for d in self.disks.keys():
412  if ud.master() == d.path:
413  self.disks[d].append(ud)
414  self.finishInit()
415 
416  ##
417  #
418  # Fin de l'initialisation
419  #
420  def finishInit(self):
421  self.mountFirstFats()
422 
423  ##
424  #
425  # fabrique la liste des partitions FAT,
426  # monte les partitions FAT si elles ne le sont pas
427  #
428  def mountFirstFats(self):
429  self.firstFats = self.getFirstFats()
430  if self.access=="firstFat":
431  for p in self.firstFats:
432  p.ensureMounted()
433 
434  ##
435  #
436  # @return le nombre de medias connectés
437  #
438  def __trunc__(self):
439  return len(self.firstFats)
440 
441  ##
442  #
443  # Sert à comparer deux collections de disques, par exemple
444  # une collection passée et une collection présente.
445  # @param other une instance de Available
446  # @return vrai si other semble être la même collection de disques USB
447  #
448  def compare(self, other):
449  result=self.summary()==other.summary()
450  return result
451 
452  ##
453  #
454  # Permet de déterminer si un disque est dans la collection
455  # @param ud une instance de uDisk
456  # @return vrai si le uDisk ud est dans la collection
457  #
458  def contains(self, ud):
459  for k in self.disks.keys():
460  if k.getProp("device-file-by-path")==ud.getProp("device-file-by-path"): return True
461  return False
462 
463  ##
464  #
465  # Fournit une représentation imprimable d'un résumé
466  # @return une représentation imprimable d'un résumé de la collection
467  #
468  def summary(self):
469  r= "Available USB discs\n"
470  r+= "===================\n"
471  for d in sorted(self.disks.keys(), key=lambda disk: disk.getFatUuid()):
472  r+="%s\n" %(d.title(),)
473  if len(self.disks[d])>0:
474  r+=" Partitions :\n"
475  for part in sorted(self.disks[d], key=lambda disk: disk.getFatUuid()):
476  r+=" %s\n" %(part.path,)
477  return r
478 
479  ##
480  #
481  # Fournit une représentation imprimable
482  # @return une représentation imprimable de la collection
483  #
484  def __str__(self):
485  r= "Available USB discs\n"
486  r+= "===================\n"
487  for d in self.disks.keys():
488  r+="%s\n" %d
489  if len(self.disks[d])>0:
490  r+=" Partitions :\n"
491  for part in self.disks[d]:
492  r+=" %s\n" %(part.path)
493  r+=part.valuableProperties(12)+"\n"
494  return r
495 
496  ##
497  #
498  # Renvoye le nième disque. Le fonctionnement dépend du paramètre
499  # self.access
500  # @param n un numéro
501  # @return le nième disque USB connecté
502  #
503  def __getitem__(self, n):
504  if self.access=="disk":
505  return self.disks.keys()[n]
506  elif self.access=="firstFat":
507  return self.firstFats[n]
508 
509  ##
510  #
511  # Renseigne sur la longueur de la collection. Le fonctionnement
512  # dépend du paramètre self.access
513  # @return la longueur de la collection de disques renvoyée
514  #
515  def __len__(self):
516  if self.access=="disk":
517  return len(self.disks)
518  elif self.access=="firstFat":
519  return len(self.firstFats)
520 
521  ##
522  #
523  # Facilite l'accès aux partitions de type DOS-FAT, et a des effets
524  # de bord :
525  # * marque le disque avec l'uuid de la première partition FAT.
526  # * construit une liste des chemins uDisk des FATs
527  # @param setOwners si égale à True,
528  # signale que la liste devra comporter des attributs de propriétaire
529  # de medias.
530  # @return une liste de partitions, constituée de la première
531  # partition de type FAT de chaque disque USB connecté
532  #
533  def getFirstFats(self, setOwners=False):
534  result=[]
535  self.fatPaths=[]
536  for d in self.disks.keys():
537  for p in self.disks[d]:
538  if p.isDosFat() or p==d :
539  # le cas p == d correspond aux disques non partitionnés
540  # on va supposer que dans ce cas la partition ne peut
541  # être que de type DOS !!!
542  result.append(p)
543  self.fatPaths.append(p.title())
544  # on marque le disque père et la partition elle-même
545  d.fatuuid=p.uuid
546  d.firstFat=p
547  p.fatuuid=p.uuid
548  if setOwners:
549  p.owner=d.owner
550  break
551  return result
552 
553  ##
554  #
555  # @param dev un chemin comme /org/freedesktop/UDisks/devices/sdb3
556  # @return True si la partition est dans la liste des partions disponibles
557  #
558  def hasDev(self, dev):
559  s="%s" %dev
560  s=s.replace("/org/freedesktop/UDisks/devices/","")
561  for p in self.fatPaths:
562  if p.split("/")[-1]==s:
563  return True
564  return False
565 
566 
567 if __name__=="__main__":
568  machin=Available()
569  print (machin)
570 
def __init__
Le constructeur.
Definition: usbDisk.py:390
def getProp
Facilite l'accès aux propriétés à l'aide des mots clés du module udisks.
Definition: usbDisk.py:198
def getFirstFat
Renvoie la première partition VFAT.
Definition: usbDisk.py:332
def __getitem__
Renvoye le nième disque.
Definition: usbDisk.py:503
def getFatUuid
renvoie l'uuid de la première partition FAT après que celle-ci aura été identifiée (utile pour les di...
Definition: usbDisk.py:90
def showableProp
Renvoie une propriété dans un type "montrable" par QT.
Definition: usbDisk.py:315
def __len__
Renseigne sur la longueur de la collection.
Definition: usbDisk.py:515
def finishInit
Fin de l'initialisation.
Definition: usbDisk.py:420
def uniqueId
renvoie un identifiant unique.
Definition: usbDisk.py:99
def contains
Permet de déterminer si un disque est dans la collection.
Definition: usbDisk.py:458
def isUsbDisk
Facilite le réprage des disques USB USB.
Definition: usbDisk.py:143
une classe pour représenter la collection des disques USB connectés
Definition: usbDisk.py:379
def getFirstFats
Facilite l'accès aux partitions de type DOS-FAT, et a des effets de bord :
Definition: usbDisk.py:533
def isTrue
Renvoie la valeur de vérité d'une propriété
Definition: usbDisk.py:132
def devicePropProxy
renvoie un proxy vers un navigateur de propriétés
Definition: usbDisk.py:122
def isDosFat
Permet de reconnaitre les partitions DOS-FAT.
Definition: usbDisk.py:209
def title
Permet d'obtenir un identifiant unique de disque.
Definition: usbDisk.py:159
def mountFirstFats
fabrique la liste des partitions FAT, monte les partitions FAT si elles ne le sont pas ...
Definition: usbDisk.py:428
def mountPoint
Permet d'accèder à l'instance par un point de montage.
Definition: usbDisk.py:181
def valuableProperties
Facilite l'accès aux propriétés intéressantes d'une instance.
Definition: usbDisk.py:224
une classe pour représenter un disque ou une partition.
Definition: usbDisk.py:44
def ensureMounted
Permet de s'assurer qu'une partition ou un disque sera bien monté
Definition: usbDisk.py:341
def __init__
Le constructeur.
Definition: usbDisk.py:52
def __str__
Fournit une représentation imprimable.
Definition: usbDisk.py:484
def unNumberProp
retire le numéro des en-têtes pour en faire un nom de propriété valide pour interroger dbus ...
Definition: usbDisk.py:282
def compare
Sert à comparer deux collections de disques, par exemple une collection passée et une collection prés...
Definition: usbDisk.py:448
def __str__
Fournit une représentation imprimable.
Definition: usbDisk.py:151
def summary
Fournit une représentation imprimable d'un résumé
Definition: usbDisk.py:468
def __getitem__
Renvoie un élément de listage de données internes au disque.
Definition: usbDisk.py:299
def file
Permet d'accèder à l'instance par un nom de fichier.
Definition: usbDisk.py:168
def master
renvoie le chemin du disque, dans le cas où self est une partition
Definition: usbDisk.py:272