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