V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
justou
V2EX  ›  C

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

  •  
  •   justou · 2019-03-23 09:44:00 +08:00 · 4093 次点击
    这是一个创建于 2072 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    但是最近写的一个 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 限定的方案也可以, 谢谢 !

    第 1 条附言  ·  2019-03-23 10:39:12 +08:00
    补充一下使用情形:
    这是一个实时采集程序, 在采集的时候也有多个操作人员在管理已经采集好的数据, 数据放在这样一个一个的独立文件夹中, 所以不能隐藏, 必须让操作人员看到. 数据量大, 我禁止了数据存 C 盘, 之前没做这个限制的时候有人选择放在了 C 盘存数据, 结果 C 盘很快满了,导致系统崩了, 还有其他很多监控程序都跟着崩了, 当时造成了很严重的数据丢失. 只能尽量加以限制来避免误操作, 但是始终也是防不了二百五的骚操作的╮(╯▽╰)╭
    第 2 条附言  ·  2019-03-23 15:00:58 +08:00

    使用情形不允许数据库也不允许隐藏文件夹, 采集的数据中包括大量高分辨率图像, 操作人员可能需要打开文件夹查看效果如何, 如果不好的话需要中断操作, 重新配置参数重新开始, 对于采集完的可能被移动(这是这儿容易误操作, 一不小心把采集正在使用的文件夹移走了), 贴一个自己的简单的笨办法

    
    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();
    
    17 条回复    2019-03-23 14:55:04 +08:00
    gaoan000
        1
    gaoan000  
       2019-03-23 09:58:20 +08:00 via Android   ❤️ 1
    hook WINAPI Filedel 不知道可以实现不
    whileFalse
        2
    whileFalse  
       2019-03-23 10:04:55 +08:00   ❤️ 1
    方法 2 超级简单,难道不是最优解?

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

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

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

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


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

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

    那句柄都不用开着了(往每个文件夹里建一个 CON,NUL,a..\ 什么的…
    orzfly
        15
    orzfly  
       2019-03-23 14:43:53 +08:00
    哎呀不过这样只能防止被删除,不怎么能防止移动……
    geelaw
        16
    geelaw  
       2019-03-23 14:45:22 +08:00
    @bakabie #13 一个 Win32 程序永远不应该尝试用 Win32 禁止的文件名命名文件——如果程序出错中止,那么用户将束手无策,除非他们也知道如何不通过 Win32 操作文件。
    zhujinhe
        17
    zhujinhe  
       2019-03-23 14:55:04 +08:00
    前提: linux 有 root 全新, 可以 chattr +a 文件 /文件夹 实现文件只能追加内容, 不能移动删除. 用完之后 chattr -a 文件 /文件夹.
    不知道是不是满足楼主需求.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2647 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 04:20 · PVG 12:20 · LAX 20:20 · JFK 23:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.