ビューの変更の処理

仮想化ルートの下のファイルとディレクトリが開かれると、プロバイダーはディスク上にプレースホルダーを作成し、ファイルが読み取られたらプレースホルダーはコンテンツでハイドレートされます。 これらのプレースホルダーは、作成時のバッキング ストアの状態を表します。 これらのキャッシュされたアイテムは、列挙体のプロバイダーによって予測される項目と組み合わせて、バッキング ストアのクライアントの "ビュー" を構成します。 プロバイダーは、バッキング ストアの変更が原因であるか、ユーザーがビューを変更するために明示的なアクションを実行したかに関係なく、クライアントのビューを更新する必要がある場合があります。

プロバイダーがビューをプロアクティブに更新する場合は、バッキング ストアの変更に合わせてください。ビューの変更は比較的頻度が低くすることをお勧めします。 これは、開かれたアイテムに対してビューが変更されるため、ディスク上にプレースホルダーが作成されているため、ビューの変更を反映するために、ディスク上のアイテムを更新または削除する必要があるためです。 頻繁に更新すると、余分なディスク アクティビティが発生し、システムのパフォーマンスに悪影響を及ぼす可能性があります。

アイテムのバージョン管理

複数のビューの維持をサポートするために、ProjFS は PRJ_PLACEHOLDER_VERSION_INFO 構造体を提供します。 プロバイダーは 、PrjMarkDirectoryAsPlaceholder の呼び出しでこの構造体をスタンドアロンで使用し、 PRJ_PLACEHOLDER_INFO および PRJ_CALLBACK_DATA 構造体に埋め込まれています。 PRJ_PLACEHOLDER_VERSION_INFO。ContentID フィールドは、プロバイダーがファイルまたはディレクトリの最大 128 バイトのバージョン情報を格納する場所です。 プロバイダーは、この値を特定のビューを識別する値に内部的に関連付けることができます。

たとえば、ソース管理リポジトリを仮想化するプロバイダーは、ファイルのコンテンツのハッシュを使用して ContentID として機能し、タイムスタンプを使用してさまざまな時点でリポジトリのビューを識別できます。 タイムスタンプは、リポジトリへのコミットが実行された時刻である可能性があります。

タイムスタンプインデックス付きリポジトリの例を使用すると、アイテムの ContentID を使用するための一般的なフローは次のようになります。

  1. クライアントは、リポジトリの内容を表示するタイムスタンプを指定してビューを確立します。 これは、ProjFS API ではなく、プロバイダーによって実装されたいくつかのインターフェイスを介して行われます。 たとえば、プロバイダーには、ユーザーが望むビューをプロバイダーに指示するために使用できるコマンド ライン ツールが含まれている場合があります。
  2. クライアントが仮想化されたファイルまたはディレクトリへのハンドルを作成すると、プロバイダーはバッキング ストアのファイル システム メタデータを使用してプレースホルダーを作成します。 使用するメタデータの特定のセットは、目的のビューのタイムスタンプに関連付けられている ContentID 値によって識別されます。 プロバイダーは PrjWritePlaceholderInfo を呼び出してプレースホルダー情報を書き込むとき、 引数 placeholderInfo の VersionInfo メンバーに ContentID を指定します。 プロバイダーは、そのファイルまたはディレクトリのプレースホルダーがこのビューで作成されたことを記録する必要があります。
  3. クライアントがプレースホルダーから読み取ると、プロバイダーの PRJ_GET_FILE_DATA_CB コールバックが呼び出されます。 callbackData パラメーターの VersionInfo メンバーには、ファイル プレースホルダーの作成時に PrjWritePlaceholderInfo で指定されたプロバイダーの ContentID が含まれています。 プロバイダーはその ContentID を使用して、バッキング ストアからファイルの正しい内容を取得します。
  4. クライアントは、新しいタイムスタンプを指定してビューの変更を要求します。 前のビューで作成されたプロバイダーのプレースホルダーごとに、そのファイルの別のバージョンが新しいビューに存在する場合、プロバイダーはディスク上のプレースホルダーを、 PrjUpdateFileIfNeeded を呼び出して ContentID が新しいタイムスタンプに関連付けられている更新されたプレースホルダーに置き換えることができます。 または、プロバイダーは PrjDeleteFile を呼び出してプレースホルダーを削除して、ファイルの新しいビューが列挙に投影されるようにすることもできます。 新しいビューにファイルが存在しない場合、プロバイダーは PrjDeleteFile を呼び出してファイルを削除する必要があります。

プロバイダーは、クライアントがディレクトリを列挙するときに、クライアントのビューに対応するコンテンツをプロバイダーが提供することを確認する必要があることに注意してください。 たとえば、プロバイダーは、PRJ_START_DIRECTORY_ENUMERATION_CBcallbackData パラメーターの VersionInfo メンバーを使用し、コールバックをPRJ_GET_DIRECTORY_ENUMERATION_CBして、すぐにディレクトリの内容を取得できます。 または、ビューの確立の一環として、そのビューのディレクトリ構造をバッキング ストアに構築し、その構造を列挙するだけです。

プレースホルダーまたは部分ファイルに対してプロバイダー コールバックが呼び出されるたびに、アイテムの作成時に指定されたプロバイダーのバージョン情報は、コールバックの callbackData パラメーターの VersionInfo メンバーに提供されます。

ビューの更新

ユーザーが新しいタイムスタンプを指定したときにプロバイダーがローカル ビューを更新する方法を示すサンプル関数を次に示します。 この関数は、ユーザーが変更したばかりのビューに対してプロバイダーが作成したプレースホルダーの一覧を取得し、新しいビューの各プレースホルダーの状態に基づいてローカル キャッシュの状態を更新します。 わかりやすくするために、このルーチンでは、ファイル システムの処理に関する特定の複雑さが省略されています。 たとえば、古いビューでは、特定のディレクトリが一部のファイルまたはディレクトリと共に存在していた可能性がありますが、新しいビューでは、ディレクトリ (およびその内容) が存在しなくなる可能性があります。 このような状況でエラーが発生しないように、プロバイダーは仮想化ルートの下にあるファイルとディレクトリの状態を深さ優先で更新する必要があります。

typedef struct MY_ON_DISK_PLACEHOLDER MY_ON_DISK_PLACEHOLDER;

typedef struct MY_ON_DISK_PLACEHOLDER {
    MY_ON_DISK_PLACEHOLDER* Next;
    PWSTR RelativePath;
} MY_ON_DISK_PLACEHOLDER;

HRESULT
MyViewUpdater(
    _In_ PRJ_CALLBACK_DATA callbackData,
    _In_ time_t newViewTime
    )
{
    HRESULT hr = S_OK;

    // MyGetOnDiskPlaceholders is a routine the provider might implement to produce
    // a pointer to a list of the placeholders the provider has created in the view.
    MY_ON_DISK_PLACEHOLDER* placeholder = MyGetOnDiskPlaceholders();
    while (placeholder != NULL)
    {
        // MyGetProjectedFileInfo is a routine the provider might implement to
        // determine whether a given file exists in its backing store at a
        // particular timestamp.  If it does, the routine returns a pointer to
        // a PRJ_PLACEHOLDER_INFO struct populated with information about the
        // file.
        PRJ_PLACEHOLDER_INFO* newViewPlaceholderInfo = NULL;
        UINT32 newViewPlaceholderInfoSize = 0;

        newViewPlaceholderInfo = MyGetProjectedFileInfo(placeholder->RelativePath,
                                                 newViewTime,
                                                 &newViewPlaceholderInfoSize);
        if (newViewPlaceholderInfo == NULL)
        {
            // The file no longer exists in the new view.  We want to remove its
            // placeholder from the local cache.
            PRJ_UPDATE_FAILURE_CAUSES deleteFailureReason;
            hr = PrjDeleteFile(callbackData->NamespaceVirtualizationContext,
                               placeholder->RelativePath,
                               PRJ_UPDATE_ALLOW_DIRTY_METADATA
                               | PRJ_UPDATE_ALLOW_DIRTY_DATA
                               | PRJ_UPDATE_ALLOW_TOMBSTONE,
                               &deleteFailureReason);

            if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION))
            {
                wprintf(L"Could not delete [%ls].\n", placeholder->RelativePath);
                wprintf(L"Failure reason: 0x%x", deleteFailureReason);
                return hr;
            }
            else
            {
                wprintf(L"Error deleting [%ls]: 0x%08x",
                        placeholder->RelativePath,
                        hr);
                return hr;
            }

            // MyRemovePlaceholderData is a routine the provider might implement
            // to remove an item from its list of placeholders it has created in
            // the view.
            MyRemovePlaceholderData(placeholder);
        }
        else
        {
            // The file exists in the new view.  Its local cache state may need
            // to be updated, for example if its content is different in this view.
            // As an optimization, the provider may compare the file's ContentID
            // in the new view (stored in newViewPlaceholderInfo->ContentId) with
            // the ContentID it had in the old view.  If they are the same, calling
            // PrjUpdateFileIfNeeded would not be needed.
            PRJ_UPDATE_FAILURE_CAUSES updateFailureReason;
            hr = PrjUpdateFileIfNeeded(callbackData->NamespaceVirtualizationContext,
                                       placeholder->RelativePath,
                                       newViewPlaceholderInfo,
                                       newViewPlaceholderInfoSize,
                                       PRJ_UPDATE_ALLOW_DIRTY_METADATA
                                       | PRJ_UPDATE_ALLOW_DIRTY_DATA
                                       | PRJ_UPDATE_ALLOW_TOMBSTONE,
                                       &updateFailureReason);

            if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION))
            {
                wprintf(L"Could not update [%ls].\n", placeholder->RelativePath);
                wprintf(L"Failure reason: 0x%x", updateFailureReason);
                return hr;
            }
            else
            {
                wprintf(L"Error updating [%ls]: 0x%08x",
                        placeholder->RelativePath,
                        hr);
                return hr;
            }

            // MyUpdatePlaceholderData is a routine the provider might implement
            // to update information about a placeholder it has created in the view.
            MyUpdatePlaceholderData(placeholder, newViewPlaceholderInfo);
        }

        placeholder = placeholder->Next;
    }

    return hr;
}