ScolaSync  4.0
usbDisk2.py
Aller à la documentation de ce fichier.
1 # -*- coding: utf-8 -*-
2 # $Id: usbDisk2.py 36 2014-03-16 19:37:27Z georgesk $
3 
4 licence={}
5 licence_en="""
6  file usbDisk2.py
7  this file is part of the project scolasync. It is a rewrite of
8  usbDisk.py to take in account udisks2.
9 
10  Copyright (C) 2014 Georges Khaznadar <georgesk@ofset.org>
11 
12  This program is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version3 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 """
25 
26 licence['en']=licence_en
27 dependences="python3-dbus python3-dbus.mainloop.qt"
28 
29 
30 import dbus, subprocess, os, os.path, re, time, threading, logging, inspect
31 from dbus.mainloop.glib import DBusGMainLoop, threads_init
32 from gi.repository import Gio, GLib, UDisks
33 from PyQt4.QtGui import *
34 
35 #################### activate debugging #######################
36 debug=False
38  return ""
39 
40 if debug :
41  logging.basicConfig(level=logging.DEBUG)
42  def inspectData():
43  caller=1
44  callerframerecord = inspect.stack()[caller]
45  frame = callerframerecord[0]
46  info = inspect.getframeinfo(frame)
47  return " -- file={0}, function={1}, line={2}".format(
48  info.filename, info.function, info.lineno
49  )
50 else:
51  pass
52  # logging.basicConfig(level=logging.NOTSET)
53 ###############################################################
54 
55 
56 ##
57 #
58 # Récupère de façon sûre le path d'une instance de UDisksObjectProxy
59 # @param obj instance de UDisksObjectProxy, ou simple chaine
60 #
61 def safePath(obj):
62  if type(obj)==type(""):
63  path=obj
64  else:
65  path= obj.get_object_path()
66  posUnderscore=path.rfind("_")
67  posSlash=path.rfind("/")
68  if posUnderscore > posSlash: # il faut retirer tout après l'underscore final
69  path=path[:posUnderscore]
70  return path
71 
72 ##
73 #
74 # Renvoie la taille d'un système de fichier et la place disponible
75 # @return un tuple : taille totale et espace libre
76 #
77 def fs_size(device):
78  try:
79  stat = os.statvfs(device)
80  except:
81  return (0, 0)
82  free = stat.f_bsize * stat.f_bavail # les blocs réservés sont inclus
83  total = stat.f_bsize * stat.f_blocks
84  return (total, free)
85 
86 
87 ############ la variable suivante a été recopiées à l'aveugle ################
88 ############ depuis un fichier du projet USBcreator ##########################
89 no_options = GLib.Variant('a{sv}', {})
90 ##############################################################################
91 ##############################################################################
92 
93 ######### des "chemins" correspondant à des disques non débranchables ########
94 not_interesting = (
95  # boucle
96  '/org/freedesktop/UDisks2/block_devices/loop',
97  # disque raid
98  '/org/freedesktop/UDisks2/block_devices/dm_',
99  # mémoire vive
100  '/org/freedesktop/UDisks2/block_devices/ram',
101  '/org/freedesktop/UDisks2/block_devices/zram',
102  # disques durs
103  '/org/freedesktop/UDisks2/drives/',
104  )
105 
106 ##
107 #
108 # Cette classe a été inspirée par le projet USBcreator.
109 # Plusieurs modifications ont été faites au code original.
110 # Les fonctions de rappel ne tiennent compte que des périphériques USB
111 #
113  ##
114  #
115  # Le constructeur.
116  # @param logger un objet permettant de journaliser les messages ;
117  # par défaut il se confond avec le module logging
118  # @param diskClass la classe à utiliser pour créer des instances de disques
119  #
120  def __init__(self, logger=logging, diskClass=object):
121  self.install_thread = None
122  self.logger=logger
123  ## self.targets est un dictionnaire des disques détectés
124  ## les clés sont les paths et les contenus des instances de diskClass
125  self.diskClass=diskClass
126  self.targets = {}
127  ## self.modified signifie une modification récente, à prendre en compte
128  ## par une application au niveau utilisateur
129  self.modified=False
130  DBusGMainLoop(set_as_default=True)
131  threads_init()
132  self.bus = dbus.SystemBus()
133  self.udisks = UDisks.Client.new_sync(None)
134  self.manager = self.udisks.get_object_manager()
135  self.cbHooks = {
136  'object-added': {
137  'profile': ['man', 'obj'],
138  'hooks' : []
139  },
140  'object-removed': {
141  'profile': ['man', 'obj'],
142  'hooks' : []
143  },
144  'interface-added': {
145  'profile': ['man', 'obj'],
146  'hooks' : []
147  },
148  'interface-removed': {
149  'profile': ['man', 'obj'],
150  'hooks' : []
151  },
152  'interface-proxy-properties-changed': {
153  'profile': ['man', 'obj', 'interface'],
154  'hooks' : []
155  },
156  }
157  # mise en place des fonctions de rappel à utiliser pour tout changement
158  self.addHook('object-added',
159  lambda man, obj: self._udisks_obj_added(obj))
160  self.addHook('object-removed',
161  lambda man, obj: self._udisks_obj_removed(obj))
162  self.addHook('interface-added',
163  lambda man, obj, iface: self._device_changed(obj))
164  self.addHook('interface-removed',
165  lambda man, obj, iface: self._device_changed(obj))
166  self.addHook('interface-proxy-properties-changed',
167  lambda man, obj, iface, props, invalid: self._device_changed(obj))
168 
169  ##
170  #
171  # ajoute une fonction à appeler pour un signal nommé, et enregistre
172  # cette fonction dans self.cbHooks, après vérification de sa liste
173  # de paramètres.
174  # @param signal une chaîne
175  # @param func une fonction
176  # @return le résultat de l'appel à self.manager.connect(signal,func)
177  #
178  def addHook(self, signal, func):
179  if inspect.getargspec(func).args == self.cbHooks[signal]['profile']:
180  cb=self.manager.connect(signal,func)
181  self.cbHooks[signal]['hooks'].append(cb)
182  return cb
183  return None
184 
185  # voir le fichier integration-test issu des sources de udisks2
186  ##
187  #
188  # Essaie de monter un système de fichier jusqu'à ce qu'il
189  # cesse d'échouer avec "Busy", ou que l'erreur soit "déjà monté".
190  # Échoue si l'erreur est autre que les deux précédentes.
191  # @param fs un système de fichier à monter
192  # @param timeout nombre de secondes d'attente au maximum
193  # @param retryDelay délai entre deux essais
194  #
195  def retry_mount(self, fs, timeout=5, retryDelay=0.3):
196  while timeout >= 0:
197  try:
198  return fs.call_mount_sync(no_options, None)
199  except GLib.GError as e:
200  if 'UDisks2.Error.AlreadyMounted' in e.message:
201  m=re.match(r".*already mounted[^/]*([^\']+).*",e.message)
202  return m.group(1)
203  elif 'UDisks2.Error.DeviceBusy' in e.message:
204  pass
205  else:
206  raise
207  time.sleep(retryDelay)
208  timeout -= retryDelay
209  return ''
210 
211  ##
212  #
213  # Fait un inventaire des disques.
214  #
215  def detect_devices(self):
216  for obj in self.manager.get_objects():
217  self._udisks_obj_added(obj)
218 
219  ##
220  #
221  # trouve si un objet est intéressant à cataloguer
222  # @param obj une instance de UDisksObjectProxy
223  # @return un triplet interesting (vrai/faux), drive (objet),
224  # partition (objet).
225  #
226  def _interesting_obj(self, obj):
227  interesting=False
228  drive=None
229  partition=None
230 
231  # ne tient pas compte des périphériques non débranchables
232  path = safePath(obj)
233  for boring in not_interesting:
234  if path.startswith(boring):
235  return interesting, drive, partition
236 
237  # ne considère que les périphériques de type block
238  block = obj.get_block()
239  if not block:
240  return interesting, drive, partition
241 
242  # initialise drive, nom du disque ?
243  drive_name = block.get_cached_property('Drive').get_string()
244  if drive_name == '/':
245  return interesting, drive, partition
246  else:
247  drive = self.udisks.get_object(drive_name).get_drive()
248 
249  # on ne tient pas compte des CDROMS ni DVDROMS
250  if drive and drive.get_cached_property('Optical').get_boolean():
251  return interesting, drive, partition
252 
253  interesting=True
254  # détermine si on a un disque ou une partition
255  partition = obj.get_partition()
256  return interesting, drive, partition
257 
258  ##
259  #
260  # Fonction de rappel pour les ajouts de disque
261  # @param obj un objet renvoyé par l'évènement
262  #
263  def _udisks_obj_added(self, obj):
264  interesting, drive, part = self._interesting_obj(obj)
265  if part:
266  self._udisks_partition_added(obj, drive, part)
267  elif drive:
268  self._udisks_drive_added (obj, drive, part)
269  return
270 
271  ##
272  #
273  # détermine si un périphérique est de type USB
274  # @param obj un objet UDisksObjectProxy
275  # @return vrai si c'est un périphérique USB
276  #
277  def objIsUsb(self,obj):
278  for s in obj.get_block().get_cached_property('Symlinks'):
279  if b'/dev/disk/by-id/usb' in bytes(s):
280  return True
281  return False
282 
283  ##
284  #
285  # Fonction de rappel pour l'ajout d'une partition,
286  # met à jour self.targets
287  # @param obj une instance de UDisksObjectProxy
288  # @param drive une instance de ...
289  # @param partition une instance de ...
290  #
291  def _udisks_partition_added(self, obj, drive, partition):
292  path = safePath(obj)
293  block = obj.get_block()
294  self.logger.debug(QApplication.translate("uDisk","Partition ajoutée %s",None, QApplication.UnicodeUTF8) % path+inspectData())
295  fstype = block.get_cached_property('IdType').get_string()
296  parent = partition.get_cached_property('Table').get_string()
297  total = drive.get_cached_property('Size').get_uint64()
298  free = -1
299  mount = ''
300  fs = obj.get_filesystem()
301  if fs:
302  mount_points = fs.get_cached_property('MountPoints').get_bytestring_array()
303  if len(mount_points)>0:
304  mount= mount_points[0]
305  if not mount and fstype == 'vfat':
306  try:
307  mount = self.retry_mount(fs)
308  except:
309  logging.exception(QApplication.translate("uDisk","Échec au montage du disque : %s",None, QApplication.UnicodeUTF8) % path)
310  if mount:
311  total, free = fs_size(mount)
312  isUsb=self.objIsUsb(obj)
313  if not isUsb:
314  self.logger.debug(QApplication.translate("uDisk","On n'ajoute pas le disque : partition non-USB",None, QApplication.UnicodeUTF8)+inspectData())
315  elif total < 1:
316  self.logger.debug(QApplication.translate("uDisk","On n'ajoute pas le disque : partition vide",None, QApplication.UnicodeUTF8)+inspectData())
317  else:
318  udisk=self.diskClass(
319  path=path, mp=mount, isUsb=isUsb,
320  vendor=drive.get_cached_property('Vendor').get_string(),
321  model=drive.get_cached_property('Model').get_string(),
322  parent=safePath(parent),
323  fstype=fstype,
324  serial=block.get_cached_property('Drive').get_string().split('_')[-1],
325  uuid=block.get_cached_property('IdUUID').get_string(),
326  free=free,
327  capacity=total,
328  device=block.get_cached_property('Device').get_bytestring().decode('utf-8'),
329  )
330  self.targets[path] = udisk
331  self.modified=True
332  return
333 
334  def _udisks_drive_added(self, obj, drive, part):
335  path = safePath(obj)
336  block = obj.get_block()
337  if path in self.targets:
338  self.logger.debug(QApplication.translate("uDisk","Disque déjà ajouté auparavant : %s",None, QApplication.UnicodeUTF8) % path+inspectData())
339  return
340  self.logger.debug(QApplication.translate("uDisk","Disque ajouté : %s",None, QApplication.UnicodeUTF8) % path+inspectData())
341  size = drive.get_cached_property('Size').get_uint64()
342  ##### désactivé, quelquefois drive.get_cached_property('Size').get_uint64()
343  ##### renvoie des résultats erronés juste après le branchement
344  """
345  if size <= 0:
346  self.logger.debug(QApplication.translate("uDisk","On n'ajoute pas le disque : partition à 0 octets.",None, QApplication.UnicodeUTF8)+inspectData())
347  return
348  """
349  isUsb = self.objIsUsb(obj)
350  if not isUsb:
351  self.logger.debug(QApplication.translate("uDisk","On n'ajoute pas le disque : partition non-USB",None, QApplication.UnicodeUTF8)+inspectData())
352  else:
353  udisk=self.diskClass(
354  path=path,
355  isUsb=isUsb,
356  parent='',
357  vendor=drive.get_cached_property('Vendor').get_string(),
358  model=drive.get_cached_property('Model').get_string(),
359  serial=block.get_cached_property('Drive').get_string().split('_')[-1],
360  uuid=block.get_cached_property('IdUUID').get_string(),
361  capacity=size,
362  device=block.get_cached_property('Device').get_bytestring().decode('utf-8'),
363  )
364  self.targets[path] =udisk
365  self.modified=True
366  return
367 
368  def _device_changed(self, obj):
369  path = safePath(obj)
370  self.logger.debug(QApplication.translate("uDisk","Changement pour le disque %s",None, QApplication.UnicodeUTF8) % path+inspectData())
371 
372  ##
373  #
374  # Fonction de rappel déclenchée par le retrait d'un disque.
375  # Met à jour self.targets
376  # @param obj une instance de UDisksObjectProxy
377  #
378  def _udisks_obj_removed(self, obj):
379  path=safePath(obj)
380  logging.debug(QApplication.translate("uDisk","Disque débranché du système : %s",None, QApplication.UnicodeUTF8) % path)
381  if path in self.targets:
382  self.targets.pop(path)
383  self.modified=True
384 
385 ##
386 #
387 # une classe pour représenter un disque ou une partition.
388 #
389 # les attributs publics sont :
390 # - \b path le chemin dans le système dbus
391 # - \b device l'objet dbus qui correspond à l'instance
392 # - \b device_prop un proxy pour questionner cet objet dbus
393 # - \b selected booléen vrai si on doit considérer cette instance comme sélectionnée. Vrai à l'initialisation
394 # - \b rlock un verrou récursif permettant de réserver l'usage du media pour un seul thread
395 #
396 class uDisk2:
397 
398  ##
399  #
400  # Le constructeur
401  # @param path un chemin comme '/org/freedesktop/UDisks2/block_devices/sdX'
402  # @param mp point de montage ('' par défaut)
403  # @param isUsb en général, vrai vu qu'on se s'intéressera qu'à des périphériques
404  # USB
405  # @param vendor indication de vendeur
406  # @param model indication de modèle
407  # @param parent périphérique parent (None par défaut)
408  # @param fstype type de système de fichiers
409  # @param serial numéro de série
410  # @param uuid identifiant donné au disque lors du formatage
411  # @param free taille de la zone libre pour l'écriture
412  # @param capacity taille du périphérique
413  # @param device pseudo-fichier pour l'accès au périphérique
414  # @param firstFat une instance de uDisk2, de type vfat parmi les partitions
415  # @param selected vrai/faux selon qu'on sélectionne ou non le périphérique (vrai par défaut)
416  #
417  def __init__(self, path, mp='', isUsb=False, vendor='', model='', parent=None,
418  fstype='', serial='', uuid='',
419  free=0, capacity=0, device='', firstFat=None, selected=True):
420  self.path=path
421  self.mp=mp
422  self.isUsb=isUsb
423  self.vendor=vendor
424  self.model=model
425  self.parent=parent
426  self.fstype=fstype
427  self.stickid=serial
428  self.uuid=uuid
429  self.free=free
430  self.capacity=capacity
431  self.devStuff=device
432  self.firstFat=firstFat
433  self.selected=selected
434  self.rlock=threading.RLock()
435  return
436 
437  _itemNames={
438  "1mp":QApplication.translate("uDisk","point de montage",None, QApplication.UnicodeUTF8),
439  "2capacity":QApplication.translate("uDisk","taille",None, QApplication.UnicodeUTF8),
440  "3vendor":QApplication.translate("uDisk","marque",None, QApplication.UnicodeUTF8),
441  "4model":QApplication.translate("uDisk","modèle de disque",None, QApplication.UnicodeUTF8),
442  "5stickid":QApplication.translate("uDisk","numéro de série",None, QApplication.UnicodeUTF8),
443  }
444 
445  _specialItems={"0Check":QApplication.translate("uDisk","cocher",None, QApplication.UnicodeUTF8)}
446 
447  _ItemPattern=re.compile("[0-9]?(.*)")
448 
449  ##
450  #
451  # renvoie un identifiant unique. Dans cette classe, cette fonction
452  # est synonyme de file()
453  # @return un identifiant unique, garanti par le système de fichiers
454  #
455  def uniqueId(self):
456  return self.file()
457 
458  ##
459  #
460  # Méthode statique, pour avoir des titres de colonne.
461  # renvoie des titres pour les items obtenus par __getitem__.
462  # @param locale la locale, pour traduire les titres éventuellement.
463  # Valeur par défaut : "C"
464  # @return une liste de titres de colonnes
465  #
466  def headers(locale="C"):
467  result= list(uDisk2._specialItems.keys())+ list(uDisk2._itemNames.keys())
468  return sorted(result)
469 
470  headers = staticmethod(headers)
471 
472  ##
473  #
474  # Fournit une représentation imprimable
475  # @return une représentation imprimable de l'instance
476  #
477  def __str__(self):
478  return self.title()+self.valuableProperties()
479 
480  ##
481  #
482  # Permet d'obtenir un identifiant unique de disque
483  # @return le chemin dbus de l'instance
484  #
485  def title(self):
486  return self.path
487 
488  ##
489  #
490  # Permet de reconnaitre les partitions DOS-FAT
491  # @return True dans le cas d'une partition FAT16 ou FAT32
492  #
493  def isDosFat(self):
494  return self.fstype=="vfat"
495 
496  ##
497  #
498  # @return True si le disque ou la partion est montée
499  #
500  def isMounted(self):
501  return bool(self.mp)
502 
503  ##
504  #
505  # Facilite l'accès aux propriétés intéressantes d'une instance
506  # @return une chaîne indentée avec les propriétés intéressantes, une par ligne
507  #
508  def valuableProperties(self,indent=4):
509  prefix="\n"+" "*indent
510  r=""
511  props=["mp", "parent", "fstype", "stickid", "uuid", "vendor", "model", "devStuff", "free", "capacity"]
512  for prop in props:
513  r+=prefix+"%s = %s" %(prop, getattr(self,prop))
514  return r
515 
516  ##
517  #
518  # @return le point de montage
519  #
520  def mountPoint(self):
521  return self.mp
522 
523  ##
524  #
525  # retire le numéro des en-têtes pour en faire un nom de propriété
526  # valide pour interroger dbus
527  # @param n un numéro de propriété qui se réfère aux headers
528  # @return une propriété renvoyée par dbus, dans un format imprimable
529  #
530  def unNumberProp(self,n):
531  m=uDisk2._ItemPattern.match(self.headers()[n])
532  try:
533  return getattr(self, m.group(1))
534  except:
535  return ""
536 
537  ##
538  #
539  # Renvoie un élément de listage de données internes au disque
540  # @param n un nombre
541  # @return un élément si n>0, et le drapeau self.selected si n==0.
542  # Les noms des éléments sont dans la liste itemNames utilisée dans
543  # la fonction statique headers
544  #
545  def __getitem__(self,n):
546  propListe=self.headers()
547  if n==0:
548  return self.selected
549  elif n <= len(propListe):
550  return self.unNumberProp(n-1)
551 
552  ##
553  #
554  # Permet de s'assurer qu'une partition ou un disque sera bien monté
555  # @result le chemin du point de montage
556  #
557  def ensureMounted(self):
558  mount_paths=self.mp
559  if mount_paths==None: # le cas où la notion de montage est hors-sujet
560  return ""
561  leftTries=5
562  while len(mount_paths)==0 and leftTries >0:
563  leftTries = leftTries - 1
564  path=self.path
565  if len(path)>0:
566  subprocess.call("udisks --mount %s > /dev/null" %path,shell=True)
567  paths=self.mp
568  print("STILL TO DEBUG: is the mount OK? is self.mp updated?")
569  if paths:
570  return paths
571  else:
572  time.sleep(0.5)
573  else:
574  time.sleep(0.5)
575  if leftTries==0:
576  raise Exception ("Could not mount the VFAT after 5 tries.")
577  else:
578  return mount_paths
579 
580 
581 
582 ##
583 #
584 # une classe pour représenter la collection des disques USB connectés
585 #
586 # les attributs publics sont :
587 # - \b access le type d'accès qu'on veut pour les items
588 # - \b targets la collection de disques USB, organisée en un dictionnaire
589 # de disques : les clés sont les disques, qui renvoient à un ensemble
590 # de partitions du disque
591 # - \b firstFats une liste composée de la première partion DOS-FAT de chaque disque USB.
592 #
594 
595  ##
596  #
597  # Le constructeur
598  # @param access définit le type d'accès souhaité. Par défaut, c'est "disk"
599  # c'est à dire qu'on veut la liste des disques USB. Autres valeurs
600  # possibles : "firstFat" pour les premières partitions vfat.
601  # @param diskClass la classe de disques à créer
602  #
603  def __init__(self, access="disk", diskClass=uDisk2):
604  UDisksBackend.__init__(self, diskClass=diskClass)
605  self.access=access
606  self.detect_devices()
607  self.finishInit()
608 
609  ##
610  #
611  # Fin de l'initialisation
612  #
613  def finishInit(self):
614  self.mountFirstFats()
615 
616  ##
617  #
618  # fabrique la liste des partitions FAT,
619  # monte les partitions FAT si elles ne le sont pas
620  #
621  def mountFirstFats(self):
622  self.firstFats = self.getFirstFats()
623  if self.access=="firstFat":
624  for p in self.firstFats:
625  uDisk2(p,self).ensureMounted()
626 
627  ##
628  #
629  # @return le nombre de medias connectés
630  #
631  def __trunc__(self):
632  return len(self.firstFats)
633 
634  ##
635  #
636  # Sert à comparer deux collections de disques, par exemple
637  # une collection passée et une collection présente.
638  # @param other une instance de Available
639  # @return vrai si other semble être la même collection de disques USB
640  #
641  def compare(self, other):
642  result=self.summary()==other.summary()
643  return result
644 
645  ##
646  #
647  # Permet de déterminer si un disque est dans la collection
648  # @param ud une instance de uDisk
649  # @return vrai si le uDisk ud est dans la collection
650  #
651  def contains(self, ud):
652  return ud.path in self.targets
653 
654  ##
655  #
656  # Récolte les enregistrements de niveau supérieur de self.targets
657  # @return la liste des chemins vers les disque USB détectés
658  #
659  def disks(self):
660  return [d for d in self.targets if not self.targets[d].parent]
661 
662  ##
663  #
664  # Récolte les partitions d'un disque
665  # @param d le chemin vers un disque
666  # @return la liste des partitions de ce disque
667  #
668  def parts(self, d):
669  return [p for p in self.targets if self.targets[p].parent==d]
670 
671  ##
672  #
673  # Récolte les enregistrements de niveau supérieur de self.targets
674  # @return la liste des objects uDisk2 détectés
675  #
676  def disks_ud(self):
677  return [self.targets[d] for d in self.targets if not self.targets[d].parent]
678 
679  ##
680  #
681  # Récolte les partitions d'un disque
682  # @param d le chemin vers un disque
683  # @return la liste des objets uDisk2 qui sont des partitions
684  # de ce disque
685  #
686  def parts_ud(self, d):
687  return [self.targets[p] for p in self.targets if self.targets[p].parent==d]
688 
689  ##
690  #
691  # Fournit une représentation imprimable d'un résumé
692  # @return une représentation imprimable d'un résumé de la collection
693  #
694  def summary(self):
695  r= "Available USB disks\n"
696  r+= "===================\n"
697  for d in sorted(self.disks()):
698  r+="%s\n" %(self.targets[d].devStuff)
699  partlist=self.parts(d)
700  if len(partlist)>0:
701  r+=" Partitions :\n"
702  for part in partlist:
703  r+=" %s\n" %(self.targets[part].devStuff,)
704  return r
705 
706  ##
707  #
708  # Fournit une représentation imprimable
709  # @return une représentation imprimable de la collection
710  #
711  def __str__(self):
712  r= "Available USB disks\n"
713  r+= "===================\n"
714  for d in self.disks():
715  r+="%s\n" %d
716  partlist=self.parts(d)
717  if len(partlist)>0:
718  r+=" Partitions :\n"
719  for part in sorted(partlist):
720  r+=" %s\n" %(self.targets[part].devStuff)
721  r+=self.targets[part].valuableProperties(12)+"\n"
722  return r
723 
724  ##
725  #
726  # Renvoye le nième disque. Le fonctionnement dépend du paramètre
727  # self.access
728  # @param n un numéro
729  # @return le nième disque USB connecté sous forme d'instance de uDisk2
730  #
731  def __getitem__(self, n):
732  if self.access=="disk":
733  path=self.targets.keys()[n]
734  elif self.access=="firstFat":
735  path=self.firstFats[n]
736  return self.targets[path]
737 
738  ##
739  #
740  # Renseigne sur la longueur de la collection. Le fonctionnement
741  # dépend du paramètre self.access
742  # @return la longueur de la collection de disques renvoyée
743  #
744  def __len__(self):
745  if self.access=="disk":
746  return len(self.targets)
747  elif self.access=="firstFat":
748  return len(self.firstFats)
749 
750  ##
751  #
752  # Facilite l'accès aux partitions de type DOS-FAT, et a des effets
753  # de bord :
754  # * marque la première vfat dans chaque instance de disque
755  # * construit une liste des chemins uDisk des FATs
756  # @return une liste de partitions, constituée de la première
757  # partition de type FAT de chaque disque USB connecté
758  #
759  def getFirstFats(self):
760  result=[]
761  disks=[d for d in self.targets if not self.targets[d].parent]
762  for d in disks:
763  parts=[p for p in self.targets if self.targets[p].parent==d]
764  for p in parts:
765  if self.targets[p].fstype=="vfat":
766  result.append(p)
767  # inscrit l'information dans l'instance du disque, par effet de bord
768  self.targets[d].firstFat=self.targets[p]
769  return result
770 
771  ##
772  #
773  # @param dev un chemin comme /org/freedesktop/UDisks/devices/sdb3
774  # @return True si la partition est dans la liste des partions disponibles
775  #
776  def hasDev(self, dev):
777  s=str(dev)
778  for p in self.fatPaths:
779  if p.split("/")[-1]==s:
780  return True
781  return False
782 
783 ##################### fin de la définition de la calsse uDisk2 ################
784 
785 
786 
787 if __name__=="__main__":
788  from PyQt4.QtCore import *
789  from PyQt4.QtGui import *
790  import sys
792  def __init__(self):
793  QMainWindow.__init__(self)
794 
795  # The only thing in the app is a quit button
796  quitbutton = QPushButton('Examinez le terminal\nbranchez et débranchez des clés USB, puis\nQuittez', self)
797  QObject.connect(quitbutton, SIGNAL("clicked()"), self.close)
798  self.setCentralWidget(quitbutton)
799 
800 
801  machin=Available()
802  print (machin)
803  def print_targets_if_modif(man, obj):
804  if machin.modified:
805  print([s.split("/")[-1] for s in machin.targets.keys()])
806  machin.modified=False
807  machin.addHook('object-added', print_targets_if_modif)
808  machin.addHook('object-removed', print_targets_if_modif)
809 
810  app = QApplication(sys.argv)
811  main = MainWindow()
812  main.show()
813  sys.exit(app.exec_())
modified
self.modified signifie une modification récente, à prendre en compte par une application au niveau ut...
Definition: usbDisk2.py:129
def inspectData()
Definition: usbDisk2.py:37
def ensureMounted(self)
Permet de s'assurer qu'une partition ou un disque sera bien monté
Definition: usbDisk2.py:557
une classe pour représenter un disque ou une partition.
Definition: usbDisk2.py:396
def isDosFat(self)
Permet de reconnaitre les partitions DOS-FAT.
Definition: usbDisk2.py:493
def parts(self, d)
Récolte les partitions d'un disque.
Definition: usbDisk2.py:668
def __getitem__(self, n)
Renvoie un élément de listage de données internes au disque.
Definition: usbDisk2.py:545
def __init__
Le constructeur.
Definition: usbDisk2.py:603
def hasDev(self, dev)
Definition: usbDisk2.py:776
def _udisks_partition_added(self, obj, drive, partition)
Fonction de rappel pour l'ajout d'une partition, met à jour self.targets.
Definition: usbDisk2.py:291
def detect_devices(self)
Fait un inventaire des disques.
Definition: usbDisk2.py:215
def contains(self, ud)
Permet de déterminer si un disque est dans la collection.
Definition: usbDisk2.py:651
def __str__(self)
Fournit une représentation imprimable.
Definition: usbDisk2.py:477
def __init__
Le constructeur.
Definition: usbDisk2.py:419
def parts_ud(self, d)
Récolte les partitions d'un disque.
Definition: usbDisk2.py:686
def mountPoint(self)
Definition: usbDisk2.py:520
def _udisks_drive_added(self, obj, drive, part)
Definition: usbDisk2.py:334
def __init__
Le constructeur.
Definition: usbDisk2.py:120
def isMounted(self)
Definition: usbDisk2.py:500
def disks(self)
Récolte les enregistrements de niveau supérieur de self.targets.
Definition: usbDisk2.py:659
def __len__(self)
Renseigne sur la longueur de la collection.
Definition: usbDisk2.py:744
diskClass
self.targets est un dictionnaire des disques détectés les clés sont les paths et les contenus des ins...
Definition: usbDisk2.py:125
def _interesting_obj(self, obj)
trouve si un objet est intéressant à cataloguer
Definition: usbDisk2.py:226
def safePath(obj)
Récupère de façon sûre le path d'une instance de UDisksObjectProxy.
Definition: usbDisk2.py:61
def print_targets_if_modif(man, obj)
Definition: usbDisk2.py:803
def fs_size(device)
Renvoie la taille d'un système de fichier et la place disponible.
Definition: usbDisk2.py:77
def getFirstFats(self)
Facilite l'accès aux partitions de type DOS-FAT, et a des effets de bord :
Definition: usbDisk2.py:759
def _device_changed(self, obj)
Definition: usbDisk2.py:368
def summary(self)
Fournit une représentation imprimable d'un résumé
Definition: usbDisk2.py:694
def _udisks_obj_removed(self, obj)
Fonction de rappel déclenchée par le retrait d'un disque.
Definition: usbDisk2.py:378
def _udisks_obj_added(self, obj)
Fonction de rappel pour les ajouts de disque.
Definition: usbDisk2.py:263
une classe pour représenter la collection des disques USB connectés
Definition: usbDisk2.py:593
def retry_mount
Essaie de monter un système de fichier jusqu'à ce qu'il cesse d'échouer avec "Busy", ou que l'erreur soit "déjà monté".
Definition: usbDisk2.py:195
def addHook(self, signal, func)
ajoute une fonction à appeler pour un signal nommé, et enregistre cette fonction dans self...
Definition: usbDisk2.py:178
def finishInit(self)
Fin de l'initialisation.
Definition: usbDisk2.py:613
def __str__(self)
Fournit une représentation imprimable.
Definition: usbDisk2.py:711
def __getitem__(self, n)
Renvoye le nième disque.
Definition: usbDisk2.py:731
def title(self)
Permet d'obtenir un identifiant unique de disque.
Definition: usbDisk2.py:485
def compare(self, other)
Sert à comparer deux collections de disques, par exemple une collection passée et une collection prés...
Definition: usbDisk2.py:641
def objIsUsb(self, obj)
détermine si un périphérique est de type USB
Definition: usbDisk2.py:277
def unNumberProp(self, n)
retire le numéro des en-têtes pour en faire un nom de propriété valide pour interroger dbus ...
Definition: usbDisk2.py:530
Cette classe a été inspirée par le projet USBcreator.
Definition: usbDisk2.py:112
def disks_ud(self)
Récolte les enregistrements de niveau supérieur de self.targets.
Definition: usbDisk2.py:676
def mountFirstFats(self)
fabrique la liste des partitions FAT, monte les partitions FAT si elles ne le sont pas ...
Definition: usbDisk2.py:621
def valuableProperties
Facilite l'accès aux propriétés intéressantes d'une instance.
Definition: usbDisk2.py:508
def uniqueId(self)
renvoie un identifiant unique.
Definition: usbDisk2.py:455