Accueil > > > RECHERCHE DE DOUBLONS DANS DES DOSSIERS.
RECHERCHE DE DOUBLONS DANS DES DOSSIERS.
Information sur la source
Description
Ce script permet de faire une recherche récursive dans un dossier pour y trouver les fichiers doublons. Ici les fichiers recherchés sont sélectionner selon (.jpg|.png|.gif|.bmp)$ Lancer : lance la recherche Reset : Remet à zéros l'affichage dans l'interface graphique. Actions : Supprimer : Supprime les éléments marqués dans la liste. Auto. : Sélectionne automatiquement les doublons à supprimer. Ouvrir : Ouvre tous les fichiers sélectionnés. Inverser : Inverse la sélection. Réinit. : Désélectionne tous les éléments. J'ai utilisé les threads pour augmenter la vitesse d'exécution, et cela au dépend de la stabilité. Si le nombre de fichiers scannés est supérieur à 1500, il y a 1/5 chances de bugger (Runtime Error). Vitesse (approx.) : 150 fichiers : 0.65 sec. 250 fichiers : 2.38 sec. 1000 fichiers : 7,9 sec. 1500 fichiers : 12,2 sec. 8500 fichiers : entre 140 et 180 sec Sous Linux : Le bind <return> n'est pas supporté. Aucun problème d'instabilité. Les performances sont deux fois supérieures en dessous de 600 fichiers, Par contre elles sont trois fois inférieurs au-dessus. Merci de votre Feed-Back Python3k
Source
- #!/usr/bin/env python3.1
-
- from tkinter import *
- import tkinter.filedialog as filedialog
- import tkinter.ttk as ttk
- import tkinter.font as tkFont
- import tkinter.messagebox as box
- import os, stat, re, time, threading
-
- class Interface (Tk) :
- def __init__ (self):
- Tk.__init__(self)
- self.grid ()
-
- self.col = ("OX","Nom","Groupe","Chemin","Taille","Date") # Titre des colonnes
- self.colWid = {"OX":15,"Nom":170,"Groupe":25,"Chemin":350,"Taille":80,"Date":140} # Taille des colonnes
- self.app = Duplicate() #Crée une instance de Duplicate, utilisée plus loin.
-
- self.d = threading.Thread() #Instancie un thread pour vérifier d.isAlive()
-
- ### FRAMES #######################################################
- self.Left = Frame(self, width=600)
- self.Right = Frame(self)
- self.Bottom = Frame (self)
- self.Bar = Frame(self,relief="sunken", width=600)
-
- self.Left.grid (row=1,column=0,columnspan=4, sticky=NW)
- self.Right.grid(row=0,column=3,sticky=NW)
- self.Bottom.grid(row=2,column=0,columnspan=2,sticky=NW)
- self.Bar.grid(row=3,column=0,columnspan=2,sticky=NW)
-
- ### Choix du Chemin ############################################
- self.EntryStr = StringVar ()
- self.EntryPath = Entry(self, textvariable=self.EntryStr,width=70)
- self.EntryPath.grid(row=0,column=0,sticky="NSEW")
-
- ### Boutons supérieurs #########################################
- Button(self, text='Lancer',command=self.Launch).grid(row=0,column=1,sticky='NEW') # Exécution.
- Button(self, text='Reset',command=self.Reset).grid(row=0,column=2,sticky='NEW') # Réinitialise StringVar et Treeview.
-
- ### Treeview - Multilist #######################################
- self.tree = ttk.Treeview(self.Left,columns=self.col, show="headings", height=30)
- vsb = ttk.Scrollbar(self.Left,orient="vertical", command=self.tree.yview)
- hsb = ttk.Scrollbar(self.Left,orient="horizontal", command=self.tree.xview)
- self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
- self.tree.grid(column=0, row=1, sticky='nsew')
- vsb.grid(column=1, row=1,sticky='nsw')
- hsb.grid(column=0, row=2, sticky='ew')
-
- # Crée les titres des colonnes :
- for col in self.col:
- self.tree.heading(col, text=col.title(),
- command=lambda c=col: sortby(self.tree, c, 0))
- self.tree.column(col, width=self.colWid[col])
-
- ### INFO #######################################################
- # INFO Barre Titles:
-
- self.Info_file_Titles = Label (self.Right, text="Fichiers",
- anchor=NW).grid(row=0,column=0, sticky=NE)
- self.Info_Original_Titles = Label (self.Right, text="Doublons",
- anchor=NW).grid(row=1,column=0, sticky=NE)
- self.Info_Size_Titles = Label (self.Right, text="Tailles",
- anchor=NW).grid(row=2,column=0, sticky=NE)
-
- # INFO Barre Values :
- Size_Info = 20
-
- self.Info_file_Str = StringVar () # Nombre d'éléments (selon le pattern de re.compile, ici fichiers images)
- self.Info_Original_Str = StringVar () # Nombre de groupes de doublons trouvés.
- self.Info_Size_Str = StringVar () # Taille totales des fichiers uniques '+' taille des doublons.
-
- self.Info_file = Label (self.Right, textvariable=self.Info_file_Str,
- anchor=NW,
- padx=10,
- width=Size_Info,
- relief='groove',
- bg='blue',
- fg="white",).grid(row=0,column=1, sticky=NW)
- self.Info_Original = Label (self.Right, textvariable=self.Info_Original_Str,
- anchor=NW,
- padx=10,
- width=Size_Info,
- relief='groove',
- bg='blue',
- fg="white").grid(row=1,column=1, sticky=NW)
- self.Info_Size = Label (self.Right, textvariable=self.Info_Size_Str,
- anchor=NW,
- padx=10,
- width=Size_Info,
- relief='groove',
- bg='blue',
- fg="white").grid(row=2,column=1, sticky=NW)
-
- ### Actions : ##################################################
- Label(self.Bottom,text='Actions : ').grid(row=0,column=0)
- Button(self.Bottom, text='Supprimer', command=self.DelSelected).grid(row=0,column=1) # Supprimer
- Button(self.Bottom, text='Auto.',command=self.Auto).grid(row=0,column=2) # Sélection automatique
- Button(self.Bottom, text='Ouvrir',command=self.OpenFile).grid(row=0,column=3) # Ouvrir les objets sélectionnés
- Button(self.Bottom, text='Inverser', command=self.Reverse).grid(row=0,column=4) # Inverser la sélection
- Button(self.Bottom, text='Réinit.',command=self.ResetTree).grid(row=0,column=5) # 'O' pour tous (aucun élément sélecionné)
-
- ### Barre d'état ###############################################
- self.Bar_File_Str = StringVar ()
- self.Bar_File = Label(self.Bar, textvariable=self.Bar_File_Str) # Affichera le temps après exécution.
- self.Bar_File.grid(row=0,column=0)
-
- ### Images #####################################################
- self.check = PhotoImage(file="check.gif") # Censé remplacer les caractères 'O' et 'X'.
- self.checked = PhotoImage(file="checked.gif") # Foncionne pas encore.
-
- ### item.Tags ##################################################
- self.tree.tag_configure(0,background='#F2F2F2',foreground='black') # Tag-1 Eléments Impairs
- self.tree.tag_configure(1,background="#BDBDBD",foreground='black') # Tag-2 Eléments Pairs
- self.tree.tag_configure(2,background="#FF6600",foreground='black') # Tag-3 Eléments supprimés
-
- ### Bind #######################################################
- self.EntryPath.bind('<Return>',lambda event:self.Launch()) # "Entrée" lance la recherche (comme le bouton lancer).
- self.EntryPath.bind('<Button-1>',lambda event:self.Path()) # Cliquer dans la zone Entry lance le selecteur de dossier.
- self.tree.bind('<<TreeviewSelect>>',lambda event:self.Select()) # Cliquer sur un item changera sa valeur -> val ? O : X.
- self.tree.bind('<Double-Button-1>',lambda event:self.OpenDir()) # Un double clique sur un item ouvrira son emplacement dans son dossier et mettra le focus sur lui.
-
- def Insert (self) : # Remplis self.tree
- Row = list()
- group = 1
- for i in self.app.doublon :
- for a in i[1] :
- F = File(a,ext=1)
- Row+=[['O', F.Name, group,
- ' '+F.Dir, self.ParseOctect(F.len),
- time.strftime("%H:%M:%S %d/%b/%Y",time.localtime(F.LastChange))]]
- group += 1
-
- for item in Row :
- if item[2]%2==0:d=0
- else : d=1
- self.tree.insert('', 'end', values=item,tags=d)#,image=self.check)
-
- info = self.app.Info() # Récupère les informations de la session Duplicate (self.app)
-
- self.Info_file_Str.set(info[0])
- self.Info_Original_Str.set(info[1])
- self.Info_Size_Str.set('{0} + {1}'.format(self.ParseOctect(info[2]-info[3]),self.ParseOctect(info[3])))
-
- #self.app.Close() # vérifier si Supprimer les variables est plus ou moins lent.
- self.bell() # Jouer un son quand l'exécution est finie.
-
- def Path (self):
- """
- Si le thread n'est pas déjà actif
- on affiche le sélecteur de dossier.
- Le résultat est affiché dans self.EntryPath
- """
- if self.d.isAlive():
- return False
- self.EntryStr.set(filedialog.askdirectory())
- return True
-
- def Launch (self):
- """
- Si le thread n'est pas actif :
- self.app.time -> chronométrer.
- Fonction récursive sur le dossier,
- Thread secondaire (principal : Tk())
- """
- if self.d.isAlive():
- return False
- self.app.t = time.time()
- en = os.path.realpath(self.EntryPath.get())
- self.Reset ()
- self.EntryStr.set(en)
- if en == "":
- return 'Error'
-
- #self.app.SearchFile(en)
- self.d=threading.Thread(None,self.app.SearchDuplicate,None,[en])
- self.d.start()
-
- def Select (self):
- """
- Bind '<<TreeviewSelect>>'
- cliquer sur un élément alternera les valeurs 'O' - 'X',
- sauf si le fichier a été supprimé ('-')
- """
-
- item = self.tree.item(self.GetCur())["values"]
- check = item[0]
-
- if check == '-' : return
- elif check == 'O': check = 'X'
- else : check = 'O'
- item[0] = check
- self.tree.item(self.GetCur(), values=item)
-
- def ParseOctect (self,Oc) :
- """
- Parser de valeur octale.
- retourne : (\d*?,\d*)(o|Ko|Mo|Go)
- """
- if Oc < pow(10,3) :
- return '{0} o'.format(Oc)
- elif Oc > pow(10,3) and Oc < pow(10,6):
- return '{0} Ko'.format(Oc/pow(10,3))
- elif Oc >= pow(10,6) and Oc < pow(10,9):
- return '{0} Mo'.format(round(Oc/pow(10,6),3))
- else :
- return '{0} Go'.format(round(Oc/pow(10,9),3))
-
- def GetCur (self):
- # Renvoie les items selectionnés, plus tellement utile.
-
- return self.tree.selection()
-
- def GetSelected (self) :
- # Retourne tous les éléments selectionné selon 'X'.
- checked = list()
- for i in self.tree.get_children() :
- if self.tree.item(i)["values"][0] =='X':
- checked+=[i]
- return checked
-
- def DelSelected (self):
- """
- Récupère la liste des éléments marqué 'X'
- Fenêtre dialogue affichant le nombre et le noms des éléments à supprimer.
- L'élémetns a supprimé est marqué '-', prend la couleur de tag-2
- """
- checked = self.GetSelected()
- if len(checked)==0 :
- box.showerror(title='Erreur',
- message='Aucun item sélectionné pour suppression.')
- return
-
- show = str()
- for i in checked :
- show += '\n {0}'.format(self.tree.item(i)["values"][1])
- if not box.askokcancel(title='Supprimer',
- message='Vous allez supprimer {0} éléments.{1}'.format(len(checked),show)) : return
-
- for i in checked :
- item = self.tree.item(i)["values"]
- item[0] = '-'
- self.tree.item(i, tags=2,values=item)
- try :
- os.remove(item[3][1:]+os.sep+item[1])
- except : box.showerror('Erreur - Suppression','Impossible de supprimer : {0}'.format(item[1]))
-
- def Reverse (self):
- # Inverse toutes les valeurs de Check selon X ? O : X
- checked = self.tree.get_children()
- for i in checked :
- item = self.tree.item(i)["values"]
- if item[0]=='O': item[0]='X'
- elif item[0]=='X' : item[0] = 'O'
- self.tree.item(i,values=item)
-
- def ResetTree (self):
- # Toutes les valeurs de Check deviennent 'O'
- checked = self.tree.get_children()
- for i in checked :
- item = self.tree.item(i)["values"]
- if item[0]=='X': item[0]='O'
- else : continue
- self.tree.item(i,values=item)
-
- def Auto (self):
- '''
- Choix basé sur la personnalisation du fichier,
- le nom contenant le moins de chiffres est sélectionné.
- '''
- groupe = int() #Tab-2
- self.ResetTree()
- Sel = self.tree.get_children()
- for i in Sel :
- item = self.tree.item(i)["values"]
- itemNext = self.tree.item(self.tree.next(i))["values"]
-
- if groupe == item[2] : continue # Même groupe ?
- else : groupe = item[2]
-
- if item[0]=='-' or itemNext[0]=='-' : continue # Déjà supprimé ?
-
- if len(re.findall('(\d)',item[1])) >\
- len(re.findall('(\d)',itemNext[1])) :
- item[0]='X'
- self.tree.item(i,values=item)
- else :
- itemNext[0]='X'
- self.tree.item(self.tree.next(i),values=itemNext)
-
- def OpenDir (self):
- # Double cliquer sur un éléments l'affiche dans explorer
- i = self.tree.item(self.GetCur())["values"]
- Iarg = i[3][1:]+os.sep+i[1]
- os.spawnl(os.P_NOWAIT,'C:/Windows/explorer','/n','/select,"{0}"'.format(Iarg))
-
- def OpenFile (self):
- # Ouvre tous les fichiers selectionné dans la liste.
- check = self.GetSelected()
- if len(check) ==0 :
- box.showerror(title='Erreur',message='Aucun item sélectionné à ouvrir.')
- for i in check :
- item = self.tree.item(i)["values"]
- f = item[3][1:]
- g = item[1]
- os.startfile('"'+f+os.sep+g+'"')
-
- def Reset (self) :
- # Remet à zéro la partie graphique.
- if len(self.tree.get_children())>0:
- for i in self.tree.get_children() :
- self.tree.delete(i)
-
- self.Info_file_Str.set('')
- self.Info_Original_Str.set('')
- self.Info_Size_Str.set('')
- self.Bar_File_Str.set('')
- self.EntryStr.set('')
-
- class Duplicate () :
- def __init__ (self):
- self.found = list()
-
- #self.pat = re.compile ('(.jpg|.png|.gif|.bmp)$', re.I) # re.I -> Insensible à la casse.
- self.ext = ['.jpg','.png','.gif','.bmp']
-
- def SearchFile (self,path):
- # Obsolète
- dos = os.listdir(os.path.realpath(path))
-
- for i in dos :
- if os.path.isdir(path+os.sep+i) :
- self.SearchFile(path+os.sep+i)
- elif os.path.isfile(path+os.sep+i) and re.findall(self.pat,i):
- self.found.append(path+os.sep+i)
- #else : print (i)
-
- return len(self.found)
-
- def SearchDuplicate (self,path):
- self.doublon = {}
- self.Size= int()
- self.SizeDouble = int()
- self.preRes = list()
-
- for root, dirs, files in os.walk(os.path.realpath(path)):
- for f in files :
- if os.path.splitext(f)[1] in self.ext:
- self.found.append(root+os.sep+f) # Ajout à la liste des fichiers
- Fstream = open(root+os.sep+f,'rb')
- stat = os.stat(root+os.sep+f)
- self.preRes.append([str(stat.st_size)+str(Fstream.read(100)),root+os.sep+f])# Ajout à la liste temporaire
- Fstream.close()
- if len(self.found)%50==0:
- app.Info_file_Str.set('fichiers {0}'.format(len(self.found)))
-
- self.Len = len(self.found)
-
- app.Info_Size_Str.set('Ok - {0}'.format(round(time.time()-self.t,5)))
- app.Info_file_Str.set('0 sur {0}'.format(self.Len))
-
- # calcul la taille totale en attendant.
- threading.Thread(None,self.DefSize,None,()).start()
-
- app.Info_file_Str.set('Ok - {0}'.format(round(time.time()-self.t,5))) # Affiche Ok et le temps écoulé dans la case 'Fichiers'
- app.Info_Original_Str.set('0 sur {0}'.format(self.Len))
-
- count=0
- for i in self.preRes :
- count+=1
- if count%10==0:
- app.Info_Original_Str.set('{0} % - doublons : {1}'.format(int(count/self.Len*100),len(self.doublon))) # tous les dix éléments on affiche le pourcentage calculé.
-
- if i[0] in self.doublon:
- self.doublon[i[0]].append(i[1])
- else:
- self.doublon[i[0]] = [i[1]]
-
- self.doublon = [k for k in self.doublon.items() if len(k[1])>1]
-
- for i in self.doublon: # Cacule la taille totale des doublons.
- for a in i[1]:
- f1 = File(a)
- self.SizeDouble += f1.len
-
- app.Info_Original_Str.set('Ok - {0}'.format(round(time.time()-self.t,5)))
-
- self.LenTotal = len(self.found)
- self.found = list()
-
- app.Bar_File_Str.set('{0} seccondes'.format(round(time.time()-self.t,5)))
-
- self.t = round(time.time()-self.t,5)
- ### statistiques #################################################
- f = open('DoublonStat.txt','a+')
- # >> Nbr d'éléments, Nbr doublons, Taille totale, Temps total
- f.write('\n{0},\t{1},\t{2},\t{3}'.format(self.Len,len(self.doublon),self.Size,self.t))
- f.close()
-
- app.Insert() # On reprend la classe principale pour y insérer les résultats.
- return True
-
- def DefSize (self):
- for i in self.found :
- f1 = File(i,ext=2)
- self.Size += f1.len
-
- def Info (self):
- return [self.LenTotal, # Nb fichiers
- len(self.doublon), # Nb doublons
- self.Size, # Taille totale
- self.SizeDouble] # Taille doublons
-
- def Close (self):
- self.found= list()
-
- del (self.LenTotal,
- self.doublon,
- self.Size,
- self.SizeDouble,
- self.t,
- self.Len,
- self.preRes)
-
- class File :
- """ File, Ext =None
- """
- def __init__ (self, file, ext=None):
- file = os.path.realpath(file)
- if not os.path.isfile(file):
- raise IOError("Le fichier n'existe pas.\n{0}".format(file))
-
- self.ext = ext
- self.stat = os.stat(file)
-
- self.mode = self.stat[stat.ST_MODE]
- self.len = self.stat.st_size # Taille du fichier
-
- if ext == 1 :
- self.Name = os.path.basename(file) # Nom du fichier
- self.Dir = os.path.dirname(file) # Chemin du fichier
- self.LastChange = self.stat[stat.ST_ATIME] # Date du fichier
- #self.LastAccess = self.stat[stat.ST_MTIME] # Date de dernier accès du fichier (pas utilisé ici)
- elif ext == 0 :
-
- if self.len > pow(10,8) : #100 000 000
- self.hash = False # Si le fichier est supérieur à 100 Mo on ne s'en occupe pas.
- print(file)
- else :
- self.file = open (file,'rb')
- #self.hash = hash(self.file.read())
- self.hash = str(self.len)+str(self.file.read(100))
-
- def sortby(tree, col, descending):
- """Sort tree contents when a column is clicked on."""
- # grab values to sort
- data = [(tree.set(child, col), child) for child in tree.get_children('')]
-
- # reorder data
- data.sort(reverse=descending)
- for indx, item in enumerate(data):
- tree.move(item[1], '', indx)
-
- # switch the heading so that it will sort in the opposite direction
- tree.heading(col,
- command=lambda col=col: sortby(tree, col, int(not descending)))
-
- if __name__ == "__main__":
- app = Interface ()
- app.title('Recherche de doublons')
- app.resizable(False,False)
- try : app.iconbitmap("ErwIco.ico")
- except : pass
- app.mainloop()
#!/usr/bin/env python3.1
from tkinter import *
import tkinter.filedialog as filedialog
import tkinter.ttk as ttk
import tkinter.font as tkFont
import tkinter.messagebox as box
import os, stat, re, time, threading
class Interface (Tk) :
def __init__ (self):
Tk.__init__(self)
self.grid ()
self.col = ("OX","Nom","Groupe","Chemin","Taille","Date") # Titre des colonnes
self.colWid = {"OX":15,"Nom":170,"Groupe":25,"Chemin":350,"Taille":80,"Date":140} # Taille des colonnes
self.app = Duplicate() #Crée une instance de Duplicate, utilisée plus loin.
self.d = threading.Thread() #Instancie un thread pour vérifier d.isAlive()
### FRAMES #######################################################
self.Left = Frame(self, width=600)
self.Right = Frame(self)
self.Bottom = Frame (self)
self.Bar = Frame(self,relief="sunken", width=600)
self.Left.grid (row=1,column=0,columnspan=4, sticky=NW)
self.Right.grid(row=0,column=3,sticky=NW)
self.Bottom.grid(row=2,column=0,columnspan=2,sticky=NW)
self.Bar.grid(row=3,column=0,columnspan=2,sticky=NW)
### Choix du Chemin ############################################
self.EntryStr = StringVar ()
self.EntryPath = Entry(self, textvariable=self.EntryStr,width=70)
self.EntryPath.grid(row=0,column=0,sticky="NSEW")
### Boutons supérieurs #########################################
Button(self, text='Lancer',command=self.Launch).grid(row=0,column=1,sticky='NEW') # Exécution.
Button(self, text='Reset',command=self.Reset).grid(row=0,column=2,sticky='NEW') # Réinitialise StringVar et Treeview.
### Treeview - Multilist #######################################
self.tree = ttk.Treeview(self.Left,columns=self.col, show="headings", height=30)
vsb = ttk.Scrollbar(self.Left,orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(self.Left,orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(column=0, row=1, sticky='nsew')
vsb.grid(column=1, row=1,sticky='nsw')
hsb.grid(column=0, row=2, sticky='ew')
# Crée les titres des colonnes :
for col in self.col:
self.tree.heading(col, text=col.title(),
command=lambda c=col: sortby(self.tree, c, 0))
self.tree.column(col, width=self.colWid[col])
### INFO #######################################################
# INFO Barre Titles:
self.Info_file_Titles = Label (self.Right, text="Fichiers",
anchor=NW).grid(row=0,column=0, sticky=NE)
self.Info_Original_Titles = Label (self.Right, text="Doublons",
anchor=NW).grid(row=1,column=0, sticky=NE)
self.Info_Size_Titles = Label (self.Right, text="Tailles",
anchor=NW).grid(row=2,column=0, sticky=NE)
# INFO Barre Values :
Size_Info = 20
self.Info_file_Str = StringVar () # Nombre d'éléments (selon le pattern de re.compile, ici fichiers images)
self.Info_Original_Str = StringVar () # Nombre de groupes de doublons trouvés.
self.Info_Size_Str = StringVar () # Taille totales des fichiers uniques '+' taille des doublons.
self.Info_file = Label (self.Right, textvariable=self.Info_file_Str,
anchor=NW,
padx=10,
width=Size_Info,
relief='groove',
bg='blue',
fg="white",).grid(row=0,column=1, sticky=NW)
self.Info_Original = Label (self.Right, textvariable=self.Info_Original_Str,
anchor=NW,
padx=10,
width=Size_Info,
relief='groove',
bg='blue',
fg="white").grid(row=1,column=1, sticky=NW)
self.Info_Size = Label (self.Right, textvariable=self.Info_Size_Str,
anchor=NW,
padx=10,
width=Size_Info,
relief='groove',
bg='blue',
fg="white").grid(row=2,column=1, sticky=NW)
### Actions : ##################################################
Label(self.Bottom,text='Actions : ').grid(row=0,column=0)
Button(self.Bottom, text='Supprimer', command=self.DelSelected).grid(row=0,column=1) # Supprimer
Button(self.Bottom, text='Auto.',command=self.Auto).grid(row=0,column=2) # Sélection automatique
Button(self.Bottom, text='Ouvrir',command=self.OpenFile).grid(row=0,column=3) # Ouvrir les objets sélectionnés
Button(self.Bottom, text='Inverser', command=self.Reverse).grid(row=0,column=4) # Inverser la sélection
Button(self.Bottom, text='Réinit.',command=self.ResetTree).grid(row=0,column=5) # 'O' pour tous (aucun élément sélecionné)
### Barre d'état ###############################################
self.Bar_File_Str = StringVar ()
self.Bar_File = Label(self.Bar, textvariable=self.Bar_File_Str) # Affichera le temps après exécution.
self.Bar_File.grid(row=0,column=0)
### Images #####################################################
self.check = PhotoImage(file="check.gif") # Censé remplacer les caractères 'O' et 'X'.
self.checked = PhotoImage(file="checked.gif") # Foncionne pas encore.
### item.Tags ##################################################
self.tree.tag_configure(0,background='#F2F2F2',foreground='black') # Tag-1 Eléments Impairs
self.tree.tag_configure(1,background="#BDBDBD",foreground='black') # Tag-2 Eléments Pairs
self.tree.tag_configure(2,background="#FF6600",foreground='black') # Tag-3 Eléments supprimés
### Bind #######################################################
self.EntryPath.bind('<Return>',lambda event:self.Launch()) # "Entrée" lance la recherche (comme le bouton lancer).
self.EntryPath.bind('<Button-1>',lambda event:self.Path()) # Cliquer dans la zone Entry lance le selecteur de dossier.
self.tree.bind('<<TreeviewSelect>>',lambda event:self.Select()) # Cliquer sur un item changera sa valeur -> val ? O : X.
self.tree.bind('<Double-Button-1>',lambda event:self.OpenDir()) # Un double clique sur un item ouvrira son emplacement dans son dossier et mettra le focus sur lui.
def Insert (self) : # Remplis self.tree
Row = list()
group = 1
for i in self.app.doublon :
for a in i[1] :
F = File(a,ext=1)
Row+=[['O', F.Name, group,
' '+F.Dir, self.ParseOctect(F.len),
time.strftime("%H:%M:%S %d/%b/%Y",time.localtime(F.LastChange))]]
group += 1
for item in Row :
if item[2]%2==0:d=0
else : d=1
self.tree.insert('', 'end', values=item,tags=d)#,image=self.check)
info = self.app.Info() # Récupère les informations de la session Duplicate (self.app)
self.Info_file_Str.set(info[0])
self.Info_Original_Str.set(info[1])
self.Info_Size_Str.set('{0} + {1}'.format(self.ParseOctect(info[2]-info[3]),self.ParseOctect(info[3])))
#self.app.Close() # vérifier si Supprimer les variables est plus ou moins lent.
self.bell() # Jouer un son quand l'exécution est finie.
def Path (self):
"""
Si le thread n'est pas déjà actif
on affiche le sélecteur de dossier.
Le résultat est affiché dans self.EntryPath
"""
if self.d.isAlive():
return False
self.EntryStr.set(filedialog.askdirectory())
return True
def Launch (self):
"""
Si le thread n'est pas actif :
self.app.time -> chronométrer.
Fonction récursive sur le dossier,
Thread secondaire (principal : Tk())
"""
if self.d.isAlive():
return False
self.app.t = time.time()
en = os.path.realpath(self.EntryPath.get())
self.Reset ()
self.EntryStr.set(en)
if en == "":
return 'Error'
#self.app.SearchFile(en)
self.d=threading.Thread(None,self.app.SearchDuplicate,None,[en])
self.d.start()
def Select (self):
"""
Bind '<<TreeviewSelect>>'
cliquer sur un élément alternera les valeurs 'O' - 'X',
sauf si le fichier a été supprimé ('-')
"""
item = self.tree.item(self.GetCur())["values"]
check = item[0]
if check == '-' : return
elif check == 'O': check = 'X'
else : check = 'O'
item[0] = check
self.tree.item(self.GetCur(), values=item)
def ParseOctect (self,Oc) :
"""
Parser de valeur octale.
retourne : (\d*?,\d*)(o|Ko|Mo|Go)
"""
if Oc < pow(10,3) :
return '{0} o'.format(Oc)
elif Oc > pow(10,3) and Oc < pow(10,6):
return '{0} Ko'.format(Oc/pow(10,3))
elif Oc >= pow(10,6) and Oc < pow(10,9):
return '{0} Mo'.format(round(Oc/pow(10,6),3))
else :
return '{0} Go'.format(round(Oc/pow(10,9),3))
def GetCur (self):
# Renvoie les items selectionnés, plus tellement utile.
return self.tree.selection()
def GetSelected (self) :
# Retourne tous les éléments selectionné selon 'X'.
checked = list()
for i in self.tree.get_children() :
if self.tree.item(i)["values"][0] =='X':
checked+=[i]
return checked
def DelSelected (self):
"""
Récupère la liste des éléments marqué 'X'
Fenêtre dialogue affichant le nombre et le noms des éléments à supprimer.
L'élémetns a supprimé est marqué '-', prend la couleur de tag-2
"""
checked = self.GetSelected()
if len(checked)==0 :
box.showerror(title='Erreur',
message='Aucun item sélectionné pour suppression.')
return
show = str()
for i in checked :
show += '\n {0}'.format(self.tree.item(i)["values"][1])
if not box.askokcancel(title='Supprimer',
message='Vous allez supprimer {0} éléments.{1}'.format(len(checked),show)) : return
for i in checked :
item = self.tree.item(i)["values"]
item[0] = '-'
self.tree.item(i, tags=2,values=item)
try :
os.remove(item[3][1:]+os.sep+item[1])
except : box.showerror('Erreur - Suppression','Impossible de supprimer : {0}'.format(item[1]))
def Reverse (self):
# Inverse toutes les valeurs de Check selon X ? O : X
checked = self.tree.get_children()
for i in checked :
item = self.tree.item(i)["values"]
if item[0]=='O': item[0]='X'
elif item[0]=='X' : item[0] = 'O'
self.tree.item(i,values=item)
def ResetTree (self):
# Toutes les valeurs de Check deviennent 'O'
checked = self.tree.get_children()
for i in checked :
item = self.tree.item(i)["values"]
if item[0]=='X': item[0]='O'
else : continue
self.tree.item(i,values=item)
def Auto (self):
'''
Choix basé sur la personnalisation du fichier,
le nom contenant le moins de chiffres est sélectionné.
'''
groupe = int() #Tab-2
self.ResetTree()
Sel = self.tree.get_children()
for i in Sel :
item = self.tree.item(i)["values"]
itemNext = self.tree.item(self.tree.next(i))["values"]
if groupe == item[2] : continue # Même groupe ?
else : groupe = item[2]
if item[0]=='-' or itemNext[0]=='-' : continue # Déjà supprimé ?
if len(re.findall('(\d)',item[1])) >\
len(re.findall('(\d)',itemNext[1])) :
item[0]='X'
self.tree.item(i,values=item)
else :
itemNext[0]='X'
self.tree.item(self.tree.next(i),values=itemNext)
def OpenDir (self):
# Double cliquer sur un éléments l'affiche dans explorer
i = self.tree.item(self.GetCur())["values"]
Iarg = i[3][1:]+os.sep+i[1]
os.spawnl(os.P_NOWAIT,'C:/Windows/explorer','/n','/select,"{0}"'.format(Iarg))
def OpenFile (self):
# Ouvre tous les fichiers selectionné dans la liste.
check = self.GetSelected()
if len(check) ==0 :
box.showerror(title='Erreur',message='Aucun item sélectionné à ouvrir.')
for i in check :
item = self.tree.item(i)["values"]
f = item[3][1:]
g = item[1]
os.startfile('"'+f+os.sep+g+'"')
def Reset (self) :
# Remet à zéro la partie graphique.
if len(self.tree.get_children())>0:
for i in self.tree.get_children() :
self.tree.delete(i)
self.Info_file_Str.set('')
self.Info_Original_Str.set('')
self.Info_Size_Str.set('')
self.Bar_File_Str.set('')
self.EntryStr.set('')
class Duplicate () :
def __init__ (self):
self.found = list()
#self.pat = re.compile ('(.jpg|.png|.gif|.bmp)$', re.I) # re.I -> Insensible à la casse.
self.ext = ['.jpg','.png','.gif','.bmp']
def SearchFile (self,path):
# Obsolète
dos = os.listdir(os.path.realpath(path))
for i in dos :
if os.path.isdir(path+os.sep+i) :
self.SearchFile(path+os.sep+i)
elif os.path.isfile(path+os.sep+i) and re.findall(self.pat,i):
self.found.append(path+os.sep+i)
#else : print (i)
return len(self.found)
def SearchDuplicate (self,path):
self.doublon = {}
self.Size= int()
self.SizeDouble = int()
self.preRes = list()
for root, dirs, files in os.walk(os.path.realpath(path)):
for f in files :
if os.path.splitext(f)[1] in self.ext:
self.found.append(root+os.sep+f) # Ajout à la liste des fichiers
Fstream = open(root+os.sep+f,'rb')
stat = os.stat(root+os.sep+f)
self.preRes.append([str(stat.st_size)+str(Fstream.read(100)),root+os.sep+f])# Ajout à la liste temporaire
Fstream.close()
if len(self.found)%50==0:
app.Info_file_Str.set('fichiers {0}'.format(len(self.found)))
self.Len = len(self.found)
app.Info_Size_Str.set('Ok - {0}'.format(round(time.time()-self.t,5)))
app.Info_file_Str.set('0 sur {0}'.format(self.Len))
# calcul la taille totale en attendant.
threading.Thread(None,self.DefSize,None,()).start()
app.Info_file_Str.set('Ok - {0}'.format(round(time.time()-self.t,5))) # Affiche Ok et le temps écoulé dans la case 'Fichiers'
app.Info_Original_Str.set('0 sur {0}'.format(self.Len))
count=0
for i in self.preRes :
count+=1
if count%10==0:
app.Info_Original_Str.set('{0} % - doublons : {1}'.format(int(count/self.Len*100),len(self.doublon))) # tous les dix éléments on affiche le pourcentage calculé.
if i[0] in self.doublon:
self.doublon[i[0]].append(i[1])
else:
self.doublon[i[0]] = [i[1]]
self.doublon = [k for k in self.doublon.items() if len(k[1])>1]
for i in self.doublon: # Cacule la taille totale des doublons.
for a in i[1]:
f1 = File(a)
self.SizeDouble += f1.len
app.Info_Original_Str.set('Ok - {0}'.format(round(time.time()-self.t,5)))
self.LenTotal = len(self.found)
self.found = list()
app.Bar_File_Str.set('{0} seccondes'.format(round(time.time()-self.t,5)))
self.t = round(time.time()-self.t,5)
### statistiques #################################################
f = open('DoublonStat.txt','a+')
# >> Nbr d'éléments, Nbr doublons, Taille totale, Temps total
f.write('\n{0},\t{1},\t{2},\t{3}'.format(self.Len,len(self.doublon),self.Size,self.t))
f.close()
app.Insert() # On reprend la classe principale pour y insérer les résultats.
return True
def DefSize (self):
for i in self.found :
f1 = File(i,ext=2)
self.Size += f1.len
def Info (self):
return [self.LenTotal, # Nb fichiers
len(self.doublon), # Nb doublons
self.Size, # Taille totale
self.SizeDouble] # Taille doublons
def Close (self):
self.found= list()
del (self.LenTotal,
self.doublon,
self.Size,
self.SizeDouble,
self.t,
self.Len,
self.preRes)
class File :
""" File, Ext =None
"""
def __init__ (self, file, ext=None):
file = os.path.realpath(file)
if not os.path.isfile(file):
raise IOError("Le fichier n'existe pas.\n{0}".format(file))
self.ext = ext
self.stat = os.stat(file)
self.mode = self.stat[stat.ST_MODE]
self.len = self.stat.st_size # Taille du fichier
if ext == 1 :
self.Name = os.path.basename(file) # Nom du fichier
self.Dir = os.path.dirname(file) # Chemin du fichier
self.LastChange = self.stat[stat.ST_ATIME] # Date du fichier
#self.LastAccess = self.stat[stat.ST_MTIME] # Date de dernier accès du fichier (pas utilisé ici)
elif ext == 0 :
if self.len > pow(10,8) : #100 000 000
self.hash = False # Si le fichier est supérieur à 100 Mo on ne s'en occupe pas.
print(file)
else :
self.file = open (file,'rb')
#self.hash = hash(self.file.read())
self.hash = str(self.len)+str(self.file.read(100))
def sortby(tree, col, descending):
"""Sort tree contents when a column is clicked on."""
# grab values to sort
data = [(tree.set(child, col), child) for child in tree.get_children('')]
# reorder data
data.sort(reverse=descending)
for indx, item in enumerate(data):
tree.move(item[1], '', indx)
# switch the heading so that it will sort in the opposite direction
tree.heading(col,
command=lambda col=col: sortby(tree, col, int(not descending)))
if __name__ == "__main__":
app = Interface ()
app.title('Recherche de doublons')
app.resizable(False,False)
try : app.iconbitmap("ErwIco.ico")
except : pass
app.mainloop()
Historique
- 30 mai 2010 04:44:00 :
- Adaptation pour linux
- 31 mai 2010 10:00:02 :
- os.rep,
quelques détails.
- 05 juin 2010 16:08:09 :
- Hash : taille + 100 premiers octets;
Quelques corrections
Sources du même auteur
Sources de la même categorie
EDITEUR CROQUISEDITEUR CROQUISsimple editeur de croquis avec un seul outils simple , encore de nonbreuse posibilité ajoût de nouvelle commande , carre , oval , arc ,ect....Tkinter ...
par grephit
ILLUSTRATION DE SINUS ET DE COSINUSILLUSTRATION DE SINUS ET DE COSINUSUn programme simple qui permet d'illustrer dynamiquement les fonctions sinus et cosinus. Ecrit pour Python 2.6, l'interface graphique fait appel à Tki...
par calogerogigante
LE MOT LE PLUS LONG PAR DICOLE MOT LE PLUS LONG PAR DICOmon premier programme python un peu élaboré, Il ouvre un dico de mots, demande à l'utilisateur la suite de lettres, si l'utilisateur rentre rand() un ...
par Clempython
EQUATION STANDARD DE LA DROITEEQUATION STANDARD DE LA DROITECe petit programme illustre l'affichage d'une droite via sa forme standard. L'intérêt principal est d'avoir fait un Canvas modifiable, et une fonction...
par calogerogigante
Commentaires et avis
Discussions en rapport avec ce code source dans le forum
Tk vs wx [ par bonac ]
Bonjour,Je doit faire une assez grosse application en python, le problème est que je ne connaissait pas python (du moins de nom) le mois dernier. Cett
Création de raccourci et lancement rapide pour un néophite [ par VickyLabRet ]
Ma fille de 6ans allume déjà mon ordi et triffouille n'importe quoi.Ce qui l'intéresse c'est d'la musique.Je voudrais faire quelque cho
wxpython : afficher et effacer des images [ par steede ]
Bonjour,J'ai besoin dans mon application d'affiicher des images et de les remplacer par d'autres en fonction des clics de la souris.Je sais les affich
Application FTP, urgent [ par mus_python ]
Bonjour tout le monde, je viens de commencer la programmation avec python et je suis chargé de faire une application qui permet la connexion entr
Recherche de chaine [ par DoudouBidou ]
Bonjour, je souhaite faire une recherche de chaine dans un texte et je pense que le module a utilisé est re mais j'ignore comment. Je voudrais
lister les fichiers d'un dossier [ par killroy988 ]
Bonjour, je vous écris car je suis débutant en Python et j'aimerais réaliser un petit programme pour m'entrainer à la base et aux fichiers...J'ai malh
Effacer le contenu d'un dossier [ par lolo38550 ]
Bonjour à tous!Je suis débutant en Python (mon language "naturel" est le C/C++). Pour commencer à me familiariser avec ce language, j'aimerai créer un
django :application bilingue [ par hasna1980 ]
BonjourDans mon application django je veux que l'utilsateur selecte sa langue preferée soit l'islandais soit l'anglais.j'ai suivi la documentation sur
Variable globale dans une application wxpython [ par tasnim86 ]
Bonsoir,j'utilise la bibliothèque wxpython comment pourais je déclarer une variable globale à toute une application wx.app? je veux avoir une variable
Comment créer des screenshots sous un dédié ? [ par Chrisvip ]
Bonsoir,Comme je fais un moteur de recherche, j'ai besoin de faire des screens, malgré qu'il y a des services sur le web (toujours un défaut), j'aimer
|
Derniers Blogs
IMAGINE CUP 2012, MAKE A SIGN EN FINALEIMAGINE CUP 2012, MAKE A SIGN EN FINALE par junarnoalg
Voilà qui est fait, la nouvelle est officielle ! L'équipe belge "Make a Sign" va au pays des kangourous défendre son projet dans la catégorie Software Design. http://www.imaginecup.com/CompetitionsContent/Competition/WorldwideFinalists.aspx V...
Cliquez pour lire la suite de l'article par junarnoalg KINECT 1.5 IS OUT !KINECT 1.5 IS OUT ! par Vko
La version 1.5 du Kinect For Microsoft vient tout juste de sortir ! Plein de nouveautés: Tracking de squelette en Near Mode Détection en position assise Détection faciale avec un SDK dédié Documentation et des guideline (enfin) Un out...
Cliquez pour lire la suite de l'article par Vko LES ACTUALITéS DE LA SEMAINE SUR C2I.FR (14 MAI - 20 MAI) LES ACTUALITéS DE LA SEMAINE SUR C2I.FR (14 MAI - 20 MAI) par richardc
Mise à jour des Web API du 14 Mai
Réservez dès maintenant votre journée du 20 juin pour le Windows Azure Dev Camp 2012 à Paris
Mise à jour de Team Foundation Service
MechCommander 2 sur Windows 8
Entity Framework 5 Release Candidate e...
Cliquez pour lire la suite de l'article par richardc REACTIVE EXTENSIONS : CONSOMMER DES SERVICES AVEC RX PARTIE 3, LES PIèGES à éVITERREACTIVE EXTENSIONS : CONSOMMER DES SERVICES AVEC RX PARTIE 3, LES PIèGES à éVITER par Groc
Une mauvaise utilisation de rx lors de l'écriture d'une couche d'accès à des services peut conduire à des cas embarassants avec des erreurs mal gérées, des appels qui ne partent lorsqu'ils le devraient, et même des résultats incorrects . le tout nuis...
Cliquez pour lire la suite de l'article par Groc SHAREPOINT BLOG SITE, PROBLèME D'ARCHIVESSHAREPOINT BLOG SITE, PROBLèME D'ARCHIVES par junarnoalg
Dernièrement, nous avons migré le site
myTIC
vers un nouveau serveur SharePoint 2010. Dans les contenus que nous vouloins récupérer, nous avions un certain nombre de blogs.
Nous avons utilisé les commandes Power...
Cliquez pour lire la suite de l'article par junarnoalg
Logiciels
974 Application Server (12.2.4.0)974 APPLICATION SERVER (12.2.4.0)Développez de puissantes applications dans un environnement de 'cloud computing', clusterisé, séc... Cliquez pour télécharger 974 Application Server vPicture (1.4.2.1)VPICTURE (1.4.2.1)Avec vPicture, hébergez vos images facilement et rapidement.
vPicture est un utilitaire simple, ... Cliquez pour télécharger vPicture Easy-Planning (2.2.1.6)EASY-PLANNING (2.2.1.6)Easy-Planning permet de créer des plannings sous la représentation de diagrammes et est adapté au... Cliquez pour télécharger Easy-Planning COM-BACKUP (2.0)COM-BACKUP (2.0)
COM-BACKUP est un logiciel de sauvegarde qui permet de planifier les sauvegardes de vos dossiers ...
Cliquez pour télécharger COM-BACKUP mySongBook Player (1.0.0)MYSONGBOOK PLAYER (1.0.0)mySongBook Player est un logiciel gratuit permettant l'accès à une archive de tablatures/partitio... Cliquez pour télécharger mySongBook Player
|