天天看点

python查找并删除相同文件-UNIQ File-wxPython-v6

相比第一版,新增:菜单,对话框,文件过滤器,操作结果保存,配置功能(自己写了一个读写配置文件的功能),提示语优化,模块分化更合理。

截图:

python查找并删除相同文件-UNIQ File-wxPython-v6
python查找并删除相同文件-UNIQ File-wxPython-v6
python查找并删除相同文件-UNIQ File-wxPython-v6

源代码:

UniqFile-wxPython-v6.py:

1 # -*- coding: gbk -*-
  2 
  3 '''
  4 Author:@DoNotSpyOnMe
  5 Blog: http://www.cnblogs.com/aaronhoo
  6 '''
  7 
  8 import wx,os
  9 from Dialogs import DialogSetFilters,DialogAboutApp,DialogAboutAuthor
 10 from WorkerThread import WorkerThread
 11 from MyConfig import MyConfig
 12 import sys
 13 reload(sys)
 14 sys.setdefaultencoding('utf-8')
 15 
 16 class MyFrame(wx.Frame):
 17     def __init__(self):
 18         wx.Frame.__init__(self,None,title='UNIQ File-wxPython',size=(810,450))
 19         self.LoadControls()
 20         self.InitConfigData()
 21 
 22     def InitConfigData(self):
 23         configFile='metaData/config.txt'
 24         #若不存在该配置文件,创建一个
 25         if not os.path.exists(configFile):
 26             os.makedirs('metaData')
 27             f=open('metaData/config.txt','w')
 28             f.close()
 29         config=MyConfig(configFile)
 30         '''Init configurations'''
 31         config.set('Program','UNIQFile')
 32         config.set('IsExcludeHiddenFiles','False')
 33         config.set('Images','.jpg/.jpeg/.bmp/.png/.gif/.ico')
 34         config.set('Audios','.mp3/.wav/.aiff/.wma/.aac')
 35         config.set('Videos','.mp4/.rmvb/.avi/.wmv/.mov')
 36         config.set('Documents','.pdf/.txt/.doc/.docx/.ppt/.pptx/.xls/.xlsx/.log')
 37         config.set('FileSizeUnit','MB')
 38         config.set('MaxFileSize',128*1024)
 39         config.set('FileSizeStart',0)
 40         config.set('FileSizeEnd', 128*1024)
 41         config.set('FileTypeOption','Default')
 42         config.set('SelectedFileTypes','None')   
 43         config.set('OtherFilesSelected','None')
 44         config.set('IsImagesSelected','False')
 45         config.set('IsAudiosSelected','False')
 46         config.set('IsVideosSelected', 'False')
 47         config.set('IsDocumentsSelected','False')
 48         config.set('IsFileTypeFilterWorking','False')
 49         config.set('IsFileSizeFilterWorking','False')
 50         config.saveConfig() 
 51          
 52     def LoadControls(self):
 53         '''必须先添加菜单,再创建panel容器,否则菜单会被包括在panel当中,导致panel布局错乱'''
 54         self.createMenu()
 55         
 56         '''再创建panel容器'''
 57         pan=wx.Panel(self)   
 58         self.lblDir=wx.StaticText(pan,-1,'Dir:',style=wx.ALIGN_LEFT)
 59         self.txtFile=wx.TextCtrl(pan,size=(380,30))
 60 
 61         self.btnOpen=wx.Button(pan,label='Pick Directory')
 62         self.btnOpen.Bind(wx.EVT_BUTTON, self.OnOpen)
 63         self.btnList=wx.Button(pan,label='Find duplicated')
 64         self.btnList.Bind(wx.EVT_BUTTON, self.OnFind)
 65         self.btnRemove=wx.Button(pan,label='Remove duplicated')
 66         self.btnRemove.Bind(wx.EVT_BUTTON, self.OnRemove)
 67         
 68         hbox=wx.BoxSizer()
 69         hbox.Add(self.lblDir,proportion=0,flag=wx.LEFT,border=5)
 70         hbox.Add(self.txtFile,proportion=0,flag=wx.LEFT,border=5)
 71         hbox.Add(self.btnOpen,proportion=0,flag=wx.LEFT,border=5)
 72         hbox.Add(self.btnList,proportion=0,flag=wx.LEFT,border=5)
 73         hbox.Add(self.btnRemove,proportion=0,flag=wx.LEFT,border=5)
 74         
 75         self.txtContent=wx.TextCtrl(pan,style=wx.TE_MULTILINE|wx.HSCROLL)
 76         vbox=wx.BoxSizer(wx.VERTICAL)
 77 #         vbox.Add(menuBar,proportion=0,flag=wx.EXPAND|wx.ALL,border=5)
 78         vbox.Add(hbox,proportion=0,flag=wx.EXPAND|wx.ALL,border=5)
 79         vbox.Add(self.txtContent,proportion=1,flag=wx.EXPAND,border=5)
 80         pan.SetSizer(vbox) 
 81            
 82     def LoadMetaData(self):
 83         dict={}
 84         dict['Image']='.jpg/.jpeg/.bmp/.png/.gif/.ico'
 85         dict['Audio']='.mp3/.wav/.aiff/.wma/.aac'
 86         dict['Video']='.mp4/.rmvb/.avi/.wmv/.mov'
 87         dict['Document']='.pdf/.txt/.doc/.docx/.ppt/.pptx/.xls/.xlsx/.log'
 88         
 89         return dict    
 90 
 91     def createMenu(self):
 92         menuBar=wx.MenuBar()
 93         
 94         menuFile=wx.Menu()
 95         mOpen=menuFile.Append(-1,'Open Directory')#添加一级子菜单
 96         mSave=menuFile.Append(-1,'Save Result')#添加一级子菜单
 97         menuFile.AppendSeparator()
 98         mExit=menuFile.Append(-1,'Exit')#添加一级子菜单
 99         menuBar.Append(menuFile,'&File')
100         
101         menuOptions=wx.Menu()
102         mSetFilter=menuOptions.Append(-1,'Set Filters')
103 #         sbmFileTypefilter=mfilter.Append(-1,'File type filter')#添加二级子菜单
104 #         sbmFileHidden=mfilter.Append(-1,'Ignore option')#添加二级子菜单 忽略隐藏文件 hidden files
105 #         mIsRecursive=mfilter.Append(-1,'About the author')#添加二级子菜单
106         menuBar.Append(menuOptions,'&Options')
107         
108         menuHelp=wx.Menu()
109         mAbout=wx.Menu()
110         sbmAboutApp=mAbout.Append(-1,'About this app')#添加二级子菜单
111         sbmAboutAuthor=mAbout.Append(-1,'About the author')#添加二级子菜单
112         menuHelp.AppendMenu(-1,'About',mAbout)#添加一级子菜单
113         menuBar.Append(menuHelp,'&Help')
114         '''绑定菜单事件'''
115         self.Bind(wx.EVT_MENU, self.OnOpen, mOpen)
116         self.Bind(wx.EVT_MENU, self.OnSave, mSave)
117         self.Bind(wx.EVT_MENU, self.OnSetFilters, mSetFilter)
118         self.Bind(wx.EVT_MENU, self.OnAboutApp, sbmAboutApp)
119         self.Bind(wx.EVT_MENU, self.OnAboutAuthor, sbmAboutAuthor)
120         self.Bind(wx.EVT_MENU, self.OnExit, mExit)
121         '''最后组装'''
122         self.SetMenuBar(menuBar)
123         
124     def OnSetFilters(self,event):
125         dlg=DialogSetFilters(self)
126         result=dlg.ShowModal()
127         if result==wx.OK:
128             pass
129         elif result==wx.CANCEL:
130             pass
131         dlg.Destroy()
132     
133     def OnExit(self,event):
134         self.Close()
135         
136     def OnAboutApp(self,event):
137         dlg=DialogAboutApp(self)
138         dlg.ShowModal()
139         dlg.Destroy()
140         
141     def OnAboutAuthor(self,event):
142         dlg=DialogAboutAuthor(self)
143         dlg.ShowModal()
144         dlg.Destroy()
145         
146     def OnOpen(self,event): 
147         dlg = wx.DirDialog(None,u"choose a folder",style=wx.DD_DEFAULT_STYLE)  
148         if dlg.ShowModal() == wx.ID_OK:
149             dlg.Destroy()
150             if dlg.GetPath():  
151                 self.dirSelected=dlg.GetPath() #文件夹路径      
152                 self.txtFile.SetValue(self.dirSelected)      
153                 
154                 self.SetButtons('selected')
155                 self.txtContent.SetValue('Selected dirctory: %s\n'%self.dirSelected)
156      
157     def OnSave(self,event):
158         if self.txtContent.GetValue()=='':
159             wx.MessageBox('No results to save.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
160             return
161         dlg=wx.FileDialog(None, message="Choose a file",defaultDir="", defaultFile="", wildcard="*.txt", style=wx.SAVE) 
162         if dlg.ShowModal() == wx.ID_OK:
163             dlg.Destroy()
164             if dlg.GetPath():
165                 newfile=dlg.GetPath();
166 #                 if os.path.exists(newfile):
167 #                     confirmDlg=wx.MessageDialog(None,'The file selected already exists,do you want to overwrite?','Tip Message',wx.YES_NO|wx.ICON_QUESTION)
168 #                     if confirmDlg.ShowModal() == wx.YES:
169                 self.SaveResult(newfile)
170                 wx.MessageBox('Result has been saved at %s.'%newfile,'Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
171                 return  
172                
173         
174     def SaveResult(self,path):
175         f=open(path,'wb')
176         content=self.txtContent.Value
177         content=content.encode('utf-8')
178         lines=[]
179         while content.find('\n')!=-1:
180             line=content[:content.index('\n')+1]
181             lines.append(line)
182             content=content[content.index('\n')+1:]
183         f.writelines(lines)
184         f.close() 
185         
186     def OnFind(self,event):
187         if not self.txtFile.GetValue() or not os.path.isdir(self.txtFile.GetValue()):
188             wx.MessageBox('please select a valid directory first.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
189             return
190         self.dirSelected=self.txtFile.GetValue()
191         self.txtContent.SetValue('')
192         msg='Finding duplicated files in %s\n'%self.dirSelected
193         self.txtContent.SetValue(msg)
194         WorkerThread(self,self.dirSelected,'find',msg)
195 
196     def OnRemove(self,event): 
197         if not self.txtFile.GetValue() or not os.path.isdir(self.txtFile.GetValue()):
198             wx.MessageBox('please select a valid directory first.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
199             return
200         self.dirSelected=self.txtFile.GetValue()
201         self.txtContent.SetValue('')
202         msg='Removing duplicated files in %s\n'%self.dirSelected
203         self.txtContent.SetValue(msg)
204         WorkerThread(self,self.dirSelected,'remove',msg)
205 
206     def BtnStopHandler(self,event): 
207         pass
208 
209     def SetButtons(self,status):
210         if status=='init':
211             self.btnOpen.Enable()
212             self.btnList.Disable()
213             self.btnRemove.Disable()
214 #             self.btnStop.Disable()
215         elif status=='operating':
216             self.btnOpen.Disable()
217             self.btnList.Disable()
218             self.btnRemove.Disable()
219 #             self.btnStop.Enable()
220         elif status=='completed':
221             self.btnOpen.Enable()
222             self.btnList.Enable()
223             self.btnRemove.Enable()
224 #             self.btnStop.Disable()  
225         elif status=='selected':
226             self.btnOpen.Enable()
227             self.btnList.Enable()
228             self.btnRemove.Enable()
229 #             self.btnStop.Disable() 
230         
231                         
232 if __name__=="__main__":
233     app=wx.App()
234     MyFrame().Show()
235     app.MainLoop()           

WorkerThread.py:

1 # -*- coding: gbk -*-
  2 
  3 '''
  4 Author:@DoNotSpyOnMe
  5 Blog: http://www.cnblogs.com/aaronhoo
  6 '''
  7 from MyConfig import MyConfig
  8 import threading
  9 import math
 10 import platform,os
 11 import hashlib
 12 from Utils import Utils
 13 
 14 class WorkerThread(threading.Thread):
 15     def __init__(self,frame,dir,operation,msg):
 16         """初始化工作线程: 把主窗口传进来"""
 17         threading.Thread.__init__(self)
 18         self.frame = frame#传入主窗口,以便在线程中操作UI界面
 19         self.dir=dir
 20         self.operation=operation
 21         self.LoadConfigFile()
 22         self.msg=msg
 23         self.setDaemon(True)#设置子线程随UI主线程结束而结束
 24 
 25         self.MEGATOBYTENUMBER=math.pow(2,20)#将MB数转为字节数
 26 #         self.SetDirSplitor()
 27         self.Utils=Utils()
 28         self.start() 
 29     #----------------------------------------------------------------------
 30         '''加载配置文件'''
 31     def LoadConfigFile(self):
 32         configFile='metaData/config.txt'
 33         self.config=MyConfig(configFile)
 34 
 35     def run(self):
 36         """执行工作线程"""
 37         self.frame.SetButtons('operating')
 38         try:
 39             if self.operation=='find':
 40                 self.listSameFile(self.dir)
 41                 self.frame.btnList.Enable()
 42             elif self.operation=='remove':
 43                 self.removeSameFile(self.dir)
 44                 self.frame.btnRemove.Enable()            
 45         except Exception,e:
 46             print e
 47         finally:
 48             self.frame.SetButtons('completed')
 49 #         
 50 #     def stop(self):
 51 #         self.keepRunning=False
 52      
 53     def appendMsg(self,msg):
 54         if self.frame:
 55             #以下方式可以实现终端式的刷新:自动滚动到最新行
 56             self.frame.txtContent.AppendText(msg+'\n')        
 57     
 58     '''Waring:disabled feature,not implemented'''
 59     def filterHiddenFiles(self):
 60         IsExcludeHiddenFiles=eval(self.config.get('IsExcludeHiddenFiles'))
 61         
 62     def filterFileTypes(self,files,FileTypeOption,selectedFileTypes):
 63         fileCopy=[f for f in files]#fileCopy is a copy of files
 64         if FileTypeOption=='Default':
 65             pass
 66         elif FileTypeOption=='Exclude':
 67             for f in files:
 68                 ftype=self.Utils.getFileType(f)
 69                 if self.Utils.isInList(selectedFileTypes, ftype):
 70                     fileCopy.remove(f)
 71         elif FileTypeOption=='Include':
 72             for f in files:
 73                 ftype=self.Utils.getFileType(f)
 74                 if not self.Utils.isInList(selectedFileTypes, ftype):
 75                     fileCopy.remove(f)
 76         del files
 77         return fileCopy
 78     
 79     def getFileRange(self):
 80         fileSizeStart=float(self.config.get('FileSizeStart'))*self.MEGATOBYTENUMBER
 81         inputFileSizeEnd=self.config.get('FileSizeEnd')
 82         if inputFileSizeEnd=='None':
 83             fileSizeEnd=eval(self.config.get('MaxFileSize'))*self.MEGATOBYTENUMBER
 84         else:
 85             fileSizeEnd=float(inputFileSizeEnd)*self.MEGATOBYTENUMBER
 86         return (fileSizeStart,fileSizeEnd)
 87     
 88     def isFileSizeInRange(self,fileSize,fileSizeStart,fileSizeEnd):        
 89         return fileSizeStart<=fileSize<=fileSizeEnd
 90             
 91     def findSameSizeFiles(self,files):
 92         dicSize={}
 93         fileTypeOption=self.config.get('FileTypeOption')
 94         selectedFileTypes=self.config.get('SelectedFileTypes').split(';')
 95         '''过滤文件类型'''
 96         files=self.filterFileTypes(files,fileTypeOption,selectedFileTypes)
 97         
 98         tpFileRange=self.getFileRange()
 99         
100         for f in files:
101             size=self.Utils.getFileSize(f)
102             '''过滤文件大小'''
103             if not self.isFileSizeInRange(size,tpFileRange[0],tpFileRange[1]):
104                 continue        
105             if not dicSize.has_key(size):
106                 dicSize[size]=f
107             else:
108                 dicSize[size]=dicSize[size]+';'+f
109         dicCopy=dicSize.copy()
110         for k in dicSize.iterkeys():
111             if dicSize[k].find(';')==-1:
112                dicCopy.pop(k) 
113         del dicSize
114         return dicCopy
115   
116            
117     def findSameMD5Files(self,files):
118         dicMD5={}
119             
120         for f in files:
121             self.appendMsg('calculating md5 value of file %s'%f)
122             md5=self.Utils.getFileMD5(f)        
123             if not dicMD5.has_key(md5):
124                 dicMD5[md5]=f
125             else:
126                 dicMD5[md5]=dicMD5[md5]+';'+f
127         dicCopy=dicMD5.copy()
128         for k in dicMD5.iterkeys():
129             if dicMD5[k].find(';')==-1:
130                dicCopy.pop(k) 
131         del dicMD5
132         return dicCopy
133     
134     def isFileTypeFilterWorking(self):
135         return eval(self.config.get('IsFileTypeFilterWorking'))  
136     
137     def isFileSizeFilterWorking(self):
138         return eval(self.config.get('IsFileTypeFilterWorking'))
139     
140     def LoadFiltersInfo(self): 
141         flag1=self.isFileTypeFilterWorking()
142         flag2=self.isFileSizeFilterWorking()
143         
144         if flag1 or flag2:
145             self.appendMsg('Tips:file filters is working:')
146         if flag1: 
147             self.appendMsg("File Type Filter:") 
148             option=self.config.get('FileTypeOption')
149             fileTypes=self.config.get('SelectedFileTypes')
150             self.appendMsg("%s:%s\n"%(option if option=='Exclude' else 'Include only',fileTypes))
151         if flag2:
152             self.appendMsg("File Size Filter:") 
153             start=self.config.get('FileSizeStart')
154             end=self.config.get('FileSizeEnd')
155             self.appendMsg("%s MB - %s MB\n"%(start,end))
156         self.appendMsg('Start working...\n')
157             
158     def listSameFile(self,mydir):
159         msg=''
160         msgUniq='\nCongratulations,all files are unique.'
161         try:
162             existsFlag=False
163             files=self.Utils.getAllFiles(mydir)
164             self.appendMsg('%s files found in directory %s\n'%(len(files),mydir))
165             self.LoadFiltersInfo()
166             dicFileOfSameSize=self.findSameSizeFiles(files)
167             groupCount=0
168             if dicFileOfSameSize=={}:
169                 self.appendMsg(msgUniq)
170                 return 
171             else:
172                 for k in dicFileOfSameSize.iterkeys():
173                     filesOfSameSize=dicFileOfSameSize[k].split(';')
174                     dicSameMD5file=self.findSameMD5Files(filesOfSameSize)
175                     if dicSameMD5file!={}:
176                         existsFlag=True
177                         for k in dicSameMD5file.iterkeys():
178                             msg=msg+'md5 %s: %s'%(k,dicSameMD5file[k])+'\n'
179                             groupCount+=1
180                 if not existsFlag:
181                     msg=msgUniq
182                 else:
183                     msg='\nDuplicated files:\n'+msg+'\nFound %s groups of duplicated files totally.'%groupCount
184         except Exception,e:
185             print e
186             msg='Exception occured.'
187         finally:
188             self.appendMsg(msg+'\n'+'\nOperation finished.')
189     
190         
191     def removeSameFile(self,mydir):
192         msg=''
193         msgUniq='\nCongratulations,no file is removed since they are all unique.'
194         try:
195             existsFlag=False
196             files=self.Utils.getAllFiles(mydir)
197             self.appendMsg('%s files found in directory %s\n'%(len(files),mydir))
198             self.LoadFiltersInfo()
199             dicFileOfSameSize=self.findSameSizeFiles(files)
200             if not self.config.get('FileTypeOption')=='Default':
201                 self.appendMsg('Tips:file filters is working.\n')
202             if dicFileOfSameSize=={}:
203                 self.appendMsg(msgUniq)
204                 return
205             else:
206                 #list the duplicated files first: 
207                 self.appendMsg('Finding duplicted files:')
208                 dicFiltered={}
209                 groupCount=0
210                 for k in dicFileOfSameSize.iterkeys():
211                     filesOfSameSize=dicFileOfSameSize[k].split(';')
212                     dicSameMD5file=self.findSameMD5Files(filesOfSameSize)
213                     if dicSameMD5file!={}:
214                         existsFlag=True
215                         for k in dicSameMD5file.iterkeys():
216                             msg=msg+'md5 %s: %s'%(k,dicSameMD5file[k])+'\n'
217                             groupCount+=1
218                             dicFiltered[k]=dicSameMD5file[k]
219                 if not existsFlag:
220                     self.appendMsg(msgUniq)
221                     return
222                 else:
223                     #then remove the duplicated files:
224                     self.appendMsg('\nDuplicated files:\n'+msg+'\nFound %s groups of duplicated files totally.'%groupCount)
225                     removeCount=0  
226                     for k in dicFiltered.iterkeys():
227                         sameFiles=dicFiltered[k].split(';')
228                         flagRemove=False
229                         for f in sameFiles:
230                             if not flagRemove:
231                                 flagRemove=True
232                             else:
233                                 self.appendMsg('Removing file: %s'%f)
234                                 os.remove(f)
235                                 removeCount=removeCount+1
236                     self.appendMsg('\n%s files are removed.\n'%removeCount)              
237         except Exception,e:
238             print e
239             msg='\nException occured.'
240             self.appendMsg(msg)
241         finally:
242             self.appendMsg('\n\nOperation finished.')           

Dialogs.py:

1 # -*- coding: gbk -*-
  2 
  3 '''
  4 Author:@DoNotSpyOnMe
  5 Blog: http://www.cnblogs.com/aaronhoo
  6 '''
  7 import wx
  8 from wx.html import HtmlWindow
  9 from MyConfig import MyConfig
 10 import re
 11 from Utils import Utils
 12 
 13 class DialogSetFilters(wx.Dialog):
 14     def __init__(self, frame):
 15         wx.Dialog.__init__(self, frame, -1,'Filters',size=(560, 560) )
 16         self.frame=frame
 17         self.LoadConfigFile()
 18         self.LoadControls()
 19         self.InitWithConfigData()
 20         #为控件绑定事件
 21         self.BindControlEvent()
 22         self.Utils=Utils()
 23         
 24         
 25     '''加载配置文件'''
 26     def LoadConfigFile(self):
 27         configFile='metaData/config.txt'
 28         self.config=MyConfig(configFile)
 29            
 30     def LoadControls(self):       
 31         panel = wx.Panel(self)
 32         sizer = wx.GridBagSizer(5,5)#预计需要5行5列,但实际可以超出该设定,GridGagSizer会自动增长
 33         '''加载隐藏文件过滤器的控件'''
 34         sb = wx.StaticBox(panel, label="Hidden File Filter",size=(500,100))
 35         boxsizer = wx.StaticBoxSizer(sb, wx.HORIZONTAL)
 36         self.cbExclHidden=wx.CheckBox(panel, label="Exclude hidden files(Waring:disabled feature)")
 37         boxsizer.Add((10,10),flag=wx.BOTTOM|wx.TOP,border=10)
 38         boxsizer.Add(self.cbExclHidden,flag=wx.BOTTOM|wx.TOP,border=10)
 39         sizer.Add(boxsizer, pos=(0,0),span=(1,5),flag=wx.LEFT|wx.TOP,border=25)
 40         #pos(0,0)表示位置在第1行,第1列,span=(1,5)表示该boxsizer占据1个行5个列的空间
 41         
 42         '''加载文件类型过滤器的控件'''
 43         sb = wx.StaticBox(panel, label="File Type Filter",size=(500,500))
 44         boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
 45         self.rdDefault=wx.RadioButton(panel,-1,'Default',style=wx.RB_GROUP)
 46         self.rdExclFtyp=wx.RadioButton(panel,-1,'Exclude')
 47         self.rdInclFtyp=wx.RadioButton(panel,-1,'Include Only')
 48         subHbox=wx.BoxSizer(wx.HORIZONTAL)
 49         subHbox.Add(self.rdDefault,flag=wx.LEFT,border=25)
 50         subHbox.Add(self.rdExclFtyp,flag=wx.LEFT,border=5)
 51         subHbox.Add(self.rdInclFtyp,flag=wx.LEFT,border=5)
 52         boxsizer.Add(subHbox)
 53         
 54         '''从配置文件读固定配置'''
 55         self.cbImage=wx.CheckBox(panel, label="Images("+self.config.get('Images')+")")
 56         self.cbAudio=wx.CheckBox(panel, label="Audios("+self.config.get('Audios')+")")
 57         self.cbVideo=wx.CheckBox(panel, label="Videos("+self.config.get('Videos')+")")
 58         self.cbDoc=wx.CheckBox(panel, label="Documents("+self.config.get('Documents')+")")
 59         
 60         self.lblOtherFiles=wx.StaticText(panel,label="Other files ( enter file extensions,split them with \";\" )")
 61         self.txtOtherFiles=wx.TextCtrl(panel,size=(400,25))
 62         subVbox=wx.BoxSizer(wx.VERTICAL)
 63         subVbox.Add(self.cbImage,flag=wx.LEFT|wx.TOP,border=10)
 64         subVbox.Add(self.cbAudio,flag=wx.LEFT|wx.TOP,border=10)
 65         subVbox.Add(self.cbVideo,flag=wx.LEFT|wx.TOP,border=10)
 66         subVbox.Add(self.cbDoc,flag=wx.LEFT|wx.TOP,border=10)
 67         subVbox.Add(self.lblOtherFiles,flag=wx.LEFT|wx.TOP,border=10)
 68         subVbox.Add(self.txtOtherFiles,flag=wx.LEFT|wx.TOP|wx.BOTTOM|wx.RIGHT,border=10)
 69         boxsizer.Add(subVbox)
 70         sizer.Add(boxsizer, pos=(1,0), span=(6,5),flag=wx.TOP|wx.LEFT,border=25)
 71         #pos(1,0)表示位置在第2行,第1列,span=(1,5)表示该boxsizer占据6个行5个列的空间
 72         
 73         self.cbExclHidden.Disable()
 74                
 75         '''加载文件大小过滤器的控件'''
 76         sb = wx.StaticBox(panel, label="File Size Filter",size=(500,200))
 77         boxsizer = wx.StaticBoxSizer(sb, wx.HORIZONTAL)
 78         self.txtFileSize1=wx.TextCtrl(panel,size=(100,25))
 79         lblFileSize1 = wx.StaticText(panel,label="MB")
 80         lblTo = wx.StaticText(panel,label="--")
 81         self.txtFileSize2=wx.TextCtrl(panel,size=(100,25))
 82         lblFileSize2 = wx.StaticText(panel,label="MB")
 83         boxsizer.Add(self.txtFileSize1,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
 84         boxsizer.Add(lblFileSize1,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
 85         boxsizer.Add((20,10))
 86         boxsizer.Add(lblTo,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
 87         boxsizer.Add((20,10))
 88         boxsizer.Add(self.txtFileSize2,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
 89         boxsizer.Add(lblFileSize2,flag=wx.LEFT|wx.BOTTOM|wx.TOP|wx.EXPAND,border=10)
 90         sizer.Add(boxsizer, pos=(7,0),span=(1, 5),flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT , border=25)#pos=(7,0)
 91         #pos等于(7,0)表示位置在第8行,第1列,之所以是第8行,因为前面两个控件已经占据了1+6=7行,所以至少从第8行开始,否则布局错乱
 92         
 93         '''加载按钮'''
 94         self.btnOK = wx.Button(panel, label="OK")
 95         self.btnCancel = wx.Button(panel, label="Cancel")
 96         sizer.Add(self.btnOK, pos=(9,3))
 97         sizer.Add(self.btnCancel, pos=(9,4),flag=wx.BOTTOM|wx.RIGHT, border=5)    
 98         
 99         panel.SetSizer(sizer)
100         panel.Layout()
101     
102     def InitWithConfigData(self):
103         self.cbExclHidden.SetValue(str(self.config.get('IsExcludeHiddenFiles'))=='True')
104         
105         self.rdDefault.SetValue(self.config.get('FileTypeOption')=='Default')
106         if self.rdDefault.GetValue():
107             self.DisableFileTypeFilterControls()
108         
109         self.rdExclFtyp.SetValue(self.config.get('FileTypeOption')=='Exclude')
110         self.rdInclFtyp.SetValue(self.config.get('FileTypeOption')=='Include')
111         
112         self.cbImage.SetValue(str(self.config.get('IsImagesSelected'))=='True')
113         self.cbAudio.SetValue(str(self.config.get('IsAudiosSelected'))=='True')
114         self.cbVideo.SetValue(str(self.config.get('IsVideosSelected'))=='True')
115         self.cbDoc.SetValue(str(self.config.get('IsDocumentsSelected'))=='True')
116         self.txtOtherFiles.SetValue('' if self.config.get('OtherFilesSelected')=='None' else self.config.get('OtherFilesSelected'))
117         
118         self.txtFileSize1.SetValue(self.config.get('FileSizeStart'))
119         inputFileSizeEnd=self.config.get('FileSizeEnd')
120         if inputFileSizeEnd=='None':
121             self.txtFileSize2.SetValue('')
122         else:
123             self.txtFileSize2.SetValue(inputFileSizeEnd)     
124                 
125     def BindControlEvent(self):
126         for eachRadio in [self.rdDefault, self.rdExclFtyp, self.rdInclFtyp]:#绑定事件
127             self.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, eachRadio)
128         self.Bind(wx.EVT_BUTTON, self.OnOK,self.btnOK)
129         self.Bind(wx.EVT_BUTTON, self.OnCancel,self.btnCancel)
130         
131     def ValidateInput(self):
132         
133         if not self.rdDefault.GetValue():
134             pattern=re.compile('[^\d\w\.\;]+')#数字、英文字母、点号之外的字符定义为非法字符
135             if re.search(pattern,self.txtOtherFiles.GetValue()):
136                 wx.MessageBox('Please enter valid file extensions and correct splitor(;).','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
137                 return False
138         if (self.rdInclFtyp.GetValue() or self.rdExclFtyp.GetValue()) and self.getSelectedFileTypes()=='':
139             wx.MessageBox('Please enter or select at least one file type.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
140             return False   
141         if self.txtFileSize1.GetValue()!='' and not self.Utils.isNumReg(self.txtFileSize1.GetValue()):
142             wx.MessageBox('Please do not enter or enter valid number in the first file size.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
143             return False
144         elif self.txtFileSize2.GetValue()!='' and not self.Utils.isNumReg(self.txtFileSize2.GetValue()):
145             wx.MessageBox('Please do not enter or enter valid number in the second file size.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
146             return False
147         elif self.Utils.isNumReg(self.txtFileSize1.GetValue()) and self.Utils.isNumReg(self.txtFileSize2.GetValue()) and eval(self.txtFileSize1.GetValue())>eval(self.txtFileSize2.GetValue()):
148             wx.MessageBox('The first file size should not be greater than the second file size.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
149             return False
150         elif self.Utils.isNumReg(self.txtFileSize2.GetValue()) and eval(self.txtFileSize2.GetValue())>(self.config.get('MaxFileSize')):
151             wx.MessageBox('The second file size should not be greater than '+self.config.get('MaxFileSize'),'Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
152             return False
153         return True
154     
155     def getSelectedFileTypes(self):
156         selectedFileTypes=''
157         if not self.rdDefault.GetValue():
158             fileType=''
159             for chk in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc]:
160                 if chk.IsChecked():
161                     label=chk.GetLabel()
162                     fileType=fileType+label[label.find('(')+1:label.find(')')]+';'
163             selectedFileTypes=fileType
164                 
165         selectedFileTypes+=self.Utils.FormatOtherFileTypes(self.txtOtherFiles.GetValue())
166         selectedFileTypes= selectedFileTypes.replace('/',';').lower()
167         return  selectedFileTypes       
168     
169     def OnOK(self,event):
170         if self.ValidateInput():
171             IsExclHiddenFiles=self.cbExclHidden.GetValue()
172             if self.rdDefault.GetValue():
173                 FileTypeOption='Default'
174             elif self.rdExclFtyp.GetValue():
175                 FileTypeOption='Exclude'
176             elif self.rdInclFtyp.GetValue():
177                 FileTypeOption='Include'
178    
179             selectedFileTypes=self.getSelectedFileTypes()
180             if selectedFileTypes=='':
181                 self.config.set('SelectedFileTypes','None')
182             else:
183                 self.config.set('SelectedFileTypes',selectedFileTypes)
184             
185             '''save filters into the config file'''
186             self.config.set('IsExcludeHiddenFiles',IsExclHiddenFiles) 
187             self.config.set('FileTypeOption',FileTypeOption)
188             self.config.set('IsImagesSelected',self.cbImage.IsChecked())
189             self.config.set('IsAudiosSelected',self.cbAudio.IsChecked())
190             self.config.set('IsVideosSelected',self.cbVideo.IsChecked())
191             self.config.set('IsDocumentsSelected',self.cbDoc.IsChecked())
192             
193             otherFileTyps=self.txtOtherFiles.GetValue()
194             if otherFileTyps=='':
195                 self.config.set('OtherFilesSelected','None')
196             else:
197                 self.config.set('OtherFilesSelected',self.Utils.FormatOtherFileTypes(self.txtOtherFiles.GetValue()))
198             
199             fileSizeStart=self.txtFileSize1.GetValue()
200             fileSizeEnd=self.txtFileSize2.GetValue()
201             if fileSizeStart!='':
202                 self.config.set('FileSizeStart',fileSizeStart)
203                 
204             if fileSizeEnd=='':
205                 self.config.set('FileSizeEnd','None')
206             else:
207                 self.config.set('FileSizeEnd',fileSizeEnd)
208                 
209             if FileTypeOption=='Default':
210                 self.config.set('IsFileTypeFilterWorking','False')
211             else:
212                 self.config.set('IsFileTypeFilterWorking','True')
213                 
214             if fileSizeStart=='0' or fileSizeEnd!=self.config.get('MaxFileSize'):
215                 self.config.set('IsFileSizeFilterWorking','True')
216             else:
217                 self.config.set('IsFileSizeFilterWorking','False')    
218             self.config.saveConfig()
219             self.Close()
220                
221     def OnCancel(self,event):
222         self.Close()
223     
224     def OnRadio(self,event):
225         selectedRadio=event.GetEventObject()
226         if selectedRadio==self.rdDefault:
227             self.DisableFileTypeFilterControls()
228         else:
229             self.EnableFileTypeFilterControls()
230         
231     def DisableFileTypeFilterControls(self):
232         for control in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc,self.txtOtherFiles,self.lblOtherFiles]:
233             control.Disable()
234         for control in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc]:
235             control.SetValue(False)
236         self.txtOtherFiles.SetValue('')
237             
238     def EnableFileTypeFilterControls(self):
239         for control in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc,self.txtOtherFiles,self.lblOtherFiles]:
240             control.Enable()
241         
242 class DialogAboutApp(wx.Dialog):
243     text = '''
244             <html>
245             <head>
246                 <style type="text/css">
247                     body {font-family:serif;background-color: #ACAA60;}
248                 </style>
249             </head>
250             <body> <!--bgcolor="#ACAA60"-->
251             <center>
252             <table bgcolor="#455481" width="100%" cellspacing="0" cellpadding="0" >
253             <tr>
254             <td align="center"><h1><span style="color:white">UNIQ File-wxPython</span></h1></td>
255             </tr>
256             </table>
257             </center>
258             <p><b>UNIQ File-wxPython</b> is a program designed for clearing duplicated files and saving
259             memory space.
260             It's broght to you by Aaron Hu(<b><span style="color:red">@DoNotSpyOnMe</span></b> on Sina Weibo).<br/>
261            
262             <!--<a href="http://weibo.com/u/1737184870" target="_blank" rel="external nofollow"  target="_blank">Contact</a> the author now.-->
263             </p>
264             </body>
265             </html>
266             '''
267     def __init__(self, parent):
268         wx.Dialog.__init__(self, parent, -1,'About this app',
269         size=(440, 400) )
270         window = HtmlWindow(self)
271         window.SetPage(self.text)
272         button = wx.Button(self, wx.ID_OK, 'OK')
273         sizer = wx.BoxSizer(wx.VERTICAL)
274         sizer.Add(window, 1, wx.EXPAND|wx.ALL, 5)
275         sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5)
276         self.SetSizer(sizer)
277         self.Layout()
278         
279 class DialogAboutAuthor(wx.Dialog):
280     text = '''
281             <html>
282             <head>
283                 <style type="text/css">
284                     body {font-family:serif;background-color: #ACAA60;}
285                 </style>
286             </head>
287             <body> <!--bgcolor="#ACAA60"-->
288             <center>
289             <table bgcolor="#455481" width="100%" cellspacing="0" cellpadding="0" >
290             <tr>
291             <td align="center"><h1><span style="color:white">UNIQ File-wxPython</span></h1></td>
292             </tr>
293             </table>
294             </center>
295             <p>
296             Contact the author at:<br/><br/>
297             Sina Weibo:<b><span style="color:red">@DoNotSpyOnMe</span></b><br/>
298             cnblogs.com:<b>http://www.cnblogs.cn/aaronhoo</b><br/>
299             </p>
300             </body>
301             </html>
302             '''
303     def __init__(self, parent):
304         wx.Dialog.__init__(self, parent, -1,'About the author',
305         size=(440, 400) )
306         window = HtmlWindow(self)
307         window.SetPage(self.text)
308         button = wx.Button(self, wx.ID_OK, 'OK')
309         sizer = wx.BoxSizer(wx.VERTICAL)
310         sizer.Add(window, 1, wx.EXPAND|wx.ALL, 5)
311         sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5)
312         self.SetSizer(sizer)
313         self.Layout()           

MyConfig.py:

1 # -*- coding: gbk -*-
 2 '''
 3 Created on 2016年4月20日
 4 
 5 Author: @DoNotSpyOnMe
 6 '''
 7 import re,os
 8 
 9 class MyConfig:
10     def __init__(self,filepath):
11         self.configFile=''
12         self.lines=[]
13         self.read(filepath)
14         
15     def read(self,filepath):
16         try:
17             if not os.path.isfile(filepath):
18                 print 'Error:file %s dose not exist.'%filepath
19                 return
20             pattern=re.compile('^.+\.txt$')
21             if not re.search(pattern, filepath):
22                 print 'Error:only .txt file is supported as config file.' 
23                 return
24             f=open(filepath,'r')
25             self.lines=f.readlines()
26             f.close()
27             self.configFile=filepath
28         except Exception,e:
29             print e
30              
31     def get(self,variable):
32         try:
33             if self.validate():
34                 pattern=re.compile('^'+variable+' = ')
35                 for line in self.lines:
36                         if re.search(pattern,line):
37                             val= line[line.find(' = ')+3:].strip()
38                             return val
39                 print "Error:variable '%s' is not found in config file."%variable
40         except Exception,e:
41             print e
42 
43     def set(self,variable,newValue):
44         try:
45             if self.validate():
46                 count=0
47                 pattern=re.compile('^'+variable+' = ')
48                 for line in self.lines:
49                     if re.search(pattern,line):
50                         newline=line.replace(line[line.find(' = ')+3:line.rfind('\n')],str(newValue))
51                         self.lines[count]=newline
52                         return
53                     count=count+1
54                 #if the variable is no found,create it.
55                 newline=variable+' = '+str(newValue)
56                 self.lines.append(newline+'\n')
57         except Exception,e:
58             print e
59     
60     def setAndSave(self,variable,newValue):
61         try:
62             if self.validate():    
63                 count=0
64                 found=False
65                 pattern=re.compile('^'+variable+' = ')
66                 for line in self.lines:
67                     if re.search(pattern,line):
68                         newline=line.replace(line[line.find(' = ')+3:line.rfind('\n')],str(newValue))
69                         self.lines[count]=newline
70                         found=True
71                         break
72                     count=count+1
73                 if not found:
74                     newline=variable+' = '+str(newValue)
75                     self.lines.append(newline+'\n')
76                 #save changes.
77                 f=open(self.configFile,'w')
78                 f.writelines(self.lines)
79                 f.close()   
80         except Exception,e:
81             print e
82             
83     def saveConfig(self):
84         try:
85             if self.validate():
86                 f=open(self.configFile,'w')
87                 f.writelines(self.lines)
88                 f.close()   
89         except Exception,e:
90             print e
91             
92     def validate(self):
93         if not self.configFile:
94             print 'Error:config file not found.'
95             return False
96         return True           

Utils.py:

1 # -*- coding: gbk -*-
  2 import hashlib
  3 import os
  4 import platform
  5 import re
  6 
  7 class Utils:
  8     def __init__(self):
  9         self.getDirSplitor()
 10         
 11     def isNumReg(self,str):
 12         regInt='^0$|^[1-9]\d*$'#不接受09这样的为整数
 13         regFloat='^0\.\d+$|^[1-9]\d*\.\d+$'  
 14         regIntOrFloat=regInt+'|'+regFloat#整数或小数
 15         patternIntOrFloat=re.compile(regIntOrFloat)#创建pattern对象,以便后续可以复用
 16         if re.search(regIntOrFloat,str):
 17             return True
 18         else:
 19             return False
 20             
 21     '''使得用户在其他文件类型框中可以输入'.xml;.html'或者'xml;html;'或者'xml;.html'这三种格式'''    
 22     def FormatOtherFileTypes(self,fileTypeInput):
 23         ls=fileTypeInput.split(';')
 24         lsnew=[]
 25         for e in ls:
 26             if e!='':
 27                 if e.find('.')==-1:
 28                     lsnew.append('.'+e)
 29                 else:
 30                     lsnew.append(e)
 31         s=''
 32         for e in lsnew:
 33             s=s+e+';'
 34         return s
 35     
 36       
 37     def divideList(self,ls,each):
 38         dividedLs=[]
 39         eachExact=float(each)
 40         groupCount=len(ls)/each
 41         groupCountExact=len(ls)/eachExact
 42         start=0
 43         for i in xrange(groupCount):
 44             dividedLs.append(ls[start:start+each])
 45             start=start+each
 46         if groupCount<groupCountExact:#假如有余数,将剩余的所有元素加入到最后一个分组
 47             dividedLs.append(ls[groupCount*each:])
 48         return dividedLs
 49         
 50                 
 51     def getFileSize(self,filePath):
 52         return os.path.getsize(filePath)        
 53     
 54     ''' 一般文件的md5计算方法,一次读取文件的全部内容'''           
 55     def CalcMD5(filepath):
 56         with open(filepath,'rb') as f:
 57             md5obj = hashlib.md5()
 58             md5obj.update(f.read())
 59             hash = md5obj.hexdigest()
 60             return hash    
 61     '''大文件计算md5的方法,分批读取文件内容,防止内存爆掉'''    
 62     def getFileMD5(self,filename):
 63         if not os.path.isfile(filename):
 64             return
 65         myhash = hashlib.md5()
 66         f = open(filename,'rb')
 67         while True:
 68             b = f.read(8*1024)
 69             if not b :
 70                 break
 71             myhash.update(b)
 72         f.close()
 73         return myhash.hexdigest()
 74     
 75     def getAllFiles(self,directory):
 76         files=[]
 77 #         dirSplitor=getDirSplitor()
 78         for dirpath, dirnames,filenames in os.walk(directory):
 79             if filenames!=[]:
 80                 for file in filenames:
 81                     files.append(dirpath+self.dirSplitor+file)
 82         files.sort(key=len)#按照文件名的长度排序
 83         return files
 84     
 85     def isInList(self,myList,someValue):
 86         try:
 87             myList.index(someValue)
 88             return True
 89         except:
 90             return False
 91         
 92     def getFileType(self,file):
 93 #         dirSplitor=getDirSplitor()
 94         fileName=file[file.rfind(self.dirSplitor)+1:]
 95         fileType=file[file.rfind('.'):]
 96         return fileType.lower()
 97     
 98     def getDirSplitor(self):
 99         osType=platform.system()
100         if osType=='Windows':
101             self.dirSplitor= '\\'
102         elif osType=='Linux':
103             self.dirSplitor= '/'
104         else:
105             self.dirSplitor= '/'           

继续阅读