列舉檔案和目錄
當提供者第一次建立虛擬化根目錄時,本機系統上是空的。 也就是說,備份資料存放區中的任何專案都尚未快取至磁片。 當每個專案開啟時,都會快取它,但在開啟專案之前,它只會存在於支援資料存放區中。
由於未開啟的專案沒有本機存在,所以一般目錄列舉不會向使用者顯示其存在。 因此,提供者必須藉由將 ProjFS 的相關資訊傳回其備份資料存放區中的專案,以參與目錄列舉。 ProjFS 會將來自提供者的資訊與本機檔案系統上的資訊合併,以向使用者呈現目錄內容的統一清單。
目錄列舉回呼
若要參與目錄列舉,提供者必須實作 PRJ_START_DIRECTORY_ENUMERATION_CB、 PRJ_GET_DIRECTORY_ENUMERATION_CB和 PRJ_END_DIRECTORY_ENUMERATION_CB 回呼。 當提供者的虛擬化根目錄被列舉時,ProjFS 會執行下列動作:
ProjFS 會呼叫提供者 的PRJ_START_DIRECTORY_ENUMERATION_CB 回呼,以啟動列舉會話。
在處理此回呼時,提供者應該準備執行列舉。 例如,它應該配置任何可能需要在其支援資料存放區中追蹤列舉進度的記憶體。
ProjFS 會識別在回呼 callbackData參數的FilePathName成員中列舉的目錄。 這會指定為相對於虛擬化根目錄的路徑。 例如,如果虛擬化根位於
C:\virtRoot
,且列舉的目錄為C:\virtRoot\dir1\dir2
, 則 FilePathName 成員將包含dir1\dir2
。 提供者會準備在其備份資料存放區中列舉該路徑。 根據提供者備份存放區的功能,在 PRJ_START_DIRECTORY_ENUMERATION_CB 回呼中執行備份存放區列舉可能很合理。由於多個列舉可能會在相同的目錄中平行發生,因此每個列舉回呼都有 enumerationId 引數,可讓提供者將回呼調用關聯至單一列舉會話。
如果提供者從PRJ_START_DIRECTORY_ENUMERATION_CB回呼傳回S_OK,它必須維護它擁有的任何列舉會話資訊,直到 ProjFS 叫用與enumerationId相同值的PRJ_END_DIRECTORY_ENUMERATION_CB回呼為止。 如果提供者從PRJ_START_DIRECTORY_ENUMERATION_CB傳回錯誤,ProjFS 將不會叫用該enumerationId的PRJ_END_DIRECTORY_ENUMERATION_CB回呼。
ProjFS 會呼叫提供者 的PRJ_GET_DIRECTORY_ENUMERATION_CB 回呼一或多次,以從提供者取得目錄內容。
為了回應此回呼,提供者會從其備份資料存放區傳回已排序的專案清單。
回呼可能會在其 searchExpression 參數中提供搜尋運算式,提供者會用來限定列舉的結果範圍。 如果 searchExpression 為 Null,提供者會傳回要列舉之目錄中的所有專案。 如果是非 Null,提供者可以使用 PrjDoesNameContainWildCards 來判斷運算式中是否有萬用字元。 如果有的話,提供者會使用 PrjFileNameMatch 來判斷目錄中的專案是否符合搜尋運算式。
提供者應該在此回呼的第一個調用上擷取 searchExpression 的值。 它會在相同列舉會話中回呼的任何後續調用上使用擷取的值,除非在回呼的 callbackData參數的Flags欄位中設定PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN。 在此情況下,它應該重新擷取 searchExpression的值。
如果其備份存放區中沒有相符的專案,提供者只會從回呼傳回S_OK。 否則,提供者會將每個相符專案從其支援存放區格式化為 PRJ_FILE_BASIC_INFO 結構,然後使用 PrjFillDirEntryBuffer 填滿回呼 的 dirEntryBufferHandle 參數所提供的緩衝區。 提供者會持續新增專案,直到其全部新增,或 直到 PrjFillDirEntryBuffer 傳回HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER) 為止。 然後提供者會從回呼傳回S_OK。 請注意,提供者必須以正確的排序次序,將專案新增至 dirEntryBufferHandle 緩衝區。 提供者應該使用 PrjFileNameCompare 來判斷正確的排序次序。
提供者必須為PRJ_FILE_BASIC_INFO的IsDirectory成員提供值。 如果 IsDirectory 為 FALSE,提供者也必須提供 FileSize 成員的值。
如果提供者未提供 CreationTime、 LastAccessTime、 LastWriteTime和 ChangeTime 成員的值,ProjFS 將會使用目前的系統時間。
ProjFS 將使用提供者在FileAttributes成員中設定的任何FILE_ATTRIBUTE旗標,但FILE_ATTRIBUTE_DIRECTORY除外;它會根據IsDirectory成員所提供的值,在FileAttributes成員中設定FILE_ATTRIBUTE_DIRECTORY的正確值。
如果 PrjFillDirEntryBuffer 傳回HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER) 提供者用完要新增的專案之前,提供者必須追蹤在收到錯誤時嘗試新增的專案。 下次 ProjFS 呼叫相同列舉會話 的PRJ_GET_DIRECTORY_ENUMERATION_CB 回呼時,提供者必須繼續將專案新增至 dirEntryBufferHandle 緩衝區,並嘗試新增的專案。
如果支援存放區支援符號連結,提供者必須使用 PrjFillDirEntryBuffer2 來填滿回呼 的 dirEntryBufferHandle 參數所提供的緩衝區。 PrjFillDirEntryBuffer2 支援額外的緩衝區輸入,可讓提供者指定列舉專案是符號連結及其目標。 否則,其行為如 上述 PrjFillDirEntryBuffer所述。 下列範例使用 PrjFillDirEntryBuffer2 來說明符號連結的支援。
請注意,自 Windows 10 版本 2004 起,支援PrjFillDirEntryBuffer2。 提供者應該探查 PrjFillDirEntryBuffer2是否存在,例如使用 GetProcAddress。
typedef struct MY_ENUM_ENTRY MY_ENUM_ENTRY; typedef struct MY_ENUM_ENTRY { MY_ENUM_ENTRY* Next; PWSTR Name; BOOLEAN IsDirectory; INT64 FileSize; BOOLEAN IsSymlink; PWSTR SymlinkTarget; } MY_ENUM_ENTRY; typedef struct MY_ENUM_SESSION { GUID EnumerationId; PWSTR SearchExpression; USHORT SearchExpressionMaxLen; BOOLEAN SearchExpressionCaptured; MY_ENUM_ENTRY* EnumHead; MY_ENUM_ENTRY* LastEnumEntry; BOOLEAN EnumCompleted; } MY_ENUM_SESSION; HRESULT MyGetEnumCallback( _In_ const PRJ_CALLBACK_DATA* callbackData, _In_ const GUID* enumerationId, _In_opt_z_ PCWSTR searchExpression, _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle ) { // MyGetEnumSession is a routine the provider might implement to find // information about the enumeration session that it first stored // when processing its PRJ_START_DIRECTORY_ENUMERATION_CB callback. // // In this example the PRJ_START_DIRECTORY_ENUMERATION_CB callback has // already retrieved a list of the items in the backing store located at // callbackData->FilePathName, sorted them using PrjFileNameCompare() // to determine collation order, and stored the list in session->EnumHead. MY_ENUM_SESSION* session = NULL; session = MyGetEnumSession(enumerationId); if (session == NULL) { return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); } if (!session->SearchExpressionCaptured || (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN)) { if (searchExpression != NULL) { if (wcsncpy_s(session->SearchExpression, session->SearchExpressionMaxLen, searchExpression, wcslen(searchExpression))) { // Failed to copy the search expression; perhaps the provider // could try reallocating session->SearchExpression. } } else { if (wcsncpy_s(session->SearchExpression, session->SearchExpressionMaxLen, "*", 1)) { // Failed to copy the search expression; perhaps the provider // could try reallocating session->SearchExpression. } } session->SearchExpressionCaptured = TRUE; } MY_ENUM_ENTRY* enumHead = NULL; // We have to start the enumeration from the beginning if we aren't // continuing an existing session or if the caller is requesting a restart. if (((session->LastEnumEntry == NULL) && !session->EnumCompleted) || (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN)) { // Ensure that if the caller is requesting a restart we reset our // bookmark of how far we got in the enumeration. session->LastEnumEntry = NULL; // In case we're restarting ensure we don't think we're done. session->EnumCompleted = FALSE; // We need to start considering items from the beginning of the list // retrieved from the backing store. enumHead = session->EnumHead; } else { // We are resuming an existing enumeration session. Note that // session->LastEnumEntry may be NULL. That is okay; it means // we got all the entries the last time this callback was invoked. // Returning S_OK without adding any new entries to the // dirEntryBufferHandle buffer signals ProjFS that the enumeration // has returned everything it can. enumHead = session->LastEnumEntry; } if (enumHead == NULL) { // There are no items to return. Remember that we've returned everything // we can. session->EnumCompleted = TRUE; } else { MY_ENUM_ENTRY* thisEntry = enumHead; int entriesAdded = 0; while (thisEntry != NULL) { // We'll insert the entry into the return buffer if it matches // the search expression captured for this enumeration session. if (PrjFileNameMatch(thisEntry->Name, session->SearchExpression)) { PRJ_FILE_BASIC_INFO fileBasicInfo = {}; fileBasicInfo.IsDirectory = thisEntry->IsDirectory; fileBasicInfo.FileSize = thisEntry->FileSize; // If our backing store says this is really a symbolic link, // set up to tell ProjFS that it is a symlink and what its // target is. PRJ_EXTENDED_INFO extraInfo = {}; if (thisEntry->IsSymlink) { extraInfo.InfoType = PRJ_EXT_INFO_SYMLINK; extraInfo.Symlink.TargetName = thisEntry->SymlinkTarget; } // Format the entry for return to ProjFS. HRESULT fillResult = PrjFillDirEntryBuffer2(dirEntryBufferHandle, thisEntry->Name, &fileBasicInfo, thisEntry->IsSymlink ? &extraInfo : NULL); if (fillResult == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { if (entriesAdded > 0) { // We couldn't add this entry to the buffer; remember where we left // off for the next time we're called for this enumeration session. session->LastEnumEntry = thisEntry; return S_OK; } else { // The buffer should always have enough space for at least one entry, but // we could not add even one entry. Return the error. return fillResult; } } else { // Any other error is unexpected. NOTE: Be sure to check for E_INVALIDARG to // ensure you are calling PrjFillDirEntryBuffer2 correctly. return fillResult; } ++entriesAdded; } thisEntry = thisEntry->Next; } // We reached the end of the list of entries; remember that we've returned // everything we can. session->EnumCompleted = TRUE; } return S_OK; }
ProjFS 會呼叫提供者 的PRJ_END_DIRECTORY_ENUMERATION_CB 回呼,以結束列舉會話。
提供者應該執行它必須執行的任何清除,以結束列舉會話,例如解除配置配置做為處理PRJ_START_DIRECTORY_ENUMERATION_CB的一部分 的記憶體。