# usbDisk.py

Aller à la documentation de ce fichier.

    00001 # -*- coding: utf-8 -*-
    00002 # $Id: usbDisk.py 36 2011-01-15 19:37:27Z georgesk $
    00003
    00004 licence={}
    00005 licence_en="""
    00006     file usbDisk.py
    00007     this file is part of the project scolasync
    00008
    00009     Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>
    00010
    00011     This program is free software: you can redistribute it and/or modify
    00012     it under the terms of the GNU General Public License as published by
    00013     the Free Software Foundation, either version3 of the License, or
    00014     (at your option) any later version.
    00015
    00016     This program is distributed in the hope that it will be useful,
    00017     but WITHOUT ANY WARRANTY; without even the implied warranty of
    00018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    00019     GNU General Public License for more details.
    00020
    00021     You should have received a copy of the GNU General Public License
    00022     along with this program.  If not, see <http://www.gnu.org/licenses/>.
    00023 """
    00024
    00025 licence['en']=licence_en
    00026
    00027 import dbus, subprocess, os.path, re
    00028 from PyQt4.QtGui import *
    00029
    00030
    00031 ##
    00032 #
    00033 #     une classe pour représenter un disque ou une partition.
    00034 #
    00035 #     les attributs publics sont :
    00036 #     - \b path  le chemin dans le système dbus
    00037 #     - \b device l'objet dbus qui correspond à l'instance
    00038 #     - \b device_prop un proxy pour questionner cet objet dbus
    00039 #     - \b selected booléen vrai si on doit considérer cette instance comme sélectionnée. Vrai à l'initialisation
    00040 #     - \b checkable booléen vrai si on veut que la sélection puisse être modifiée par l'utilisateur dans l'interface graphique
    00041 #
    00042 class uDisk:
    00043
    00044     ##
    00045     #
    00046     #         Le constructeur
    00047     #         @param path un chemin dans le système dbus
    00048     #         @param bus un objet dbus.BusSystem
    00049     #         @param checkable vrai si on fera usage de self.selected
    00050     #
    00051     def __init__(self, path, bus, checkable=False):
    00052         self.path=path
    00053         self.device = bus.get_object("org.freedesktop.UDisks", self.path)
    00054         self.device_prop = dbus.Interface(self.device, "org.freedesktop.DBus.Properties")
    00055         self.selected=True
    00056         self.checkable=checkable
    00057         self.stickid=unicode(self.getProp("drive-serial"))
    00058         self.uuid=self.getProp("id-uuid")
    00059         self.fatuuid=None  # pour l'uuid de la première partion vfat
    00060         self.firstFat=None # poignée de la première partition vfat
    00061         #
    00062
    00063
    00064     _itemNames={
    00065         "1device-mount-paths":QApplication.translate("uDisk","point de montage",None, QApplication.UnicodeUTF8),
    00066         "2device-size":QApplication.translate("uDisk","taille",None, QApplication.UnicodeUTF8),
    00067         "3drive-vendor":QApplication.translate("uDisk","vendeur",None, QApplication.UnicodeUTF8),
    00068         "4drive-model":QApplication.translate("uDisk","modèle de disque",None, QApplication.UnicodeUTF8),
    00069         "5drive-serial":QApplication.translate("uDisk","numéro de série",None, QApplication.UnicodeUTF8),
    00070         }
    00071
    00072     _specialItems={"0Check":QApplication.translate("uDisk","cocher",None, QApplication.UnicodeUTF8)}
    00073
    00074     _ItemPattern=re.compile("[0-9]?(.*)")
    00075
    00076     ##
    00077     #
    00078     #         renvoie l'uuid de la première partition FAT après que celle-ci aura été
    00079     #         identifiée (utile pour les disques partitionnés)
    00080     #         @return un uuid
    00081     #
    00082     def getFatUuid(self):
    00083         return "%s" %self.fatuuid
    00084
    00085     ##
    00086     #
    00087     #         renvoie un identifiant unique. Dans cette classe, cette fonction
    00088     #         est synonyme de getFatUuid
    00089     #         @return un identifiant supposément unique
    00090     #
    00091     def uniqueId(self):
    00092         return self.getFatUuid()
    00093
    00094     ##
    00095     #
    00096     #         Méthode statique, pour avoir des titres de colonne.
    00097     #         renvoie des titres pour les items obtenus par __getitem__. Le
    00098     #         résultat dépend du paramètre checkable.
    00099     #         @param checkable vrai si le premier en-tête correspond à une colonne de cases à cocher
    00100     #         @param locale la locale, pour traduire les titres éventuellement.
    00101     #         Valeur par défaut : "C"
    00102     #         @return une liste de titres de colonnes
    00103     #
    00104     def headers(checkable=False,locale="C"):
    00105         if checkable:
    00106             result=uDisk._specialItems.keys()+ uDisk._itemNames.keys()
    00107             return sorted(result)
    00108         else:
    00109             return sorted(uDisk._itemNames.keys())
    00110
    00111     headers = staticmethod(headers)
    00112
    00113     ##
    00114     #
    00115     #         renvoie un proxy vers un navigateur de propriétés
    00116     #         @param bus une instace de dbus.SystemBus
    00117     #         @return l'objet proxy
    00118     #
    00119     def devicePropProxy(self, bus):
    00120         return self.device_prop
    00121
    00122     ##
    00123     #
    00124     #         Renvoie la valeur de vérité d'une propriété
    00125     #         @param prop une propriété
    00126     #         @param value
    00127     #         @return vrai si la propriété est vraie (cas où value==None) ou vrai si la propriété a exactement la valeur value.
    00128     #
    00129     def isTrue(self,prop, value=None):
    00130         if value==None:
    00131             return  bool(self.getProp(prop))
    00132         else:
    00133             return self.getProp(prop)==value
    00134
    00135     ##
    00136     #
    00137     #         Facilite le réprage des disques USB USB
    00138     #         @return vrai dans le cas d'un disque USB
    00139     #
    00140     def isUsbDisk(self):
    00141         return self.isTrue("device-is-removable") and self.isTrue("drive-connection-interface","usb") and self.isTrue("device-size")
    00142
    00143     ##
    00144     #
    00145     #         Fournit une représentation imprimable
    00146     #         @return une représentation imprimable de l'instance
    00147     #
    00148     def __str__(self):
    00149         return self.title()+self.valuableProperties()
    00150
    00151     ##
    00152     #
    00153     #         Permet d'obtenir un identifiant unique de disque
    00154     #         @return le chemin dbus de l'instance
    00155     #
    00156     def title(self):
    00157         return self.path
    00158
    00159     ##
    00160     #
    00161     #         Permet d'accèder à l'instance par un nom de fichier
    00162     #         @return un nom valide dans le système de fichiers, pour accéder
    00163     #         à l'instance.
    00164     #
    00165     def file(self):
    00166         fileById=self.getProp("device-file-by-id")
    00167         if isinstance(fileById, dbus.Array): fileById=fileById[0]
    00168         return fileById
    00169
    00170     ##
    00171     #
    00172     #         Permet d'accèder à l'instance par un point de montage
    00173     #         @return un point de montage, s'il en existe, sinon None
    00174     #
    00175     def mountPoint(self):
    00176         paths=self.getProp("device-mount-paths")
    00177         if isinstance(paths, dbus.Array) and len(paths)>0:
    00178             return paths[0]
    00179         else:
    00180             return None
    00181
    00182     ##
    00183     #
    00184     #         Facilite l'accès aux propriétés à l'aide des mots clés du module udisks
    00185     #         @param name le nom d'une propriété
    00186     #         @return une propriété dbus du disque ou de la partition, sinon None si le nom name est illégal
    00187     #
    00188     def getProp(self, name):
    00189         try:
    00190             return self.device_prop.Get("org.freedesktop.UDisks", name)
    00191         except:
    00192             return None
    00193
    00194     ##
    00195     #
    00196     #         Permet de reconnaitre les partitions DOS-FAT
    00197     #         @return vrai dans le cas d'une partition FAT16 ou FAT32
    00198     #
    00199     def isDosFat(self):
    00200         try:
    00201             code=eval(self.getProp("partition-type"))
    00202             fatCodes = [0x04, 0x06, 0x0b, 0x0c, 0x0d, 0x0e]
    00203             return code in fatCodes
    00204         except:
    00205             return False
    00206
    00207     ##
    00208     #
    00209     #         Facilite l'accès aux propriétés intéressantes d'une instance
    00210     #         @return une chaîne indentée avec les propriétés intéressantes, une par ligne
    00211     #
    00212     def valuableProperties(self,indent=4):
    00213         prefix="\n"+" "*indent
    00214         r=""
    00215         props=["device-file-by-id",
    00216                "device-mount-paths",
    00217                "device-is-partition-table",
    00218                "partition-table-count",
    00219                "device-is-read-only",
    00220                "device-is-drive",
    00221                "device-is-optical-disc",
    00222                "device-is-mounted",
    00223                "drive-vendor",
    00224                "drive-model",
    00225                "drive-serial",
    00226                "id-uuid",
    00227                "partition-slave",
    00228                "partition-type",
    00229                "device-size"]
    00230         for prop in props:
    00231             p=self.getProp(prop)
    00232             if isinstance(p,dbus.Array):
    00233                 if len(p)>0:
    00234                     r+=prefix+"%s = array:" %(prop)
    00235                     for s in p:
    00236                         r+=prefix+" "*indent+s
    00237             elif isinstance(p,dbus.Boolean):
    00238                 r+=prefix+"%s = %s" %(prop, bool(p))
    00239             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):
    00240                 if p < 10*1024:
    00241                     r+=prefix+"%s = %s" %(prop,p)
    00242                 elif p < 10*1024*1024:
    00243                     r+=prefix+"%s = %s k" %(prop,p/1024)
    00244                 elif p < 10*1024*1024*1024:
    00245                     r+=prefix+"%s = %s M" %(prop,p/1024/1024)
    00246                 else:
    00247                     r+=prefix+"%s = %s G" %(prop,p/1024/1024/1024)
    00248             else:
    00249                 r+=prefix+"%s = %s" %(prop,p)
    00250         return r
    00251
    00252     ##
    00253     #
    00254     #         renvoie le chemin du disque, dans le cas où self est une partition
    00255     #         @return le chemin dbus du disque maître, sinon "/"
    00256     #
    00257     def master(self):
    00258         return self.getProp("partition-slave")
    00259
    00260     ##
    00261     #
    00262     #         retire le numéro des en-têtes pour en faire un nom de propriété
    00263     #         valide pour interroger dbus
    00264     #         @param n un numéro de propriété qui se réfère aux headers
    00265     #         @return une propriété renvoyée par dbus, dans un format imprimable
    00266     #
    00267     def unNumberProp(self,n):
    00268         m=uDisk._ItemPattern.match(self.headers()[n])
    00269         try:
    00270             prop=m.group(1)
    00271             result=self.showableProp(prop)
    00272             return result
    00273         except:
    00274             return ""
    00275
    00276     ##
    00277     #
    00278     #         Renvoie un élément de listage de données internes au disque
    00279     #         @param n un nombre
    00280     #         @param checkable vrai si on doit renvoyer une propriété supplémentaire pour n==0
    00281     #         @return si checkable est vrai, un élément si n>0, et le drapeau self.selected si n==0 ; sinon un élément de façon ordinaire. Les noms des éléments sont dans la liste itemNames utilisée dans la fonction statique headers
    00282     #
    00283     def __getitem__(self,n):
    00284         propListe=self.headers()
    00285         if self.checkable:
    00286             if n==0:
    00287                 return self.selected
    00288             elif n <= len(propListe):
    00289                 return self.unNumberProp(n-1)
    00290         else:
    00291             if n < len(propListe):
    00292                 return self.unNumberProp(n)
    00293
    00294     ##
    00295     #
    00296     #         Renvoie une propriété dans un type "montrable" par QT.
    00297     #         les propriétés que renvoie dbus ont des types inconnus de Qt4,
    00298     #         cette fonction les transtype pour que QVariant arrive à les
    00299     #         prendre en compte.
    00300     #         @param name le nom de la propriété
    00301     #         @return une nombre ou une chaîne selon le type de propriété
    00302     #
    00303     def showableProp(self, name):
    00304         p=self.getProp(name)
    00305         if isinstance(p,dbus.Array):
    00306             if len(p)>0: return str(p[0])
    00307             else: return ""
    00308         elif isinstance(p,dbus.Boolean):
    00309             return "%s" %bool(p)
    00310         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):
    00311             return int(p)
    00312         else:
    00313             return "%s" %p
    00314
    00315     ##
    00316     #
    00317     #         Renvoie la première partition VFAT
    00318     #         @result la première partition VFAT ou None s'il n'y en a pas
    00319     #
    00320     def getFirstFat(self):
    00321         if self.isDosFat(): return self
    00322         ## if self.firstFat==None:
    00323         ##     # alors le parcours des clés USB n'a pas encore été fait.
    00324         ##     # on fait le parcours.
    00325         ##     fake=Available(access="firstFat")
    00326         return self.firstFat
    00327
    00328     ##
    00329     #
    00330     #         Permet de s'assurer qu'une partition ou un disque sera bien monté
    00331     #         @result le chemin du point de montage
    00332     #
    00333     def ensureMounted(self):
    00334         mount_paths=self.getProp("device-mount-paths")
    00335         if len(mount_paths)==0:
    00336             path=self.getProp("device-file-by-id")
    00337             if isinstance(path,dbus.Array):
    00338                 path=path[0]
    00339             subprocess.call("udisks --mount %s" %path,shell=True)
    00340             return path
    00341         else:
    00342             return mount_paths[0]
    00343
    00344
    00345
    00346 ##
    00347 #
    00348 #     une classe pour représenter la collection des disques USB connectés
    00349 #
    00350 #     les attributs publics sont :
    00351 #     - \b checkable booléen vrai si on veut gérer des sélections de disques
    00352 #     - \b access le type d'accès qu'on veut pour les items
    00353 #     - \b bus une instance de dbus.SystemBus
    00354 #     - \b disks la collection de disques USB, organisée en un dictionnaire
    00355 #        de disques : les clés sont les disques, qui renvoient à un ensemble
    00356 #        de partitions du disque
    00357 #     - \b enumdev une liste de chemins dbus vers les disques trouvés
    00358 #     - \b firstFats une liste composée de la première partion DOS-FAT de chaque disque USB.
    00359 #
    00360 class Available:
    00361
    00362     ##
    00363     #
    00364     #         Le constructeur
    00365     #         @param checkable : vrai si on veut pouvoir cocher les disques de la
    00366     #           collection. Faux par défaut.
    00367     #         @param access définit le type d'accès souhaité. Par défaut, c'est "disk"
    00368     #           c'est à dire qu'on veut la liste des disques USB. Autres valeurs
    00369     #           possibles : "firstFat" pour les premières partitions vfat.
    00370     #
    00371     def __init__(self, checkable=False, access="disk"):
    00372         self.checkable=checkable
    00373         self.access=access
    00374         self.bus = dbus.SystemBus()
    00375         proxy = self.bus.get_object("org.freedesktop.UDisks",
    00376                                     "/org/freedesktop/UDisks")
    00377         iface = dbus.Interface(proxy, "org.freedesktop.UDisks")
    00378         self.disks={}
    00379         self.enumDev=iface.EnumerateDevices()
    00380         ### récupération des disques usb dans le dictionnaire self.disks
    00381         for path in self.enumDev:
    00382             ud=uDisk(path, self.bus, checkable)
    00383             if ud.isUsbDisk():
    00384                 self.disks[ud]=[]
    00385                 # cas des disques sans partitions
    00386                 if bool(ud.getProp("device-is-partition-table")) == False:
    00387                     # la propriété "device-is-partition-table" est fausse,
    00388                     # probablement qu'il y a un système de fichiers
    00389                     self.disks[ud].append(ud)
    00390         ### une deuxième passe pour récupérer et associer les partitions
    00391         for path in self.enumDev:
    00392             ud=uDisk(path, self.bus, checkable)
    00393             for d in self.disks.keys():
    00394                 if ud.master() == d.path:
    00395                     self.disks[d].append(ud)
    00396         ### on fabrique la liste des premières partitions FAT
    00397         self.firstFats = self.getFirstFats()
    00398         ### on monte les partitions si nécessaire
    00399         if self.access=="firstFat":
    00400             for p in self.firstFats:
    00401                 p.ensureMounted()
    00402
    00403     ##
    00404     #
    00405     #         Sert à comparer deux collections de disques, par exemple
    00406     #         une collection passée et une collection présente.
    00407     #         @param other une instance de Available
    00408     #         @return vrai si other semble être la même collection de disques USB
    00409     #
    00410     def compare(self, other):
    00411         result=self.summary()==other.summary()
    00412         return result
    00413
    00414     ##
    00415     #
    00416     #         Permet de déterminer si un disque est dans la collection
    00417     #         @param ud une instance de uDisk
    00418     #         @return vrai si le uDisk ud est dans la collection
    00419     #
    00420     def contains(self, ud):
    00421         for k in self.disks.keys():
    00422             if k.getProp("device-file-by-id")==ud.getProp("device-file-by-id"): return True
    00423         return False
    00424
    00425     ##
    00426     #
    00427     #         Fournit une représentation imprimable d'un résumé
    00428     #         @return une représentation imprimable d'un résumé de la collection
    00429     #
    00430     def summary(self):
    00431         r=  "Available USB discs\n"
    00432         r+= "===================\n"
    00433         for d in sorted(self.disks.keys(), key=lambda disk: disk.getFatUuid()):
    00434             r+="%s\n" %(d.title(),)
    00435             if len(self.disks[d])>0:
    00436                 r+="    Partitions :\n"
    00437                 for part in sorted(self.disks[d], key=lambda disk: disk.getFatUuid()):
    00438                     r+="        %s\n" %(part.path,)
    00439         return r
    00440
    00441     ##
    00442     #
    00443     #         Fournit une représentation imprimable
    00444     #         @return une représentation imprimable de la collection
    00445     #
    00446     def __str__(self):
    00447         r=  "Available USB discs\n"
    00448         r+= "===================\n"
    00449         for d in self.disks.keys():
    00450             r+="%s\n" %d
    00451             if len(self.disks[d])>0:
    00452                 r+="    Partitions :\n"
    00453                 for part in self.disks[d]:
    00454                     r+="        %s\n" %(part.path)
    00455                     r+=part.valuableProperties(12)+"\n"
    00456         return r
    00457
    00458     ##
    00459     #
    00460     #         Renvoye le nième disque. Le fonctionnement dépend du paramètre
    00461     #         self.access
    00462     #         @param n un numéro
    00463     #         @return le nième disque USB connecté
    00464     #
    00465     def __getitem__(self, n):
    00466         if self.access=="disk":
    00467             return self.disks.keys()[n]
    00468         elif self.access=="firstFat":
    00469             return self.firstFats[n]
    00470
    00471     ##
    00472     #
    00473     #         Renseigne sur la longueur de la collection. Le fonctionnement
    00474     #         dépend du paramètre self.access
    00475     #         @return la longueur de la collection de disques renvoyée
    00476     #
    00477     def __len__(self):
    00478         if self.access=="disk":
    00479             return len(self.disks)
    00480         elif self.access=="firstFat":
    00481             return len(self.firstFats)
    00482
    00483     ##
    00484     #
    00485     #         Facilite l'accès aux partitions de type DOS-FAT, et a un effet de bord :
    00486     #         marque le disque avec l'uuid de la première partition FAT.
    00487     #         @return une liste de partitions, constituée de la première
    00488     #         partition de type FAT de chaque disque USB connecté
    00489     #
    00490     def getFirstFats(self):
    00491         result=[]
    00492         for d in self.disks.keys():
    00493             for p in self.disks[d]:
    00494                 if p.isDosFat() or p==d :
    00495                     # le cas p == d correspond aux disques non partitionnés
    00496                     # on va supposer que dans ce cas la partition ne peut
    00497                     # être que de type DOS !!!
    00498                     result.append(p)
    00499                     # on marque le disque père et la partition elle-même
    00500                     d.fatuuid=p.uuid
    00501                     d.firstFat=p
    00502                     p.fatuuid=p.uuid
    00503                     break
    00504         return result
    00505
    00506 if __name__=="__main__":
    00507     machin=Available()
    00508     print machin
    00509
    00510