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" border="1"> 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">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" border="1"> 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= '/'