보기 변경 내용 처리

가상화 루트 아래의 파일 및 디렉터리를 열면 공급자는 디스크에 자리 표시자를 만들고 파일을 읽을 때 자리 표시자는 콘텐츠로 하이드레이션됩니다. 이러한 자리 표시자는 생성 당시의 백업 저장소 상태를 나타냅니다. 이러한 캐시된 항목은 공급자가 열거형으로 프로젝션한 항목과 결합되어 클라이언트의 백업 저장소 "보기"를 구성합니다. 때때로 공급자는 백업 저장소의 변경으로 인해 또는 사용자가 보기를 변경하기 위해 수행한 명시적 작업으로 인해 클라이언트 보기를 업데이트하려고 할 수 있습니다.

공급자가 뷰를 사전에 업데이트하는 경우 백업 저장소가 변경될 때 보기 변경 내용이 비교적 자주 변경되지 않는 것이 좋습니다. 이는 뷰가 열린 항목으로 변경되어 디스크에 자리 표시자가 만들어졌기 때문에 보기 변경 사항을 반영하기 위해 디스크 내 항목을 업데이트하거나 삭제해야 하기 때문입니다. 자주 업데이트하면 시스템 성능에 부정적인 영향을 미칠 수 있는 추가 디스크 작업이 발생할 수 있습니다.

항목 버전 관리

여러 뷰 유지 관리를 지원하기 위해 ProjFS는 PRJ_PLACEHOLDER_VERSION_INFO 구조체를 제공합니다. 공급자는 PrjMarkDirectoryAsPlaceholder 호출에서 이 구조체 독립 실행형을 사용하며 PRJ_PLACEHOLDER_INFOPRJ_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;
}