在后台跟踪文件系统的更改

重要的应用程序接口(API)

StorageLibraryChangeTracker 类允许应用在用户在系统中移动文件和文件夹时,跟踪这些文件和文件夹的更改。 使用 StorageLibraryChangeTracker 类,应用可以跟踪:

  • 文件操作,包括添加、删除、修改。
  • 文件夹操作如重命名和删除。
  • 驱动器上的文件和文件夹正在移动。

使用本指南可了解用于处理更改跟踪器的编程模型,查看一些示例代码,并了解 StorageLibraryChangeTracker 跟踪的不同类型的文件作。

StorageLibraryChangeTracker 适用于用户库或本地计算机上的任何文件夹。 这包括辅助驱动器或可移动驱动器,但不包括 NAS 驱动器或网络驱动器。

使用变更跟踪功能

更改跟踪器在系统上实现为一个循环缓冲区,存储最后 N 次文件系统操作。 应用可以从缓冲区读取更改,然后将其处理成自己的体验。 应用完成更改后,它会将更改标记为已处理,并且永远不会再次看到这些更改。

若要对文件夹使用更改跟踪器,请执行以下步骤:

  1. 为文件夹启用更改跟踪。
  2. 等待更改。
  3. 查看更改
  4. 接受更改。

下一部分将演练每个步骤,其中包含一些代码示例。 本文末尾提供了完整的代码示例。

启用更改跟踪功能

应用需要做的第一件事是告诉系统,它有兴趣跟踪给定库的更改。 它通过在相关库的变更跟踪器上调用 启用 方法来执行此操作。

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

一些重要说明:

  • 在创建 StorageLibrary 对象之前,请确保应用有权访问清单中的正确库。 有关更多详细信息,请参阅 文件访问权限
  • 启用 是线程安全的,不会重置指针,可以根据需要多次调用(稍后对此进行更多作)。

启用空更改跟踪器

等待更改

初始化更改跟踪器后,它将开始记录库内发生的所有操作,即使在应用未运行时也是如此。 应用可以通过为 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();
}