相比第一版,新增:菜单,对话框,文件过滤器,操作结果保存,配置功能(自己写了一个读写配置文件的功能),提示语优化,模块分化更合理。
截图:
源代码:
UniqFile-wxPython-v6.py:
# -*- coding: gbk -*- '''
Author:@DoNotSpyOnMe
Blog: http://www.cnblogs.com/aaronhoo
''' import wx,os
from Dialogs import DialogSetFilters,DialogAboutApp,DialogAboutAuthor
from WorkerThread import WorkerThread
from MyConfig import MyConfig
import sys
reload(sys)
sys.setdefaultencoding('utf-8') class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,title='UNIQ File-wxPython',size=(810,450))
self.LoadControls()
self.InitConfigData() def InitConfigData(self):
configFile='metaData/config.txt'
#若不存在该配置文件,创建一个
if not os.path.exists(configFile):
os.makedirs('metaData')
f=open('metaData/config.txt','w')
f.close()
config=MyConfig(configFile)
'''Init configurations'''
config.set('Program','UNIQFile')
config.set('IsExcludeHiddenFiles','False')
config.set('Images','.jpg/.jpeg/.bmp/.png/.gif/.ico')
config.set('Audios','.mp3/.wav/.aiff/.wma/.aac')
config.set('Videos','.mp4/.rmvb/.avi/.wmv/.mov')
config.set('Documents','.pdf/.txt/.doc/.docx/.ppt/.pptx/.xls/.xlsx/.log')
config.set('FileSizeUnit','MB')
config.set('MaxFileSize',128*1024)
config.set('FileSizeStart',0)
config.set('FileSizeEnd', 128*1024)
config.set('FileTypeOption','Default')
config.set('SelectedFileTypes','None')
config.set('OtherFilesSelected','None')
config.set('IsImagesSelected','False')
config.set('IsAudiosSelected','False')
config.set('IsVideosSelected', 'False')
config.set('IsDocumentsSelected','False')
config.set('IsFileTypeFilterWorking','False')
config.set('IsFileSizeFilterWorking','False')
config.saveConfig() def LoadControls(self):
'''必须先添加菜单,再创建panel容器,否则菜单会被包括在panel当中,导致panel布局错乱'''
self.createMenu() '''再创建panel容器'''
pan=wx.Panel(self)
self.lblDir=wx.StaticText(pan,-1,'Dir:',style=wx.ALIGN_LEFT)
self.txtFile=wx.TextCtrl(pan,size=(380,30)) self.btnOpen=wx.Button(pan,label='Pick Directory')
self.btnOpen.Bind(wx.EVT_BUTTON, self.OnOpen)
self.btnList=wx.Button(pan,label='Find duplicated')
self.btnList.Bind(wx.EVT_BUTTON, self.OnFind)
self.btnRemove=wx.Button(pan,label='Remove duplicated')
self.btnRemove.Bind(wx.EVT_BUTTON, self.OnRemove) hbox=wx.BoxSizer()
hbox.Add(self.lblDir,proportion=0,flag=wx.LEFT,border=5)
hbox.Add(self.txtFile,proportion=0,flag=wx.LEFT,border=5)
hbox.Add(self.btnOpen,proportion=0,flag=wx.LEFT,border=5)
hbox.Add(self.btnList,proportion=0,flag=wx.LEFT,border=5)
hbox.Add(self.btnRemove,proportion=0,flag=wx.LEFT,border=5) self.txtContent=wx.TextCtrl(pan,style=wx.TE_MULTILINE|wx.HSCROLL)
vbox=wx.BoxSizer(wx.VERTICAL)
# vbox.Add(menuBar,proportion=0,flag=wx.EXPAND|wx.ALL,border=5)
vbox.Add(hbox,proportion=0,flag=wx.EXPAND|wx.ALL,border=5)
vbox.Add(self.txtContent,proportion=1,flag=wx.EXPAND,border=5)
pan.SetSizer(vbox) def LoadMetaData(self):
dict={}
dict['Image']='.jpg/.jpeg/.bmp/.png/.gif/.ico'
dict['Audio']='.mp3/.wav/.aiff/.wma/.aac'
dict['Video']='.mp4/.rmvb/.avi/.wmv/.mov'
dict['Document']='.pdf/.txt/.doc/.docx/.ppt/.pptx/.xls/.xlsx/.log' return dict def createMenu(self):
menuBar=wx.MenuBar() menuFile=wx.Menu()
mOpen=menuFile.Append(-1,'Open Directory')#添加一级子菜单
mSave=menuFile.Append(-1,'Save Result')#添加一级子菜单
menuFile.AppendSeparator()
mExit=menuFile.Append(-1,'Exit')#添加一级子菜单
menuBar.Append(menuFile,'&File') menuOptions=wx.Menu()
mSetFilter=menuOptions.Append(-1,'Set Filters')
# sbmFileTypefilter=mfilter.Append(-1,'File type filter')#添加二级子菜单
# sbmFileHidden=mfilter.Append(-1,'Ignore option')#添加二级子菜单 忽略隐藏文件 hidden files
# mIsRecursive=mfilter.Append(-1,'About the author')#添加二级子菜单
menuBar.Append(menuOptions,'&Options') menuHelp=wx.Menu()
mAbout=wx.Menu()
sbmAboutApp=mAbout.Append(-1,'About this app')#添加二级子菜单
sbmAboutAuthor=mAbout.Append(-1,'About the author')#添加二级子菜单
menuHelp.AppendMenu(-1,'About',mAbout)#添加一级子菜单
menuBar.Append(menuHelp,'&Help')
'''绑定菜单事件'''
self.Bind(wx.EVT_MENU, self.OnOpen, mOpen)
self.Bind(wx.EVT_MENU, self.OnSave, mSave)
self.Bind(wx.EVT_MENU, self.OnSetFilters, mSetFilter)
self.Bind(wx.EVT_MENU, self.OnAboutApp, sbmAboutApp)
self.Bind(wx.EVT_MENU, self.OnAboutAuthor, sbmAboutAuthor)
self.Bind(wx.EVT_MENU, self.OnExit, mExit)
'''最后组装'''
self.SetMenuBar(menuBar) def OnSetFilters(self,event):
dlg=DialogSetFilters(self)
result=dlg.ShowModal()
if result==wx.OK:
pass
elif result==wx.CANCEL:
pass
dlg.Destroy() def OnExit(self,event):
self.Close() def OnAboutApp(self,event):
dlg=DialogAboutApp(self)
dlg.ShowModal()
dlg.Destroy() def OnAboutAuthor(self,event):
dlg=DialogAboutAuthor(self)
dlg.ShowModal()
dlg.Destroy() def OnOpen(self,event):
dlg = wx.DirDialog(None,u"choose a folder",style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
dlg.Destroy()
if dlg.GetPath():
self.dirSelected=dlg.GetPath() #文件夹路径
self.txtFile.SetValue(self.dirSelected) self.SetButtons('selected')
self.txtContent.SetValue('Selected dirctory: %s\n'%self.dirSelected) def OnSave(self,event):
if self.txtContent.GetValue()=='':
wx.MessageBox('No results to save.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return
dlg=wx.FileDialog(None, message="Choose a file",defaultDir="", defaultFile="", wildcard="*.txt", style=wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
dlg.Destroy()
if dlg.GetPath():
newfile=dlg.GetPath();
# if os.path.exists(newfile):
# confirmDlg=wx.MessageDialog(None,'The file selected already exists,do you want to overwrite?','Tip Message',wx.YES_NO|wx.ICON_QUESTION)
# if confirmDlg.ShowModal() == wx.YES:
self.SaveResult(newfile)
wx.MessageBox('Result has been saved at %s.'%newfile,'Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return def SaveResult(self,path):
f=open(path,'wb')
content=self.txtContent.Value
content=content.encode('utf-8')
lines=[]
while content.find('\n')!=-1:
line=content[:content.index('\n')+1]
lines.append(line)
content=content[content.index('\n')+1:]
f.writelines(lines)
f.close() def OnFind(self,event):
if not self.txtFile.GetValue() or not os.path.isdir(self.txtFile.GetValue()):
wx.MessageBox('please select a valid directory first.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return
self.dirSelected=self.txtFile.GetValue()
self.txtContent.SetValue('')
msg='Finding duplicated files in %s\n'%self.dirSelected
self.txtContent.SetValue(msg)
WorkerThread(self,self.dirSelected,'find',msg) def OnRemove(self,event):
if not self.txtFile.GetValue() or not os.path.isdir(self.txtFile.GetValue()):
wx.MessageBox('please select a valid directory first.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return
self.dirSelected=self.txtFile.GetValue()
self.txtContent.SetValue('')
msg='Removing duplicated files in %s\n'%self.dirSelected
self.txtContent.SetValue(msg)
WorkerThread(self,self.dirSelected,'remove',msg) def BtnStopHandler(self,event):
pass def SetButtons(self,status):
if status=='init':
self.btnOpen.Enable()
self.btnList.Disable()
self.btnRemove.Disable()
# self.btnStop.Disable()
elif status=='operating':
self.btnOpen.Disable()
self.btnList.Disable()
self.btnRemove.Disable()
# self.btnStop.Enable()
elif status=='completed':
self.btnOpen.Enable()
self.btnList.Enable()
self.btnRemove.Enable()
# self.btnStop.Disable()
elif status=='selected':
self.btnOpen.Enable()
self.btnList.Enable()
self.btnRemove.Enable()
# self.btnStop.Disable() if __name__=="__main__":
app=wx.App()
MyFrame().Show()
app.MainLoop()
WorkerThread.py:
# -*- coding: gbk -*- '''
Author:@DoNotSpyOnMe
Blog: http://www.cnblogs.com/aaronhoo
'''
from MyConfig import MyConfig
import threading
import math
import platform,os
import hashlib
from Utils import Utils class WorkerThread(threading.Thread):
def __init__(self,frame,dir,operation,msg):
"""初始化工作线程: 把主窗口传进来"""
threading.Thread.__init__(self)
self.frame = frame#传入主窗口,以便在线程中操作UI界面
self.dir=dir
self.operation=operation
self.LoadConfigFile()
self.msg=msg
self.setDaemon(True)#设置子线程随UI主线程结束而结束 self.MEGATOBYTENUMBER=math.pow(2,20)#将MB数转为字节数
# self.SetDirSplitor()
self.Utils=Utils()
self.start()
#----------------------------------------------------------------------
'''加载配置文件'''
def LoadConfigFile(self):
configFile='metaData/config.txt'
self.config=MyConfig(configFile) def run(self):
"""执行工作线程"""
self.frame.SetButtons('operating')
try:
if self.operation=='find':
self.listSameFile(self.dir)
self.frame.btnList.Enable()
elif self.operation=='remove':
self.removeSameFile(self.dir)
self.frame.btnRemove.Enable()
except Exception,e:
print e
finally:
self.frame.SetButtons('completed')
#
# def stop(self):
# self.keepRunning=False def appendMsg(self,msg):
if self.frame:
#以下方式可以实现终端式的刷新:自动滚动到最新行
self.frame.txtContent.AppendText(msg+'\n') '''Waring:disabled feature,not implemented'''
def filterHiddenFiles(self):
IsExcludeHiddenFiles=eval(self.config.get('IsExcludeHiddenFiles')) def filterFileTypes(self,files,FileTypeOption,selectedFileTypes):
fileCopy=[f for f in files]#fileCopy is a copy of files
if FileTypeOption=='Default':
pass
elif FileTypeOption=='Exclude':
for f in files:
ftype=self.Utils.getFileType(f)
if self.Utils.isInList(selectedFileTypes, ftype):
fileCopy.remove(f)
elif FileTypeOption=='Include':
for f in files:
ftype=self.Utils.getFileType(f)
if not self.Utils.isInList(selectedFileTypes, ftype):
fileCopy.remove(f)
del files
return fileCopy def getFileRange(self):
fileSizeStart=float(self.config.get('FileSizeStart'))*self.MEGATOBYTENUMBER
inputFileSizeEnd=self.config.get('FileSizeEnd')
if inputFileSizeEnd=='None':
fileSizeEnd=eval(self.config.get('MaxFileSize'))*self.MEGATOBYTENUMBER
else:
fileSizeEnd=float(inputFileSizeEnd)*self.MEGATOBYTENUMBER
return (fileSizeStart,fileSizeEnd) def isFileSizeInRange(self,fileSize,fileSizeStart,fileSizeEnd):
return fileSizeStart<=fileSize<=fileSizeEnd def findSameSizeFiles(self,files):
dicSize={}
fileTypeOption=self.config.get('FileTypeOption')
selectedFileTypes=self.config.get('SelectedFileTypes').split(';')
'''过滤文件类型'''
files=self.filterFileTypes(files,fileTypeOption,selectedFileTypes) tpFileRange=self.getFileRange() for f in files:
size=self.Utils.getFileSize(f)
'''过滤文件大小'''
if not self.isFileSizeInRange(size,tpFileRange[0],tpFileRange[1]):
continue
if not dicSize.has_key(size):
dicSize[size]=f
else:
dicSize[size]=dicSize[size]+';'+f
dicCopy=dicSize.copy()
for k in dicSize.iterkeys():
if dicSize[k].find(';')==-1:
dicCopy.pop(k)
del dicSize
return dicCopy def findSameMD5Files(self,files):
dicMD5={} for f in files:
self.appendMsg('calculating md5 value of file %s'%f)
md5=self.Utils.getFileMD5(f)
if not dicMD5.has_key(md5):
dicMD5[md5]=f
else:
dicMD5[md5]=dicMD5[md5]+';'+f
dicCopy=dicMD5.copy()
for k in dicMD5.iterkeys():
if dicMD5[k].find(';')==-1:
dicCopy.pop(k)
del dicMD5
return dicCopy def isFileTypeFilterWorking(self):
return eval(self.config.get('IsFileTypeFilterWorking')) def isFileSizeFilterWorking(self):
return eval(self.config.get('IsFileTypeFilterWorking')) def LoadFiltersInfo(self):
flag1=self.isFileTypeFilterWorking()
flag2=self.isFileSizeFilterWorking() if flag1 or flag2:
self.appendMsg('Tips:file filters is working:')
if flag1:
self.appendMsg("File Type Filter:")
option=self.config.get('FileTypeOption')
fileTypes=self.config.get('SelectedFileTypes')
self.appendMsg("%s:%s\n"%(option if option=='Exclude' else 'Include only',fileTypes))
if flag2:
self.appendMsg("File Size Filter:")
start=self.config.get('FileSizeStart')
end=self.config.get('FileSizeEnd')
self.appendMsg("%s MB - %s MB\n"%(start,end))
self.appendMsg('Start working...\n') def listSameFile(self,mydir):
msg=''
msgUniq='\nCongratulations,all files are unique.'
try:
existsFlag=False
files=self.Utils.getAllFiles(mydir)
self.appendMsg('%s files found in directory %s\n'%(len(files),mydir))
self.LoadFiltersInfo()
dicFileOfSameSize=self.findSameSizeFiles(files)
groupCount=0
if dicFileOfSameSize=={}:
self.appendMsg(msgUniq)
return
else:
for k in dicFileOfSameSize.iterkeys():
filesOfSameSize=dicFileOfSameSize[k].split(';')
dicSameMD5file=self.findSameMD5Files(filesOfSameSize)
if dicSameMD5file!={}:
existsFlag=True
for k in dicSameMD5file.iterkeys():
msg=msg+'md5 %s: %s'%(k,dicSameMD5file[k])+'\n'
groupCount+=1
if not existsFlag:
msg=msgUniq
else:
msg='\nDuplicated files:\n'+msg+'\nFound %s groups of duplicated files totally.'%groupCount
except Exception,e:
print e
msg='Exception occured.'
finally:
self.appendMsg(msg+'\n'+'\nOperation finished.') def removeSameFile(self,mydir):
msg=''
msgUniq='\nCongratulations,no file is removed since they are all unique.'
try:
existsFlag=False
files=self.Utils.getAllFiles(mydir)
self.appendMsg('%s files found in directory %s\n'%(len(files),mydir))
self.LoadFiltersInfo()
dicFileOfSameSize=self.findSameSizeFiles(files)
if not self.config.get('FileTypeOption')=='Default':
self.appendMsg('Tips:file filters is working.\n')
if dicFileOfSameSize=={}:
self.appendMsg(msgUniq)
return
else:
#list the duplicated files first:
self.appendMsg('Finding duplicted files:')
dicFiltered={}
groupCount=0
for k in dicFileOfSameSize.iterkeys():
filesOfSameSize=dicFileOfSameSize[k].split(';')
dicSameMD5file=self.findSameMD5Files(filesOfSameSize)
if dicSameMD5file!={}:
existsFlag=True
for k in dicSameMD5file.iterkeys():
msg=msg+'md5 %s: %s'%(k,dicSameMD5file[k])+'\n'
groupCount+=1
dicFiltered[k]=dicSameMD5file[k]
if not existsFlag:
self.appendMsg(msgUniq)
return
else:
#then remove the duplicated files:
self.appendMsg('\nDuplicated files:\n'+msg+'\nFound %s groups of duplicated files totally.'%groupCount)
removeCount=0
for k in dicFiltered.iterkeys():
sameFiles=dicFiltered[k].split(';')
flagRemove=False
for f in sameFiles:
if not flagRemove:
flagRemove=True
else:
self.appendMsg('Removing file: %s'%f)
os.remove(f)
removeCount=removeCount+1
self.appendMsg('\n%s files are removed.\n'%removeCount)
except Exception,e:
print e
msg='\nException occured.'
self.appendMsg(msg)
finally:
self.appendMsg('\n\nOperation finished.')
Dialogs.py:
# -*- coding: gbk -*- '''
Author:@DoNotSpyOnMe
Blog: http://www.cnblogs.com/aaronhoo
'''
import wx
from wx.html import HtmlWindow
from MyConfig import MyConfig
import re
from Utils import Utils class DialogSetFilters(wx.Dialog):
def __init__(self, frame):
wx.Dialog.__init__(self, frame, -1,'Filters',size=(560, 560) )
self.frame=frame
self.LoadConfigFile()
self.LoadControls()
self.InitWithConfigData()
#为控件绑定事件
self.BindControlEvent()
self.Utils=Utils() '''加载配置文件'''
def LoadConfigFile(self):
configFile='metaData/config.txt'
self.config=MyConfig(configFile) def LoadControls(self):
panel = wx.Panel(self)
sizer = wx.GridBagSizer(5,5)#预计需要5行5列,但实际可以超出该设定,GridGagSizer会自动增长
'''加载隐藏文件过滤器的控件'''
sb = wx.StaticBox(panel, label="Hidden File Filter",size=(500,100))
boxsizer = wx.StaticBoxSizer(sb, wx.HORIZONTAL)
self.cbExclHidden=wx.CheckBox(panel, label="Exclude hidden files(Waring:disabled feature)")
boxsizer.Add((10,10),flag=wx.BOTTOM|wx.TOP,border=10)
boxsizer.Add(self.cbExclHidden,flag=wx.BOTTOM|wx.TOP,border=10)
sizer.Add(boxsizer, pos=(0,0),span=(1,5),flag=wx.LEFT|wx.TOP,border=25)
#pos(0,0)表示位置在第1行,第1列,span=(1,5)表示该boxsizer占据1个行5个列的空间 '''加载文件类型过滤器的控件'''
sb = wx.StaticBox(panel, label="File Type Filter",size=(500,500))
boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
self.rdDefault=wx.RadioButton(panel,-1,'Default',style=wx.RB_GROUP)
self.rdExclFtyp=wx.RadioButton(panel,-1,'Exclude')
self.rdInclFtyp=wx.RadioButton(panel,-1,'Include Only')
subHbox=wx.BoxSizer(wx.HORIZONTAL)
subHbox.Add(self.rdDefault,flag=wx.LEFT,border=25)
subHbox.Add(self.rdExclFtyp,flag=wx.LEFT,border=5)
subHbox.Add(self.rdInclFtyp,flag=wx.LEFT,border=5)
boxsizer.Add(subHbox) '''从配置文件读固定配置'''
self.cbImage=wx.CheckBox(panel, label="Images("+self.config.get('Images')+")")
self.cbAudio=wx.CheckBox(panel, label="Audios("+self.config.get('Audios')+")")
self.cbVideo=wx.CheckBox(panel, label="Videos("+self.config.get('Videos')+")")
self.cbDoc=wx.CheckBox(panel, label="Documents("+self.config.get('Documents')+")") self.lblOtherFiles=wx.StaticText(panel,label="Other files ( enter file extensions,split them with \";\" )")
self.txtOtherFiles=wx.TextCtrl(panel,size=(400,25))
subVbox=wx.BoxSizer(wx.VERTICAL)
subVbox.Add(self.cbImage,flag=wx.LEFT|wx.TOP,border=10)
subVbox.Add(self.cbAudio,flag=wx.LEFT|wx.TOP,border=10)
subVbox.Add(self.cbVideo,flag=wx.LEFT|wx.TOP,border=10)
subVbox.Add(self.cbDoc,flag=wx.LEFT|wx.TOP,border=10)
subVbox.Add(self.lblOtherFiles,flag=wx.LEFT|wx.TOP,border=10)
subVbox.Add(self.txtOtherFiles,flag=wx.LEFT|wx.TOP|wx.BOTTOM|wx.RIGHT,border=10)
boxsizer.Add(subVbox)
sizer.Add(boxsizer, pos=(1,0), span=(6,5),flag=wx.TOP|wx.LEFT,border=25)
#pos(1,0)表示位置在第2行,第1列,span=(1,5)表示该boxsizer占据6个行5个列的空间 self.cbExclHidden.Disable() '''加载文件大小过滤器的控件'''
sb = wx.StaticBox(panel, label="File Size Filter",size=(500,200))
boxsizer = wx.StaticBoxSizer(sb, wx.HORIZONTAL)
self.txtFileSize1=wx.TextCtrl(panel,size=(100,25))
lblFileSize1 = wx.StaticText(panel,label="MB")
lblTo = wx.StaticText(panel,label="--")
self.txtFileSize2=wx.TextCtrl(panel,size=(100,25))
lblFileSize2 = wx.StaticText(panel,label="MB")
boxsizer.Add(self.txtFileSize1,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
boxsizer.Add(lblFileSize1,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
boxsizer.Add((20,10))
boxsizer.Add(lblTo,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
boxsizer.Add((20,10))
boxsizer.Add(self.txtFileSize2,flag=wx.LEFT|wx.BOTTOM|wx.TOP,border=10)
boxsizer.Add(lblFileSize2,flag=wx.LEFT|wx.BOTTOM|wx.TOP|wx.EXPAND,border=10)
sizer.Add(boxsizer, pos=(7,0),span=(1, 5),flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT , border=25)#pos=(7,0)
#pos等于(7,0)表示位置在第8行,第1列,之所以是第8行,因为前面两个控件已经占据了1+6=7行,所以至少从第8行开始,否则布局错乱 '''加载按钮'''
self.btnOK = wx.Button(panel, label="OK")
self.btnCancel = wx.Button(panel, label="Cancel")
sizer.Add(self.btnOK, pos=(9,3))
sizer.Add(self.btnCancel, pos=(9,4),flag=wx.BOTTOM|wx.RIGHT, border=5) panel.SetSizer(sizer)
panel.Layout() def InitWithConfigData(self):
self.cbExclHidden.SetValue(str(self.config.get('IsExcludeHiddenFiles'))=='True') self.rdDefault.SetValue(self.config.get('FileTypeOption')=='Default')
if self.rdDefault.GetValue():
self.DisableFileTypeFilterControls() self.rdExclFtyp.SetValue(self.config.get('FileTypeOption')=='Exclude')
self.rdInclFtyp.SetValue(self.config.get('FileTypeOption')=='Include') self.cbImage.SetValue(str(self.config.get('IsImagesSelected'))=='True')
self.cbAudio.SetValue(str(self.config.get('IsAudiosSelected'))=='True')
self.cbVideo.SetValue(str(self.config.get('IsVideosSelected'))=='True')
self.cbDoc.SetValue(str(self.config.get('IsDocumentsSelected'))=='True')
self.txtOtherFiles.SetValue('' if self.config.get('OtherFilesSelected')=='None' else self.config.get('OtherFilesSelected')) self.txtFileSize1.SetValue(self.config.get('FileSizeStart'))
inputFileSizeEnd=self.config.get('FileSizeEnd')
if inputFileSizeEnd=='None':
self.txtFileSize2.SetValue('')
else:
self.txtFileSize2.SetValue(inputFileSizeEnd) def BindControlEvent(self):
for eachRadio in [self.rdDefault, self.rdExclFtyp, self.rdInclFtyp]:#绑定事件
self.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, eachRadio)
self.Bind(wx.EVT_BUTTON, self.OnOK,self.btnOK)
self.Bind(wx.EVT_BUTTON, self.OnCancel,self.btnCancel) def ValidateInput(self): if not self.rdDefault.GetValue():
pattern=re.compile('[^\d\w\.\;]+')#数字、英文字母、点号之外的字符定义为非法字符
if re.search(pattern,self.txtOtherFiles.GetValue()):
wx.MessageBox('Please enter valid file extensions and correct splitor(;).','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return False
if (self.rdInclFtyp.GetValue() or self.rdExclFtyp.GetValue()) and self.getSelectedFileTypes()=='':
wx.MessageBox('Please enter or select at least one file type.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return False
if self.txtFileSize1.GetValue()!='' and not self.Utils.isNumReg(self.txtFileSize1.GetValue()):
wx.MessageBox('Please do not enter or enter valid number in the first file size.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return False
elif self.txtFileSize2.GetValue()!='' and not self.Utils.isNumReg(self.txtFileSize2.GetValue()):
wx.MessageBox('Please do not enter or enter valid number in the second file size.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return False
elif self.Utils.isNumReg(self.txtFileSize1.GetValue()) and self.Utils.isNumReg(self.txtFileSize2.GetValue()) and eval(self.txtFileSize1.GetValue())>eval(self.txtFileSize2.GetValue()):
wx.MessageBox('The first file size should not be greater than the second file size.','Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return False
elif self.Utils.isNumReg(self.txtFileSize2.GetValue()) and eval(self.txtFileSize2.GetValue())>(self.config.get('MaxFileSize')):
wx.MessageBox('The second file size should not be greater than '+self.config.get('MaxFileSize'),'Tip Message',wx.YES_DEFAULT|wx.ICON_INFORMATION)
return False
return True def getSelectedFileTypes(self):
selectedFileTypes=''
if not self.rdDefault.GetValue():
fileType=''
for chk in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc]:
if chk.IsChecked():
label=chk.GetLabel()
fileType=fileType+label[label.find('(')+1:label.find(')')]+';'
selectedFileTypes=fileType selectedFileTypes+=self.Utils.FormatOtherFileTypes(self.txtOtherFiles.GetValue())
selectedFileTypes= selectedFileTypes.replace('/',';').lower()
return selectedFileTypes def OnOK(self,event):
if self.ValidateInput():
IsExclHiddenFiles=self.cbExclHidden.GetValue()
if self.rdDefault.GetValue():
FileTypeOption='Default'
elif self.rdExclFtyp.GetValue():
FileTypeOption='Exclude'
elif self.rdInclFtyp.GetValue():
FileTypeOption='Include' selectedFileTypes=self.getSelectedFileTypes()
if selectedFileTypes=='':
self.config.set('SelectedFileTypes','None')
else:
self.config.set('SelectedFileTypes',selectedFileTypes) '''save filters into the config file'''
self.config.set('IsExcludeHiddenFiles',IsExclHiddenFiles)
self.config.set('FileTypeOption',FileTypeOption)
self.config.set('IsImagesSelected',self.cbImage.IsChecked())
self.config.set('IsAudiosSelected',self.cbAudio.IsChecked())
self.config.set('IsVideosSelected',self.cbVideo.IsChecked())
self.config.set('IsDocumentsSelected',self.cbDoc.IsChecked()) otherFileTyps=self.txtOtherFiles.GetValue()
if otherFileTyps=='':
self.config.set('OtherFilesSelected','None')
else:
self.config.set('OtherFilesSelected',self.Utils.FormatOtherFileTypes(self.txtOtherFiles.GetValue())) fileSizeStart=self.txtFileSize1.GetValue()
fileSizeEnd=self.txtFileSize2.GetValue()
if fileSizeStart!='':
self.config.set('FileSizeStart',fileSizeStart) if fileSizeEnd=='':
self.config.set('FileSizeEnd','None')
else:
self.config.set('FileSizeEnd',fileSizeEnd) if FileTypeOption=='Default':
self.config.set('IsFileTypeFilterWorking','False')
else:
self.config.set('IsFileTypeFilterWorking','True') if fileSizeStart=='' or fileSizeEnd!=self.config.get('MaxFileSize'):
self.config.set('IsFileSizeFilterWorking','True')
else:
self.config.set('IsFileSizeFilterWorking','False')
self.config.saveConfig()
self.Close() def OnCancel(self,event):
self.Close() def OnRadio(self,event):
selectedRadio=event.GetEventObject()
if selectedRadio==self.rdDefault:
self.DisableFileTypeFilterControls()
else:
self.EnableFileTypeFilterControls() def DisableFileTypeFilterControls(self):
for control in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc,self.txtOtherFiles,self.lblOtherFiles]:
control.Disable()
for control in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc]:
control.SetValue(False)
self.txtOtherFiles.SetValue('') def EnableFileTypeFilterControls(self):
for control in [self.cbImage,self.cbAudio,self.cbVideo,self.cbDoc,self.txtOtherFiles,self.lblOtherFiles]:
control.Enable() class DialogAboutApp(wx.Dialog):
text = '''
<html>
<head>
<style type="text/css">
body {font-family:serif;background-color: #ACAA60;}
</style>
</head>
<body> <!--bgcolor="#ACAA60"-->
<center>
<table bgcolor="#455481" width="100%" cellspacing="0" cellpadding="0" border="1">
<tr>
<td align="center"><h1><span style="color:white">UNIQ File-wxPython</span></h1></td>
</tr>
</table>
</center>
<p><b>UNIQ File-wxPython</b> is a program designed for clearing duplicated files and saving
memory space.
It's broght to you by Aaron Hu(<b><span style="color:red">@DoNotSpyOnMe</span></b> on Sina Weibo).<br/> <!--<a href="http://weibo.com/u/1737184870" target="_blank">Contact</a> the author now.-->
</p>
</body>
</html>
'''
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1,'About this app',
size=(440, 400) )
window = HtmlWindow(self)
window.SetPage(self.text)
button = wx.Button(self, wx.ID_OK, 'OK')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(window, 1, wx.EXPAND|wx.ALL, 5)
sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5)
self.SetSizer(sizer)
self.Layout() class DialogAboutAuthor(wx.Dialog):
text = '''
<html>
<head>
<style type="text/css">
body {font-family:serif;background-color: #ACAA60;}
</style>
</head>
<body> <!--bgcolor="#ACAA60"-->
<center>
<table bgcolor="#455481" width="100%" cellspacing="0" cellpadding="0" border="1">
<tr>
<td align="center"><h1><span style="color:white">UNIQ File-wxPython</span></h1></td>
</tr>
</table>
</center>
<p>
Contact the author at:<br/><br/>
Sina Weibo:<b><span style="color:red">@DoNotSpyOnMe</span></b><br/>
cnblogs.com:<b>http://www.cnblogs.cn/aaronhoo</b><br/>
</p>
</body>
</html>
'''
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1,'About the author',
size=(440, 400) )
window = HtmlWindow(self)
window.SetPage(self.text)
button = wx.Button(self, wx.ID_OK, 'OK')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(window, 1, wx.EXPAND|wx.ALL, 5)
sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5)
self.SetSizer(sizer)
self.Layout()
MyConfig.py:
# -*- coding: gbk -*-
'''
Created on 2016年4月20日 Author: @DoNotSpyOnMe
'''
import re,os class MyConfig:
def __init__(self,filepath):
self.configFile=''
self.lines=[]
self.read(filepath) def read(self,filepath):
try:
if not os.path.isfile(filepath):
print 'Error:file %s dose not exist.'%filepath
return
pattern=re.compile('^.+\.txt$')
if not re.search(pattern, filepath):
print 'Error:only .txt file is supported as config file.'
return
f=open(filepath,'r')
self.lines=f.readlines()
f.close()
self.configFile=filepath
except Exception,e:
print e def get(self,variable):
try:
if self.validate():
pattern=re.compile('^'+variable+' = ')
for line in self.lines:
if re.search(pattern,line):
val= line[line.find(' = ')+3:].strip()
return val
print "Error:variable '%s' is not found in config file."%variable
except Exception,e:
print e def set(self,variable,newValue):
try:
if self.validate():
count=0
pattern=re.compile('^'+variable+' = ')
for line in self.lines:
if re.search(pattern,line):
newline=line.replace(line[line.find(' = ')+3:line.rfind('\n')],str(newValue))
self.lines[count]=newline
return
count=count+1
#if the variable is no found,create it.
newline=variable+' = '+str(newValue)
self.lines.append(newline+'\n')
except Exception,e:
print e def setAndSave(self,variable,newValue):
try:
if self.validate():
count=0
found=False
pattern=re.compile('^'+variable+' = ')
for line in self.lines:
if re.search(pattern,line):
newline=line.replace(line[line.find(' = ')+3:line.rfind('\n')],str(newValue))
self.lines[count]=newline
found=True
break
count=count+1
if not found:
newline=variable+' = '+str(newValue)
self.lines.append(newline+'\n')
#save changes.
f=open(self.configFile,'w')
f.writelines(self.lines)
f.close()
except Exception,e:
print e def saveConfig(self):
try:
if self.validate():
f=open(self.configFile,'w')
f.writelines(self.lines)
f.close()
except Exception,e:
print e def validate(self):
if not self.configFile:
print 'Error:config file not found.'
return False
return True
Utils.py:
# -*- coding: gbk -*-
import hashlib
import os
import platform
import re class Utils:
def __init__(self):
self.getDirSplitor() def isNumReg(self,str):
regInt='^0$|^[1-9]\d*$'#不接受09这样的为整数
regFloat='^0\.\d+$|^[1-9]\d*\.\d+$'
regIntOrFloat=regInt+'|'+regFloat#整数或小数
patternIntOrFloat=re.compile(regIntOrFloat)#创建pattern对象,以便后续可以复用
if re.search(regIntOrFloat,str):
return True
else:
return False '''使得用户在其他文件类型框中可以输入'.xml;.html'或者'xml;html;'或者'xml;.html'这三种格式'''
def FormatOtherFileTypes(self,fileTypeInput):
ls=fileTypeInput.split(';')
lsnew=[]
for e in ls:
if e!='':
if e.find('.')==-1:
lsnew.append('.'+e)
else:
lsnew.append(e)
s=''
for e in lsnew:
s=s+e+';'
return s def divideList(self,ls,each):
dividedLs=[]
eachExact=float(each)
groupCount=len(ls)/each
groupCountExact=len(ls)/eachExact
start=0
for i in xrange(groupCount):
dividedLs.append(ls[start:start+each])
start=start+each
if groupCount<groupCountExact:#假如有余数,将剩余的所有元素加入到最后一个分组
dividedLs.append(ls[groupCount*each:])
return dividedLs def getFileSize(self,filePath):
return os.path.getsize(filePath) ''' 一般文件的md5计算方法,一次读取文件的全部内容'''
def CalcMD5(filepath):
with open(filepath,'rb') as f:
md5obj = hashlib.md5()
md5obj.update(f.read())
hash = md5obj.hexdigest()
return hash
'''大文件计算md5的方法,分批读取文件内容,防止内存爆掉'''
def getFileMD5(self,filename):
if not os.path.isfile(filename):
return
myhash = hashlib.md5()
f = open(filename,'rb')
while True:
b = f.read(8*1024)
if not b :
break
myhash.update(b)
f.close()
return myhash.hexdigest() def getAllFiles(self,directory):
files=[]
# dirSplitor=getDirSplitor()
for dirpath, dirnames,filenames in os.walk(directory):
if filenames!=[]:
for file in filenames:
files.append(dirpath+self.dirSplitor+file)
files.sort(key=len)#按照文件名的长度排序
return files def isInList(self,myList,someValue):
try:
myList.index(someValue)
return True
except:
return False def getFileType(self,file):
# dirSplitor=getDirSplitor()
fileName=file[file.rfind(self.dirSplitor)+1:]
fileType=file[file.rfind('.'):]
return fileType.lower() def getDirSplitor(self):
osType=platform.system()
if osType=='Windows':
self.dirSplitor= '\\'
elif osType=='Linux':
self.dirSplitor= '/'
else:
self.dirSplitor= '/'