扩展 boost::filesystem 以支持 Windows 和 Linux:第二部分。






4.88/5 (11投票s)
使用 C++ 管理 Linux 回收站和 Windows 回收站。
- 第一部分文章 下载 FSHelper_app_for_Windows_and_Linux.zip
- 第一部分文章 下载 FSHelper-VS2012.zip
- 第一部分文章 下载 FSHelper_Qt_project.zip
引言
在我**上一篇文章**中,我分享了我对一个我找不到很多示例的特定领域的观察,那就是使用 C++ 在 **Linux 上实现文件系统监视器**。现在,我将尝试总结我关于下一个主题的发现——**在 Linux 上将文件删除到回收站和从回收站恢复文件**,并简要概述 Windows 关于回收站的类似功能。
当我开始着手扩展 `boost::filesystem` 命名空间提供的基本功能时,我知道要找到像 .NET 风格的便携式文件系统监视器(我在**第一部分**中讨论过)这样的资源会很困难,但令我非常惊讶的是,像处理 Linux 回收站这样的领域也完全被忽视了,几乎没有可用的 C++ 实现答案或文档。即使在 Windows 上,虽然可以找到遵循的资源,但我一直无法找到使用 C++ 管理回收站并且可以无缝工作的代码示例。我计划将本文的很大一部分用于 Linux 实现,但在此之前,我将尝试分享一些关于我提供的代码(您可以下载)中的 Windows 实现的观察结果。同样,要编译此代码,请参阅**第一部分**文章中提供的说明。
补充:我添加了一个新的项目,可以与 Visual Studio 2012 编译,所以现在您也有这个选项了。有关其他说明,请参阅**第一部分**文章。
使用 C++ 处理 Windows 回收站
我必须说,列出回收站内容以及将文件/文件夹删除到回收站并不难,一旦您了解了一些不那么明显的事情,特别是对于像我这样没有太多 Windows Shell API 经验的人来说。没有这些知识,您仍然可以创建在某些情况下有效但在其他情况下“神秘地”失败的代码示例。至少,我遇到的情况是这样的。要列出回收站的内容,请查看我的 `FileSystemController::ListRecycleBin()` 函数实现。我看到很多人在使用**MinGW**编译器编译我提供的代码时遇到了问题。具体来说,如果您使用的是 `CoTaskMemFree()` API 调用,您很可能会遇到链接器错误,如“*undefined reference to CoTaskMemFree@4*”。经过进一步研究,我发现 `CoTaskMemFree()` 函数的实现应该在 _ole32.dll_ 中,我尝试将其添加到库中。之后,一切都编译正常。
第二组问题发生在实现删除到回收站时。我的实现如下所示:
OpResults moveToRecycleBin(const STRING_TYPE& source)
{
OpResults opres;
SHFILEOPSTRUCTW operation;
operation.wFunc = FO_DELETE;
unique_ptr<wchar_t[]> name(createDoubleNullTermString(source));
operation.pFrom = (LPCWSTR)name.get();//source.c_str();
operation.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION;
//without values below defined operation will CRASH on WINDOWS XP!!!
operation.hNameMappings = NULL;
operation.lpszProgressTitle = NULL;
operation.fAnyOperationsAborted = FALSE;
operation.hwnd = NULL;
operation.pTo = NULL;
//return errors (values bigger that 0) are mostly defined in <winerror.h>
int result = SHFileOperationW(&operation);
if(result != 0)
opres.AddResult(OperationResult(false, source, TO_STRING_TYPE("Return Error value:") +
Helpers::ToString<int, STR_SET>(result)));
return opres;
}
基本上,我们提供要移至回收站的文件系统项的路径,并在我实现的专用容器类(`OpResults`)中返回操作结果。这或多或少与我在 `FileSystemController` 类中提供的许多函数类似,您可以在下载的 Qt Creator 项目中找到该类。我起初不知道的第一件事是,您需要以**双空终止字符串**的形式向 `SHFILEOPSTRUCTW` 提供项的路径。如果您不这样做,此操作将只能部分成功,或者更确切地说,有时会成功,有时会失败。要从标准的 `std::wstring` 获取双空终止字符串,您可以使用以下函数:
wchar_t* createDoubleNullTermString(const wstring& ws)
{
wchar_t* buff = new wchar_t[MAX_PATH + 2];
wcscpy(buff, ws.c_str());
memcpy(buff + ws.length() + 1, L"\0\0", 2);
return buff;
}
请注意,这样我们就在堆上创建了一个 `wchar_t*` 数组,以后需要删除它。如果您像我的代码那样使用标准库中的 `unique_ptr` 智能指针,请确保使用“`wchar_t[]`”作为提供的指针类型,以指示应使用专门的数组删除。
下一个问题是我的实现无法在 Windows XP 上运行。经过我在网上浏览的广泛研究,我发现了一个来自完全不相关项目的代码片段,其中有一个小注释指出 `SHFileOperation()` 调用可能会在未将值设置给 `SHFILEOPSTRUCTW` 字段(如 `hNameMappings`、`lpszProgressTitle`、`fAnyOperationsAborted`、`hwnd` 和 `pTo`)的情况下崩溃。在分配了值(如提供的示例所示)后,XP 问题就消失了。
最后,在我开始讨论 Linux 回收站实现之前,我必须指出,似乎没有简单的方法可以使用 C++ 从 Windows 回收站恢复文件(至少我没有成功找到)。我尝试了几种不同的方法,包括 `ShellExecute()` API 调用,该调用需要一个特定的“verb”,我尝试在注册表中查找它但未成功。这是我想要的唯一功能,但在 `FileSystemController` 类中未能提供。
使用 C++ 操作 Linux 回收站
正如我之前提到的,当我开始在各种互联网论坛上查找可用信息时,我发现关于这个主题几乎没有可用的数据。我找到的唯一有用的来源是 freedesktop.org 回收站规范。我说“有用”并不是说您必须严格遵循它。您应该测试其中描述的每一个功能,以确保您打算使用的发行版上的实际行为确实如所述。例如,根据 freedesktop.org 回收站规范,真正的家庭回收站位置应可通过环境变量 `$XDG_DATA_HOME/Trash` 访问。在我测试过的任何主要发行版上都不是这样。我必须说,我绝对不是 Linux 专家,所以也许我在过程中遗漏了什么,但似乎在大多数流行发行版上,我们可以期望两个可能的回收站位置。第一个是所谓的**家庭回收站**位置,通常可以在 _/home/<user>/.local/share/Trash_ 隐藏文件夹中找到,它的行为大部分是按照前面提到的规范描述实现的。另一个(或多个)可以在可附加媒体(如 USB 驱动器等)上找到,并且位于媒体的根目录下,是一个 _ .Trash-$uid_ 隐藏文件夹,其中 _$uid_ 代表当前用户 ID。
所有这些位置都包含两个子文件夹:_files_ 和 _info_。第一个(**files 子目录**)用于存储移动到回收站的实际数据(文件/文件夹)。另一个(**info 子目录**)包含对应的 .trashinfo 信息文件,其中包含 URI 编码的原始项路径(用于恢复操作)和一个表示删除时间的日期时间值。尽管对应的文件和信息条目从移动到回收站的原始项名称构建其名称,但规范指出不应使用这些名称作为恢复操作的来源。在回收站文件夹中,有时还可以找到一个附加文件。那就是**元数据**文件,它保存当前回收站的大小。据我所知,这个文件是使用回收站时各种意外行为的主要来源(这是我以前在 Open SUSE 12.1 操作系统上的个人经验)。首先,如果您遇到回收站已满的情况,但您确定它实际上没有满,您应该在这里查找问题来源。看起来**元数据**中存储的值是无符号的(这很合理),而操作系统提供的实际校正有时会是错误的,导致其下溢为负值。这将导致一个非常大的值写入元数据文件,从而回收站似乎已达到大小限制。我也尝试创建一个更新元数据文件中回收站大小值的机制,但过了一段时间我就放弃了,因为我无法弄清楚系统是如何为新大小值执行某些计算的,特别是在我们移动符号链接到回收站的情况下。它似乎既不加上符号链接的大小,也不加上它指向的实际项的大小。这似乎不是一个 bug,只是我无法在文档中追踪到的东西,所以我通过“简单”的方法解决了它——每次对回收站内容进行某种更改时,使用 `FileSystemController` 类**删除元数据**文件。在这种情况下,环境将使用重新计算后的值重建元数据文件,这恰好是一个很好的便利,可以让我们将这个任务推卸给我们。
关于 Linux 回收站位置,还有一件事似乎对许多人来说不清楚——如果您还没有使用过回收站,那么之前描述的实际回收站文件夹可能还不存在。它们通常在第一次使用时创建。如果我们打算模仿 Linux 回收站操作,那么在访问回收站时,我们应该实现创建这些路径(如果它们不存在)的能力。
为了建立移动到回收站和从回收站恢复的功能,我需要一些不同的辅助函数来负责这项工作的每个部分:
- 获取用户 ID
inline UINT getUserID()
{
return getuid();
}
inline UINT getDeviceID(const string& source)
{
struct stat _info;
const char* orgPath = source.c_str();
if(stat(orgPath, &_info) == 0)
{
return _info.st_dev;
}
else
return 0; // Unsuccessfull stat() !!!
}
std::map<UINT, std::string> getMountPoints()
{
std::map<UINT, std::string> mount_points;
struct mntent* ent;
FILE* aFile;
aFile = setmntent("/proc/mounts", "r");
if (aFile != NULL)
{
while (NULL != (ent = getmntent(aFile)))
{
mount_points.insert(std::make_pair(getDeviceID(ent->mnt_dir), ent->mnt_dir));
}
}
endmntent(aFile);
return mount_points;
}
//If user didn't move anything so far to Trash, chances are that directory
//doesn't exist yet
OperationResult createTrash(const std::string& trash_path)
{
OperationResult opOK = createOKOpResult(trash_path);
system::error_code ec;
bfs::create_directory(trash_path, ec);
if(ec.value() != 0)
return OperationResult(false, trash_path, ec.message());
//updateTrashMetadata(trash_path, 0, true); //create new file
bfs::path trashInfoPath = trash_path;
bfs::path trashfilesPath = trash_path;
trashInfoPath /= "info";
trashfilesPath /= "files";
bfs::create_directory(trashInfoPath, ec);
if(ec.value() != 0)
{
deleteItem(trash_path);
return OperationResult(false, trashInfoPath.string(), ec.message());
}
bfs::create_directory(trashfilesPath, ec);
if(ec.value() != 0)
{
deleteItem(trash_path);
return OperationResult(false, trashfilesPath.string(), ec.message());
}
return opOK;
}
//Creates home trash if doesn't exist
inline std::string getHomeTrashPath()
{
bfs::path trash;
std::string trashPath;
if((trashPath = freedesktop_org_HomeDataPath()).empty())
{
trash = homePath();
trash /= ".local/share/Trash";
}
else
{
trash = trashPath;
trash /= "Trash";
}
if(!bfs::exists(trash))
{
OperationResult _result = createTrash(trash.string());
if(!_result.result)
trash = bfs::path(EMPTYSTR);
}
return trash.string();
}
//MAX value for device_id means home trash
inline std::string getTrashPath(UINT device_id = UINT_MAX, bool fallBackToHomeTrash = true)
{
if(device_id == UINT_MAX)
{
return getHomeTrashPath();
}
std::map<UINT, std::string> mount_points(getMountPoints());
std::string trashOnMount(mount_points[device_id]);
bfs::path trash;
if(!trashOnMount.empty())
{
UINT usrID = getUserID();
std::string trashName(".Trash-");
trashName += Helpers::ToString(usrID);
trash = trashOnMount;
trash /= trashName;
//freedesktop.org specification doesn't seem to be implemented to the letter on any
//distro I tested. The only common behavior is existence of ./Thash-uid as local
//trash implementation on medias like USB sticks
if(!bfs::exists(trash))
{
std::string strTrash = trash.string();
//On Fedora 17 x64 usb stick is mounted as "/run/media..." and on other systems I have tested as "/media..."
if((strTrash.find("/media/") == 0 && strTrash.length() > 7) ||
(strTrash.find("/run/media/") == 0 && strTrash.length() > 10))
{
OperationResult _result = createTrash(trash.string());
if(!_result.result)
trash = bfs::path(EMPTYSTR);
}
else if(fallBackToHomeTrash)
{
//Fallback to Home Trash
trash = getHomeTrashPath();
}
}
}
return trash.string();
}
//Get All available trash paths from mount points(where they exist)
std::vector<std::string> getAvailableTrashPaths()
{
std::map<UINT, std::string> mount_points(getMountPoints());
vector<std::string> trashPaths;
trashPaths.push_back(getHomeTrashPath());
for_each(mount_points.begin(), mount_points.end(), [&trashPaths](const std::pair<UINT, std::string> p)
{
std::string possibleTrashPath(getTrashPath(p.first, false));
if(bfs::exists(possibleTrashPath))
trashPaths.push_back(possibleTrashPath);
});
return trashPaths;
}
要列出所有回收站内容,我们需要收集系统中每个可用回收站路径中可以找到的所有条目。在此过程中,我们还需要获取在上一搜索中获得的每个项的实际路径和恢复路径。要做到这一点,必须读取回收站子文件夹 _info_ 中对应的 _ .trashinfo_ 文件。最后,我们需要对获取的恢复路径进行 URI 解码。这是我的 `FileSystemController::ListTrash()` 函数:
fInfosPtr FileSystemController::ListTrash(OpResults* resultOutcomes)
{
//TODO:REWRITE !!!
vector<std::string> availableTrashPaths(getAvailableTrashPaths());
fInfosPtr returnEntries = new vector<F_INFO>;
std::string homeTrash(getHomeTrashPath());
for(auto& trashStr : availableTrashPaths)
{
unique_ptr<vector<F_INFO> > entries(getEntries(getTrash_Files(trashStr), true));
unique_ptr<vector<F_INFO> > infoEntries(getEntries(getTrash_Info(trashStr), true));//, _SortInfo::dirFirst_asc));
for_each(entries->begin(), entries->end(), [&infoEntries, &resultOutcomes](F_INFO& entry)
{
for(auto beg=infoEntries->begin(), end = infoEntries->end(); beg != end; ++beg)
{
const F_INFO& infoEntry = *beg;
string::size_type pos = infoEntry.name.find(".trashinfo");
if(pos != string::npos)
{
string baseInfoName = infoEntry.name.substr(0, pos);
if(entry.name == baseInfoName)
{
try
{
entry.orgTrashPath = entry.path;
entry.path = getOriginalPathFromInfoTrashFile(infoEntry.path);
entry.name = boost::filesystem::path(entry.path).filename().string();
resultOutcomes->AddResult(createOKOpResult(entry.path));
}
catch(const string& errorMsg)
{
resultOutcomes->AddResult(OperationResult(false, infoEntry.path, errorMsg));
}
break;
}
}
};
});
Helpers::AddVectors(*returnEntries, *entries);
}
return returnEntries;
}
每个 _ .trashinfo_ 文件都有一个结构,该结构在 freedesktop.org 回收站规范中得到了很好的描述(并且在所有我测试过的发行版上都被严格遵循)。
[Trash Info]
Path=<URI encoded restore path>
DeletionDate=<deletion date and time>
此文件的名称(除了 _ .trashinfo_ 扩展名)与回收站中属于实际已删除内容的 _files_ 子文件夹中存在的名称相同,这是它们匹配的主要标准,当需要进行文件恢复时。要读取填充 _ .trashinfo_ 文件的内容,您可以使用函数:
std::string getOriginalPathFromInfoTrashFile(const string& infoFilePath) throw(std::string)
{
//The whole purpose of this prefix is to fill in
// the missing parts of full restore path which is
//the case when accessing parts of Trash
//on other places than home Trash...
std::string prefix;
//home Trash has different name than ".Trash-xyz"
auto pref_pos = infoFilePath.find(".Trash-");
if(pref_pos != std::string::npos)
prefix = infoFilePath.substr(0, pref_pos);
std::string text = Helpers::textFileRead(infoFilePath);
std::string::size_type pos = text.find("Path=");
std::string::size_type posEnd = text.find("\nDeletionDate=", pos);
if(pos == std::string::npos || posEnd == std::string::npos)
throw(std::string("Incorrect info file structure in ") + infoFilePath);
pos += 5;
std::string orgPath = text.substr(pos, posEnd - pos);
return prefix + Helpers::URIDec(orgPath);
}
回收站的 _files_ 和 _info_ 子文件夹中的对应文件名也存在一个特殊情况。当您已经删除了一个名称与您想移动到回收站的项名称相同的项时,系统会进行检查并添加一个后缀,如“name 1”、“name 2”、“name 3”...等(具体取决于您已经在回收站中存储了多少同名文件;我认为您可以遵循不同的命名模式,只要您确保存储在回收站中的每个文件都有一个唯一的名称,并且 _files_ 和 _info_ 子文件夹中的条目名称匹配(除了 _ .trashinfo_ 扩展名)。我个人遵循了我在我的主开发系统——Open SUSE Linux——中观察到的行为。一旦我们想通过自己的代码管理回收站,我们就需要实现为回收站中的每个已删除项生成唯一名称的能力(在下一个函数中,`bfs` 是 `boost::filesystem` 的别名)。
//corrects first provided path and returns the corrected file name + extension for string concatenation
inline std::string correctTrashFileName(bfs::path& filePathToCorrect)
{
typedef std::string::size_type POS_TYPE;
string filePath;
string parent_path = filePathToCorrect.parent_path().string();
string ext = filePathToCorrect.extension().string();
string fName = filePathToCorrect.filename().string();
//fName still contains extension. This needs to be removed...
fName = fName.substr(0, fName.find_last_of('.'));
POS_TYPE pos = fName.find_last_of(' ');
if(pos != string::npos)
{
string substr = fName.substr(++pos);
int num = atoi(substr.c_str());
if(num != 0)
{
++num;
substr = fName.substr(0, pos);
fName = substr + Helpers::ToString(num) + ext;
filePath = parent_path + "/" + fName;
filePathToCorrect = bfs::path(filePath);
}
else
{
if(bfs::exists(filePathToCorrect))
{
fName += " ";
fName += "1";
fName += ext;
filePath = parent_path + "/" + fName;
filePathToCorrect = bfs::path(filePath);
}
else
fName += ext;
}
}
else
{
if(bfs::exists(filePathToCorrect))
{
fName += " ";
fName += "1";
fName += ext;
filePath = parent_path + "/" + fName;
filePathToCorrect = bfs::path(filePath);
}
else
fName += ext;
}
if(bfs::exists(filePathToCorrect))
fName = correctTrashFileName(filePathToCorrect);
return fName;
}
在调用此函数之前,我们必须已经构建了要将当前文件/文件夹项移至的目标位置(回收站路径)的完整路径,并检查它是否存在于回收站中。前面介绍的函数然后会检查要添加到我们已有的文件名中的后缀,以使其唯一。这意味着检查是否已经有一个带有该后缀的被丢弃的文件,如果有,则将该后缀解析为数字并递增。那么,回收站的最终名称将是“name 1”或“name x”,其中 x 代表尚未被另一个被丢弃项占用的最小增量。
有关提供回收站功能的实际函数,您可以查看下载链接中的 Qt Creator 项目。这些函数是此处已介绍的 `FileSystemController::ListTrash()`,位于 _FSC_Helpers_Linux.hpp_ 中的 `moveToTrash()` 函数,以及 `FileSystemController::RestoreFile()` 函数。`moveToTrash()` 函数将实际“删除”文件系统项并将其移至回收站。
OpResults moveToTrash(const string &source)
{
OpResults results;
bfs::path file(source);
if(!bfs::exists(file))
{
results.AddResult(OperationResult(false, source, "source path does not exist"));
return results;
}
string fName = file.filename().string();
//this will create Home Trash location path if necessary (in case it still doesn't exist)
std::string trashPath;
if(goesInHomeTrash(source))
trashPath = getHomeTrashPath();
else //Creates or gets .Trash-$uid or falls back to home trash path
trashPath = getTrashPath(getDeviceID(source));
if(trashPath.empty())
{
results.AddResult(OperationResult(false, source, "Trash cannot be found!"));
return results;
}
//Checking if moving should be made in "local" Trash rather than home Trash
//"local" trash on attachable devices like USB sticks have a different folder
//path
bool isLocalTrash = trashPath.find(".Trash-") != std::string::npos ? true : false;
std::string trashFiles(getTrash_Files(trashPath));
std::string trashInfo(getTrash_Info(trashPath));
bfs::path newFilePath(trashFiles);
newFilePath /= fName;
//If name already exists OS adds " 1" or other number
//to the name to be unique - path provided is modified by ref and
//file name + extension are in return value since it has to be used again to create new info file
//It wouldn't make much sense to call this function twice.
fName = correctTrashFileName(newFilePath);
bfs::path trashInfoFilePath(trashInfo);
trashInfoFilePath /= (fName + ".trashinfo");
//write data to info folder
//by freedesktop.org specification info file should be written first
//If done otherwise, multiple files with the same name+extension (from different locations)
//won't be recognized correctly in Trash folder by other file browsers like Dolphin
std::string infoName;
if(!isLocalTrash)
{
//Home Trash operates with ABSOLUTE PATHS only
//according to freedesktop.org specification
infoName = TrashRecoveryName(source);
}
else
{
//in case of local trash folder we should
//put relative recovery path to Path field in info file
infoName = LocalTrashRecoveryName(source);
}
std::string infoDate = getFileDeletionTime();
try
{
//Write info file
Helpers::FileWriter<> _writer(trashInfoFilePath.string());
_writer << string("[Trash Info]");
_writer << (string("Path=") + infoName);
_writer << (string("DeletionDate=") + infoDate);
//_writer will close stream upon D-tor execution
}
catch(const string& str)
{
results.AddResult(OperationResult(false, trashInfoFilePath.string(), str));
return results;
}
catch(...)
{
results.AddResult(OperationResult(false, trashInfoFilePath.string(),
"Undefined error ocurred while trying to write info trash file"));
return results;
}
//Actual movement to Trash location
results = moveItem(source, newFilePath.string(), true);
//check for errors and abort if error occured
if(results.hasErrors())
{
if(!bfs::exists(source))
results += moveItem(newFilePath.string(), source, true);
return results;
}
if(bfs::exists(infoName))
results += deleteItem(infoName);
//check for errors and abort if error occured
if(results.hasErrors())
{
if(!bfs::exists(source))
results += moveItem(newFilePath.string(), source, true);
return results;
}
/******************************************************************/
/*Skipping this step... I will delete metadata file instead...*/
//update size recorded in $Trash/metadata file
/* updateTrashMetadata(trashPath, entrySize); */
/******************************************************************/
//By deleting this file KDE/Dolphin will create new one when needed and
//update it with appropriate info
std::string metadataPath(getTrashMetadataPath(trashPath));
if(bfs::exists(metadataPath))
deleteItem(metadataPath);
return results;
}
`moveToTrash()` 函数首先需要检查要删除的项路径是否存在,并获取要移动到的相应回收站路径。借助前面描述的辅助函数,它应该(重新)构建回收站目录(如果它在此过程中不存在)。接下来,它应该为我们要删除的项构建一个 _ .trashinfo_ 文件。这必须在将选定的项实际移动到回收站的**files 子目录****之前**完成。否则,在使用 KDE Dolphin 等标准文件管理器时,我们可能会遇到回收站内容未被正确检测的情况。这主要发生在我们需要为新的回收站项进行名称“更正”时,因为已经有一个同名项存在于其中。之后,过程很容易理解——该函数将 **.trashinfo** 文件写入 **info 子目录**,并将相应的项移动到 **files 子目录**,如果存在,则删除 **metadata** 文件。
最后,可以使用 `FileSystemController::RestoreFile()` 函数来实际从回收站位置进行恢复。为此,它需要遍历相应回收站位置的 _files_ 和 _info_ 子目录的内容,并在两个目录中找到匹配的条目对,从相应的 _ .trashinfo_ 文件中获取原始恢复路径,删除回收站 **info 子目录**中的 _ .trashinfo_ 文件,最后通过将回收站项从回收站 **files 子目录**移至其原始位置来恢复该项。由于我们通过这样做对回收站大小进行了更改,因此该函数会删除保存当前回收站大小的 **metadata** 回收站文件,强制系统创建一个新的(如果使用)并带有重新计算的值。
结论
我知道这篇文章很长,但我觉得这个领域有很多未知之处,因为处理 Linux 回收站就像在黑暗中行走。几乎每一步都需要我观察(或假设)系统在做什么,并通过我的代码来模仿这种行为。我认为我的回收站实现已经达到了可以进行标准管理任务的程度,可以在主要的 Linux 发行版上使用,但它仍然存在一些缺陷——例如,不遵守回收站大小限制。这是因为我找不到获取此限制信息的方法。可能还有其他缺陷,但考虑到我为此任务所花费的时间,我认为我无法做得更好。
总而言之,如果您打算在您的代码中使用我的实现,请随时使用,但我建议您先进行测试。最简单的方法是下载编译好的 Linux **FSHelper** 终端应用程序“可执行文件”,并尝试使用它进行回收站管理(如果您使用 x86 Linux,您可能运气不好,需要重新编译提供的 Qt 项目)。如果它运行正常,那么底层 `FileSystemController` 类实现很可能也能正常工作。
有关下载链接中代码的说明,请参阅**第一部分文章**中的“**使用代码**”部分。