處理檢視變更
當虛擬化根目錄下的檔案和目錄開啟時,提供者會在磁片上建立預留位置,而且當讀取檔案時,預留位置會與內容一起凍結。 這些預留位置代表建立備份存放區時的狀態。 這些快取專案會結合列舉中提供者所投影的專案,構成用戶端的備份存放區的「檢視」。 從一開始,提供者可能會想要更新用戶端的檢視,無論是因為備份存放區中的變更,還是因為使用者採取明確的動作來變更其檢視。
如果提供者主動更新檢視,因為備份存放區變更,建議檢視變更相對不常。 這是因為已開啟之專案的檢視變更,因此在磁片上建立預留位置時,需要更新或刪除內部磁片專案以反映檢視變更。 經常更新可能會導致額外的磁片活動對系統效能造成負面影響。
專案版本控制
為了支援維護多個檢視,ProjFS 提供 PRJ_PLACEHOLDER_VERSION_INFO 結構。 提供者會在 呼叫 PrjMarkDirectoryAsPlaceholder時使用此獨立結構,並內嵌在 PRJ_PLACEHOLDER_INFO 和 PRJ_CALLBACK_DATA 結構中。 PRJ_PLACEHOLDER_VERSION_INFO。ContentID 欄位是提供者儲存檔案或目錄最多 128 個位元組的版本資訊的位置。 提供者接著可以在內部將此值與識別特定檢視的某個值產生關聯。
例如,虛擬化原始檔控制存放庫的提供者可能會選擇使用檔案內容的雜湊作為 ContentID,而且可能會使用時間戳來識別不同時間點的存放庫檢視。 時間戳記可能是執行認可至存放庫的時間。
使用時間戳索引存放庫的範例,使用專案的 ContentID 的一般流程可能是:
- 用戶端會藉由指定要呈現存放庫內容的時間戳記來建立檢視。 這會透過提供者實作的一些介面來完成,而不是透過 ProjFS API。 例如,提供者可能會有命令列工具,可用來告訴提供者使用者所需的檢視。
- 當用戶端建立虛擬化檔案或目錄的控制碼時,提供者會使用備份存放區的檔案系統中繼資料,為其建立預留位置。 它所使用的特定元資料集是由與所需檢視時間戳記相關聯的 ContentID 值所識別。 當提供者呼叫 PrjWritePlaceholderInfo 來寫入預留位置資訊時,它會在 預留位置Info 引數的 VersionInfo 成員中提供 ContentID。 然後提供者應該記錄該檔案或目錄的預留位置已在此檢視中建立。
- 當用戶端從預留位置讀取時,會叫用提供者 的PRJ_GET_FILE_DATA_CB 回呼。 callbackData參數的 VersionInfo 成員包含建立檔案預留位置時,PrjWritePlaceholderInfo中指定的提供者 ContentID。 提供者會使用該 ContentID 從其備份存放區擷取檔案的正確內容。
- 用戶端會藉由指定新的時間戳記來要求檢視變更。 對於在上一個檢視中建立的每個預留位置,如果新檢視中存在不同版本的該檔案,提供者可能會藉由呼叫 PrjUpdateFileIfNeeded,以更新的預留位置取代磁片上的預留位置,其 ContentID 與新的時間戳記相關聯。 或者,提供者可以藉由呼叫 PrjDeleteFile 來刪除預留位置,以便將檔案的新檢視投影在列舉中。 如果檔案不存在於新檢視中,提供者必須藉由呼叫 PrjDeleteFile將其刪除。
請注意,提供者必須確保當用戶端列舉目錄時,提供者會提供對應至用戶端檢視的內容。 例如,提供者可以使用PRJ_START_DIRECTORY_ENUMERATION_CB的callbackData參數 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;
}