程序的主体框架如下列代码所示:
//by btwsmile #include "stdafx.h" #include "juice.h" // entry int _tmain(int argc, _TCHAR* argv[]) { CJuice juicer(argc, argv); juicer.Process(); return 0; }
不难看出,真正进行处理的是CJuice类对象juicer。CJuice类定义在juice.h头文件中,它包含的成员变量有:
PTSTR m_pszWrongSyntax; PTSTR m_pszInvalidPath; PTSTR m_pszHelp; int m_argc; PTSTR* m_argv; // complete delete BOOL m_bComplete; // report DWORD m_dwFileCount; DWORD m_dwFolderCount; __int64 m_nFileSize; DWORD m_dwMilliseconds; // enum type enum { VSJT_WRONGSYNTAX = 1, VSJT_INVALIDPATH, VSJT_QUERYHELP, VSJT_JUICE };
前3个变量是字符串指针对象,它们是输出到屏幕上的提示信息。比如,当用户键入的命令有错误,则输出m_pszWrongSyntax提示语法错误。
m_argc和m_argv由main函数指定,它来自用户的输入。比如,用户键入命令vsj /help,此时m_argc = 2,而m_argv含两个字符串,即"vsj"和"/help"。
bool变量m_bComplete表示是否彻底删除文件和目录,缺省为FALSE,只有用户键入命令时使用了/C开关才将它置为TRUE。彻底删除的文件和目录不会进入回收站。
随后的4个整数变量是一些运行统计数据,依次表示:删除的文件计数、删除的目录计数、节省的磁盘空间以及操作所用的时间。
最后是一个匿名枚举,表示用户键入命令的意图。
CJuice类首先要实现自己的构造方法,在构造方法中初始化成员变量。
// constructor CJuice(int argc, PTSTR* argv) : m_argc(argc), m_argv(argv), m_bComplete(FALSE), m_dwFileCount(0), m_dwFolderCount(0), m_nFileSize(0), m_dwMilliseconds(0) { m_pszWrongSyntax = _T("Command syntax is incorrect."); m_pszInvalidPath = _T("Invalid directory path."); m_pszHelp = _T("\nVisual Studio Juicer (c)2012 by btwsmile") _T("\nDelete insignificant files and directories of visual studio solutions.") _T("\n\nVSJ path [/C]") _T("\n\npath\tDirctory path containing visual studio solutions.") _T("\n/C\tCompletely delete files and directories.") _T("\n\nThis command only delete") _T("\n(1) files with extension .sdf, .suo and .aps") _T("\n(2) directories named ipch, debug and release."); }
为了代码美观一点,3个字符串指针对象的初始化并未放在初始化列表中,这样做也并不会损害程序的效率。
main函数构造了CJuice类对象后,立马调用了Process方法,因此,CJuice需向外提供Process方法。定义如下所示:
// process void Process() { int uRet = check_arguments(); switch(uRet) { case VSJT_WRONGSYNTAX: display(m_pszWrongSyntax); break; case VSJT_INVALIDPATH: display(m_pszInvalidPath); break; case VSJT_QUERYHELP: display(m_pszHelp); break; case VSJT_JUICE: m_dwMilliseconds = ::GetTickCount(); juice(m_argv[1]); m_dwMilliseconds = ::GetTickCount() - m_dwMilliseconds; report(); }; }
首先调用了私有方法check_arguments,判断用户键入命令的意图,然后对不同情况进行响应。display方法的作用是打印传入的字符串参数,实现很简单:
// display message void display(PTSTR psz) { _tprintf(_T("%s\n"), psz); }
juice是程序的核心方法,它将对目录进行提取处理,删除那些多余的中间文件。而report方法的作用是打印vsjuicer运行相关的统计数据。
下面依次来看check_arguments,juice以及report方法的实现。首先是check_arguments,其定义如下列代码所示:
// check arguments UINT check_arguments() { if(m_argc < 2 || m_argc > 3) return VSJT_WRONGSYNTAX; if(m_argc == 2) { if( ::lstrcmpi(m_argv[1], _T("/?")) == 0 || ::lstrcmpi(m_argv[1], _T("/help")) == 0) return VSJT_QUERYHELP; return is_path_valid() ? VSJT_JUICE : VSJT_INVALIDPATH; } if(::lstrcmpi(m_argv[2], _T("/c")) != 0) return VSJT_WRONGSYNTAX; else m_bComplete = TRUE; return is_path_valid() ? VSJT_JUICE : VSJT_INVALIDPATH; }
对用户键入的命令进行检查,也就是检查命令参数是否正确。vsjuicer仅仅支持3条命令:
因此m_argc只能是2或3,接着再分别对参数个数为2和3两种情况分别进行判断。check_arguments调用了私有方法is_path_valid,其作用是检查path是否有效,其定义为:
// is path valid BOOL is_path_valid() { if(!::PathFileExists(m_argv[1])) return FALSE; if(::PathIsDirectory(m_argv[1]) != FILE_ATTRIBUTE_DIRECTORY) return FALSE; return TRUE; }
is_path_valid方法调用两个API函数来实现。
如果用户键入命令的意图是对path目录下的文件进行清理,check_arguments方法的返回值就是VSJT_JUICE。接着,juice方法就会被调用。前面已经说过,juice方法是程序最重要的部分,其实现相对复杂一些,我们先理一理思路:
基于这样的思路,juice方法将是一个递归方法,传入参数pszPath是目录的全路径。juice方法的代码如下,稍微有一点复杂,随后我会对它进行说明。
// juice void juice(PTSTR pszPath) { // delete folder if matched PTSTR pszFolderName = folder_name(pszPath); if(pszFolderName) { if( ::lstrcmpi(pszFolderName, _T("ipch\\")) == 0 || ::lstrcmpi(pszFolderName, _T("debug\\")) == 0 || ::lstrcmpi(pszFolderName, _T("release\\")) == 0 ) { __int64 nSize = folder_size(pszPath); if(delete_item(pszPath)) { m_dwFolderCount++; m_nFileSize += nSize; } return; } } // delete files if matched TCHAR szSubPath[MAX_PATH]; TCHAR szFileName[MAX_PATH]; ::lstrcpy(szFileName, pszPath); ::PathAddBackslash(szFileName); ::lstrcat(szFileName, _T("*.*")); WIN32_FIND_DATA fd; BOOL bRet = TRUE; HANDLE hSearch = ::FindFirstFile(szFileName, &fd); while(hSearch != INVALID_HANDLE_VALUE && bRet) { // skip . and .. if(::lstrcmpi(fd.cFileName, _T(".")) == 0 || ::lstrcmpi(fd.cFileName, _T("..")) == 0) { bRet = ::FindNextFile(hSearch, &fd); continue; } // match and delete ::memset(szSubPath, 0, sizeof(TCHAR)*MAX_PATH); ::lstrcpy(szSubPath, pszPath); ::PathAddBackslash(szSubPath); ::lstrcat(szSubPath, fd.cFileName); if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) juice(szSubPath); else { PTSTR pszExtension = ::PathFindExtension(szSubPath); if( ::lstrcmpi(pszExtension, _T(".sdf")) == 0 || ::lstrcmpi(pszExtension, _T(".suo")) == 0 || ::lstrcmpi(pszExtension, _T(".aps")) == 0 ) if(delete_item(szSubPath)) { m_dwFileCount++; m_nFileSize += (fd.nFileSizeHigh * ((__int64)MAXDWORD+1)) + fd.nFileSizeLow; } } bRet = ::FindNextFile(hSearch, &fd); }// end while ::FindClose(hSearch); }
首先判断目录名称是否为ipch,debug或release,如果是则直接删除它,否则遍历该目录,分别处理各个文件和子目录。对于子目录的处理是递归调用juice方法来实现的。
juice方法调用了folder_name方法,它的作用是从目录全路径字符串中分解出目录的名称,本质是字符查找。其实现如下所示:
// folder name PTSTR folder_name(PTSTR pszPath) { ::PathAddBackslash(pszPath); int nLen = ::lstrlen(pszPath); for(int i = nLen - 2; i > -1; --i) if(pszPath[i] == _T('\\') || pszPath[i] == _T('/')) return pszPath + i + 1; return NULL; }
juice方法还调用了folder_size方法,它的作用是获取某个目录中所有文件的大小之和。因为没有直接的API方法获得目录的大小,所以需递归的遍历目录中的所有文件,将它们的大小累加起来。folder_size的方法如下所示:
// folder size __int64 folder_size(PTSTR pszPath) { __int64 nSize = 0; TCHAR szFileName[MAX_PATH]; TCHAR szSubPath[MAX_PATH]; BOOL bRet = TRUE; ::lstrcpy(szFileName, pszPath); ::PathAddBackslash(szFileName); ::lstrcat(szFileName, _T("*.*")); WIN32_FIND_DATA fd; HANDLE hSearch = ::FindFirstFile(szFileName, &fd); while(hSearch != INVALID_HANDLE_VALUE && bRet) { // skip . and .. if(::lstrcmpi(fd.cFileName, _T(".")) == 0 || ::lstrcmpi(fd.cFileName, _T("..")) == 0) { bRet = ::FindNextFile(hSearch, &fd); continue; } // calculate size if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!= 0) { ::memset(szSubPath, 0, MAX_PATH*sizeof(TCHAR)); ::lstrcpy(szSubPath, pszPath); ::PathAddBackslash(szSubPath); ::lstrcat(szSubPath, fd.cFileName); nSize += folder_size(szSubPath); } else nSize += fd.nFileSizeHigh*((__int64)MAXDWORD+1) + fd.nFileSizeLow; bRet = ::FindNextFile(hSearch, &fd); } ::FindClose(hSearch); return nSize; }
folder_size内部采用了与juice内部一样的遍历方法,即调用FindFirstFile,FindNextFile以及FindClose这3个API函数来实现。
对目录和文件的删除使用了统一的方式,都是调用的delete_item来实现的。delete_item方法的定义为:
// delete item BOOL delete_item(PTSTR pszPath) { TCHAR szTempPath[MAX_PATH] = { 0 }; ::lstrcpy(szTempPath, pszPath); FILEOP_FLAGS fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO; if(m_bComplete) fFlags &= (~FOF_ALLOWUNDO); SHFILEOPSTRUCT fops = { NULL, FO_DELETE, szTempPath, NULL, fFlags, FALSE, NULL, NULL }; int nRet = ::SHFileOperation(&fops); return nRet == 0 ? TRUE : FALSE; }
其内部调用了API函数SHFileOperation。在填充SHFILEOPSTRUCT变量fops时,根据m_bComplete的值取舍FOF_ALLOWUNDO标志。
好了,就快大功告成了,还剩下最后一个方法report,它的定义再简单不过了:
// report void report() { _tprintf(_T("Juicing finished.") _T("\n\nTarget directory: %s") _T("\n\nDeleted\t %10d files.") _T("\nDeleted\t %10d directories.") _T("\n Saved\t %10.2lf kilobytes.") _T("\n Spent\t %10d milliseconds.\n"), m_argv[1], m_dwFileCount, m_dwFolderCount, (double)m_nFileSize/1024, m_dwMilliseconds); }
4个整数变量的计算穿插在上述各个方法之中,比较简单,所以不在此处赘述。
这篇文件贴出了vsjuicer所用到的所有源码,读者完全可以将它们组织起来,顺利通过编译链接。如果你需要vsjuicer的solution files,请留言或发站内信索取。