寫個 .NET 程式解決 Windows 版微信 3.9 收到檔案“只讀”的問題

邊城發表於2023-02-17

Windows 版微信升級到 3.9 之後,接收到的檔案都變成了只讀屬性,對需要經常修改微信接收檔案進行交流的人來說極為不變。雖然從業務功能上來說,需要頻繁交流的文件還是用線上協同(比如騰訊文件)比較好一些,但從技術的角度來看,應該如何解決這個問題呢?

其實很多技術棧都提供了監聽系統檔案變化的 API,比如 .NET 就在其 System.IO 名稱空間下提供了 FileSystemWatcher 類用於監聽檔案的變化,包括建立檔案、刪除檔案、檔案改名/移動、檔案屬性變化等。所以可以使用 C# 和 .NET 快速的寫一個程式用來監聽指定資料夾下的新建檔案,如果新建立的檔案屬性是隻讀,則去掉其只讀屬性。

第一步,找到微信接收檔案的目錄。

在微信的「設定→檔案管理」中就可以找到,比如 C:\Users\James\Documents\WeChat Files。然後需要識別這個目錄下的使用者目錄,一般是微訊號。點選微信視窗上自己的頭像就可以看到。使用者目錄下的 FileStorage\File 目錄就是我們的目標目錄了。

為了幫助使用者快速定位到這個目錄,在假設使用者沒有手工改變微信檔案目錄的情況下,可以這樣來查詢

  • 透過 Environment.GetFolderPath() 獲取到當前使用者的“文件”目錄;
  • 拼接得到 WeChat Files 目錄的路徑;
  • 遍歷 WeChat Files 目錄下的子目錄,去掉特殊命名的 All UsersApplet 等,在剩下的子目錄中進一步猜測
  • 一般最近訪問過的就是自己的微信賬號目錄

那麼整個猜測目錄的過程程式碼示例如下:

string? GuessReceivePath() {
    var docDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
    var wxDir = Path.Combine(docDir, "WeChat Files");
    if (!Directory.Exists(wxDir)) { return null; }

    HashSet<string> excludes = new(StringComparer.InvariantCulture) {
        "All Users",
        "Applet"
    };

    var userDir = Directory.EnumerateDirectories(wxDir)
        .Select(it => new FileInfo(it))         // 對每個目錄產生 FileInfo 物件(小效能損耗可以忽略)
        .ExceptBy(excludes, fi => fi.Name)      // 去掉特定名稱的目錄
        .OrderByDescending(it => it.LastAccessTime)    // 按最近訪問時間排序
        .FirstOrDefault()                       // 取最最後訪問的那一個,注意有可能是 null
        ?.FullName;                             // 取得完整路徑

    if (userDir == null) { return null; }
    return Path.Combine(userDir, "FileStorage", "File");
}

當然,猜測的目錄不一定準確,所以寫程式的時候最好能提供給使用者一個可以手工修改/設定目錄的手段,當然別忘了目錄的有效性檢查(檢查目錄存在)。

第二步,監聽檔案建立

監聽檔案變化只需要新建一個 FileSystemWatcher 物件,設定一些屬性即可。

  • 設定 Path 到要監聽的目錄
  • 設定 .IncludeSubdirectories = true 表示需要監聽其子目錄
  • 新增 Created 事件在監聽到有檔案建立時進行處理

示例程式碼:

var watcher = new FileSystemWatcher() {
    IncludeSubdirectories = true,
};

watcher.Created += (_, e) => { ... };

第三步,對監聽到的新建檔案進行處理

如果目錄下有新建立的檔案,上面 Created 事件的 e 引數(FileSystemEventArgs 型別)會攜帶檔案的資訊,透過 e.FullPath 可以拿檔案的完整路徑。然後使用 FileInfo 判斷其是否只讀,並解除其只讀屬性即可:

(_, e) => {
    var info = new FileInfo(e.FullPath);
    if (info.IsReadOnly) {
        info.IsReadOnly = false;
        Log($"[去掉只讀] {e.Name}");
    }
};

第四步,監聽檔案屬性變更

實際使用中發現,只有在傳送檔案的時候,會在微信檔案目錄建立只讀檔案。接收檔案的時候多數是收到的非只讀檔案,然後再改成只讀屬性的。所以還需要監聽檔案屬性的變化。

這樣就需要設定 FileSystemWatcher 物件的 NotifyFilter 屬性,把 NotifyFilters.Attributes 標記加上。NotifyFilter 的預設值是 LastWriteFileNameDirectoryName,所以修改之後(不需要處理目錄)是:

var watcher = new FileSystemWatcher() {
    IncludeSubdirectories = true,
    NotifyFilter = NotifyFilters.LastWrite
        | NotifyFilters.Attributes
        | NotifyFilters.FileName,
};

此外還需要為 wathcer 新增 Changed 事件的處理函式,在這裡檢查並修改檔案屬性:

watcher.Changed += (_, e) => {
    new FileInfo(e.FullPath).Also(info => {
        if (info.IsReadOnly) {
            info.IsReadOnly = false;
            Log($"[改為可寫] {e.Name}");
        }
    });
};
注:AlsoViyi.Util 提供的擴充套件。

第五步,介面和使用者體驗

麻雀雖小,但也應該是五臟俱全。核心功能完成之後就需要考慮使用者操作介面和使用體驗上的一些問題。不用考慮得太複雜,但至少也應該考慮:

  • 這是一個常駐類應用,雖然不用註冊成系統服務,但也應該考慮使用系統工作列圖示方式常駐;
  • 允許使用者修改路徑,開始/停止監聽操作;
  • 提供簡單的日誌功能,讓使用者知道它處理了哪些檔案。

image.png


相關資源

相關文章