2016 年 12 月

第 31 卷,第 13 期

此文章由机器翻译。

通用 Windows 平台 - 通用 Windows 平台应用中的文件系统监视

通过 Adam Wilson | 2016 年 12 月

快速更改的文件系统的 Windows 设备。共享库,例如照相机胶卷,其中在设备上的所有进程可以同时进行都交互使用相同的数据的几个位置之一。构建更完美的体验提供用户的照片的通用 Windows 平台 (UWP) 应用程序意味着您将需要进行研究,深入了解此混乱不堪的局面。

在窗口 10 周年更新中,Microsoft 添加了一些新功能以使管理更容易此混乱不堪的局面。系统现在就可以提供在库中,一直到要删除的整个文件夹执行图片中所发生的所有更改的列表。如果您希望构建云备份提供程序、 跟踪文件移出该设备或甚至只是显示最新的照片,这是一个巨大帮助。

显示最新的照片拍摄一台设备不只是用于开发人员在尝试生成下一步 Instagram。最近我整年都可以生成有关其组织的检查应用程序时,与企业合作伙伴一起工作。应用程序都遵循类似的模式︰ 检查器访问站点与 Windows 平板电脑或手机上,在报表中包含的站点,某些信息填充拍摄照片使用设备的站点,并最后,将报表上载到安全的服务器。对于所有的公司,很重要的正确的、 未经修改的照片会上载的报表。

接下来的几页通过我要介绍构建企业检查应用程序。此过程中我指出的是我在开发过程中遇到的一些非常具有挑战性问题,并注意如何在您的应用程序中避免它们。这些课程也可以应用于任何其他应用程序希望在文件系统中,例如云备份服务跟踪更改,但因为检查应用程序相当常见,我将首先并且可以修改您要构建任何类型的应用程序的代码。

检查︰ 每个人都未它们

跨各种规模和行业的企业,一件事很常见︰ 有监督关键业务流程检查器。从跟踪其设备的状态以确保显示正确组合、 业务所依赖于使确保一切的零售商的大规模生产公司会完成一致和安全地跨所有其站点。

我将以此处支持基本过程是非常简单︰

  1. 检查器在他的平板电脑上创建新的报表实例的站点。
  2. 该检查器拍摄照片的网站的报表。
  3. 将图片上载到与报表一起安全的服务器。

在步骤 2,但是,许多事情可能会出现错误︰

  • 该检查器可以挑选错误的图片将附加到该报表。
  • 无法修改图片,以显示不正确的信息。
  • 图片可能是意外删除,将报表上载之前,但该检查器离开某个位置后删除。

在这种情况下,报表将无效,需要该检查器以重复执行检查,为企业增加的开销。幸运的是,与在 Windows 10 周年更新新的更改跟踪 Api,没有一种用于防止出现这种错误,并帮助用户快速而准确地完成其任务的简单方法。

基础知识: 从照相机获得图片

第一步确保应用程序有权访问来自相机的图片。在 Windows 中,系统摄像机将写入到本机照片文件夹,即图片库的子文件夹。我可以 (并且实际上未) 编写整篇文章有关如何访问图片库 (bit.ly/2dCdj4O),但这是基本︰

  • 声明你想要访问您的清单中的图片库 (bit.ly/2dqoRJX) 通过添加功能名 ="musicLibrary"/ 1> 清单编辑器中或检查在向导中的功能选项卡下的图片库框中。
  • 获取表示正在写入到使用 KnownFolders.CameraRoll 的照相机中的图片的位置 StorageFolder (bit.ly/2dEiVMS)。
  • 获取一个对象,表示整个图片库使用 StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures) (bit.ly/2dmZ85X)。

如果设备具有 SD 卡和用户设置的内部存储或 SD 卡编写新图片,请不要担心。KnownFolder 类将为您的抽象化,并使包括照相机可以将文件写入到的所有位置的虚拟文件夹。

发生更改时收到通知

一旦对文件具有访问权限,就可以开始跟踪所做的更改。我建议尽可能早执行此操作。在理想情况下,检查器将始终创建一个新报表之前他们开始拍摄照片,但在现实生活中他们通常已经少数图片之前它们,请务必创建一个新报表。

设置通知和跟踪的更改涉及三个步骤︰

  1. 正在初始化更改跟踪器,它告知系统您感兴趣跟踪哪些库。即使您的应用程序没有运行,且应用程序可以在任何时候读取更改的列表,将继续跟踪。
  2. 注册在后台,将激活应用程序的后台任务在库中,任何更改时当前正在运行的更改通知。
  3. 注册在前景中的更改通知。如果您的应用程序在前台,您可以注册时在文件被更改的特定范围内的其他事件。

请注意,步骤 2 和 3 可能会重叠。我将介绍如何设置两种类型的这篇文章中的通知,但图表中的 图 1可以帮助您选择您想要在您的应用程序中使用。常规建议是要始终用于 StorageLibraryContentChangeTrigger,背景更改通知并使用前景事件,如果您有特定的 UI 需要,如向用户显示的文件系统的视图。

图 1 类型的更改通知

  前景色更改事件 背景更改通知
使用期限 仅当你的应用运行时可用 即使未在运行您的应用程序将触发后台任务
范围 可自定义对任何文件夹或在系统上的库 命名的库 (图片、 视频、 音乐、 文档)
筛选器 可以进行筛选以引发事件仅对特定文件类型 将引发事件的文件或文件夹的任何更改
触发机制 已命名的事件 背景任务触发器

StorageLibraryChangeTracker 是在周年更新中添加一个新类 (bit.ly/2dMFlfu)。它允许应用程序订阅在库中所发生的更改的列表。系统监视库中的所有文件,并建立列表对其进行操作的更改。您的应用程序可以请求更改的列表,并且在其方便的时候对它们进行处理。

如果您曾经使用过,但它适用于 FAT 格式化的驱动器,以及 StorageLibraryChangeTracker 非常类似于 NTFS 变更日志。有关详细信息您可以阅读我的博客上的深入讨论 (bit.ly/2dQ6MEK)。

初始化 StorageLibraryChangeTracker 是非常简单 — 只是获取更改跟踪器的库特定实例,然后调用启用︰

StorageLibrary picsLib =
  await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
picsLib.ChangeTracker.Enable();

从该点,请更改跟踪器,将保留到库的所有更改的列表。现在我们必须确保您的应用程序获取向您发送通知的每个更改。

侦听前景通知

若要收听前景通知您的应用程序必须创建、 执行和保持打开一个查询通过其更改涉及您的位置。创建和执行该查询告诉系统哪些位置都是向应用程序感兴趣。之后保持对该查询的结果的引用指示您的应用程序想要的内容更改时得到通知︰

StorageFolder photos = KnownFolders.CameraRoll;
// Create a query containing all the files your app will be tracking
QueryOptions option = new QueryOptions(CommonFileQuery.DefaultQuery,
  supportedExtentions);
option.FolderDepth = FolderDepth.Shallow;
// This is important because you are going to use indexer for notifications
option.IndexerOption = IndexerOption.UseIndexerWhenAvailable;
StorageFileQueryResult resultSet =
  photos.CreateFileQueryWithOptions(option);
// Indicate to the system the app is ready to change track
await resultSet.GetFilesAsync(0, 1);
// Attach an event handler for when something changes on the system
resultSet.ContentsChanged += resultSet_ContentsChanged;

如您所见,我使用几个有趣的优化可能会在您的应用程序中有所帮助︰

  • CommonFileQuery.DefaultQuery 在索引器不是可用的情况下使整个操作速度快得多。如果使用另一个排序顺序而且索引器不可用,则系统必须遍历整个查询空间,才会返回第一个结果。
  • 查询为浅表查询。这是因为照相机会始终将写入根目录照相机胶卷中,并避免深层查询降至最低的系统所能做的更改跟踪的文件数。
  • 使用索引器不是必需的但它可加快应用程序中显示的通知。而无需为索引器通知可能需要 30 秒来访问您的应用程序。
  • 尽管您可能想要查询更多的文件,在 UI 需要它们的情况下,查询为一个文件是最快的方法开始跟踪中的索引位置的更改。

现在只要在查询中的项更改时,事件处理程序将会触发,使您的应用程序有机会来处理该更改。

正在注册的背景更改触发器

并非所有更改都将在前台,是您的应用程序,即使您的应用程序在前台中可能不需要的前景色通知粒度时出现。StorageLibraryContentsChangedTrigger 是在库中的任何内容更改时得到通知的好办法。由于注册后台任务使用标准的过程 (bit.ly/2dqKt9i),我将快速通查它 (请参阅 图 2)。

图 2 注册后台任务

// Check if your app has access to the background
var requestStatus = await BackgroundExecutionManager.RequestAccessAsync();
if (!(requestStatus ==
  BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity ||
  requestStatus == BackgroundAccessStatus.AllowedSubjectToSystemPolicy ||
  requestStatus ==
    BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity ||
  requestStatus == BackgroundAccessStatus.AlwaysAllowed))
{
  log("Failed to get access to the background");
  return;
}
// Build up the trigger to fire when something changes in the pictures library
var builder = new BackgroundTaskBuilder();
builder.Name = "Photo Change Trigger";
StorageLibrary picturesLib =
  await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
var picturesTrigger = StorageLibraryContentChangedTrigger.Create(picturesLib);
// We are registering to be activated in OnBackgroundActivated instead of
// BackgroundTask.Run; either works, but I prefer the single-process model
builder.SetTrigger(picturesTrigger);
BackgroundTaskRegistration task = builder.Register();

可以通过多种中的示例中要注意的重要事项 图 2

您仍然可以使用旧的两个进程模型注册您的后台任务,但需要清楚地看到周年更新中添加的单个进程模型。它快速地让我刮目相看结合使用是多么简单和接收背景触发器,而您的应用程序是在前台是多么容易。

图片库中,其中可能包括不是您的应用程序感兴趣的文件中的任何更改的情况下,会触发 StorageLibraryContentChangedTrigger。我将介绍如何在中筛选这些出更高版本的部分中,但始终要注意,有时将不会出现在激活您的应用程序时执行操作。

最好检查是否正在背景或前景中运行的因为资源分配不同的后台任务。您可以找到更详细地处的资源分配模型 bit.ly/2cNvcSr。 

读取所做的更改

现在您的应用程序要在此时每次照相机胶卷,在前台或后台中发生更改时运行代码。要找出发生了什么变化,您需要从 StorageLibraryChangeTracker 读取的变更集。第一步是获取读取器对象,这将允许您枚举的上次检查您的应用程序以来已发生了更改。在你一下,您还可以获取第一批要处理的更改︰

StorageLibrary picturesLib =
  await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);           
StorageLibraryChangeTracker picturesTracker= picturesLib.ChangeTracker;
picturesTracker.Enable();
StorageLibraryChangeReader changeReader = picturesTracker.GetChangeReader();
IReadOnlyList<StorageLibraryChange> changes = await changeReader.ReadBatchAsync();

一旦您获取一组的更改,就可以处理它们。在此应用程序,我仅在要更改的图片上将重点介绍并忽略所有其他文件和文件夹的更改。如果您感兴趣其他类型的系统上的更改,更改跟踪器,我深入了解具有不同类型的更改有关的详细信息 (bit.ly/2dQ6MEK)。

我要遍历所做的更改,并提取出在其中我感兴趣。对于此应用程序,它将会对.jpg 文件的内容的任何更改中所示 图 3

图 3 检查对文件的更改

foreach (StorageLibraryChange change in changes)
{
  if (change.ChangeType == StorageLibraryChangeType.ChangeTrackingLost)
  {
    // Change tracker is in an invalid state and must be reset
    // This should be a very rare case, but must be handled
    picturesLib.ChangeTracker.Reset();
    return;
  }
  if (change.IsOfType(StorageItemTypes.File))
  {
    await ProcessFileChange(change);
  }
  else if (change.IsOfType(StorageItemTypes.Folder))
  {
    // No-op; not interested in folders
  }
  else
  {
    if (change.ChangeType == StorageLibraryChangeType.Deleted)
    {
      UnknownItemRemoved(change.Path);
    }
  }
}
// Mark that all the changes have been seen and for the change tracker
// to never return these changes again
await changeReader.AcceptChangesAsync();

以下是一些有趣的事情中的代码段 图 3

我所做的第一件事是检查 StorageLibraryChangeType.ChangeTrackingLost。应出现此错误仅在一个较大的文件系统操作 (其中系统没有足够的存储区的整个更改集) 之后或在严重的内部故障的情况下。在任一情况下,任何更改跟踪器,从硬盘读出可以再也不受信任。应用程序必须重置更改跟踪器,为将来的结果是值得信任。

UnknownItemRemoved (更改。路径) 将被命中的文件的任何时间或 FAT 分区,例如 SD 卡中删除文件夹。一旦从系统无法判断它是否是一个目录或文件已被删除的 FAT 分区中删除项。我来遍历有点显示中的某些代码,则可以如何找出应用程序中发生了什么情况。

在所有更改都已都处理,可调用 changeReader.AcceptChangesAsync。您的应用程序已处理的所有更改,并且不需要再次看到它们,这会告诉更改跟踪器。下次您创建 StorageLibraryChangeReader 它将包含此点以来已发生了的更改。不调用 AcceptChangesAsync 将导致内部更改缓冲区溢出,导致 StorageLibraryChangeType.ChangeTrackingLost 地填满。

处理更改

既然您知道如何遍历所做的更改下, 一步是充实 ProcessFileChange 方法。要记住的一个关键概念是,打开文件 (通过创建 StorageFile 对象) 的开销极大。若要获取 StorageFile 对象在过程中,系统必须进行进程间调用以检查权限、 创建该文件的句柄、 从磁盘中读取一些有关文件的元数据和随后的句柄和元数据回您的应用程序进程封送处理。因此,您想要尽可能减少 StorageFiles 您创建的尤其是如果不想要打开的文件流。

更改跟踪器提供了您的应用程序使用的路径和创建 StorageFile 之前的更改可帮助您确定文件的类型是否为您的应用程序的类型对感兴趣。首先,通过执行尽可能多筛选尽可能在最前创建 StorageFile,此信息中所示 图 4

图 4 处理更改

private async Task ProcessFileChange(StorageLibraryChange change)
{
  // Temp variable used for instantiating StorageFiles for sorting if needed later
  StorageFile newFile = null;
  switch (change.ChangeType)
  {
    // New File in the Library
    case StorageLibraryChangeType.Created:
    case StorageLibraryChangeType.MovedIntoLibrary:
    case StorageLibraryChangeType.MovedOrRenamed:
      if (change.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase))
      {
        StorageFile image = (StorageFile)(await change.GetStorageItemAsync());
        AddImageToReport(image);                                               
      }                   
      break;
    // File Removed From Library
    case StorageLibraryChangeType.Deleted:
    case StorageLibraryChangeType.MovedOutOfLibrary:
      if (change.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase))
      {
        var args = new FileDeletedWarningEventArgs();
        args.Path = change.Path;
        FileDeletedWarningEvent(this, args);
      }
      break;
    // Modified Contents
    case StorageLibraryChangeType.ContentsChanged:
      if (change.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase))
      {
        newFile = (StorageFile)(await change.GetStorageItemAsync());
        var imageProps = await newFile.Properties.GetImagePropertiesAsync();
        DateTimeOffset dateTaken = imageProps.DateTaken;
        DateTimeOffset dateModified = newFile.DateCreated;
        if (DateTimeOffset.Compare(dateTaken.AddSeconds(70), dateModified) > 0)
        {
          // File was modified by the user
          log("File path: " + newFile.Path + " was modified after being taken");
        }
      }                  
      break;
    // Ignored Cases
    case StorageLibraryChangeType.EncryptionChanged:
    case StorageLibraryChangeType.ContentsReplaced:
    case StorageLibraryChangeType.IndexingStatusChanged:
    default:
      // These are safe to ignore in this application
      break;                   
  }
}

让我们分解这里会发生什么通过查看每个 case 语句。

库中的新文件︰ 在第一条语句中要查找的新文件的已添加到库。对于系统照像机上,该文件将在库中创建,我将看到类型 StorageLibraryChangeType.Created 的更改。某些第三方应用程序实际上在其应用程序数据容器中创建图像,然后将其移到库中,因此我将 StorageLibraryChangeType.MovedIntoLibrary 视为一次创建,太。

我将处理 StorageLibraryChangeType.MovedOrRenamed 作为一个新文件。这是为了解决在 Windows phone 上的内置摄像机奇怪之处。在照相机获得图片时,写出的临时文件结束.jpg。 ~ tmp。更高版本,它将确定该图片,删除。 ~ tmp 扩展。如果您的应用程序比较迅速,以捕获该文件完成照相机之前完成图像,它可能会看到重命名事件而不是创建事件。

由于此应用程序将要感只有在用户所执行的图片,我将向仅具有.jpg 扩展名的文件进行筛选。无法执行此操作通过创建一个 StorageFile 并检查其 ContentType 属性中,但我正在尝试避免创建不需要的 StorageFiles。一旦我知道文件便是其中我感兴趣,我将移交文件到另一种方法来执行某些数据处理。

我使用的强制转换而不作为关键字因为我已经提过 StorageLibraryChange 将使用哪种类型的邮件︰ StorageLibraryChange.IsOfType(StorageItemTypes.File)。

从库中删除的文件︰ 在第二种情况下,应用程序在其中一个文件已被从库中删除的情况下查找。您将注意到,我已经两种不同的更改类型一起重新组合。每当从磁盘,即删除使用文件系统 Api 的典型事例永久删除文件,将会引发 StorageLibraryChangeType.Deleted。但是,如果用户手动删除该文件从文件资源管理器相反,该文件将发送到回收站中。此时将显示为 StorageLibraryChangeType.MovedOutOfLibrary 因为该文件是仍驻留在磁盘上。

在这种情况下,我要在已意外删除的情况下引发警告到该文件已不再存在,该检查器。在更多注重安全的应用程序,它可能会存储文件的删除或修改为更高版本发生了调查审核有意义。

修改内容︰ 正在修改的文件的内容是一个有趣的情形,此应用程序。由于此应用程序可能用于安全检查,您不想允许用户更改的图像之前上载到报表中,虽然可能有正当理由的映像的内容之后拍摄图片进行更改。

您可能会看到 StorageLibraryChangeType.ContentsChanged 引发大约三到 60 秒后使用照相机拍摄图片种类型的通知。这是因为它有时会占用一分钟的时间要获取其坐标从 GPS 系统。此应用程序中我不关心 GPS 信息因此我将立即处理文件。在某些应用程序,它可能会有用检查是否位置数据写入该文件,以及如果没有,则等待,直到确定位置。

在这种情况下,我要继续下一步很不安全的折中方案。如果修改了某个文件后拍摄多个 70 秒内,我将假定它已被用户修改并注销以后调查的法规遵从性问题。如果在 70-第二个窗口中进行过修改,我假设这通过系统,添加 GPS 数据时,可以安全地忽略。

更改加密︰ 是否应关心这种情况下要归结到一个问题︰ 没有您的企业使用 Windows 信息保护 (WIP) 使用加密来保护其敏感信息? 如果出现这种情况,这种更改是一个巨大的问题。它意味着人更改了该文件上的保护该文件可以坐在未受保护磁盘上。遍历所有有关如何检查该文件是否安全 (而不只是被移动到另一个保护级别) 的 WIP 信息已超出本文的讨论范围。如果你在企业中部署 WIP,这种更改是重要的监视。

对于那些不使用 WIP 或加密来保护敏感的公司资产,我强烈建议您深入了解它,但现在这种更改是放心地忽略。

已忽略的情况下︰ 最后,在此应用程序最好忽略一些可能在文件系统发生的情况。在桌面应用程序将执行重复的查询的库和每次需要相同的结果的情况下仅重要 StorageLibraryChangeType.IndexingStatusChanged。StorageLibraryChangeType.ContentsReplaced 指示该文件的硬链接已被更改,这并不为此应用程序感兴趣。

检测到 SD 卡上的图像的删除

当您回调时,系统将无法确定是否已移除的项是一个文件或文件夹后已在 FAT 上删除驱动器如 SD 卡。因此,如果您关心在 FAT 驱动器上的删除操作,则需要一些特殊的分类代码。我的应用程序,我只想知道是否正在删除图片从 SD 卡,这会使代码非常简单︰

private void UnknownItemRemoved(StorageLibraryChange change)
{
  if (change.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase))
  {
    var args = new FileDeletedWarningEventArgs();
    args.Path = change.Path;
    FileDeletedWarningEvent(this, args);
  }
}

该代码只需将检查以查看使用的项以具有.jpg 扩展名,并如果是这样引发相同的用户界面事件,如下所示图 4

结论

一次的基本代码的位置,在有几个值得考虑的事项,可帮助确保新的应用程序将尽可能顺畅地运行。

您的配置体验 ! 这是建议的最重要我可以为您提供有关创建的应用程序跟踪文件系统的更改。编写快速应用程序日志中记录发生在你的设备上之前您深入研究编码。许多中的组件尝试逃避写出临时文件,并且尝试快速删除它们,如我前面讨论的内置摄像机。我甚至已经看到了广泛的应用程序已编写出不完整的文件,关闭句柄,然后立即重新打开的句柄,以完成写出到磁盘文件的情况。与更改跟踪应用程序,您不仅可以转到开始查看所有这些更改,但可能获得这些操作的中间。请务必要系统,这意味着了解和尊重其他应用程序将尝试执行一个好公民。

请记住,因为它已关闭,并且在基于 FAT 文件系统上没有无日志记录时,可以从设备中删除 SD 卡,更改跟踪不能保证在 SD 卡上跨多个启动会话。如果您的企业都有非常严格要求有关确保该文件未被篡改,请考虑使用移动设备管理策略以强制加密的照相机图像都写入到仅限于内部存储。这将确保数据受到保护静止,对文件的所有更改都考虑都在内的。

总结

就是这样。检查应用现已准备就绪可以自动将文件添加它们在系统上,创建时,当其中一个被删除时通知用户。虽然它可能看上去很简单,启用这种体验,用户可以专注于其检查而不是选取的系统选择器体验的缩略图。如果该检查器不断地拍摄照片的外观类似的设备,会自动在报表中包括的最新映像可以极大地减少成本企业时间和金钱的错误。最重要的是,它可以帮助您针对 Windows 脱颖而出在企业应用程序的内容很多字段之间的应用程序。


Adam Wilson是 Windows 开发人员生态系统和平台团队的项目经理,从事 Windows 索引器和推送通知。 您可以通过 Adam.D.Wilson@microsoft.com

衷心感谢以下 Microsoft 技术专家对本文的审阅: Brendan Flynn、 Mary Anne Noskowski 和 Kyle 元