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
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 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         return self.firstFat
00323     
00324     ##
00325     # 
00326     #         Permet de s'assurer qu'une partition ou un disque sera bien monté
00327     #         @result le chemin du point de montage
00328     #         
00329     def ensureMounted(self):
00330         mount_paths=self.getProp("device-mount-paths")
00331         if len(mount_paths)==0:
00332             path=self.getProp("device-file-by-id")
00333             if isinstance(path,dbus.Array):
00334                 path=path[0]
00335             subprocess.call("udisks --mount %s" %path,shell=True)
00336             return path
00337         else:
00338             return mount_paths[0]
00339 
00340             
00341         
00342 ##
00343 # 
00344 #     une classe pour représenter la collection des disques USB connectés
00345 # 
00346 #     les attributs publics sont :
00347 #     - \b checkable booléen vrai si on veut gérer des sélections de disques
00348 #     - \b access le type d'accès qu'on veut pour les items
00349 #     - \b bus une instance de dbus.SystemBus
00350 #     - \b disks la collection de disques USB, organisée en un dictionnaire
00351 #        de disques : les clés sont les disques, qui renvoient à un ensemble
00352 #        de partitions du disque
00353 #     - \b enumdev une liste de chemins dbus vers les disques trouvés
00354 #     - \b firstFats une liste composée de la première partion DOS-FAT de chaque disque USB.
00355 #     
00356 class Available:
00357 
00358     ##
00359     # 
00360     #         Le constructeur
00361     #         @param checkable : vrai si on veut pouvoir cocher les disques de la
00362     #           collection. Faux par défaut.
00363     #         @param access définit le type d'accès souhaité. Par défaut, c'est "disk"
00364     #           c'est à dire qu'on veut la liste des disques USB. Autres valeurs
00365     #           possibles : "firstFat" pour les premières partitions vfat.
00366     #         
00367     def __init__(self, checkable=False, access="disk"):
00368         self.checkable=checkable
00369         self.access=access
00370         self.bus = dbus.SystemBus()
00371         proxy = self.bus.get_object("org.freedesktop.UDisks", 
00372                                     "/org/freedesktop/UDisks")
00373         iface = dbus.Interface(proxy, "org.freedesktop.UDisks")
00374         self.disks={}
00375         self.enumDev=iface.EnumerateDevices()
00376         ### récupération des disques usb dans le dictionnaire self.disks
00377         for path in self.enumDev:
00378             ud=uDisk(path, self.bus, checkable)
00379             if ud.isUsbDisk():
00380                 self.disks[ud]=[]
00381                 # cas des disques sans partitions
00382                 if bool(ud.getProp("device-is-partition-table")) == False:
00383                     # la propriété "device-is-partition-table" est fausse,
00384                     # probablement qu'il y a un système de fichiers
00385                     self.disks[ud].append(ud)
00386         ### une deuxième passe pour récupérer et associer les partitions
00387         for path in self.enumDev:
00388             ud=uDisk(path, self.bus, checkable)
00389             for d in self.disks.keys():
00390                 if ud.master() == d.path:
00391                     self.disks[d].append(ud)
00392         ### on fabrique la liste des premières partitions FAT
00393         self.firstFats = self.getFirstFats()
00394         ### on monte les partitions si nécessaire
00395         if self.access=="firstFat":
00396             for p in self.firstFats:
00397                 p.ensureMounted()
00398 
00399     ##
00400     # 
00401     #         @return le nombre de medias connectés
00402     #         
00403     def __trunc__(self):
00404         return len(self.firstFats)
00405 
00406     ##
00407     # 
00408     #         Sert à comparer deux collections de disques, par exemple
00409     #         une collection passée et une collection présente.
00410     #         @param other une instance de Available
00411     #         @return vrai si other semble être la même collection de disques USB
00412     #         
00413     def compare(self, other):
00414         result=self.summary()==other.summary()
00415         return result
00416 
00417     ##
00418     # 
00419     #         Permet de déterminer si un disque est dans la collection
00420     #         @param ud une instance de uDisk
00421     #         @return vrai si le uDisk ud est dans la collection
00422     #         
00423     def contains(self, ud):
00424         for k in self.disks.keys():
00425             if k.getProp("device-file-by-id")==ud.getProp("device-file-by-id"): return True
00426         return False
00427     
00428     ##
00429     # 
00430     #         Fournit une représentation imprimable d'un résumé
00431     #         @return une représentation imprimable d'un résumé de la collection
00432     #         
00433     def summary(self):
00434         r=  "Available USB discs\n"
00435         r+= "===================\n"
00436         for d in sorted(self.disks.keys(), key=lambda disk: disk.getFatUuid()):
00437             r+="%s\n" %(d.title(),)
00438             if len(self.disks[d])>0:
00439                 r+="    Partitions :\n"
00440                 for part in sorted(self.disks[d], key=lambda disk: disk.getFatUuid()):
00441                     r+="        %s\n" %(part.path,)
00442         return r
00443 
00444     ##
00445     # 
00446     #         Fournit une représentation imprimable
00447     #         @return une représentation imprimable de la collection
00448     #         
00449     def __str__(self):
00450         r=  "Available USB discs\n"
00451         r+= "===================\n"
00452         for d in self.disks.keys():
00453             r+="%s\n" %d
00454             if len(self.disks[d])>0:
00455                 r+="    Partitions :\n"
00456                 for part in self.disks[d]:
00457                     r+="        %s\n" %(part.path)
00458                     r+=part.valuableProperties(12)+"\n"
00459         return r
00460 
00461     ##
00462     # 
00463     #         Renvoye le nième disque. Le fonctionnement dépend du paramètre
00464     #         self.access
00465     #         @param n un numéro
00466     #         @return le nième disque USB connecté
00467     #         
00468     def __getitem__(self, n):
00469         if self.access=="disk":
00470             return self.disks.keys()[n]
00471         elif self.access=="firstFat":
00472             return self.firstFats[n]
00473 
00474     ##
00475     # 
00476     #         Renseigne sur la longueur de la collection. Le fonctionnement
00477     #         dépend du paramètre self.access
00478     #         @return la longueur de la collection de disques renvoyée
00479     #         
00480     def __len__(self):
00481         if self.access=="disk":
00482             return len(self.disks)
00483         elif self.access=="firstFat":
00484             return len(self.firstFats)
00485 
00486     ##
00487     # 
00488     #         Facilite l'accès aux partitions de type DOS-FAT, et a un effet de bord :
00489     #         marque le disque avec l'uuid de la première partition FAT.
00490     #         @param setOwners si égale à True,
00491     #           signale que la liste devra comporter des attributs de propriétaire
00492     #           de medias.
00493     #         @return une liste de partitions, constituée de la première
00494     #           partition de type FAT de chaque disque USB connecté
00495     #         
00496     def getFirstFats(self, setOwners=False):
00497         result=[]
00498         for d in self.disks.keys():
00499             for p in self.disks[d]:
00500                 if p.isDosFat() or p==d :
00501                     # le cas p == d correspond aux disques non partitionnés
00502                     # on va supposer que dans ce cas la partition ne peut
00503                     # être que de type DOS !!!
00504                     result.append(p)
00505                     # on marque le disque père et la partition elle-même
00506                     d.fatuuid=p.uuid
00507                     d.firstFat=p
00508                     p.fatuuid=p.uuid
00509                     if setOwners:
00510                         p.owner=d.owner
00511                     break
00512         return result
00513 
00514 if __name__=="__main__":
00515     machin=Available()
00516     print machin
00517     
00518 
 Tout Classes Espaces de nommage Fichiers Fonctions Variables