很多时候在删除某本地文件夹时老是会弹出一个比较恼人的提示:
文件夹正在使用
操作无法完成, 因为其中的文件夹或文件已在另一程序中打开
请关闭该文件夹或文件, 然后重试。
但是最近写的一个 Qt 应用却需要这样一个功能: 在进行某些长时操作时应用会创建一些文件夹, 比如:
MainFolder
subfolder1
subfolder2
...
该长时操作过程中多个任务线程会不断往这些文件夹里写入大量文件, 写完一个文件即关闭. 为防止人为的误操作(大概率事件), 在写入数据的过程中禁止这些文件夹被移动或删除, 除非已经停止这个连续的写入过程, 虽然可以在写入的时候判断文件夹是否还在, 不在时提示或报错, 但是这种结果无法接受, 因为得到了不完整的数据. 于是想这样来保护这些文件夹:
lock(folders_on_writing) // 将无法移动或删除 folders_on_writing
long_run_writing();
unlock(folders_on_writing) // 可以移动或删除 folders_on_writing 了
我想了以下办法来实现lock
, unlock
, 但都不怎么好:
使用QFileSystemWatcher
来监听这些文件夹, lock
的时候监听, unlock
的时候停止监听. 这样在删除MainFolder
时会提示权限不够, 但是可以任意删除子文件夹, 当把子文件夹都删除后, 可以直接删除主文件夹. 因为只需要禁止文件夹被移动或删除, 强行使用监听功能有点过, 而且"权限不够"的提示也很微妙, 想要的提示是"正在被使用".
lock
时在各个文件夹中打开一个临时文件, unlock
时才关闭这些文件并删除. 目前用的这种简单的笨办法, 可以达到效果.
那么是否还有更好的办法? 比如可以获取到文件夹的句柄什么的就禁止了移动或删除, 之后释放掉句柄就变得可以移动或删除了, 如果有现成的 Qt 方案就更好了, windows 限定的方案也可以, 谢谢 !
使用情形不允许数据库也不允许隐藏文件夹, 采集的数据中包括大量高分辨率图像, 操作人员可能需要打开文件夹查看效果如何, 如果不好的话需要中断操作, 重新配置参数重新开始, 对于采集完的可能被移动(这是这儿容易误操作, 一不小心把采集正在使用的文件夹移走了), 贴一个自己的简单的笨办法
class FolderGuard
{
public:
using Guarder = QFile;
using GuarderPtr = std::unique_ptr<Guarder>;
FolderGuard(const QStringList& folders_to_guard) : folders{ folders_to_guard } {}
~FolderGuard();
void guard();
void unguard();
private:
bool is_valid_folder(const QString folder_name) {
return QFileInfo(folder_name).isDir() && QDir(folder_name).exists();
}
bool is_active_guarder(const GuarderPtr& guarder ) {
return guarder->exists() && guarder->isOpen();
}
QStringList folders;
std::vector<GuarderPtr> guarders;
QString guarder_name{".guard"};
};
FolderGuard::~FolderGuard()
{
unguard();
}
void FolderGuard::guard() {
for (const auto& folder : folders) {
if (is_valid_folder(folder)) {
GuarderPtr guarder{ new Guarder(QString("%1/%2").arg(folder).arg(guarder_name)) };
guarder->open(QFile::WriteOnly); // ignore open failure.
guarders.push_back(std::move(guarder));
}
}
}
void FolderGuard::unguard() {
for (auto& guarder : guarders) {
if (is_active_guarder(guarder)) {
guarder->close();
guarder->remove();
}
}
}
这样来使用
// 采集开始前配置各种路径
// 每次开始采集时生成一个新的FolderGuard
FolderGuard folder_guard({path1, path2, path3, path4});
// 开始采集, 保证正在使用的文件夹不被移动
folder_guard.guard();
// 停止采集, 文件夹不再被使用, 可以移动了
folder_guard.unguard();
1
gaoan000 2019-03-23 09:58:20 +08:00 via Android 1
hook WINAPI Filedel 不知道可以实现不
|
2
whileFalse 2019-03-23 10:04:55 +08:00 1
方法 2 超级简单,难道不是最优解?
还有一个办法,使用临时文件夹,完成操作后移动到目标位置。只不过临时文件夹总是放在 c 盘,如果这个文件内容非常大,且目标文件夹在 D 盘,就不是一个好办法了。 |
3
geelaw 2019-03-23 10:08:38 +08:00 via iPhone 1
最佳实践:独占打开你需要的文件(这样其他人就不能删除或打开你的文件),并且仍然处理错误。
即使你确保文件夹树不能被挪动或删除,你也不能确保你“数据完整”,因为可以出现写到一半磁盘满了的情况。 |
4
geelaw 2019-03-23 10:10:37 +08:00 via iPhone
另外,你应该设置这个文件夹为隐藏属性,可以减少误操作。
|
5
xenme 2019-03-23 10:26:13 +08:00 via iPhone 1
如果你需要独占而且要防止别人中途使用,最好是上面提到的打开的时候模式改成独享,最后不要关闭释放 handle。
确认不用了最后一起释放。 如果这个地方没法改,只能一楼的方法 hook 文件操作 api,打开、移动、删除,如果是你正在用的目录下的,直接拒绝就好了。 |
6
yzwduck 2019-03-23 11:01:30 +08:00 1
lock: `HANDLE handle = CreateFile(target, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);`
unlock: `CloseHandle(handle);` 要点: dwFlagsAndAttributes 里加上 FILE_FLAG_BACKUP_SEMANTICS,dwShareMode 不要有 FILE_SHARE_DELETE。 |
7
yzwduck 2019-03-23 11:07:09 +08:00
补充说明一下,target 可以是“文件夹”的路径。
|
8
lihongjie0209 2019-03-23 11:30:38 +08:00 1
为什么不直接用数据库呢, 嵌入式比如 sqlite? 这样的话基本没有人会把数据库文件误操作吧?
如果最后你真的需要导出一大推文件, 那么从 sqlite 中读取数据重新创建文件就好了 |
9
orzfly 2019-03-23 12:44:37 +08:00
@lihongjie0209 #8 从 SQLite 中读取数据重新创建文件的过程也可以算是一个 Long Running Writing Task (跑
|
10
lihongjie0209 2019-03-23 13:29:16 +08:00
@orzfly 但是这个过程是可控的, 比如说每次导出文件的时候都在 /tmp/{uuid} 文件夹下执行,那么基本上被误操作的概率就很小了。
|
11
hx1997 2019-03-23 13:58:28 +08:00 via Android 1
3, 4L 是对的,CreateFile 以共享读写方式打开文件夹,然后这个文件夹就无法删除了,用完后关闭句柄。
|
12
geelaw 2019-03-23 14:24:15 +08:00 1
针对追加的内容:
1. 把父文件夹设为只读并打开句柄。 2. 正在收集数据的子文件夹设置为隐藏,并且加上明显的标记(例如使用随机命名而不是规律命名,可以用 GetTempFileName 或者自己生成随机文件名);收集完毕的文件夹改名并取消隐藏。 3. 你已经知道要处理磁盘满了的情况了,所以请继续处理这类错误。 |
13
bakabie 2019-03-23 14:36:14 +08:00 1
3L 独占+1。或者我记得 win 有特殊字符可以无法删除吧
|
14
orzfly 2019-03-23 14:42:14 +08:00
楼上这个"win 有特殊字符无法删除"哈哈哈…
那句柄都不用开着了(往每个文件夹里建一个 CON,NUL,a..\ 什么的… |
15
orzfly 2019-03-23 14:43:53 +08:00
哎呀不过这样只能防止被删除,不怎么能防止移动……
|
16
geelaw 2019-03-23 14:45:22 +08:00
@bakabie #13 一个 Win32 程序永远不应该尝试用 Win32 禁止的文件名命名文件——如果程序出错中止,那么用户将束手无策,除非他们也知道如何不通过 Win32 操作文件。
|
17
zhujinhe 2019-03-23 14:55:04 +08:00
前提: linux 有 root 全新, 可以 chattr +a 文件 /文件夹 实现文件只能追加内容, 不能移动删除. 用完之后 chattr -a 文件 /文件夹.
不知道是不是满足楼主需求. |