begin process at 2013 05 18 23:15:08
  Trouver un code source :
 
dans
 
Accueil > 

Code

 > 

Application complète

 > RECHERCHE DE DOUBLONS DANS DES DOSSIERS.

RECHERCHE DE DOUBLONS DANS DES DOSSIERS.


 Information sur la source

Note :
7 / 10 - par 1 personne
7,00 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10
Catégorie :Application complète Classé sous :doublon, dossier, application, recherche, double Niveau :Initié Date de création :29/05/2010 Date de mise à jour :05/06/2010 16:08:08 Vu :5 011

Auteur : Rano Its

Ecrire un message privé
Commentaire sur cette source (17)
Ajouter un commentaire et/ou une note

 Description

Cliquez pour voir la capture en taille normale
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

SUPPRESSION
Source avec Zip Source avec une capture I.A. (DÉBUTANT)

 Sources de la même categorie

Source avec Zip Source avec une capture SUDOKU EN PYTHON AVEC TKINTER ! par karmik
CALCULETTE POUR LES PRO-POINTS W-W PYTHON 3.2 par pierre3401
SOUS-TITRE YOUTUBE (.SRT) par simopr
Source avec Zip Source avec une capture GÉNÉRATEUR DE SCRIPT PYTHON par mickat92
Source avec une capture SIMULATEUR DE PLACEMENT par mickat92

 Sources en rapport avec celle ci

Source avec une capture TRIER SES FILMS PAR GENRE - INTERFACE ZENITY OU LIGNE DE COM... par sarathai
PYSCANLOG SCANNER DE FICHIER LOG par Guillamue06
Source avec Zip Source avec une capture PROGRAMME POUR LES MOTS CROISÉS par Clempython
Source avec Zip Source avec une capture OPTI2 OU COMMENT OPTIMISER WINDOWS XP ET/2000 EN QUELQUES CL... par stephane70
RECHERCHE DE CHAINES DANS UN REPERTOIRE AVEC IGU par guillaume_pays_cevenol

Commentaires et avis

Commentaire de mouss11 le 31/05/2010 08:30:46

Salut,
j'ai pas encore essayé ton source mais premier constat :
utilise os.sep a la place de '//' pour concatener tes chemins, comme ca ton source marchera sur toutes les plateformes.

Commentaire de Rano Its le 31/05/2010 10:02:21

Merci,
C'est fait :).

Commentaire de mouss11 le 31/05/2010 19:31:02

J'ai essayé ton code, ça marche pas mal ;) juste check.gif et checked.gif que je n'avais pas et qui a planté, donc ça vite corrigé. je tourne sous Python 3.1 comme toi donc OK, pas essayé <3.
Par contre tu affiches la taille des fichiers en octet et tu affiches Ko a coté. donc soit tu écris 1ko ou 1000o (octets) mais pas 1000ko sinon ça fait 1Mo. Enfin rien de bien méchant.
Sinon j'avais fait un copier/coller d'un dossier et il m'a bien trouvé les fichiers en double et c'est bien là l'essentiel.
Je suis en train de coder un petit script pour lister des fichiers donc c'est intéressant de voir comment tu fais et si jamais j'ai des choses à optimiser je te le ferais savoir.

PS : tu as mis les extensions et taille max en dur, il serait intéressant de pouvoir paramétrer ça.
PS : je continues de regarder ton code, je repasserai par là si besoin ;)
A+

Commentaire de mouss11 le 31/05/2010 20:41:31

J'ai oublié, ton interface n'est pas redimmensionnable et moi zé un tout petit écran 12" et ça ne rentre pas ! alors j'ai mis redimmensionnable mais les boutons du bas ne remontent pas. Bon pas bien méchant, j'imagine que ce n'est pas ta priorité l'IHM.

Sinon pour la fonction searchFile, tu utilises une fonction récursive, rien à redire, mais si ça t'intéresses il existe une fonction sympas : os.walk

tu pourrais remplacer ta fonction avec ce code :

listExt = ['.jpg', '.png', '.gif', '.bmp']
for root, dirs, files in os.walk(os.path.realpath(path)):
    for file in files:
        if os.path.splitext(file)[1] in listExt:
            self.found.append(root+os.sep+file)

Bon j'en suis à comprendre comment tu trouves les doublons, c'est intéressant... A+

Commentaire de mouss11 le 31/05/2010 23:41:24

En partant du principe que ces 3 images sont identiques :
D:\IMG\1\img1.gif
D:\IMG\2\img2.gif
D:\IMG\3\img3.gif
ton programme va créer 3 groupes de 2 alors qu'il n'y a qu'un seul groupe de 3
Après je ne sais pas si on doit dire que l'on a 1 duplicata, 2 ou 3 ???

en parallèle je me fait mon propre script de doublon, ça me permet de faire des vérifications et je comptais m'en faire un. C'est très pratique d'avoir ton code en parallèle.
Je travailles sur le fait que je veux les doublons aussi pour de gros fichiers,et effectivement le hash est très lent !! du coup je suis parti sur la lecture des 100 premiers octets + la taille du fichier, et déjà ça donne une bonne idée même si on ne peut pas être sûr qu'il soit identique comme avec du hash. Par contre en vitesse, ça n'a rien à voir.
En partant de ça je me dis, pourquoi pas rajouter un bouton pour chaque doublon qui vérifierai le hash, donc au cas par cas si besoin.

Je te propose ce code, il est très rapide quelque soit la taille des fichiers (testé sur 229Go avec environ 1700 fichiers et ça met 1 seconde), faut appeler la fonction afficheDoublon avec un chemin en paramètre et ça affiche des groupes de duplicatas puis le nombre de groupe (pour info je trouves 117 groupes contre 209 avec ton programme pour la même recherche d'où mes premières phrases de se commentaire):

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import os, os.path, re, stat

def afficheDoublon(path):
    listExt = ['.jpg', '.png', '.gif', '.bmp']
    dicoDupli = {}
    for root, dirs, files in os.walk(path):
        for file in files:
            if os.path.splitext(file)[1] in listExt:
                fileStream = open(root+os.sep+file,'rb')
                stat = os.stat(root+os.sep+file)
                #création d'une clé avec taille du fichier + 100 premiers octets
                #ici on peut remplacer par le hash mais c'est lent : hashKey = hash(fileStream .read())
                hashKey = str(stat.st_size) + str(fileStream.read(100))
                fileStream.close()
                if hashKey in dicoDupli:
                    dicoDupli[hashKey].append(root+os.sep+file)
                else:
                    dicoDupli[hashKey] = [root+os.sep+file]
    nbDupli = 0
    for d in dicoDupli.keys():
        if len(dicoDupli[d]) > 1:
            print(dicoDupli[d])
            nbDupli+=1
    print(nbDupli)


Voilà n'hésite pas à me dire ce que tu en penses pour améliorer tout ça et j'espère t'avoir donné une piste pour améliorer ton source.
A+.

Commentaire de Rano Its le 01/06/2010 14:58:02

Merci Mouss11, pour ton visible intérêt :p.

La fonction os.walk semble effectivement plus efficace qu'une fonction récursive.

Autrement la solution que tu utilise à la place du hashage est celle que j'avais utilisée au début, mais je l'avais repoussée car avec les images je ne pense pas que les 100 premiers octets soient significatifs. Mais si ça peut augmenter à ce point la vitesse de traitement, il est peut-être plus intéressant de vérifier le hashage a posteriori.

Pour les groupes je suis d'accord, mon programme ne repère que deux par deux. Il serait facile de changer cela.

Enfin, l'interface graphique est statique, car je n'ai pas eu le courage de paramétrer un dynamisme. Tkinter est trop précis (et buggé), ça demanderai pas mal de code en plus.

Je suis en partiel en ce moment, du coup je ferai les modifications en fin de semaine.
Merci beaucoup pour ton aide.

Commentaire de mouss11 le 01/06/2010 19:50:38

Pour l'IHM je suis comme toi ! ça me gave ! lol

Effectivement je ne suis pas certain que ma méthode fonctionne dans tous les cas comme en hash.
Mais ne pas oublier que j'ai couplé avec la taille donc 2 critères.
Pour le hash, on peut peut-être décidé de l'appliquer pour les fichiers inférieur à 1Mo (c'est un exemple) et au dessus avec une méthode plus rapide. (ou pour certain type de fichier je ne sais pas)
J'aimerais bien trouvé une autre technique mais je ne vois pas.

J'ai modifié un poil le code pour pouvoir vérifier dans plusieurs dossiers différents, ce qui est plus pratique :
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import os, os.path, stat

def afficheDoublon(listPath):
    listExt = ['.jpg', '.png', '.gif', '.bmp']
    dicoDupli = {}
    for path in listPath: #boucle sur la liste de dossier
        for root, dirs, files in os.walk(path):
            for file in files:
                if os.path.splitext(file)[1] in listExt:
                    fileStream = open(root+os.sep+file,'rb')
                    stat = os.stat(root+os.sep+file)
                    #création d'une clé avec taille du fichier + 100 premiers octets
                    #ici on peut remplacer par le hash mais c'est lent :
                    #hashKey = hash(fileStream .read())
                    hashKey = str(stat.st_size) + str(fileStream.read(100))
                    fileStream.close()
                    if hashKey in dicoDupli:
                        dicoDupli[hashKey].append(root+os.sep+file)
                    else:
                        dicoDupli[hashKey] = [root+os.sep+file]
    nbDupli = 0
    for d in dicoDupli.keys():
        if len(dicoDupli[d]) > 1:
            print(dicoDupli[d])
            nbDupli+=1
    print(nbDupli)

J'appelle ainsi autant de dossier que je veux, ici un vieux dossier de sauvegarde et mon dossier de photo actuel:

afficheDoublon( ["D:\AUTRES\ARCHIVE\Framakey_Backup\Data\Photos","D:\PICTURE"] )

"D:\AUTRES\ARCHIVE\Framakey_Backup\Data\Photos" = 3390 fichiers/132 dossiers (environ 2,42Go)
"D:\PICTURE" = 13023 fichiers/273 dossiers (environ 28,4Go de photos)

J'ai essayé avec hash puis 100octets+taille fichier.
Résultat 897 groupes dans les 2 cas, c'est plutôt assez concluant.
temps en hash : 41 secondes à chaque lancement (seulement 3 lancements)
temps méthode 2 : 5 secondes à chaque lancement (seulement 3 lancements)

Il me reste à vérifier le contenu des groupes

Commentaire de Rano Its le 05/06/2010 16:17:50

Salut,
j'ai mis à jour finalement en utilisant ta méthode de hashage qui devrait suffire pour comparer.
J'ai ainsi pu enlever une bonne partie du code et améliorer les performances ainsi que diminuer les ressources.

J'en suis à 16 secondes pour 9 000 fichiers.
les groupes sont maintenant complets. Il n'y a plus trois fois les fichiers dans des groupes différents s'ils sont en triples.

Les seules erreurs qui restent sont des bugs de Tkinter (qui sont pas mal ennuyants encore !).

Encore merci pour ton aide.

Commentaire de aera group le 27/06/2010 19:32:29

Yep !

Ah ça faisait longtemps que j'avais pas vu un code aussi riche !!! Merci pour mon désespoir chronique :p !

Voila une source intéressante. Comment améliorer la vitesse de comparaison sans sacrifier la stabilité (qui a mon sens est la priorité).
L'idée d'utiliser des threads est bonne, mais je pense savoir où tu commets une erreur : Pour chaque fichier tu lances un ou plusieurs threads (je n'ai pas bien regarder j'avoue). Le problème c'est que tu bouffes toutes les ressources de ton pc (processeur et accessoirement mémoire vive). Be fait qu'il bug au bout d'un certain nombre de fichier, vivent simplement du fait que le pc est surexploité !!! À vérifier s'il s'agit bel et bien de ce problème et tenter de le résoudre ...

En ce qui consterne l'interface si j'ai un peu de temps et si tu le souhaite, je peux toujours essayer de t'en faire une en xwPython plus stable ...

L'idée de vérifier la taille et les 1000 premier octets est une bonne méthode. Je pense qu'il faut faire une distinction de cas :


taille = taille_du_fichier
if taille <= taille_limite:
    methode_hashage()
else:
    methode_premier_octets()


Ensuite l'interface doit proposer pour chaque fichier vérifier par la deuxième méthode d'être vérifier par la méthode de hashage qui donne la confirmation ou l'exception ....


Voila, je n'ai malheureusement pas pu tester ton code car mon pc est (encore) tombé en panne (merci nvidia) et est partit faire un petit stage germanique de remise à niveau.
Cela dis je vais bien sûre le tester et tenté d'y apporté des amélioration si nécessaire.
Bonne continuation et bon courage pour la suite. Merci pour ce joli petit code.

PS : ligne 295 : 'C:/Windows/explorer' => pas compatible avec Linux je pense :p
Cela dis, es réellement important ?
____
Aéra

Commentaire de xyz33 le 27/06/2010 20:22:59 7/10

Bonjour,
je découvre python et clairement je ne comprends pas encore le code présenté. Mais le concept et l'approche me semblent intéressant et j'aime comprendre comment le logiciel que j'utilise fonctionne.
Encore faut il le faire fonctionner...Et le premier essai sous linux ne donne rien, mais la remarque sur la ligne 295 me laisse penser qu'il faut que j'insiste dans le monde Windows.

Si Aéra nous fournit une version + stable, + fun coté IHM et portable win-linux-MAC, je rajoute 3 étoiles.

D'avance merci et bon courage !

Commentaire de mouss11 le 27/06/2010 20:27:21

@xyz33 et attention à ta version de python, ce code est pour python v3.X, pas compatible avec v2.X
après faut regarder les quelques lignes qui pourraient être incompatible avec linux, doit pas y en avoir des masses, l'auteur à déjà modifié le problème de slash dans les chemins, voir os.sep

Commentaire de Rano Its le 27/06/2010 22:09:24

aera group : salut, j'adorerai une version avec wxPython, mais le code est en Py3K et WX n'est pas encore porté à ma connaissance.
             L'ancien code faisait les vérification par groupe de 50 en threading, mais celui-ci n'a plus besoin, toute la fonction est le thread.
             Malgré cela il y a encore un bug sur 3 lancements au-dessus de 5 000 fichiers.
             On vérifie la taille des 100 premiers octets, mais depuis j'ai remarqué que ça n'est pas suffisant, je l'ai passé à 220 octets.
             Effectivement la partie explorateur ne fonctionne pas avec Linux malheureusement.
            
xyz33 : salut, j'ai testé ce code avec la dernière mouture de Ubuntu et cela marche sans problème.
        "Insister dans le monde windows" n'est pas du tout une nécessité en programmation.
        Commence par vérifier si tu as bien python3.0 ou plus.
        PS : comment on se sent à 101 ans ?

mouss11 : merci, content de voir que tu traines toujours près de mon code :p.

Commentaire de Rano Its le 27/06/2010 22:10:21

P.S. : xyz33  -> si t'as des questions pour mieux comprendre le programme, j'y répondrai avec plaisir.

Commentaire de xyz33 le 28/06/2010 00:54:59

101 ans, j'exagère un peu mais comme mon premier ordinateur était un ZX81 (cf http://fr.wikipedia.org/wiki/ZX81) j'ai l'impression que cela fait un siècle, et puis passer du code binaire en hexa à python ça fait du chemein à parcourir...

Bon, ceci étant, dans ma mandriva, j'ai du du python 3.1.1, du 2.6.4 et du 2.4.5. Une commande "python" en console indique 2.6.4, donc faisons un peu de nettoyage en éliminant le 2.4.5.
Et puis en revenant à la console je teste à tout hazard "python3" qui me répond 3.1.1, alors je rejoue en ajoutant le nom du fichier, ce qui me donne quelques messages d'erreur concernant des .gif (pb connu même si les gif en question ne servent (pour le moment) à rien:-)), un message d'erreur sur "tkinter", ben oui il manque le paquetage en 3.1.1 ! Et puis le miracle se produit.

Un test rapide sur un répertoire donne quelques doublons "pas idiots" et j'en resterai là pour ce soir !
Bonne nuit et A+

Commentaire de aera group le 22/07/2010 12:17:04

Ah ok, bug CodeS-SourceS ou erreur de ma part, je n'ai pas eu d'alerte mail !

J'ai une question : il est bien le petit Python 3 000 ?

Effectivement impossible de passer en wx sous Py3K, ça devra attendre.

Pour revenir au bug, je n'ai pas vraiment encore étudier le code, mais quand tu dis "toute la fonction est le thread", ça veut dire quoi ? Que tu créer un thread par fichier ou un thread pour tous les fichiers ?


Avec 1000 octets tu devrais être plus sure pour la vérif.

Ton bug ressemble à un problème matériel. Je vais te créer si j'ai le temps un petit script pour vérifier tout ça. Mais avant il faut que je sache si tu es capable de tester le programme sous Windows ?

Commentaire de Rano Its le 22/07/2010 14:33:51

Personnellement je préfère py3k il est mieux construit et plus logique dans sa globalité, mais il y a tellement de modules non-adaptés que ça en devient handicapant.
Ici par exemple j'aurai préféré utiliser WxPy.

je suis sur un autre projet alors j'ai un peu de mal à me remettre à celui-ci,
normalement :
        self.d=threading.Thread(None,self.app.SearchDuplicate,None,[en])
        self.d.start()
ceci appel en tant que thread (unique) la fonction pour rechercher les doublons, au sein de celle-ci :
        threading.Thread(None,self.DefSize,None,()).start()
ce thread sert juste à définir la taille totale des fichiers doublons, si les processus suivants se termine avant ce sous-thread le résultats dans self.Info_Size_Str sera erroné.


En fait le question du nombre d'octets a utiliser est très importantes car elle influence directement la reconnaissance bien sûr, mais surtout la vitesse du programme.
Après pas mal d'essais je me suis stabilisé avec 200 octets et ça semble convenir, cela couplé avec la vérification directe de la date et de la taille permet d'éviter les erreurs.

Pour ce qui est du bug, je crois que c'est à cause de l'instabilité de Tkinter, comme je manipule pas mal de StringVar et autres de ce genre à la voléee, le module me sort parfois des erreurs et crash le programme.

Je suis effectivement capable de tester le prog sous windows, merci de ton aide.

Commentaire de aera group le 22/07/2010 20:47:17

Dans ce cas je vais créer un petit code avec la procédure de test pour voir si le problème est matériel (Win only). En effet c'est peu être du a Tk.

Je te dé-conseil l'utilisation de py 3.x pour des raisons évidente de compatibilité, et même de stabilité, il n'a pas assez de passif ... même s'il règle plein de problème entre autre l'unicode !

 Ajouter un commentaire


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&#233;j&#224; mon ordi et triffouille n'importe quoi.Ce qui l'int&#233;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&#233; 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&#233; 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


Nos sponsors


Sondage...

CalendriCode

Mai 2013
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
2728293031  

Consulter la suite du CalendriCode

Photothèque

A découvrir



 
Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils.
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés

Google Coop CodeS-SourceS Google Coop CodeS-SourceS
Temps d'éxécution de la page : 1,014 sec (4)

Nous contacter | Annoncer sur CodeS-SourceS | Mentions légales