之前写过一篇《c++实现移动文件夹到指定文件夹》,最近正好又需要这样的功能,而且还增加了一个需求,希望能够记录移动的具体内容,并且能够根据记录实现恢复功能,这个实现起来没什么难度,唯一需要注意的是,记录需要加锁,并且考虑到程序崩溃等问题,需要将每次成功或者失败记录即时写入文件,而不能在程序结束时写入,否则程序一旦崩溃,所有的移动记录将丢失。
于是我写了一个类,实现了文件和目录的移动,纯c++实现,可以记录移动过程,也有恢复功能呢,可以指定记录的文件名,默认为"restore.log"。全部使用静态函数实现,可以直接调用。
//author:autumoon
//联系邮箱:9506#163.com
//日期:2023-03-24
#pragma once
#include <string>
#include <vector>
#include <mutex>
#include <tchar.h>
//移动文件或者目录,并带有恢复功能,内部自动判断是文件还是目录
#ifdef _UNICODE
#include <string>
#ifndef _tstring
#define _tstring std::wstring
#endif // _tstring
#else
#ifndef _tstring
#define _tstring std::string
#endif // _tstring
#endif
#if _MSC_VER >=1900 // VC2015或者以上
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#endif
//nMode 0记录移动信息 记录错误信息 -1仅仅记录错误信息 -2无任何记录
class CMoveItemsLog
{
public:
CMoveItemsLog() {};
~CMoveItemsLog() {};
public:
static bool CleanHistoryLog();
static bool MoveOneItem(const _tstring& stSrcPath, const _tstring& stDstPath, bool bFailIfExists = false);
static bool MoveItems(const std::vector<_tstring>& lSrcPaths, const std::vector<_tstring>& lDstPaths, bool bFailIfExists = false);
static bool RestoreItemsByLog(const _tstring& stLogPath = _T("restore.log"));
//只存储移动记录,可用于外部调用
static void LogItemMove(const _tstring& stSrcPath, const _tstring& stDstPath);
//默认无须调用
static void SetLogFilePath(const _tstring& stLogPath = _T("restore.log")) { m_stLogFilePath = stLogPath;}
static void SetLogMode(int nMode = 0){m_nMode = nMode;}
private:
static bool moveOneFile(const _tstring& stSrcPath, const _tstring& stDstPath, bool bFailIfExists = false);
static bool moveOneDirectory(const _tstring& stSrcPath, const _tstring& stDstPath, bool bFailIfExists = false);
static void logErrItemMove(const _tstring& stSrcPath, const _tstring& stDstPath);
private:
static std::recursive_mutex m_mutexLog;
static std::recursive_mutex m_mutexErrLog;
static int m_nMode;
static _tstring m_stLogFilePath;
static _tstring m_stErrLogFilePath;
};
实现文件:
//author:autumoon
//联系邮箱:9506#163.com
//日期:2023-03-24
#include "MoveItemsLog.h"
#include "StdStrFile.h"
int CMoveItemsLog::m_nMode = 0;
_tstring CMoveItemsLog::m_stLogFilePath = _T("restore.log");
_tstring CMoveItemsLog::m_stErrLogFilePath = _T("moveErr.log");
std::recursive_mutex CMoveItemsLog::m_mutexLog;
std::recursive_mutex CMoveItemsLog::m_mutexErrLog;
inline bool isSpecialDir(const char* path)
{
return strcmp(path, "..") == 0 || strcmp(path, ".") == 0;
}
bool CMoveItemsLog::CleanHistoryLog()
{
if (CStdFile::IfAccessFile(m_stLogFilePath) && CStdFile::RemoveFile(m_stLogFilePath.c_str()) != 0)
{
return false;
}
if (CStdFile::IfAccessFile(m_stErrLogFilePath))
{
CStdFile::RemoveFile(m_stErrLogFilePath);
}
return true;
}
void CMoveItemsLog::LogItemMove(const _tstring& stSrcPath, const _tstring& stDstPath)
{
m_mutexLog.lock();
switch (m_nMode)
{
case 0:
CStdFile::SaveTXTLine(m_stLogFilePath, stSrcPath + _T("\n"), true);
CStdFile::SaveTXTLine(m_stLogFilePath, stDstPath + _T("\n"), true);
break;
case -1:
break;
case -2:
break;
default:
break;
}
m_mutexLog.unlock();
}
bool CMoveItemsLog::MoveOneItem(const _tstring& stSrcPath, const _tstring& stDstPath, bool bFailIfExists)
{
if (CStdFile::IfAccessFile(stSrcPath))
{
return moveOneFile(stSrcPath, stDstPath);
}
else if (CStdDir::IfAccessDir(stSrcPath))
{
if (moveOneDirectory(stSrcPath, stDstPath, bFailIfExists))
{
LogItemMove(stSrcPath, stDstPath);
return true;
}
else
{
logErrItemMove(stSrcPath, stDstPath);
return false;
}
}
return false;
}
bool CMoveItemsLog::MoveItems(const std::vector<_tstring>& lSrcPaths, const std::vector<_tstring>& lDstPaths, bool bFailIfExists)
{
bool bRet = true;
size_t nItemCount = std::min(lSrcPaths.size(), lDstPaths.size());
for (size_t i = 0; i < nItemCount; ++i)
{
const _tstring& stSrcPath = lSrcPaths[i];
const _tstring& stDstPath = lDstPaths[i];
if (!MoveOneItem(stSrcPath, stDstPath, bFailIfExists))
{
bRet = false;
}
}
return bRet;
}
bool CMoveItemsLog::RestoreItemsByLog(const _tstring& stLogPath)
{
std::vector<_tstring> vContentInFile;
int nFileCount = static_cast<int>(CStdFile::ParseTXTFile(stLogPath, vContentInFile) >> 1);
bool bRet = true;
//注意此时不能使用size_t 因为有边界负值判断
for (int i = nFileCount - 1; i >= 0 ; --i)
{
const _tstring& stCurItem = vContentInFile[i * 2 + 1];
const _tstring& stCurDstItem = vContentInFile[i * 2];
if (!MoveOneItem(stCurItem, stCurDstItem, false))
{
bRet = false;
}
}
if (bRet)
{
//恢复成功时删除记录文件
CStdFile::RemoveFile(m_stLogFilePath);
}
return bRet;
}
bool CMoveItemsLog::moveOneFile(const _tstring& stSrcPath, const _tstring& stDstPath, bool bFailIfExists)
{
if (bFailIfExists && CStdFile::IfAccessFile(stDstPath))
{
logErrItemMove(stSrcPath, stDstPath);
return false;
}
_tstring strDirPath = CStdStr::GetDirOfFile(stDstPath);
if (!CStdDir::IfAccessDir(strDirPath) && !CStdDir::CreateDir(strDirPath))
{
logErrItemMove(stSrcPath, stDstPath);
return false;
}
if (CStdFile::RenameFile(stSrcPath.c_str(), stDstPath.c_str()) != 0)
{
logErrItemMove(stSrcPath, stDstPath);
return false;
}
LogItemMove(stSrcPath, stDstPath);
return true;
}
bool CMoveItemsLog::moveOneDirectory(const _tstring& srcPath, const _tstring& dstPath, bool bFailIfExists)
{
#ifdef _UNICODE
std::string pathBak = CStdStr::ws2s(CStdStr::AddSlashIfNeeded(srcPath));
#else
std::string pathBak = CStdStr::AddSlashIfNeeded(srcPath);
#endif // _UNICODE
_finddata_t fileinfo;
intptr_t hFile;
std::string p;
if ((hFile = _findfirst(p.assign(pathBak).append("*.*").c_str(), &fileinfo)) != -1)
{
while (_findnext(hFile, &fileinfo) == 0)
{
if (isSpecialDir(fileinfo.name))
continue;
std::string sCurDirFile = p.assign(pathBak).append(fileinfo.name);
if (fileinfo.attrib & _A_SUBDIR)
{
_tstring stSubDstPath = CStdStr::AddSlashIfNeeded(dstPath) + CStdStr::GetNameOfDir(sCurDirFile);
//如果是目录,开始递归删除目录中的内容
#ifdef _UNICODE
_tstring stCurDirFile = CStdStr::s2ws(sCurDirFile);
moveOneDirectory(stCurDirFile, stSubDstPath);
#else
moveOneDirectory(sCurDirFile, stSubDstPath);
#endif // _UNICODE
}
else
{
#ifdef _UNICODE
_tstring stPathBak = CStdStr::s2ws(pathBak);
_tstring stCurDirFile = CStdStr::s2ws(sCurDirFile);
#else
_tstring stPathBak = pathBak;
_tstring stCurDirFile = sCurDirFile;
#endif
_tstring stSubDstFile = CStdStr::AddSlashIfNeeded(dstPath) + CStdStr::GetNameOfFile(stCurDirFile);
_tstring stSubDir = CStdStr::GetDirOfFile(stSubDstFile);
if (!CStdDir::IfAccessDir(stSubDir) && !CStdDir::CreateDir(stSubDir))
{
continue;
}
if (CStdFile::RenameFile(stCurDirFile, stSubDstFile) != 0)
{
//出现错误时终止移动
_findclose(hFile);
return false;
}
}
}
_findclose(hFile);//关闭打开的文件句柄,并释放关联资源,否则无法删除空目录
}
//删除空目录,必须在递归返回前调用_findclose,否则无法删除目录
if (_rmdir(pathBak.c_str()) == -1)
{
logErrItemMove(srcPath, dstPath);
return false;
}
return true;
}
void CMoveItemsLog::logErrItemMove(const _tstring& stSrcPath, const _tstring& stDstPath)
{
m_mutexErrLog.lock();
switch (m_nMode)
{
case 0:
case -1:
CStdFile::SaveTXTLine(m_stErrLogFilePath, stSrcPath + _T("\n"), true);
CStdFile::SaveTXTLine(m_stErrLogFilePath, stDstPath + _T("\n"), true);
break;
case -2:
break;
default:
break;
}
m_mutexErrLog.unlock();
}
注意,部分函数需要另外实现,实现起来也不难,欢迎交流与讨论。