开发背景:手机上的文件越来越多,想要备份下文件结果文件太多杂乱无章且重复文件冗余存储造成存储空间滥发,所以决定开发一个小工具来整理重复文件。由于本人电脑运行慢,选择VC++6.0作为开发IDE,操作虽然麻烦点,但是编译效率高,编译出来的软件兼容性好,编译的可执行文件体积小运行效率高,这个优点非常好。
设计思路:1、文件扫描基于Win API实现;2、文件对比使用运行效率较高的MD5做特征识别;3、文件记录分析存储使用轻量级别的Sqlite;
准备工作:
(1)VC++6.0移植openssl库的MD5算法
https://blog.youkuaiyun.com/qwestw/article/details/138806623
(2)VC++6.0 Sqlite3调用例子
https://blog.youkuaiyun.com/qwestw/article/details/138901027
(3)VC++6.0 常用的文件对话框和目录选择对话框
https://blog.youkuaiyun.com/qwestw/article/details/138911837
(4)VC++6.0 ListViewReport报表使用例子
https://blog.youkuaiyun.com/qwestw/article/details/138914699
(5)VC++6.0自定义实现日志记录到文件及界面显示
https://blog.youkuaiyun.com/qwestw/article/details/138924562
(6)线程
(7)List
算法实现:
(1)开启一个线程,通过遍历文件路径,查找所有文件,并对文件MD5编码
(2)每次找到一个文件后在全局的list列表中查找重复的文件,并统计数据
(3)统计好数据后显示到ListView控件,如果需要移动文件则在这里一并操作,使用Rename操作
核心代码实现:
#include "stdafx.h"
#include "CController.h"
#include "duplicatefilesclearDlg.h"
#include "openssl_md5.h"
static int iLevel= 0;
BYTE *pbyFileBuf = NULL;
CController::CController(CWnd *pDlg)
{
m_pDlg = pDlg;
g_bWorkThreadSwitch = true;
#if 1
ThreadWorkHandle = CreateThread (NULL,NULL,HidWorkThreadProc,this,0,0);
LogWriteOneRecord("CString(__FUNCTION__)","[I] "," CController 构造函数...");
#else
//E:\data\手机备份\035 honor30\IMG_20210812_124144.jpg
clock_t start_time=clock();
pbyFileBuf = (BYTE*)malloc(constIMd5FileMaxLen);
if(NULL == pbyFileBuf)
{
LogWriteOneRecord("CString(__FUNCTION__)","[error] "," (BYTE*)malloc(status.m_size) error!");
return;
}
for (int iLoop=0;iLoop<100;iLoop++)
{
CreateFileBean("E:\\data\\手机备份\\035 honor30\\IMG_20210812_124144.jpg","IMG_20210812_124144.jpg",1);
}
TRACE("for (int iLoop=0;iLoop<100;iLoop++)计算md5 spend time =%d\r\n",clock()-start_time);
#endif
#if 0
CString strMd5="";
CString strMsg;
const unsigned char Data[5] = "abcd"; //待计算md5的数据,若要计算文件的md5,则Data为从文件中读取的数据。
unsigned char md[MD5_DIGEST_LENGTH] = { 0 }; //md5存储的变量
unsigned char* P = MD5(Data, strlen((const char*)Data), md); //计算md5
for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { //输出md5
strMd5.Format("%02x-", *(md + i));
strMsg+=strMd5;
}
LogWriteOneRecord("CString(__FUNCTION__)MD5","[I] ",strMsg);
#endif
}
void CController::ExitCController()
{
g_bWorkThreadSwitch = false;
::TerminateThread(ThreadWorkHandle,0);
}
CController::~CController()
{
}
CString CController::GetAppPath()
{
char szLongPathName[_MAX_PATH];
GetModuleFileName(NULL, szLongPathName, _MAX_PATH);
CString strRunDir = szLongPathName;
int nPos = strRunDir.ReverseFind('\\');
if(nPos != -1)
{
strRunDir = strRunDir.Left(nPos+1);
}
return strRunDir;
}
CString CController::GetLogFileName()
{
//Get Run-directory
if("" == g_strLogFileName)
{
char szLongPathName[_MAX_PATH];
GetModuleFileName(NULL, szLongPathName, _MAX_PATH);
CString strRunDir = szLongPathName;
int nPos = strRunDir.ReverseFind('\\');
if(nPos != -1)
{
strRunDir = strRunDir.Left(nPos+1);
}
if(strRunDir.IsEmpty())
{
char szPath[_MAX_PATH];
GetCurrentDirectory(_MAX_PATH,szPath);
strcat(szPath,"\\");
strRunDir=szPath;
}
strRunDir += "Log";
if(!IsDirectoryExists(strRunDir))
{
CreateDirectory(strRunDir// pointer to directory path string
,NULL // pointer to security descriptor
);
}
g_strLogFileName = strRunDir + "\\runLog.log";
}
return g_strLogFileName;
}
void CController::LogWriteOneRecord(CString strFunction,CString strType,CString strText)
{
CDuplicatefilesclearDlg *pDlg = (CDuplicatefilesclearDlg*)m_pDlg;
//LogWriteRecord("["+ CTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S")+"]"+strText,GetLogFileName());
TRACE(strText);
TRACE("\r\n");
m_strWriteText=("["+ CTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S")+"]"+strFunction+"() "+strType+strText);
LogWriteRecord(m_strWriteText,GetLogFileName());
//显示到界面
pDlg->AppendLog(m_strWriteText);
}
void CController::LogWriteRecord(CString strText,CString strFileName)
{
CStdioFile csFile;
try
{
BOOL bRet = csFile.Open(strFileName,CFile::modeCreate
|CFile::modeNoTruncate
|CFile::modeReadWrite|CFile::shareDenyNone|CFile::typeText);
csFile.SeekToEnd();
CString strWriteText=strText+"\r\n";
csFile.WriteString(strWriteText);
//csFile.Write(strWriteText.GetBuffer(strWriteText.GetLength()),1);
strWriteText.ReleaseBuffer();
DWORD64 dwFileLen = csFile.GetLength();
if(!bRet)
{
TRACE("Open failed exception\r\n");
}
if(dwFileLen > constIFileMaxLen)
{
csFile.Seek(-constIFileMaxLen,CFile::end);
char *pcBuf = new char[constIFileMaxLen];
memset(pcBuf,0,constIFileMaxLen);
csFile.Read(pcBuf,constIFileMaxLen);
int iLoop = 0;
for( iLoop = 0;iLoop<constIFileMaxLen;iLoop++)
{
if(0x0a == pcBuf[iLoop])
{
break;
}
}
iLoop++;
csFile.SeekToBegin();
csFile.Write(pcBuf+iLoop,constIFileMaxLen-iLoop);
csFile.SetLength((DWORD)constIFileMaxLen-iLoop);
delete [] pcBuf;
pcBuf = NULL;
}
csFile.Flush();
csFile.Close();
}
catch (...)
{
csFile.Close();
TRACE("Write log exception\r\n");
}
}
bool CController::IsDirectoryExists(CString const& path)
{ //判断是否存在
WIN32_FIND_DATA findFileData;
HANDLE hFind = ::FindFirstFile(path, &findFileData);
if (INVALID_HANDLE_VALUE == hFind)
{
return FALSE;
}/*else
{
CloseHandle(hFind);
}*/
return true;
}
static DWORD WINAPI HidWorkThreadProc(LPVOID lpParameter)
{
if(NULL != lpParameter)
{
return ((CController*)lpParameter)->HidWorkRunning();
}
return -1;
}
DWORD CController::HidWorkRunning()//工作运行
{
//CSFPDevHostAppDlg *pDlg = (CSFPDevHostAppDlg*)m_pDlg;
LogWriteOneRecord("CString(__FUNCTION__)","[I] "," HidWorkRunning()线程启动...");
CString strPath;
(CDuplicatefilesclearDlg*)m_pDlg->GetDlgItemText(IDC_EDIT_Path,strPath);
CString strMovePath;
(CDuplicatefilesclearDlg*)m_pDlg->GetDlgItemText(IDC_EDIT_Move_Path,strMovePath);
if(!IsDirectoryExists(strPath))
{
CreateDirectory(strPath,NULL);
}
iLevel = 0;
pbyFileBuf = (BYTE*)malloc(constIMd5FileMaxLen);
if(NULL == pbyFileBuf)
{
LogWriteOneRecord("CString(__FUNCTION__)","[error] "," (BYTE*)malloc(status.m_size) error!");
return NULL;
}
// CreateFileBean("C:\\Users\\stw\\dir_test\\1.txt","1.txt",1);
SanFiles(strPath);
free(pbyFileBuf);
pbyFileBuf =NULL;
//showListView
CDuplicatefilesclearDlg *pDlg = (CDuplicatefilesclearDlg*)m_pDlg;
//显示到界面
BOOL chbMove = pDlg->IsDlgButtonChecked(IDC_CHECK_Move);
list<CFileBean*>::iterator it;
CFileBean* pFb = NULL;
CString strMoveName;
for(it = g_listFileBeans.begin();it != g_listFileBeans.end();it++)
{
pFb =*it;
if(chbMove && -1 !=pFb->iRepeatId)
{
strMoveName.Format("%s\\(%d)%s",strMovePath,pFb->iRepeatCount,pFb->strRepeatName);
// 确保目标目录存在
if (rename(pFb->strFileFullName, strMoveName) != 0)
{
g_strPrintText.Format("error rename(pFb->strFileFullName, strMoveName) %s ->%s",pFb->strFileFullName, strMoveName);
LogWriteOneRecord("::HidWorkRunning()","[Error] ",g_strPrintText);
}
}
pDlg->AppendListView(pFb->iId,pFb);
}
((CDuplicatefilesclearDlg*)m_pDlg)->SetDlgItemText(IDC_STATIC_Bottom,"扫描完成!");
((CDuplicatefilesclearDlg*)m_pDlg)->SetDlgItemText(IDC_BUTTON_Clear,"结束");
return -1;
}
BOOL CController::SanFiles(CString strFileDir)
{
iLevel++;
//导入bin文件
CFileFind file;
CString strFileFind = strFileDir+"\\*.*";
BOOL nContinue = file.FindFile(strFileFind);
if(!nContinue)
{
//strErrMsg="路径错误!";
return FALSE;
}
CString fileName;
CString filePath;
while(nContinue)
{
nContinue = file.FindNextFile();
filePath = file.GetFilePath();
if(file.IsDots())
{
continue;
}
else if(file.IsDirectory())
{
TRACE("level=%d Dir=%s Find a Dir\r\n",iLevel,filePath);
if(iLevel==1 && filePath.Find(constDuplicateFileDir,filePath.GetLength()-strlen(constDuplicateFileDir)-1) >=0 )
{
TRACE("level=%d Dir=duplicate_files skip。。。。 \r\n",iLevel);
}else
{
SanFiles(filePath);
iLevel--;
}
}else
{
fileName = file.GetFileName();
TRACE("level=%d Dir=%s Find a file =%s\r\n",iLevel,filePath,fileName);
CreateFileBean(filePath,fileName,iLevel);
}
}
return TRUE;
}
CFileBean* CController::CreateFileBean(CString strFileFullName,CString strFileName,int iLevel)
{
CFileBean* pFileBean = NULL ;
CFile cfile;
if (!cfile.Open(strFileFullName, CFile::modeRead))
{
// 打开文件失败
LogWriteOneRecord("CString(__FUNCTION__)","[error] "," cfile.Open(strFileFullName, CFile::modeRead) error!");
return NULL;
}
// 获取文件大小
//DWORD dwSize = cfile.GetLength();
CFileStatus status;
cfile.GetStatus(status); //获取文件的状态
#if 0
//根据需要 获取年、月、日、时、分、秒
CString strCreateTime = status.m_ctime.Format("%Y-%m-%d %H:%M:%S"); //创建时间
CString strModifyTime = status.m_mtime.Format("%Y-%m-%d %H:%M-%S"); //修改时间
//额外的方法 大小、文件名称、文件状态
TRACE("大小:%d\r\n", status.m_size);
TRACE("文件名称:%s\r\n", status.m_szFullName);
TRACE("CFileStatus:%d\r\n", sizeof(CFileStatus));
#endif
unsigned char md[MD5_DIGEST_LENGTH] = { 0 }; //md5存储的变量
//获取MD5值
if(status.m_size<constIMd5FileMaxLen && status.m_size>0)//小于100M时计算MD5值
{
// BYTE *pbyFileBuf = (BYTE*)malloc(status.m_size);
if(NULL == pbyFileBuf)
{
LogWriteOneRecord("CString(__FUNCTION__)","[error] "," (BYTE*)malloc(status.m_size) error!");
return NULL;
}
clock_t start_time=clock(), end_time;
if(!cfile.Read(pbyFileBuf,status.m_size))
{
LogWriteOneRecord("CString(__FUNCTION__)","[error] "," cfile.Read(pbyFileBuf,status.m_size) error!");
return NULL;
}
end_time =clock();
TRACE("cfile.Read( spend time =%d\r\n",end_time-start_time);
unsigned char* P = MD5(pbyFileBuf, status.m_size, md); //计算md5
TRACE("计算md5 spend time =%d\r\n",clock()-end_time);
#if 1
//create a new file bean
pFileBean = new CFileBean(g_listFileBeans.size(),strFileFullName,strFileName,status.m_size,md,iLevel,status.m_ctime,status.m_mtime,0);
AddAFileBean(pFileBean);
#endif
}else
{
TRACE("大小:%d MB超过100M不计算MD5值\r\n", status.m_size/1024/1024);
}
// 关闭文件
cfile.Close();
return NULL;
}
BOOL CController::AddAFileBean(CFileBean *pbean)
{
//添加到list
//计算重复 BYTE byRepeat; //int iRepeatCount;
list<CFileBean*>::iterator it;
for(it = g_listFileBeans.begin();it != g_listFileBeans.end();it++)
{
CFileBean* pFb =*it;
if(pFb->lSize == pbean->lSize && 0 == memcmp(pFb->byMd5,pbean->byMd5,MD5_DIGEST_LENGTH))
{
//计算重复 BYTE byRepeat; //int iRepeatCount;
pFb->iRepeatCount++;
pbean->iRepeatCount = pFb->iRepeatCount;
pbean->iRepeatId=pFb->iId;
pbean->strRepeatName = pFb->strFileName;
break;
}
}
g_listFileBeans.push_back(pbean);
//BYTE byMoved;
g_strPrintText.Format("scan(%d):%s",g_listFileBeans.size(),pbean->strFileFullName);
((CDuplicatefilesclearDlg*)m_pDlg)->SetDlgItemText(IDC_STATIC_Bottom,g_strPrintText);
return TRUE;
}
工具成品:
资源下载地址:https://download.youkuaiyun.com/download/qwestw/89318559?spm=1001.2014.3001.5503
自己的需求解决了,可以使用起来,Release版本用的是静态MFC库,编译大小160K,压缩后71K,如果exe压个壳应该也在80~90KB,这才是我想要的。