共用方式為


在背景中監控檔案系統的變動

重要的應用程式介面

StorageLibraryChangeTracker 類別允許應用程式追蹤使用者在系統中移動檔案和資料夾時的變更。 使用 StorageLibraryChangeTracker 類別,應用程式可以追蹤:

  • 檔案操作包括新增、刪除、修改。
  • 資料夾操作如重新命名和刪除。
  • 硬碟上的檔案和資料夾都在移動。

請使用本指南學習使用變更追蹤器的程式設計模型,查看一些範例程式碼,並了解 StorageLibraryChangeTracker 追蹤的不同檔案操作類型。

StorageLibraryChangeTracker 適用於使用者函式庫,或是本地機器上的任何資料夾。 這包括次要硬碟或可移動硬碟,但不包括 NAS 硬碟或網路硬碟。

使用變更追蹤器

變更追蹤器在系統上實作為圓形緩衝區,儲存最後 N 個檔案系統操作。 應用程式能夠讀取緩衝區的變更,然後將它們處理成自己的體驗。 應用程式完成變更後會標記為處理中,且不會再看到。

要在資料夾上使用變更追蹤器,請依照以下步驟操作:

  1. 啟用資料夾的變更追蹤。
  2. 等變化。
  3. 查看變更。
  4. 接受改變。

接下來的章節會帶著程式碼範例逐步介紹每個步驟。 完整程式碼範例於文章末尾提供。

啟用變更追蹤器

應用程式首先需要做的是告訴系統它有興趣追蹤某個函式庫的變更。 它透過呼叫感興趣函式庫變更追蹤器的 Enable 方法來達成此目標。

StorageLibrary videosLib = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Videos);
StorageLibraryChangeTracker videoTracker = videosLib.ChangeTracker;
videoTracker.Enable();

有幾點重要說明:

  • 在建立 StorageLibrary 物件之前,請確認你的應用程式在清單中擁有正確的函式庫權限。 詳情請參閱 檔案存取權限
  • Enable 是執行緒安全的,不會重置指標,且可以無限次呼叫(稍後會詳細說明)。

啟用空變更追蹤器

等待變化

變更追蹤器初始化後,它會開始記錄函式庫內發生的所有操作,即使應用程式未運行。 應用程式可以透過註冊 StorageLibraryChangedTrigger 事件,當有變更時隨時被啟動。

變更正在被加入變更追蹤器,而應用程式卻未讀取它們。

閱讀變更內容

應用程式接著可以從變更追蹤器中輪詢變更,並收到自上次檢查以來的變更清單。 以下程式碼說明如何從變更追蹤器取得變更清單。

StorageLibrary videosLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Videos);
videosLibrary.ChangeTracker.Enable();
StorageLibraryChangeReader videoChangeReader = videosLibrary.ChangeTracker.GetChangeReader();
IReadOnlyList changeSet = await changeReader.ReadBatchAsync();

應用程式負責將變更處理成自身的體驗或資料庫,視需要而定。

將變更追蹤器的變更資料讀取到應用程式資料庫

小提示

第二次呼叫啟用是為了防止競爭狀態發生,以防使用者在您的應用程式讀取變更時新增另一個資料夾到資料庫。 若不額外呼叫啟用,當使用者更改函式庫中的資料夾時,程式碼將因 ecSearchFolderScopeViolation (0x80070490) 而失敗。

接受這些改變

應用程式處理完變更後,應該會透過呼叫 AcceptChangesAsync 方法,告訴系統永遠不要再顯示這些變更。

await changeReader.AcceptChangesAsync();

將變更標記為已讀,這樣就不會再顯示了

未來應用程式只會在讀取變更追蹤器時接收新變更。

  • 如果在呼叫 ReadBatchAsyncAcceptChangesAsync 之間有任何變更發生,指標只會前進到應用程式所看到的最新變更。 這些其他變更在下次呼叫 ReadBatchAsync 時仍可使用。
  • 不接受變更時,系統下次呼叫 ReadBatchAsync 時會回傳相同的變更。

要記住的重要事項

使用變更追蹤器時,有幾件事你應該注意,以確保一切運作正常。

緩衝區超支

即使我們在變更追蹤器中盡量保留足夠的空間來存儲系統上所有正在發生的操作,以便應用程式可以及時讀取這些變更,但可以很容易想像出一種情況:應用程式在循環緩衝區自我覆寫之前,沒有讀取到這些變更。 尤其是當使用者從備份還原資料或同步大量手機照片時。

此時,ReadBatchAsync 會回傳錯誤碼 StorageLibraryChangeType.ChangeTrackingLost。 如果你的應用程式收到這個錯誤代碼,代表幾種原因:

  • 自從你上次查看後,緩衝區已經被覆蓋了。 最好的做法是重新爬取資料庫,因為追蹤器中的資訊會不完整。
  • 變更追蹤器不會回傳任何變更,除非你呼叫 重置。 應用程式呼叫重置後,指標會移到最近的變更,追蹤也會恢復正常。

這種情況應該很少見,但當使用者在磁碟上移動大量檔案時,我們不希望變更追蹤器膨脹,佔用過多儲存空間。 這應該能讓應用程式能對龐大的檔案系統操作做出反應,同時不會損害 Windows 的用戶體驗。

StorageLibrary 的變更

StorageLibrary 類別存在於包含其他資料夾的根資料夾的虛擬群組。 為了配合檔案系統變更追蹤器,我們做出以下選擇:

  • 根目錄資料夾後代的任何變更都會顯示在變更追蹤器中。 根函式庫資料夾可以透過 Folders 屬性找到。
  • 從 StorageLibrary 新增或移除根目錄(透過 RequestAddFolderAsyncRequestRemoveFolderAsync)不會在變更追蹤器中建立條目。 這些變更可以透過 DefinitionChanged 事件追蹤,或使用 Folders 屬性列舉函式庫中的根資料夾。
  • 如果資料夾中已有內容被加入函式庫,則不會產生變更通知或變更追蹤條目。 該資料夾的任何子資料夾的後續變更都會產生通知及變更追蹤器條目。

呼叫啟用方法

應用程式應在開始追蹤檔案系統以及在每次列舉變更之前呼叫Enable。 這樣可以確保所有變更都能被變更追蹤器記錄下來。

整合

這裡是所有用來從影片庫註冊變更並開始從變更追蹤器拉取變更的程式碼。

private async void EnableChangeTracker()
{
    StorageLibrary videosLib = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Videos);
    StorageLibraryChangeTracker videoTracker = videosLib.ChangeTracker;
    videoTracker.Enable();
}

private async void GetChanges()
{
    StorageLibrary videosLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Videos);
    videosLibrary.ChangeTracker.Enable();
    StorageLibraryChangeReader videoChangeReader = videosLibrary.ChangeTracker.GetChangeReader();
    IReadOnlyList changeSet = await changeReader.ReadBatchAsync();


    //Below this line is for the blog post. Above the line is for the magazine
    foreach (StorageLibraryChange change in changeSet)
    {
        if (change.ChangeType == StorageLibraryChangeType.ChangeTrackingLost)
        {
            //We are in trouble. Nothing else is going to be valid.
            log("Resetting the change tracker");
            videosLibrary.ChangeTracker.Reset();
            return;
        }
        if (change.IsOfType(StorageItemTypes.Folder))
        {
            await HandleFileChange(change);
        }
        else if (change.IsOfType(StorageItemTypes.File))
        {
            await HandleFolderChange(change);
        }
        else if (change.IsOfType(StorageItemTypes.None))
        {
            if (change.ChangeType == StorageLibraryChangeType.Deleted)
            {
                RemoveItemFromDB(change.Path);
            }
        }
    }
    await changeReader.AcceptChangesAsync();
}