ScolaSync 1.0
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, time
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","marque",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 True dans le cas d'une partition FAT16 ou FAT32
00198     #         
00199     def isDosFat(self):
00200         return self.getProp("id-type")=="vfat"
00201 
00202     ##
00203     # 
00204     #         @return True si le disque ou la partion est montée
00205     #         
00206     def isMounted(self):
00207         return bool(self.getProp("device-is-mounted"))
00208         
00209     ##
00210     # 
00211     #         Facilite l'accès aux propriétés intéressantes d'une instance
00212     #         @return une chaîne indentée avec les propriétés intéressantes, une par ligne
00213     #         
00214     def valuableProperties(self,indent=4):
00215         prefix="\n"+" "*indent
00216         r=""
00217         props=["device-file-by-id",
00218                "device-mount-paths",
00219                "device-is-partition-table",
00220                "partition-table-count",
00221                "device-is-read-only",
00222                "device-is-drive",
00223                "device-is-optical-disc",
00224                "device-is-mounted",
00225                "drive-vendor",
00226                "drive-model",
00227                "drive-serial",
00228                "id-uuid",
00229                "partition-slave",
00230                "partition-type",
00231                "device-size",
00232                "id-type"]
00233         for prop in props:
00234             p=self.getProp(prop)
00235             if isinstance(p,dbus.Array):
00236                 if len(p)>0:
00237                     r+=prefix+"%s = array:" %(prop)
00238                     for s in p:
00239                         r+=prefix+" "*indent+s
00240             elif isinstance(p,dbus.Boolean):
00241                 r+=prefix+"%s = %s" %(prop, bool(p))
00242             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):
00243                 if p < 10*1024:
00244                     r+=prefix+"%s = %s" %(prop,p)
00245                 elif p < 10*1024*1024:
00246                     r+=prefix+"%s = %s k" %(prop,p/1024)
00247                 elif p < 10*1024*1024*1024:
00248                     r+=prefix+"%s = %s M" %(prop,p/1024/1024)
00249                 else:
00250                     r+=prefix+"%s = %s G" %(prop,p/1024/1024/1024)
00251             else:
00252                 r+=prefix+"%s = %s" %(prop,p)
00253         return r
00254 
00255     ##
00256     # 
00257     #         renvoie le chemin du disque, dans le cas où self est une partition
00258     #         @return le chemin dbus du disque maître, sinon "/"
00259     #         
00260     def master(self):
00261         return self.getProp("partition-slave")
00262 
00263     ##
00264     # 
00265     #         retire le numéro des en-têtes pour en faire un nom de propriété
00266     #         valide pour interroger dbus
00267     #         @param n un numéro de propriété qui se réfère aux headers
00268     #         @return une propriété renvoyée par dbus, dans un format imprimable
00269     #         
00270     def unNumberProp(self,n):
00271         m=uDisk._ItemPattern.match(self.headers()[n])
00272         try:
00273             prop=m.group(1)
00274             result=self.showableProp(prop)
00275             return result
00276         except:
00277             return ""
00278         
00279     ##
00280     # 
00281     #         Renvoie un élément de listage de données internes au disque
00282     #         @param n un nombre
00283     #         @param checkable vrai si on doit renvoyer une propriété supplémentaire pour n==0
00284     #         @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
00285     #         
00286     def __getitem__(self,n):
00287         propListe=self.headers()
00288         if self.checkable:
00289             if n==0:
00290                 return self.selected
00291             elif n <= len(propListe):
00292                 return self.unNumberProp(n-1)
00293         else:
00294             if n < len(propListe):
00295                 return self.unNumberProp(n)
00296 
00297     ##
00298     # 
00299     #         Renvoie une propriété dans un type "montrable" par QT.
00300     #         les propriétés que renvoie dbus ont des types inconnus de Qt4,
00301     #         cette fonction les transtype pour que QVariant arrive à les
00302     #         prendre en compte.
00303     #         @param name le nom de la propriété
00304     #         @return une nombre ou une chaîne selon le type de propriété
00305     #         
00306     def showableProp(self, name):
00307         p=self.getProp(name)
00308         if isinstance(p,dbus.Array):
00309             if len(p)>0: return str(p[0])
00310             else: return ""
00311         elif isinstance(p,dbus.Boolean):
00312             return "%s" %bool(p)
00313         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):
00314             return int(p)
00315         else:
00316             return "%s" %p
00317 
00318     ##
00319     # 
00320     #         Renvoie la première partition VFAT
00321     #         @result la première partition VFAT ou None s'il n'y en a pas
00322     #         
00323     def getFirstFat(self):
00324         if self.isDosFat(): return self
00325         return self.firstFat
00326 
00327     ##
00328     # 
00329     #         Permet de s'assurer qu'une partition ou un disque sera bien monté
00330     #         @result le chemin du point de montage
00331     #         
00332     def ensureMounted(self):
00333         mount_paths=self.getProp("device-mount-paths")
00334         if mount_paths==None: # le cas où la notion de montage est hors-sujet
00335             return ""
00336         leftTries=5
00337         while len(mount_paths)==0 and leftTries >0:
00338             leftTries = leftTries - 1
00339             path=self.getProp("device-file-by-id")
00340             if isinstance(path,dbus.Array) and len(path)>0:
00341                 path=path[0]
00342                 subprocess.call("udisks --mount %s > /dev/null" %path,shell=True)
00343                 paths=self.getProp("device-mount-paths")
00344                 if paths:
00345                     return self.getProp("device-mount-paths")[0]
00346                 else:
00347                     time.sleep(0.5)
00348             else:
00349                 time.sleep(0.5)
00350         if leftTries==0:
00351             raise Exception, "Could not mount the VFAT after 5 tries." 
00352         else:
00353             return mount_paths[0]
00354 
00355             
00356         
00357 ##
00358 # 
00359 #     une classe pour représenter la collection des disques USB connectés
00360 # 
00361 #     les attributs publics sont :
00362 #     - \b checkable booléen vrai si on veut gérer des sélections de disques
00363 #     - \b access le type d'accès qu'on veut pour les items
00364 #     - \b bus une instance de dbus.SystemBus
00365 #     - \b disks la collection de disques USB, organisée en un dictionnaire
00366 #        de disques : les clés sont les disques, qui renvoient à un ensemble
00367 #        de partitions du disque
00368 #     - \b enumdev une liste de chemins dbus vers les disques trouvés
00369 #     - \b firstFats une liste composée de la première partion DOS-FAT de chaque disque USB.
00370 #     
00371 class Available:
00372 
00373     ##
00374     # 
00375     #         Le constructeur
00376     #         @param checkable : vrai si on veut pouvoir cocher les disques de la
00377     #           collection. Faux par défaut.
00378     #         @param access définit le type d'accès souhaité. Par défaut, c'est "disk"
00379     #           c'est à dire qu'on veut la liste des disques USB. Autres valeurs
00380     #           possibles : "firstFat" pour les premières partitions vfat.
00381     #         @param diskClass la classe de disques à créer
00382     #         @param diskDict un dictionnaire des disque maintenu par deviceListener
00383     #         
00384     def __init__(self, checkable=False, access="disk", diskClass=uDisk, diskDict=None):
00385         ## print "GRRRR should use diskDict=", diskDict
00386         self.checkable=checkable
00387         self.access=access
00388         self.bus = dbus.SystemBus()
00389         proxy = self.bus.get_object("org.freedesktop.UDisks", 
00390                                     "/org/freedesktop/UDisks")
00391         iface = dbus.Interface(proxy, "org.freedesktop.UDisks")
00392         self.disks={}
00393         self.enumDev=iface.EnumerateDevices()
00394         ### récupération des disques usb dans le dictionnaire self.disks
00395         for path in self.enumDev:
00396             ud=diskClass(path, self.bus, checkable)
00397             if ud.isUsbDisk():
00398                 self.disks[ud]=[]
00399                 # cas des disques sans partitions
00400                 if bool(ud.getProp("device-is-partition-table")) == False:
00401                     # la propriété "device-is-partition-table" est fausse,
00402                     # probablement qu'il y a un système de fichiers
00403                     self.disks[ud].append(ud)
00404         ### une deuxième passe pour récupérer et associer les partitions
00405         for path in self.enumDev:
00406             ud=diskClass(path, self.bus, checkable)
00407             for d in self.disks.keys():
00408                 if ud.master() == d.path:
00409                     self.disks[d].append(ud)
00410         self.finishInit()
00411 
00412     ##
00413     # 
00414     #         Fin de l'initialisation
00415     #         
00416     def finishInit(self):
00417         self.mountFirstFats()
00418 
00419     ##
00420     # 
00421     #         fabrique la liste des partitions FAT,
00422     #         monte les partitions FAT si elles ne le sont pas
00423     #         
00424     def mountFirstFats(self):
00425         self.firstFats = self.getFirstFats()
00426         if self.access=="firstFat":
00427             for p in self.firstFats:
00428                 p.ensureMounted()
00429 
00430     ##
00431     # 
00432     #         @return le nombre de medias connectés
00433     #         
00434     def __trunc__(self):
00435         return len(self.firstFats)
00436 
00437     ##
00438     # 
00439     #         Sert à comparer deux collections de disques, par exemple
00440     #         une collection passée et une collection présente.
00441     #         @param other une instance de Available
00442     #         @return vrai si other semble être la même collection de disques USB
00443     #         
00444     def compare(self, other):
00445         result=self.summary()==other.summary()
00446         return result
00447 
00448     ##
00449     # 
00450     #         Permet de déterminer si un disque est dans la collection
00451     #         @param ud une instance de uDisk
00452     #         @return vrai si le uDisk ud est dans la collection
00453     #         
00454     def contains(self, ud):
00455         for k in self.disks.keys():
00456             if k.getProp("device-file-by-id")==ud.getProp("device-file-by-id"): return True
00457         return False
00458     
00459     ##
00460     # 
00461     #         Fournit une représentation imprimable d'un résumé
00462     #         @return une représentation imprimable d'un résumé de la collection
00463     #         
00464     def summary(self):
00465         r=  "Available USB discs\n"
00466         r+= "===================\n"
00467         for d in sorted(self.disks.keys(), key=lambda disk: disk.getFatUuid()):
00468             r+="%s\n" %(d.title(),)
00469             if len(self.disks[d])>0:
00470                 r+="    Partitions :\n"
00471                 for part in sorted(self.disks[d], key=lambda disk: disk.getFatUuid()):
00472                     r+="        %s\n" %(part.path,)
00473         return r
00474 
00475     ##
00476     # 
00477     #         Fournit une représentation imprimable
00478     #         @return une représentation imprimable de la collection
00479     #         
00480     def __str__(self):
00481         r=  "Available USB discs\n"
00482         r+= "===================\n"
00483         for d in self.disks.keys():
00484             r+="%s\n" %d
00485             if len(self.disks[d])>0:
00486                 r+="    Partitions :\n"
00487                 for part in self.disks[d]:
00488                     r+="        %s\n" %(part.path)
00489                     r+=part.valuableProperties(12)+"\n"
00490         return r
00491 
00492     ##
00493     # 
00494     #         Renvoye le nième disque. Le fonctionnement dépend du paramètre
00495     #         self.access
00496     #         @param n un numéro
00497     #         @return le nième disque USB connecté
00498     #         
00499     def __getitem__(self, n):
00500         if self.access=="disk":
00501             return self.disks.keys()[n]
00502         elif self.access=="firstFat":
00503             return self.firstFats[n]
00504 
00505     ##
00506     # 
00507     #         Renseigne sur la longueur de la collection. Le fonctionnement
00508     #         dépend du paramètre self.access
00509     #         @return la longueur de la collection de disques renvoyée
00510     #         
00511     def __len__(self):
00512         if self.access=="disk":
00513             return len(self.disks)
00514         elif self.access=="firstFat":
00515             return len(self.firstFats)
00516 
00517     ##
00518     # 
00519     #         Facilite l'accès aux partitions de type DOS-FAT, et a des effets
00520     #         de bord :
00521     #           * marque le disque avec l'uuid de la première partition FAT.
00522     #           * construit une liste des chemins uDisk des FATs
00523     #         @param setOwners si égale à True,
00524     #           signale que la liste devra comporter des attributs de propriétaire
00525     #           de medias.
00526     #         @return une liste de partitions, constituée de la première
00527     #           partition de type FAT de chaque disque USB connecté
00528     #         
00529     def getFirstFats(self, setOwners=False):
00530         result=[]
00531         self.fatPaths=[]
00532         for d in self.disks.keys():
00533             for p in self.disks[d]:
00534                 if p.isDosFat() or p==d :
00535                     # le cas p == d correspond aux disques non partitionnés
00536                     # on va supposer que dans ce cas la partition ne peut
00537                     # être que de type DOS !!!
00538                     result.append(p)
00539                     self.fatPaths.append(p.title())
00540                     # on marque le disque père et la partition elle-même
00541                     d.fatuuid=p.uuid
00542                     d.firstFat=p
00543                     p.fatuuid=p.uuid
00544                     if setOwners:
00545                         p.owner=d.owner
00546                     break
00547         return result
00548 
00549     ##
00550     # 
00551     #         @param dev un chemin comme /org/freedesktop/UDisks/devices/sdb3
00552     #         @return True si la partition est dans la liste des partions disponibles
00553     #         
00554     def hasDev(self, dev):
00555         s="%s" %dev
00556         s=s.replace("/org/freedesktop/UDisks/devices/","")
00557         for p in self.fatPaths:
00558             if p.split("/")[-1]==s:
00559                 return True
00560         return False
00561     
00562 
00563 if __name__=="__main__":
00564     machin=Available()
00565     print machin
00566     
00567 
 Tout Classes Espaces de nommage Fichiers Fonctions Variables