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