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
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
CONF'SHAREPOINT : 10 BONNES RAISONS POUR NE PAS LA RATERCONF'SHAREPOINT : 10 BONNES RAISONS POUR NE PAS LA RATER par pierre
Si vous hésitez encore à venir à la conférence, ci-après 10 bonnes raisons pour ne pas rater cet évènement unique : La Conf'SharePoint, c'est la 1ère conférence en France et en Français dédié à SharePoint : pas de barrière de la langue La Conf...
Cliquez pour lire la suite de l'article par pierre [EVENT] SOIRéE DE LANCEMENT AGILE .NET FRANCE à LYON[EVENT] SOIRéE DE LANCEMENT AGILE .NET FRANCE à LYON par thavo
Agile.Net France débarque à Lyon fin juin !! Je viens d'arriver à Lyon, et l'Agile .Net France aussi. Pour ceux/celles qui habitent en Rhône-Alpes, seriez-vous disponible pour une soirée « Agile .Net France » ?? (je sais que certains vi...
Cliquez pour lire la suite de l'article par thavo SHAREPOINT : INCOMPATIBILITé AVEC INTERNET EXPLORER 10 (IE10)SHAREPOINT : INCOMPATIBILITé AVEC INTERNET EXPLORER 10 (IE10) par ROMELARD Fabrice
Depuis plusieurs mois, Microsoft a publié un patch (comme très régulièrement) qui est passé relativement inaperçu à l'époque. L'arrivée de plus en plus de postes sous Windows 8 et surtout le déploiement par Windows Update de ...
Cliquez pour lire la suite de l'article par ROMELARD Fabrice AUTOSPINSTALLER POUR SHAREPOINT 2013 MAINTENANT DISPONIBLE EN "RTM"AUTOSPINSTALLER POUR SHAREPOINT 2013 MAINTENANT DISPONIBLE EN "RTM" par neodante
Alors qu'il n'était qu'en Beta et que quelques dysfonctionnements persistaient, la nouvelle version du fabuleux script AutoSPInstaller permettant d'installer SharePoint 2010/2013 en full script (idéal pour répliquer des fermes de dev/qual/prod) est mainte...
Cliquez pour lire la suite de l'article par neodante
Logiciels
Devis-Factures PHMSD (2.1.0.1)DEVIS-FACTURES PHMSD (2.1.0.1)Configuration minimale
Nécessite Windows™ 2000, XP, Windows 7, 8, Vista (Service Pack à... Cliquez pour télécharger Devis-Factures PHMSD Ludoprêt (3.2)LUDOPRêT (3.2)Logiciel gratuit de gestion de ludothèque.
Gestion des jeux et des adhérents.
Gestion des for... Cliquez pour télécharger Ludoprêt Revealer Keylogger Free (2.05)REVEALER KEYLOGGER FREE (2.05)Keylogger invisible et gratuit pour Windows 8, 7, Vista ou XP. Revealer Keylogger Free vous perme... Cliquez pour télécharger Revealer Keylogger Free 974 Application Server (13.2.1.3)974 APPLICATION SERVER (13.2.1.3)Ecommerce, Blogueur, Vitrine, Newsletter, Java IDE, ..., in the cloud et sous haute dispo. Facile... Cliquez pour télécharger 974 Application Server WDmemoCode (1.0.0)WDMEMOCODE (1.0.0)WDmemoCode a été créé pour aider les développeurs Windev à créer/compléter et conserver une base ... Cliquez pour télécharger WDmemoCode
|