实现"文件夹正在使用,操作无法完成..."

2019-03-23 09:44:00 +08:00
 justou

很多时候在删除某本地文件夹时老是会弹出一个比较恼人的提示:

文件夹正在使用
操作无法完成, 因为其中的文件夹或文件已在另一程序中打开
请关闭该文件夹或文件, 然后重试。

但是最近写的一个 Qt 应用却需要这样一个功能: 在进行某些长时操作时应用会创建一些文件夹, 比如:

MainFolder
    subfolder1
    subfolder2
    ...

该长时操作过程中多个任务线程会不断往这些文件夹里写入大量文件, 写完一个文件即关闭. 为防止人为的误操作(大概率事件), 在写入数据的过程中禁止这些文件夹被移动或删除, 除非已经停止这个连续的写入过程, 虽然可以在写入的时候判断文件夹是否还在, 不在时提示或报错, 但是这种结果无法接受, 因为得到了不完整的数据. 于是想这样来保护这些文件夹:

lock(folders_on_writing)   // 将无法移动或删除 folders_on_writing
long_run_writing();
unlock(folders_on_writing) // 可以移动或删除 folders_on_writing 了

我想了以下办法来实现lock, unlock, 但都不怎么好:

  1. 使用QFileSystemWatcher来监听这些文件夹, lock的时候监听, unlock的时候停止监听. 这样在删除MainFolder时会提示权限不够, 但是可以任意删除子文件夹, 当把子文件夹都删除后, 可以直接删除主文件夹. 因为只需要禁止文件夹被移动或删除, 强行使用监听功能有点过, 而且"权限不够"的提示也很微妙, 想要的提示是"正在被使用".

  2. lock时在各个文件夹中打开一个临时文件, unlock时才关闭这些文件并删除. 目前用的这种简单的笨办法, 可以达到效果.

那么是否还有更好的办法? 比如可以获取到文件夹的句柄什么的就禁止了移动或删除, 之后释放掉句柄就变得可以移动或删除了, 如果有现成的 Qt 方案就更好了, windows 限定的方案也可以, 谢谢 !

4127 次点击
所在节点    C
17 条回复
gaoan000
2019-03-23 09:58:20 +08:00
hook WINAPI Filedel 不知道可以实现不
whileFalse
2019-03-23 10:04:55 +08:00
方法 2 超级简单,难道不是最优解?

还有一个办法,使用临时文件夹,完成操作后移动到目标位置。只不过临时文件夹总是放在 c 盘,如果这个文件内容非常大,且目标文件夹在 D 盘,就不是一个好办法了。
geelaw
2019-03-23 10:08:38 +08:00
最佳实践:独占打开你需要的文件(这样其他人就不能删除或打开你的文件),并且仍然处理错误。

即使你确保文件夹树不能被挪动或删除,你也不能确保你“数据完整”,因为可以出现写到一半磁盘满了的情况。
geelaw
2019-03-23 10:10:37 +08:00
另外,你应该设置这个文件夹为隐藏属性,可以减少误操作。
xenme
2019-03-23 10:26:13 +08:00
如果你需要独占而且要防止别人中途使用,最好是上面提到的打开的时候模式改成独享,最后不要关闭释放 handle。

确认不用了最后一起释放。

如果这个地方没法改,只能一楼的方法 hook 文件操作 api,打开、移动、删除,如果是你正在用的目录下的,直接拒绝就好了。
yzwduck
2019-03-23 11:01:30 +08:00
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。
yzwduck
2019-03-23 11:07:09 +08:00
补充说明一下,target 可以是“文件夹”的路径。
lihongjie0209
2019-03-23 11:30:38 +08:00
为什么不直接用数据库呢, 嵌入式比如 sqlite? 这样的话基本没有人会把数据库文件误操作吧?


如果最后你真的需要导出一大推文件, 那么从 sqlite 中读取数据重新创建文件就好了
orzfly
2019-03-23 12:44:37 +08:00
@lihongjie0209 #8 从 SQLite 中读取数据重新创建文件的过程也可以算是一个 Long Running Writing Task (跑
lihongjie0209
2019-03-23 13:29:16 +08:00
@orzfly 但是这个过程是可控的, 比如说每次导出文件的时候都在 /tmp/{uuid} 文件夹下执行,那么基本上被误操作的概率就很小了。
hx1997
2019-03-23 13:58:28 +08:00
3, 4L 是对的,CreateFile 以共享读写方式打开文件夹,然后这个文件夹就无法删除了,用完后关闭句柄。
geelaw
2019-03-23 14:24:15 +08:00
针对追加的内容:

1. 把父文件夹设为只读并打开句柄。
2. 正在收集数据的子文件夹设置为隐藏,并且加上明显的标记(例如使用随机命名而不是规律命名,可以用 GetTempFileName 或者自己生成随机文件名);收集完毕的文件夹改名并取消隐藏。
3. 你已经知道要处理磁盘满了的情况了,所以请继续处理这类错误。
bakabie
2019-03-23 14:36:14 +08:00
3L 独占+1。或者我记得 win 有特殊字符可以无法删除吧
orzfly
2019-03-23 14:42:14 +08:00
楼上这个"win 有特殊字符无法删除"哈哈哈…

那句柄都不用开着了(往每个文件夹里建一个 CON,NUL,a..\ 什么的…
orzfly
2019-03-23 14:43:53 +08:00
哎呀不过这样只能防止被删除,不怎么能防止移动……
geelaw
2019-03-23 14:45:22 +08:00
@bakabie #13 一个 Win32 程序永远不应该尝试用 Win32 禁止的文件名命名文件——如果程序出错中止,那么用户将束手无策,除非他们也知道如何不通过 Win32 操作文件。
zhujinhe
2019-03-23 14:55:04 +08:00
前提: linux 有 root 全新, 可以 chattr +a 文件 /文件夹 实现文件只能追加内容, 不能移动删除. 用完之后 chattr -a 文件 /文件夹.
不知道是不是满足楼主需求.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/547640

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX