// 算法是:
// 遍历 dstDir,将所有 srcDir 中不存在的文件删除;
// 遍历 srcDir,若与 dstDir 目录中对应文件的修改日期不一致则替换该文件;
// 忽略 excludeFile 文件里指示的目录和文件。
struct IgnoreName
{
bool isDir;
tchar fileName[MAX_PATH];
};
bool GetIgnoreNames(const char* excludeFile, std::vector<IgnoreName>& ignoreNames)
{
bool result = true;
ignoreNames.clear();
if (excludeFile)
{
FILE* fp = NULL;
if (_tfopen_s(&fp, Utf8ToTStr(excludeFile).c_str(), _T("rt")) == 0)
{
char line[MAX_PATH] = {0};
while (fgets(line, MAX_PATH, fp))
{
size_t len = strlen(line);
if (line[len - 1] == _T('\n'))
{
line[len - 1] = '\0';
}
IgnoreName ignore = {0};
tstring fileName = Utf8ToTStr(line);
ignore.isDir = (fileName[0] == _T('\\'));
if (ignore.isDir)
{
fileName = GetFileName(fileName);
}
_tcscpy_s(ignore.fileName, MAX_PATH, fileName.c_str());
ignoreNames.push_back(ignore);
}
fclose(fp);
}
else
{
g_logHelper.OutputErrorInfo(_T("GetIgnoreNames 打开文件(%s)失败!"), Utf8ToTStr(excludeFile).c_str());
result = false;
}
}
return result;
}
bool IsIgnoreFile(WIN32_FIND_DATA& findData, const std::vector<IgnoreName>& ignoreNames)
{
bool isDir = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
std::vector<IgnoreName>::const_iterator it = ignoreNames.begin();
for (; it != ignoreNames.end(); ++it)
{
if (isDir == it->isDir && _tcsicmp(findData.cFileName, it->fileName) == 0)
{
return true;
}
}
return false;
}
bool DeleteDirRecursively(tstring dirPath)
{
bool result = true;
WIN32_FIND_DATA finddata;
HANDLE hfind = FindFirstFile(JoinPath(dirPath, _T("*.*")).c_str(), &finddata);
if (hfind == INVALID_HANDLE_VALUE)
{
return result;
}
do
{
tstring path = JoinPath(dirPath, finddata.cFileName);
if (_tcscmp(finddata.cFileName, _T(".")) == 0 || _tcscmp(finddata.cFileName, _T("..")) == 0)
{
continue;
}
if(finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (! DeleteDirRecursively(path))
{
result = false;
}
}
else
{
if (_tremove(path.c_str()) != 0)
{
g_logHelper.OutputErrorInfo(_T("DeleteDirRecursively 删除文件(%s)失败!"), path.c_str());
result = false;
}
}
} while (FindNextFile(hfind, &finddata) != FALSE && result);
FindClose(hfind);
if (result && _trmdir(dirPath.c_str()) != 0)
{
g_logHelper.OutputErrorInfo(_T("DeleteDirRecursively 删除目录(%s)失败!"), dirPath.c_str());
result = false;
}
return result;
}
// 删除dstDir中多余的文件
bool DeleteUnnecessaryFileInDst(tstring srcDir, tstring dstDir, const std::vector<IgnoreName>& ignoreNames)
{
bool result = true;
WIN32_FIND_DATA findData;
HANDLE dstFindHandle = FindFirstFile(JoinPath(dstDir, _T("*.*")).c_str(), &findData);
if (dstFindHandle != INVALID_HANDLE_VALUE)
{
do
{
if (_tcscmp(findData.cFileName, _T(".")) == 0
|| _tcscmp(findData.cFileName, _T("..")) == 0
|| IsIgnoreFile(findData, ignoreNames))
{
continue;
}
tstring srcPath = JoinPath(srcDir, findData.cFileName);
tstring dstPath = JoinPath(dstDir, findData.cFileName);
if (! FileExists(srcPath))
{ // 删除dstDir目录中的该文件
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
result = DeleteDirRecursively(dstPath);
}
else
{
result = (_tremove(dstPath.c_str()) == 0);
if (! result)
{
g_logHelper.OutputErrorInfo(_T("DeleteUnnecessaryFileInDst 删除文件(%s)失败!"), dstPath.c_str());
}
}
}
if (result && (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
result = DeleteUnnecessaryFileInDst(srcPath, dstPath, ignoreNames);
}
} while (FindNextFile(dstFindHandle, &findData) != FALSE && result);
FindClose(dstFindHandle);
}
return result;
}
bool IsSame(WIN32_FIND_DATA& srcData, WIN32_FIND_DATA& dstData)
{
// todo! 判断两个文件是否一致,为了高效,目前仅使用文件修改时间的判断
bool result = (srcData.ftLastWriteTime.dwHighDateTime == dstData.ftLastWriteTime.dwHighDateTime);
if (result)
{
result = (srcData.ftLastWriteTime.dwLowDateTime == dstData.ftLastWriteTime.dwLowDateTime);
}
return result;
}
// 将dstDir中与srcDir中不一致的文件替换为srcDir中对应的文件
bool SyncDir(tstring srcDir, tstring dstDir, const std::vector<IgnoreName>& ignoreNames)
{
bool result = true;
WIN32_FIND_DATA findData;
HANDLE srcFindHandle = FindFirstFile(JoinPath(srcDir, _T("*.*")).c_str(), &findData);
if (srcFindHandle != INVALID_HANDLE_VALUE)
{
do
{
if (_tcscmp(findData.cFileName, _T(".")) == 0
|| _tcscmp(findData.cFileName, _T("..")) == 0
|| IsIgnoreFile(findData, ignoreNames))
{
continue;
}
tstring srcPath = JoinPath(srcDir, findData.cFileName);
tstring dstPath = JoinPath(dstDir, findData.cFileName);
bool shouldCopy = false;
WIN32_FIND_DATA dstData;
HANDLE dstFindHandle = FindFirstFile(dstPath.c_str(), &dstData);
if (dstFindHandle != INVALID_HANDLE_VALUE)
{
if ((dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != (findData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY))
{
if (dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
result = DeleteDirRecursively(dstPath);
}
else
{
result = _tremove(dstPath.c_str()) == 0;
if (! result)
{
g_logHelper.OutputErrorInfo(_T("SyncDir 删除文件(%s)失败"), dstPath.c_str());
}
}
shouldCopy = true;
}
else if ((dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 && !IsSame(findData, dstData))
{
shouldCopy = true;
result = _tremove(dstPath.c_str()) == 0;
if (! result)
{
g_logHelper.OutputErrorInfo(_T("SyncDir 删除文件(%s)失败"), dstPath.c_str());
}
}
FindClose(dstFindHandle);
}
else // dstPath对应文件或者目录不存在
{
shouldCopy = true;
}
if (result && shouldCopy)
{
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
result = _tmkdir(dstPath.c_str()) == 0;
if (! result)
{
g_logHelper.OutputErrorInfo(_T("SyncDir 创建目录(%s)失败"), dstPath.c_str());
}
}
else
{
result = ::CopyFile(srcPath.c_str(), dstPath.c_str(), TRUE) != FALSE;
if (! result)
{
g_logHelper.OutputErrorInfo(_T("SyncDir 复制文件(%s)到(%s)失败"), srcPath.c_str(), dstPath.c_str());
}
}
}
if (result && findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
result = SyncDir(srcPath, dstPath, ignoreNames);
}
} while (FindNextFile(srcFindHandle, &findData) != FALSE && result);
FindClose(srcFindHandle);
}
return result;
}
bool RestoreDir(const char* srcDir, const char* dstDir, const char* excludeFile/* = NULL*/)
{
std::vector<IgnoreName> ignoreNames;
if (! GetIgnoreNames(excludeFile, ignoreNames))
{
return false;
}
bool result = DeleteUnnecessaryFileInDst(Utf8ToTStr(srcDir), Utf8ToTStr(dstDir), ignoreNames);
if (result)
{
result = SyncDir(Utf8ToTStr(srcDir), Utf8ToTStr(dstDir), ignoreNames);
}
return result;
}