From 8481e24e301431fc707f3ed27af8093f6087039f Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Fri, 26 Dec 2025 18:16:32 -0800 Subject: [PATCH 01/10] updating to P4API 25.2 --- external/OpenSSL/OpenSSL.Module.cs | 4 ++-- external/P4API/P4API.Module.cs | 2 +- source/P4VFS.Console/P4VFS.Notes.txt | 12 ++++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/external/OpenSSL/OpenSSL.Module.cs b/external/OpenSSL/OpenSSL.Module.cs index 482c690..d30a5e5 100644 --- a/external/OpenSSL/OpenSSL.Module.cs +++ b/external/OpenSSL/OpenSSL.Module.cs @@ -12,7 +12,7 @@ namespace Microsoft.P4VFS.External { public class OpensslModule : Module { - private const string OPENSSL_VERSION = "3.3.3"; + private const string OPENSSL_VERSION = "3.0.18"; private const string PERL_PACKAGE_NAME = "StrawberryPerl"; private const string PERL_VERSION = "5.28.0.1"; @@ -75,7 +75,7 @@ private void BuildOpensslLibrary(string opensslArchiveFolder, string opensslTarg $"@ECHO ON", $"CALL \"{vcvarsScriptPath}\"", $"CD /D \"{opensslArchiveFolder}\"", - $"\"{perlExe}\" Configure VC-WIN64A no-asm no-apps no-tests \"--prefix={opensslConfigurationFolder}\" \"--openssldir={opensslConfigurationFolder}-ssl\" --{configuration}", + $"\"{perlExe}\" Configure VC-WIN64A no-asm no-tests \"--prefix={opensslConfigurationFolder}\" \"--openssldir={opensslConfigurationFolder}-ssl\" --{configuration}", $"IF %ERRORLEVEL% NEQ 0 EXIT /B 1", $"nmake clean", $"IF %ERRORLEVEL% NEQ 0 EXIT /B 1", diff --git a/external/P4API/P4API.Module.cs b/external/P4API/P4API.Module.cs index 71799be..9c9df93 100644 --- a/external/P4API/P4API.Module.cs +++ b/external/P4API/P4API.Module.cs @@ -12,7 +12,7 @@ namespace Microsoft.P4VFS.External { public class P4apiModule : Module { - private const string P4API_VERSION = "r24.1"; + private const string P4API_VERSION = "r25.2"; private const string P4API_VISUAL_STUDIO_EDITION = "2022"; public override string Name diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 0b968ce..6ab0dae 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -1,5 +1,17 @@ Microsoft P4VFS Release Notes +Version [1.29.4.0] +* Updating to latest P4API 25.2 +* Migrating to OpenSSL 3.0 LTS as recommended by P4API 25.2 with latest 3.0.18 +* Addition of configuration setting MaxDiff2StatFileCount setting for specifying + the maximum number of files for when fstat commands should be used instead of diff2 + for determining the have & head filetype of files to sync. Using fstat can be + significantly faster than diff2 when only a few files are updated under a large + file spec. A MaxDiff2StatFileCount of zero (default) will use legacy behavior to only use + diff2. A MaxDiff2StatFileCount of less than zero will always use fstat. A + MaxDiff2StatFileCount of greater than zero will use fstat if the number of modifications + are less than the value. + Version [1.29.3.0] * Perforce SSO login from URL now uses impersonated p4vfs login command and ShellExecute instead of using cmd.exe. This fixes rare case lingering cmd.exe processes from failed From dd837c936d0f46ffc002effdb5e80cd49e8c78e0 Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Mon, 29 Dec 2025 17:10:51 -0800 Subject: [PATCH 02/10] isolated diff2stat utility for customization --- source/P4VFS.Core/Include/DepotClient.h | 2 +- source/P4VFS.Core/Include/DepotOperations.h | 8 ++ source/P4VFS.Core/Include/DepotResult.h | 18 +++ source/P4VFS.Core/Include/SettingManager.h | 3 +- source/P4VFS.Core/Source/DepotOperations.cpp | 142 +++++++++++-------- source/P4VFS.Core/Source/DepotResult.cpp | 16 +++ 6 files changed, 129 insertions(+), 60 deletions(-) diff --git a/source/P4VFS.Core/Include/DepotClient.h b/source/P4VFS.Core/Include/DepotClient.h index 91c8cc3..b3f68c5 100644 --- a/source/P4VFS.Core/Include/DepotClient.h +++ b/source/P4VFS.Core/Include/DepotClient.h @@ -114,7 +114,7 @@ namespace P4 { template Result FDepotClient::Run(const DepotCommand& cmd) { - Result result = std::make_shared(); + Result result = MakeResult(); Run(cmd, *result.get()); return result; } diff --git a/source/P4VFS.Core/Include/DepotOperations.h b/source/P4VFS.Core/Include/DepotOperations.h index e8d0e8b..044733a 100644 --- a/source/P4VFS.Core/Include/DepotOperations.h +++ b/source/P4VFS.Core/Include/DepotOperations.h @@ -147,6 +147,14 @@ namespace P4 { CreateFileSpecFlags::Enum flags = CreateFileSpecFlags::None ); + static DepotResultDiff2 + DepotOperations::Diff2Stat( + DepotClient& depotClient, + DepotSyncFlags::Enum syncFlags, + const DepotStringArray& fileSpecs, + const DepotSyncActionInfoArray& fileModifications + ); + static DepotResultDiff2 Diff2( DepotClient& depotClient, diff --git a/source/P4VFS.Core/Include/DepotResult.h b/source/P4VFS.Core/Include/DepotResult.h index ca52e80..136ba8c 100644 --- a/source/P4VFS.Core/Include/DepotResult.h +++ b/source/P4VFS.Core/Include/DepotResult.h @@ -80,6 +80,10 @@ namespace P4 { P4VFS_CORE_API const Array& TagList() const; P4VFS_CORE_API const Array& TextList() const; const DepotString& GetTagValue(const DepotString& tagKey) const; + + void Append(const FDepotResult& src); + void Append(const Array& srcTagList); + void Append(const Array& srcTextList); protected: friend class DepotClientCommand; @@ -162,6 +166,20 @@ namespace P4 { return FDepotResultNode::Create(index < m_TagList.size() ? m_TagList[index] : nullptr); } }; + + template + Result MakeResult() + { + return std::make_shared(); + } + + template + Result MakeErrorResult(const DepotString& errorText) + { + Result result = MakeResult(); + result->SetError(errorText.c_str()); + return result; + } }}} #pragma managed(pop) diff --git a/source/P4VFS.Core/Include/SettingManager.h b/source/P4VFS.Core/Include/SettingManager.h index f32b14a..bf79ad4 100644 --- a/source/P4VFS.Core/Include/SettingManager.h +++ b/source/P4VFS.Core/Include/SettingManager.h @@ -31,6 +31,7 @@ namespace FileCore { _N( int32_t, PoolDefaultNumberOfThreads, 8 ) \ _N( int32_t, GarbageCollectPeriodMs, 5*60*1000 ) \ _N( int32_t, DepotClientCacheIdleTimeoutMs, 5*60*1000 ) \ + _N( int32_t, MaxDiff2StatFileCount, 0 ) \ class SettingManager; @@ -82,7 +83,7 @@ namespace FileCore { P4VFS_CORE_API void SetInt32(int32_t value); P4VFS_CORE_API void SetString(const String& value); - template T Get(const T& defaultValue) const = 0; + template T Get(const T& defaultValue) const; template <> bool Get(const bool& defaultValue) const { return GetBool(defaultValue); } template <> int32_t Get(const int32_t& defaultValue) const { return GetInt32(defaultValue); } template <> String Get(const String& defaultValue) const { return GetString(defaultValue); } diff --git a/source/P4VFS.Core/Source/DepotOperations.cpp b/source/P4VFS.Core/Source/DepotOperations.cpp index d4c3507..73bfdaf 100644 --- a/source/P4VFS.Core/Source/DepotOperations.cpp +++ b/source/P4VFS.Core/Source/DepotOperations.cpp @@ -833,64 +833,6 @@ DepotOperations::SyncCommand( return nullptr; } - typedef HashSet DepotFileHashSet; - typedef Map DepotFileClientSizeMapType; - - DepotFileHashSet writeableHeadDepotFiles; - DepotFileHashSet writeableHaveDepotFiles; - DepotFileHashSet symlinkDepotFiles; - DepotFileHashSet diffDepotFiles; - DepotFileClientSizeMapType depotFileClientSizeMap; - - if ((syncFlags & (DepotSyncFlags::Preview | DepotSyncFlags::Flush)) != 0 && (syncFlags & DepotSyncFlags::IgnoreOutput) == 0) - { - DepotRevision haveRevision = (syncFlags & DepotSyncFlags::Force) ? FDepotRevision::New() : FDepotRevision::New(); - for (const DepotString& fileSpec : fileSpecs) - { - const DepotString haveFileSpec = CreateFileSpec(fileSpec, haveRevision, CreateFileSpecFlags::OverrideRevison); - if (haveFileSpec.empty()) - { - reportError(StringInfo::Format("Invalid haveFileSpec for fileSpec='%s' rev='%s'", fileSpec.c_str(), FDepotRevision::ToString(haveRevision).c_str())); - return nullptr; - } - - const DepotString& headFileSpec = fileSpec; - if (headFileSpec.empty()) - { - reportError(StringInfo::Format("Invalid headFileSpec for fileSpec='%s' rev='%s'", fileSpec.c_str(), FDepotRevision::ToString(revision).c_str())); - return nullptr; - } - - DepotResultDiff2 diff2 = Diff2(depotClient, haveFileSpec, headFileSpec); - for (size_t diff2NodeIndex = 0; diff2NodeIndex < diff2->NodeCount(); ++diff2NodeIndex) - { - FDepotResultDiff2Node node = diff2->Node(diff2NodeIndex); - diffDepotFiles.insert(node.DepotFile()); - diffDepotFiles.insert(node.DepotFile2()); - - if (DepotInfo::IsWritableFileType(node.Type())) - writeableHaveDepotFiles.insert(node.DepotFile()); - if (DepotInfo::IsWritableFileType(node.Type2())) - writeableHeadDepotFiles.insert(node.DepotFile2()); - - if (DepotInfo::IsSymlinkFileType(node.Type())) - symlinkDepotFiles.insert(node.DepotFile()); - if (DepotInfo::IsSymlinkFileType(node.Type2())) - symlinkDepotFiles.insert(node.DepotFile2()); - } - - if ((syncFlags & DepotSyncFlags::ClientSize) != 0 && depotClient->GetServerApiLevel() >= DepotProtocol::SERVER_SIZES_C) - { - DepotResultSizes sizes = Sizes(depotClient, headFileSpec, SizesFlags::ClientSize); - for (size_t sizesNodeIndex = 0; sizesNodeIndex < sizes->NodeCount(); ++sizesNodeIndex) - { - FDepotResultSizesNode node = sizes->Node(sizesNodeIndex); - depotFileClientSizeMap[node.DepotFile()] = node; - } - } - } - } - DepotClientLogCallback onClientLogCallback = std::make_shared([log](LogChannel::Enum channel, const char* severity, const char* text) -> void { if (log != nullptr) @@ -972,6 +914,55 @@ DepotOperations::SyncCommand( } } + typedef HashSet DepotFileHashSet; + typedef Map DepotFileClientSizeMapType; + + DepotFileHashSet writeableHeadDepotFiles; + DepotFileHashSet writeableHaveDepotFiles; + DepotFileHashSet symlinkDepotFiles; + DepotFileHashSet diffDepotFiles; + DepotFileClientSizeMapType depotFileClientSizeMap; + + if (syncFlags & (DepotSyncFlags::Preview | DepotSyncFlags::Flush)) + { + DepotResultDiff2 diff2 = Diff2Stat(depotClient, syncFlags, fileSpecs, modifications); + if (diff2.get() == nullptr || diff2->HasError()) + { + reportError(StringInfo::Format("Failed get filetype differences. %s", diff2.get() ? diff2->GetError().c_str() : "Invalid result")); + return nullptr; + } + + for (size_t diff2NodeIndex = 0; diff2NodeIndex < diff2->NodeCount(); ++diff2NodeIndex) + { + FDepotResultDiff2Node node = diff2->Node(diff2NodeIndex); + diffDepotFiles.insert(node.DepotFile()); + diffDepotFiles.insert(node.DepotFile2()); + + if (DepotInfo::IsWritableFileType(node.Type())) + writeableHaveDepotFiles.insert(node.DepotFile()); + if (DepotInfo::IsWritableFileType(node.Type2())) + writeableHeadDepotFiles.insert(node.DepotFile2()); + + if (DepotInfo::IsSymlinkFileType(node.Type())) + symlinkDepotFiles.insert(node.DepotFile()); + if (DepotInfo::IsSymlinkFileType(node.Type2())) + symlinkDepotFiles.insert(node.DepotFile2()); + } + + for (const DepotString& fileSpec : fileSpecs) + { + if ((syncFlags & DepotSyncFlags::ClientSize) != 0 && depotClient->GetServerApiLevel() >= DepotProtocol::SERVER_SIZES_C) + { + DepotResultSizes sizes = Sizes(depotClient, fileSpec, SizesFlags::ClientSize); + for (size_t sizesNodeIndex = 0; sizesNodeIndex < sizes->NodeCount(); ++sizesNodeIndex) + { + FDepotResultSizesNode node = sizes->Node(sizesNodeIndex); + depotFileClientSizeMap[node.DepotFile()] = node; + } + } + } + } + typedef Map OpenedHeadDepotFilesType; OpenedHeadDepotFilesType openedHeadDepotFiles; @@ -1159,6 +1150,41 @@ DepotOperations::CreateFileSpecs( return fileSpecs; } +DepotResultDiff2 +DepotOperations::Diff2Stat( + DepotClient& depotClient, + DepotSyncFlags::Enum syncFlags, + const DepotStringArray& fileSpecs, + const DepotSyncActionInfoArray& fileModifications + ) +{ + DepotResultDiff2 diff2stat = MakeResult(); + + DepotRevision haveRevision = (syncFlags & DepotSyncFlags::Force) ? FDepotRevision::New() : FDepotRevision::New(); + for (const DepotString& fileSpec : fileSpecs) + { + const DepotString haveFileSpec = CreateFileSpec(fileSpec, haveRevision, CreateFileSpecFlags::OverrideRevison); + if (haveFileSpec.empty()) + { + return MakeErrorResult(StringInfo::Format("Invalid haveFileSpec for fileSpec='%s' rev='%s'", fileSpec.c_str(), FDepotRevision::ToString(haveRevision).c_str())); + } + + const DepotString& headFileSpec = fileSpec; + if (headFileSpec.empty()) + { + return MakeErrorResult(StringInfo::Format("Invalid headFileSpec for fileSpec='%s'", fileSpec.c_str())); + } + + DepotResultDiff2 diff2 = Diff2(depotClient, haveFileSpec, headFileSpec); + if (diff2.get()) + { + diff2stat->Append(diff2->TagList()); + } + } + + return diff2stat; +} + DepotResultDiff2 DepotOperations::Diff2( DepotClient& depotClient, diff --git a/source/P4VFS.Core/Source/DepotResult.cpp b/source/P4VFS.Core/Source/DepotResult.cpp index 39fe4a5..c01e0a3 100644 --- a/source/P4VFS.Core/Source/DepotResult.cpp +++ b/source/P4VFS.Core/Source/DepotResult.cpp @@ -185,4 +185,20 @@ const DepotString& FDepotResult::GetTagValue(const DepotString& tagKey) const return StringInfo::EmptyA(); } +void FDepotResult::Append(const FDepotResult& src) +{ + Append(src.m_TagList); + Append(src.m_TextList); +} + +void FDepotResult::Append(const Array& srcTagList) +{ + Algo::Append(m_TagList, srcTagList); +} + +void FDepotResult::Append(const Array& srcTextList) +{ + Algo::Append(m_TextList, srcTextList); +} + }}} From ec10a7af22de438023a8eb1d95ad32c0085f3a2f Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Thu, 1 Jan 2026 19:32:34 -0800 Subject: [PATCH 03/10] fixing diff2stat for basic testing --- source/P4VFS.Console/P4VFS.Notes.txt | 2 + source/P4VFS.Core/Include/DepotOperations.h | 13 ++ source/P4VFS.Core/Include/DepotResult.h | 1 + source/P4VFS.Core/Include/FileCore.h | 6 + source/P4VFS.Core/Include/SettingManager.h | 4 +- source/P4VFS.Core/Source/DepotOperations.cpp | 123 +++++++++++++++++-- source/P4VFS.Core/Source/DepotResult.cpp | 5 + 7 files changed, 140 insertions(+), 14 deletions(-) diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 6ab0dae..627fbc3 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -11,6 +11,8 @@ Version [1.29.4.0] diff2. A MaxDiff2StatFileCount of less than zero will always use fstat. A MaxDiff2StatFileCount of greater than zero will use fstat if the number of modifications are less than the value. +* Addition to default ExcludedProcessNames of MpDlpService.exe which is common Windows + Defender application Version [1.29.3.0] * Perforce SSO login from URL now uses impersonated p4vfs login command and ShellExecute diff --git a/source/P4VFS.Core/Include/DepotOperations.h b/source/P4VFS.Core/Include/DepotOperations.h index 044733a..d28004d 100644 --- a/source/P4VFS.Core/Include/DepotOperations.h +++ b/source/P4VFS.Core/Include/DepotOperations.h @@ -147,6 +147,14 @@ namespace P4 { CreateFileSpecFlags::Enum flags = CreateFileSpecFlags::None ); + static DepotStringArray + DepotOperations::CreateFileSpecs( + DepotClient& depotClient, + const DepotSyncActionInfoArray& fileModifications, + const DepotRevision& revision, + CreateFileSpecFlags::Enum flags + ); + static DepotResultDiff2 DepotOperations::Diff2Stat( DepotClient& depotClient, @@ -183,6 +191,11 @@ namespace P4 { SizesFlags::Enum flags = SizesFlags::None ); + static bool + IsFullDepotFileSpec( + const DepotString& fileSpec + ); + static bool IsFileTypeAlwaysResident( const DepotString& syncResident, diff --git a/source/P4VFS.Core/Include/DepotResult.h b/source/P4VFS.Core/Include/DepotResult.h index 136ba8c..c3aa56d 100644 --- a/source/P4VFS.Core/Include/DepotResult.h +++ b/source/P4VFS.Core/Include/DepotResult.h @@ -84,6 +84,7 @@ namespace P4 { void Append(const FDepotResult& src); void Append(const Array& srcTagList); void Append(const Array& srcTextList); + void Append(const DepotResultTag& srcTag); protected: friend class DepotClientCommand; diff --git a/source/P4VFS.Core/Include/FileCore.h b/source/P4VFS.Core/Include/FileCore.h index b868cec..7006f2a 100644 --- a/source/P4VFS.Core/Include/FileCore.h +++ b/source/P4VFS.Core/Include/FileCore.h @@ -603,6 +603,12 @@ namespace FileCore { return std::any_of(elements.begin(), elements.end(), predicate); } + template + static bool All(const ArrayType& elements, Predicate predicate) + { + return std::all_of(elements.begin(), elements.end(), predicate); + } + template static void RemoveIf(ArrayType& elements, Predicate predicate) { diff --git a/source/P4VFS.Core/Include/SettingManager.h b/source/P4VFS.Core/Include/SettingManager.h index bf79ad4..2dfa273 100644 --- a/source/P4VFS.Core/Include/SettingManager.h +++ b/source/P4VFS.Core/Include/SettingManager.h @@ -25,13 +25,13 @@ namespace FileCore { _N( String, SyncResidentPattern, L"" ) \ _N( bool, Unattended, false ) \ _N( String, Verbosity, CSTR_ATOW(FileCore::LogChannel::ToString(FileCore::LogChannel::Info)) ) \ - _N( String, ExcludedProcessNames, L"MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe" ) \ + _N( String, ExcludedProcessNames, L"MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe" ) \ _N( int32_t, CreateFileRetryCount, 8 ) \ _N( int32_t, CreateFileRetryWaitMs, 250 ) \ _N( int32_t, PoolDefaultNumberOfThreads, 8 ) \ _N( int32_t, GarbageCollectPeriodMs, 5*60*1000 ) \ _N( int32_t, DepotClientCacheIdleTimeoutMs, 5*60*1000 ) \ - _N( int32_t, MaxDiff2StatFileCount, 0 ) \ + _N( int32_t, MaxDiff2StatFileCount, -1 ) \ class SettingManager; diff --git a/source/P4VFS.Core/Source/DepotOperations.cpp b/source/P4VFS.Core/Source/DepotOperations.cpp index 73bfdaf..de1b237 100644 --- a/source/P4VFS.Core/Source/DepotOperations.cpp +++ b/source/P4VFS.Core/Source/DepotOperations.cpp @@ -1150,6 +1150,34 @@ DepotOperations::CreateFileSpecs( return fileSpecs; } +DepotStringArray +DepotOperations::CreateFileSpecs( + DepotClient& depotClient, + const DepotSyncActionInfoArray& fileModifications, + const DepotRevision& revision, + CreateFileSpecFlags::Enum flags + ) +{ + DepotStringArray fileSpecs; + if (fileModifications.get()) + { + fileSpecs.reserve(fileModifications->size()); + for (const DepotSyncActionInfo& modification : *fileModifications) + { + if (modification.get()) + { + const DepotRevision& modificationRevision = flags & CreateFileSpecFlags::OverrideRevison ? revision : modification->m_Revision; + DepotString fileSpec = CreateFileSpec(modification->m_DepotFile, modificationRevision, CreateFileSpecFlags::None); + if (fileSpec.empty() == false) + { + fileSpecs.push_back(fileSpec); + } + } + } + } + return fileSpecs; +} + DepotResultDiff2 DepotOperations::Diff2Stat( DepotClient& depotClient, @@ -1159,29 +1187,85 @@ DepotOperations::Diff2Stat( ) { DepotResultDiff2 diff2stat = MakeResult(); + const DepotRevision haveRevision = (syncFlags & DepotSyncFlags::Force) ? FDepotRevision::New() : FDepotRevision::New(); + bool useFStat = false; - DepotRevision haveRevision = (syncFlags & DepotSyncFlags::Force) ? FDepotRevision::New() : FDepotRevision::New(); - for (const DepotString& fileSpec : fileSpecs) + if (fileModifications.get() && fileModifications->size() > 0) { - const DepotString haveFileSpec = CreateFileSpec(fileSpec, haveRevision, CreateFileSpecFlags::OverrideRevison); - if (haveFileSpec.empty()) + const int32_t maxDiff2FileCount = SettingManager::StaticInstance().MaxDiff2StatFileCount.GetValue(); + if (maxDiff2FileCount < 0 || (maxDiff2FileCount > 0 && int32_t(fileModifications->size()) >= maxDiff2FileCount)) { - return MakeErrorResult(StringInfo::Format("Invalid haveFileSpec for fileSpec='%s' rev='%s'", fileSpec.c_str(), FDepotRevision::ToString(haveRevision).c_str())); + useFStat = Algo::All(*fileModifications, [](const DepotSyncActionInfo& modification) -> bool { return IsFullDepotFileSpec(modification->m_DepotFile); }); } + } + + if (useFStat) + { + DepotStringArray haveFileSpecs = CreateFileSpecs(depotClient, fileModifications, haveRevision, CreateFileSpecFlags::OverrideRevison); + DepotResultFStat haveFStat = FStat(depotClient, haveFileSpecs, "", FDepotResultFStatField::DepotFile | FDepotResultFStatField::HeadType); + + DepotStringArray headFileSpecs = CreateFileSpecs(depotClient, fileModifications, nullptr, CreateFileSpecFlags::None); + DepotResultFStat headFStat = FStat(depotClient, headFileSpecs, "", FDepotResultFStatField::DepotFile | FDepotResultFStatField::HeadType); - const DepotString& headFileSpec = fileSpec; - if (headFileSpec.empty()) + typedef Map Diff2NodeMap; + Diff2NodeMap nodeMap; + + auto addNodes = [&nodeMap](const DepotResultFStat& fstat, const DepotString& depotFileName, const DepotString& revName, const DepotString& typeName) -> void { - return MakeErrorResult(StringInfo::Format("Invalid headFileSpec for fileSpec='%s'", fileSpec.c_str())); - } + for (size_t fstatNodeIndex = 0; fstatNodeIndex < fstat->NodeCount(); ++fstatNodeIndex) + { + const FDepotResultFStatNode& node = fstat->Node(fstatNodeIndex); + const DepotString& depotFile = node.DepotFile(); + DepotResultTag tag = nullptr; + + Diff2NodeMap::iterator tagIt = nodeMap.find(depotFile); + if (tagIt == nodeMap.end()) + { + tag = std::make_shared(); + nodeMap.insert(Diff2NodeMap::value_type(depotFile, tag)); + } + else + { + tag = tagIt->second; + } + + tag->SetValue(depotFileName, depotFile); + tag->SetValue(revName, node.GetTagValue(FDepotResultFStatField::Name::HeadRev)); + tag->SetValue(typeName, node.GetTagValue(FDepotResultFStatField::Name::HeadType)); + } + }; + + addNodes(haveFStat, FDepotResultDiff2Field::Name::DepotFile, FDepotResultDiff2Field::Name::Rev, FDepotResultDiff2Field::Name::Type); + addNodes(headFStat, FDepotResultDiff2Field::Name::DepotFile2, FDepotResultDiff2Field::Name::Rev2, FDepotResultDiff2Field::Name::Type2); - DepotResultDiff2 diff2 = Diff2(depotClient, haveFileSpec, headFileSpec); - if (diff2.get()) + for (Diff2NodeMap::value_type& node : nodeMap) { - diff2stat->Append(diff2->TagList()); + diff2stat->Append(node.second); } } + else + { + for (const DepotString& fileSpec : fileSpecs) + { + const DepotString haveFileSpec = CreateFileSpec(fileSpec, haveRevision, CreateFileSpecFlags::OverrideRevison); + if (haveFileSpec.empty()) + { + return MakeErrorResult(StringInfo::Format("Invalid haveFileSpec for fileSpec='%s' rev='%s'", fileSpec.c_str(), FDepotRevision::ToString(haveRevision).c_str())); + } + + const DepotString& headFileSpec = fileSpec; + if (headFileSpec.empty()) + { + return MakeErrorResult(StringInfo::Format("Invalid headFileSpec for fileSpec='%s'", fileSpec.c_str())); + } + DepotResultDiff2 diff2 = Diff2(depotClient, haveFileSpec, headFileSpec); + if (diff2.get()) + { + diff2stat->Append(diff2->TagList()); + } + } + } return diff2stat; } @@ -1246,6 +1330,21 @@ DepotOperations::FStat( return depotClient->Run("fstat", fstatArgs); } +bool +DepotOperations::IsFullDepotFileSpec( + const DepotString& fileSpec + ) +{ + if (StringInfo::StartsWith(fileSpec.c_str(), "//")) + { + const char* path = fileSpec.c_str() + 2; + return StringInfo::IsNullOrWhiteSpace(path) == false && + StringInfo::Contains(path, '*') == false && + StringInfo::Contains(path, "...") == false; + } + return false; +} + bool DepotOperations::IsFileTypeAlwaysResident( const DepotString& syncResident, diff --git a/source/P4VFS.Core/Source/DepotResult.cpp b/source/P4VFS.Core/Source/DepotResult.cpp index c01e0a3..7b69c1f 100644 --- a/source/P4VFS.Core/Source/DepotResult.cpp +++ b/source/P4VFS.Core/Source/DepotResult.cpp @@ -201,4 +201,9 @@ void FDepotResult::Append(const Array& srcTextList) Algo::Append(m_TextList, srcTextList); } +void FDepotResult::Append(const DepotResultTag& srcTag) +{ + m_TagList.push_back(srcTag); +} + }}} From fa4a1d125d5d705a24aecd84b6150996b3a0d4c0 Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Thu, 1 Jan 2026 20:44:51 -0800 Subject: [PATCH 04/10] fixing tests for diff2stat, disabled by default --- source/P4VFS.Core/Include/SettingManager.h | 2 +- source/P4VFS.Core/Source/DepotOperations.cpp | 6 +++--- source/P4VFS.Driver/Include/DriverVersion.h | 2 +- source/P4VFS.UnitTest/Source/UnitTestBase.cs | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/source/P4VFS.Core/Include/SettingManager.h b/source/P4VFS.Core/Include/SettingManager.h index 2dfa273..a673625 100644 --- a/source/P4VFS.Core/Include/SettingManager.h +++ b/source/P4VFS.Core/Include/SettingManager.h @@ -31,7 +31,7 @@ namespace FileCore { _N( int32_t, PoolDefaultNumberOfThreads, 8 ) \ _N( int32_t, GarbageCollectPeriodMs, 5*60*1000 ) \ _N( int32_t, DepotClientCacheIdleTimeoutMs, 5*60*1000 ) \ - _N( int32_t, MaxDiff2StatFileCount, -1 ) \ + _N( int32_t, MaxDiff2StatFileCount, 0 ) \ class SettingManager; diff --git a/source/P4VFS.Core/Source/DepotOperations.cpp b/source/P4VFS.Core/Source/DepotOperations.cpp index de1b237..64b5094 100644 --- a/source/P4VFS.Core/Source/DepotOperations.cpp +++ b/source/P4VFS.Core/Source/DepotOperations.cpp @@ -1202,12 +1202,12 @@ DepotOperations::Diff2Stat( if (useFStat) { DepotStringArray haveFileSpecs = CreateFileSpecs(depotClient, fileModifications, haveRevision, CreateFileSpecFlags::OverrideRevison); - DepotResultFStat haveFStat = FStat(depotClient, haveFileSpecs, "", FDepotResultFStatField::DepotFile | FDepotResultFStatField::HeadType); + DepotResultFStat haveFStat = FStat(depotClient, haveFileSpecs, "", FDepotResultFStatField::DepotFile | FDepotResultFStatField::HeadType | FDepotResultFStatField::HeadRev); DepotStringArray headFileSpecs = CreateFileSpecs(depotClient, fileModifications, nullptr, CreateFileSpecFlags::None); - DepotResultFStat headFStat = FStat(depotClient, headFileSpecs, "", FDepotResultFStatField::DepotFile | FDepotResultFStatField::HeadType); + DepotResultFStat headFStat = FStat(depotClient, headFileSpecs, "", FDepotResultFStatField::DepotFile | FDepotResultFStatField::HeadType | FDepotResultFStatField::HeadRev); - typedef Map Diff2NodeMap; + typedef Map Diff2NodeMap; Diff2NodeMap nodeMap; auto addNodes = [&nodeMap](const DepotResultFStat& fstat, const DepotString& depotFileName, const DepotString& revName, const DepotString& typeName) -> void diff --git a/source/P4VFS.Driver/Include/DriverVersion.h b/source/P4VFS.Driver/Include/DriverVersion.h index 657a651..91f0f03 100644 --- a/source/P4VFS.Driver/Include/DriverVersion.h +++ b/source/P4VFS.Driver/Include/DriverVersion.h @@ -4,7 +4,7 @@ #define P4VFS_VER_MAJOR 1 // Increment this number almost never #define P4VFS_VER_MINOR 29 // Increment this number whenever the driver changes -#define P4VFS_VER_BUILD 3 // Increment this number when a major user mode change has been made +#define P4VFS_VER_BUILD 4 // Increment this number when a major user mode change has been made #define P4VFS_VER_REVISION 0 // Increment this number when we rebuild with any change #define P4VFS_VER_STRINGIZE_EX(v) L#v diff --git a/source/P4VFS.UnitTest/Source/UnitTestBase.cs b/source/P4VFS.UnitTest/Source/UnitTestBase.cs index 922c33d..503dc8f 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestBase.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestBase.cs @@ -244,6 +244,7 @@ public string ExcludedProcessNames "SenseCE.exe", "SenseIR.exe", "SearchProtocolHost.exe", + "MpDlpService.exe", }); } } From fa078eeb1df18d6bd1bfcc95a7755998a9e68720 Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Thu, 1 Jan 2026 21:50:00 -0800 Subject: [PATCH 05/10] transparent comparators --- source/P4VFS.Console/P4VFS.Notes.txt | 2 ++ source/P4VFS.Core/Include/DepotResult.h | 22 ++++++++++---------- source/P4VFS.Core/Include/FileCore.h | 6 ++++++ source/P4VFS.Core/Source/DepotOperations.cpp | 2 +- source/P4VFS.Core/Source/DepotResult.cpp | 20 +++++++++--------- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 627fbc3..8a16e79 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -13,6 +13,8 @@ Version [1.29.4.0] are less than the value. * Addition to default ExcludedProcessNames of MpDlpService.exe which is common Windows Defender application +* Now using C++ transparent comparators on std::map to enable heterogeneous + lookup from either c_str or string Version [1.29.3.0] * Perforce SSO login from URL now uses impersonated p4vfs login command and ShellExecute diff --git a/source/P4VFS.Core/Include/DepotResult.h b/source/P4VFS.Core/Include/DepotResult.h index c3aa56d..ddee104 100644 --- a/source/P4VFS.Core/Include/DepotResult.h +++ b/source/P4VFS.Core/Include/DepotResult.h @@ -29,13 +29,13 @@ namespace P4 { struct FDepotResultTag { - bool ContainsKey(const DepotString& tagKey) const; - void RemoveKey(const DepotString& tagKey); - void SetValue(const DepotString& tagKey, const DepotString& tagValue); + bool ContainsKey(const char* tagKey) const; + void RemoveKey(const char* tagKey); + void SetValue(const char* tagKey, const DepotString& tagValue); - bool TryGetValue(const DepotString& tagKey, DepotString& value) const; - const DepotString& GetValue(const DepotString& tagKey) const; - const DepotString* GetValuePtr(const DepotString& tagKey) const; + bool TryGetValue(const char* tagKey, DepotString& value) const; + const DepotString& GetValue(const char* tagKey) const; + const DepotString* GetValuePtr(const char* tagKey) const; int32_t GetValueInt32(const char* tagKey, int32_t defaultValue = 0) const; int64_t GetValueInt64(const char* tagKey, int64_t defaultValue = 0) const; @@ -79,7 +79,7 @@ namespace P4 { P4VFS_CORE_API const Array& TagList() const; P4VFS_CORE_API const Array& TextList() const; - const DepotString& GetTagValue(const DepotString& tagKey) const; + const DepotString& GetTagValue(const char* tagKey) const; void Append(const FDepotResult& src); void Append(const Array& srcTagList); @@ -100,12 +100,12 @@ namespace P4 { return m_Tag.get() ? *m_Tag : Empty; } - bool ContainsTagKey(const DepotString& tagKey) const + bool ContainsTagKey(const char* tagKey) const { return Tag().ContainsKey(tagKey); } - void RemoveTagKey(const DepotString& tagKey) + void RemoveTagKey(const char* tagKey) { if (m_Tag.get()) { @@ -113,7 +113,7 @@ namespace P4 { } } - void SetTagValue(const DepotString& tagKey, const DepotString& tagValue) + void SetTagValue(const char* tagKey, const DepotString& tagValue) { if (m_Tag.get() == nullptr) { @@ -122,7 +122,7 @@ namespace P4 { m_Tag->SetValue(tagKey, tagValue); } - const DepotString& GetTagValue(const DepotString& tagKey) const + const DepotString& GetTagValue(const char* tagKey) const { return Tag().GetValue(tagKey); } diff --git a/source/P4VFS.Core/Include/FileCore.h b/source/P4VFS.Core/Include/FileCore.h index 7006f2a..41382e6 100644 --- a/source/P4VFS.Core/Include/FileCore.h +++ b/source/P4VFS.Core/Include/FileCore.h @@ -288,36 +288,42 @@ namespace FileCore { { template bool operator()(const TL& a, const TR& b) const { return StringInfo::Strcmp(StringInfo::CStr(a), StringInfo::CStr(b)) < 0; } + using is_transparent = int; }; struct LessInsensitive { template bool operator()(const TL& a, const TR& b) const { return StringInfo::Stricmp(StringInfo::CStr(a), StringInfo::CStr(b)) < 0; } + using is_transparent = int; }; struct Greater { template bool operator()(const TL& a, const TR& b) const { return StringInfo::Strcmp(StringInfo::CStr(a), StringInfo::CStr(b)) > 0; } + using is_transparent = int; }; struct GreaterInsensitive { template bool operator()(const TL& a, const TR& b) const { return StringInfo::Stricmp(StringInfo::CStr(a), StringInfo::CStr(b)) > 0; } + using is_transparent = int; }; struct Equal { template bool operator()(const TL& a, const TR& b) const { return StringInfo::Strcmp(StringInfo::CStr(a), StringInfo::CStr(b)) == 0; } + using is_transparent = int; }; struct EqualInsensitive { template bool operator()(const TL& a, const TR& b) const { return StringInfo::Stricmp(StringInfo::CStr(a), StringInfo::CStr(b)) == 0; } + using is_transparent = int; }; class WtoA diff --git a/source/P4VFS.Core/Source/DepotOperations.cpp b/source/P4VFS.Core/Source/DepotOperations.cpp index 64b5094..f9766e0 100644 --- a/source/P4VFS.Core/Source/DepotOperations.cpp +++ b/source/P4VFS.Core/Source/DepotOperations.cpp @@ -1210,7 +1210,7 @@ DepotOperations::Diff2Stat( typedef Map Diff2NodeMap; Diff2NodeMap nodeMap; - auto addNodes = [&nodeMap](const DepotResultFStat& fstat, const DepotString& depotFileName, const DepotString& revName, const DepotString& typeName) -> void + auto addNodes = [&nodeMap](const DepotResultFStat& fstat, const char* depotFileName, const char* revName, const char* typeName) -> void { for (size_t fstatNodeIndex = 0; fstatNodeIndex < fstat->NodeCount(); ++fstatNodeIndex) { diff --git a/source/P4VFS.Core/Source/DepotResult.cpp b/source/P4VFS.Core/Source/DepotResult.cpp index 7b69c1f..80f7cb4 100644 --- a/source/P4VFS.Core/Source/DepotResult.cpp +++ b/source/P4VFS.Core/Source/DepotResult.cpp @@ -14,25 +14,25 @@ FDepotResultText::FDepotResultText(DepotResultChannel channel, const DepotString { } -bool FDepotResultTag::ContainsKey(const DepotString& tagKey) const +bool FDepotResultTag::ContainsKey(const char* tagKey) const { return GetValuePtr(tagKey) != nullptr; } -void FDepotResultTag::RemoveKey(const DepotString& tagKey) +void FDepotResultTag::RemoveKey(const char* tagKey) { m_Fields.erase(tagKey); } -void FDepotResultTag::SetValue(const DepotString& tagKey, const DepotString& tagValue) +void FDepotResultTag::SetValue(const char* tagKey, const DepotString& tagValue) { - if (tagKey.empty() == false) + if (StringInfo::IsNullOrEmpty(tagKey) == false) { m_Fields[tagKey] = tagValue; } } -bool FDepotResultTag::TryGetValue(const DepotString& tagKey, DepotString& tagValue) const +bool FDepotResultTag::TryGetValue(const char* tagKey, DepotString& tagValue) const { if (const DepotString* valuePtr = GetValuePtr(tagKey)) { @@ -42,15 +42,15 @@ bool FDepotResultTag::TryGetValue(const DepotString& tagKey, DepotString& tagVal return false; } -const DepotString& FDepotResultTag::GetValue(const DepotString& tagKey) const +const DepotString& FDepotResultTag::GetValue(const char* tagKey) const { const DepotString* valuePtr = GetValuePtr(tagKey); return valuePtr ? *valuePtr : StringInfo::EmptyA(); } -const DepotString* FDepotResultTag::GetValuePtr(const DepotString& tagKey) const +const DepotString* FDepotResultTag::GetValuePtr(const char* tagKey) const { - if (tagKey.empty() == false) + if (StringInfo::IsNullOrEmpty(tagKey) == false) { DepotResultFields::const_iterator v = m_Fields.find(tagKey); if (v != m_Fields.end()) @@ -172,9 +172,9 @@ const Array& FDepotResult::TextList() const return m_TextList; } -const DepotString& FDepotResult::GetTagValue(const DepotString& tagKey) const +const DepotString& FDepotResult::GetTagValue(const char* tagKey) const { - if (tagKey.empty() == false) + if (StringInfo::IsNullOrEmpty(tagKey) == false) { for (const DepotResultTag& tag : m_TagList) { From f767ec52f91eb93c241e063fc0de1cdf6703eea3 Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Thu, 1 Jan 2026 21:51:10 -0800 Subject: [PATCH 06/10] transparent comparators --- source/P4VFS.Console/P4VFS.Notes.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 8a16e79..285c142 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -13,8 +13,8 @@ Version [1.29.4.0] are less than the value. * Addition to default ExcludedProcessNames of MpDlpService.exe which is common Windows Defender application -* Now using C++ transparent comparators on std::map to enable heterogeneous - lookup from either c_str or string +* Optimization to use C++ transparent comparators on std::map to enable + heterogeneous lookup from either c_str or string Version [1.29.3.0] * Perforce SSO login from URL now uses impersonated p4vfs login command and ShellExecute From 3a7871a99dfd79213ad58d71261516addfc04836 Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Sun, 4 Jan 2026 14:35:07 -0800 Subject: [PATCH 07/10] removing some unecessary logging channel string conversions --- source/P4VFS.Console/P4VFS.Notes.txt | 21 ++- source/P4VFS.Console/Source/Program.cs | 3 +- source/P4VFS.Core/Include/FileCore.h | 13 +- source/P4VFS.Core/Include/LogDevice.h | 4 +- source/P4VFS.Core/Include/SettingManager.h | 9 +- source/P4VFS.Core/Source/DepotOperations.cpp | 4 + source/P4VFS.Core/Source/DepotSyncAction.cpp | 34 ++-- source/P4VFS.Core/Source/DepotSyncOptions.cpp | 4 +- source/P4VFS.Core/Source/FileSystem.cpp | 6 +- source/P4VFS.Core/Source/LogDevice.cpp | 20 +-- source/P4VFS.Core/Source/SettingManager.cpp | 4 + .../Include/SettingManagerInterop.h | 40 +++++ source/P4VFS.UnitTest/Source/UnitTestBase.cs | 155 ++++++++++++------ .../P4VFS.UnitTest/Source/UnitTestCommon.cs | 43 ++--- .../Source/UnitTestUtilities.cs | 4 +- .../P4VFS.UnitTest/Source/UnitTestWorkflow.cs | 12 +- 16 files changed, 248 insertions(+), 128 deletions(-) diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 285c142..2219383 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -3,18 +3,25 @@ Microsoft P4VFS Release Notes Version [1.29.4.0] * Updating to latest P4API 25.2 * Migrating to OpenSSL 3.0 LTS as recommended by P4API 25.2 with latest 3.0.18 -* Addition of configuration setting MaxDiff2StatFileCount setting for specifying +* Addition to default ExcludedProcessNames of MpDlpService.exe which is common Windows + Defender application +* Optimization to use C++ transparent comparators on std::map to enable + heterogeneous lookup from either c_str or string +* Addition of default property values to SettingManager. It is now used to test + against combinations of default and overridden settings values +* Fixing bug where p4vfs.exe common option '-v' would not actually enable verbose logging + for the command +* Addition of experimental configuration setting MaxDiff2StatFileCount for specifying the maximum number of files for when fstat commands should be used instead of diff2 for determining the have & head filetype of files to sync. Using fstat can be - significantly faster than diff2 when only a few files are updated under a large - file spec. A MaxDiff2StatFileCount of zero (default) will use legacy behavior to only use + faster than diff2 in the rare case where a very few number files are updated under a + very large file spec. For that reason, we'll keep this feature disabled by default indefinitly. + A MaxDiff2StatFileCount of zero (default) will use legacy behavior to only use diff2. A MaxDiff2StatFileCount of less than zero will always use fstat. A MaxDiff2StatFileCount of greater than zero will use fstat if the number of modifications are less than the value. -* Addition to default ExcludedProcessNames of MpDlpService.exe which is common Windows - Defender application -* Optimization to use C++ transparent comparators on std::map to enable - heterogeneous lookup from either c_str or string +* Addition of a new verbose mode "Preview Time" profile time to the sync command which + indicates the time spend gathering virtual sync file modifications to be applied. Version [1.29.3.0] * Perforce SSO login from URL now uses impersonated p4vfs login command and ShellExecute diff --git a/source/P4VFS.Console/Source/Program.cs b/source/P4VFS.Console/Source/Program.cs index d0e13e1..9eb5da1 100644 --- a/source/P4VFS.Console/Source/Program.cs +++ b/source/P4VFS.Console/Source/Program.cs @@ -264,6 +264,8 @@ public static int Main(string[] args) try { VirtualFileSystemLog.Intitialize(); + VirtualFileSystemLog.Info("P4VFS version {0}", VirtualFileSystem.CurrentVersion); + SettingManager.RemoteLogging = SettingManager.ConsoleRemoteLogging; SettingManager.ImmediateLogging = SettingManager.ConsoleImmediateLogging; _P4Directory = Environment.CurrentDirectory; @@ -317,7 +319,6 @@ public static int Main(string[] args) } } - VirtualFileSystemLog.Info("P4VFS version {0}", VirtualFileSystem.CurrentVersion); if (argIndex >= args.Length) { argIndex = 0; diff --git a/source/P4VFS.Core/Include/FileCore.h b/source/P4VFS.Core/Include/FileCore.h index 41382e6..0f515ce 100644 --- a/source/P4VFS.Core/Include/FileCore.h +++ b/source/P4VFS.Core/Include/FileCore.h @@ -383,6 +383,7 @@ namespace FileCore { }; #define LITERAL(CharType, str) ::Microsoft::P4VFS::FileCore::StringInfo::Traits::Type::Literal(str, L##str) + #define LITERAL_STRING(StringType, str) LITERAL(typename std::decay::type::value_type, str) }; struct P4VFS_CORE_API FileInfo @@ -751,18 +752,20 @@ namespace FileCore { do { if (((v) == (ns::ev)) || ((ns::ev) != 0 && ((v) & (ns::ev)))) \ { \ if (s.empty() == false) \ - s += "|"; \ - s += #ev; \ + { \ + s += LITERAL_STRING(s, "|"); \ + } \ + s += LITERAL_STRING(s, #ev); \ } } while(0) - #define P4VFS_ENUM_TO_STRING_RETURN(v, ns, ev) \ + #define P4VFS_ENUM_TO_STRING_RETURN(rt, v, ns, ev) \ do { if ((v) == (ns::ev)) \ { \ - return #ev; \ + return LITERAL(rt::value_type, #ev); \ } } while (0) #define P4VFS_STRING_TO_ENUM_RETURN(v, ns, ev) \ - do { if (Microsoft::P4VFS::FileCore::StringInfo::Stricmp(#ev, Microsoft::P4VFS::FileCore::StringInfo::CStr(v)) == 0) \ + do { if (Microsoft::P4VFS::FileCore::StringInfo::Stricmp(LITERAL_STRING(v, #ev), Microsoft::P4VFS::FileCore::StringInfo::CStr(v)) == 0) \ { \ return ns::ev; \ } } while (0) diff --git a/source/P4VFS.Core/Include/LogDevice.h b/source/P4VFS.Core/Include/LogDevice.h index ce76868..f8d3b86 100644 --- a/source/P4VFS.Core/Include/LogDevice.h +++ b/source/P4VFS.Core/Include/LogDevice.h @@ -21,8 +21,8 @@ namespace FileCore { Error }; - P4VFS_CORE_API static AString ToString(Enum value); - P4VFS_CORE_API static LogChannel::Enum FromString(const AString& value); + P4VFS_CORE_API static String ToString(Enum value); + P4VFS_CORE_API static LogChannel::Enum FromString(const String& value); }; struct LogElement diff --git a/source/P4VFS.Core/Include/SettingManager.h b/source/P4VFS.Core/Include/SettingManager.h index a673625..fa2beee 100644 --- a/source/P4VFS.Core/Include/SettingManager.h +++ b/source/P4VFS.Core/Include/SettingManager.h @@ -24,7 +24,7 @@ namespace FileCore { _N( bool, SyncDefaultQuiet, false ) \ _N( String, SyncResidentPattern, L"" ) \ _N( bool, Unattended, false ) \ - _N( String, Verbosity, CSTR_ATOW(FileCore::LogChannel::ToString(FileCore::LogChannel::Info)) ) \ + _N( String, Verbosity, FileCore::LogChannel::ToString(FileCore::LogChannel::Info).c_str() ) \ _N( String, ExcludedProcessNames, L"MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe" ) \ _N( int32_t, CreateFileRetryCount, 8 ) \ _N( int32_t, CreateFileRetryWaitMs, 250 ) \ @@ -155,6 +155,13 @@ namespace FileCore { SETTING_MANAGER_PROPERTIES(SETTING_MANAGER_DECLARE_PROP) #undef SETTING_MANAGER_DECLARE_PROP + struct Default + { + #define SETTING_MANAGER_DECLARE_PROP(type, name, value) static type name(); + SETTING_MANAGER_PROPERTIES(SETTING_MANAGER_DECLARE_PROP) + #undef SETTING_MANAGER_DECLARE_PROP + }; + private: PropertyMap m_PropertMap; HANDLE m_PropertMapMutex; diff --git a/source/P4VFS.Core/Source/DepotOperations.cpp b/source/P4VFS.Core/Source/DepotOperations.cpp index f9766e0..f37580e 100644 --- a/source/P4VFS.Core/Source/DepotOperations.cpp +++ b/source/P4VFS.Core/Source/DepotOperations.cpp @@ -90,13 +90,16 @@ DepotOperations::SyncVirtual( } // Retrieve a list of files to be added, deleted, and updated + DepotStopwatch previewTime(DepotStopwatch::Init::Start); DepotSyncActionInfoArray modifications = SyncCommand(depotClient, syncOptions.m_Files, revision, primarySyncFlags | DepotSyncFlags::Quiet); if (modifications.get() == nullptr) { return std::make_shared(DepotSyncStatus::Error); } + previewTime.Stop(); depotClient->Log(LogChannel::Info, StringInfo::Format("%I64u Modification message%s to act on.", uint64_t(modifications->size()), modifications->size() ? "s" : "")); + ThreadPool::ForEach::Execute( modifications->data(), modifications->size(), @@ -260,6 +263,7 @@ DepotOperations::SyncVirtual( depotClient->Log(LogChannel::Verbose, StringInfo::Format("Flush Time: %s", ToDisplayStringMilliseconds(flushTime).c_str())); depotClient->Log(LogChannel::Verbose, StringInfo::Format("Placeholder Time: %s", ToDisplayStringMilliseconds(placeholderTime).c_str())); depotClient->Log(LogChannel::Verbose, StringInfo::Format("Sync Time: %s", ToDisplayStringMilliseconds(syncTime).c_str())); + depotClient->Log(LogChannel::Verbose, StringInfo::Format("Preview Time: %s", ToDisplayStringMilliseconds(previewTime.TotalMilliseconds()).c_str())); return std::make_shared(status, resultModifications); } diff --git a/source/P4VFS.Core/Source/DepotSyncAction.cpp b/source/P4VFS.Core/Source/DepotSyncAction.cpp index d05dbe9..cbb6e5f 100644 --- a/source/P4VFS.Core/Source/DepotSyncAction.cpp +++ b/source/P4VFS.Core/Source/DepotSyncAction.cpp @@ -24,8 +24,8 @@ DepotString DepotSyncFlags::ToString(DepotSyncFlags::Enum value) DepotString DepotFlushType::ToString(DepotFlushType::Enum value) { - P4VFS_ENUM_TO_STRING_RETURN(value, DepotFlushType, Single); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotFlushType, Atomic); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotFlushType, Single); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotFlushType, Atomic); return DepotString(); } @@ -74,21 +74,21 @@ bool DepotSyncActionType::IsLocalChanged(DepotSyncActionType::Enum value) DepotString DepotSyncActionType::ToString(DepotSyncActionType::Enum value) { - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, None); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, Added); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, Deleted); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, Updated); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, Refreshed); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, Replaced); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, UpToDate); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, NoFilesFound); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, NoFileAtRevision); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, InvalidPattern); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, NotInClientView); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, OpenedNotChanged); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, CantClobber); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, NeedsResolve); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncActionType, GenericError); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, None); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, Added); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, Deleted); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, Updated); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, Refreshed); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, Replaced); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, UpToDate); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, NoFilesFound); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, NoFileAtRevision); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, InvalidPattern); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, NotInClientView); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, OpenedNotChanged); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, CantClobber); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, NeedsResolve); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncActionType, GenericError); return DepotString(); } diff --git a/source/P4VFS.Core/Source/DepotSyncOptions.cpp b/source/P4VFS.Core/Source/DepotSyncOptions.cpp index a79f07b..b5a4d54 100644 --- a/source/P4VFS.Core/Source/DepotSyncOptions.cpp +++ b/source/P4VFS.Core/Source/DepotSyncOptions.cpp @@ -9,8 +9,8 @@ namespace P4 { DepotString DepotSyncMethod::ToString(DepotSyncMethod::Enum value) { - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncMethod, Regular); - P4VFS_ENUM_TO_STRING_RETURN(value, DepotSyncMethod, Virtual); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncMethod, Regular); + P4VFS_ENUM_TO_STRING_RETURN(DepotString, value, DepotSyncMethod, Virtual); return DepotString(); } diff --git a/source/P4VFS.Core/Source/FileSystem.cpp b/source/P4VFS.Core/Source/FileSystem.cpp index d3783d6..ac563ff 100644 --- a/source/P4VFS.Core/Source/FileSystem.cpp +++ b/source/P4VFS.Core/Source/FileSystem.cpp @@ -336,9 +336,9 @@ FilePopulateMethod::ToString( FilePopulateMethod::Enum value ) { - P4VFS_ENUM_TO_STRING_RETURN(value, FilePopulateMethod, Copy); - P4VFS_ENUM_TO_STRING_RETURN(value, FilePopulateMethod, Move); - P4VFS_ENUM_TO_STRING_RETURN(value, FilePopulateMethod, Stream); + P4VFS_ENUM_TO_STRING_RETURN(AString, value, FilePopulateMethod, Copy); + P4VFS_ENUM_TO_STRING_RETURN(AString, value, FilePopulateMethod, Move); + P4VFS_ENUM_TO_STRING_RETURN(AString, value, FilePopulateMethod, Stream); return AString(); } diff --git a/source/P4VFS.Core/Source/LogDevice.cpp b/source/P4VFS.Core/Source/LogDevice.cpp index ed321da..a1d8d40 100644 --- a/source/P4VFS.Core/Source/LogDevice.cpp +++ b/source/P4VFS.Core/Source/LogDevice.cpp @@ -11,17 +11,17 @@ namespace Microsoft { namespace P4VFS { namespace FileCore { -AString LogChannel::ToString(Enum value) +String LogChannel::ToString(Enum value) { - P4VFS_ENUM_TO_STRING_RETURN(value, LogChannel, Verbose); - P4VFS_ENUM_TO_STRING_RETURN(value, LogChannel, Debug); - P4VFS_ENUM_TO_STRING_RETURN(value, LogChannel, Info); - P4VFS_ENUM_TO_STRING_RETURN(value, LogChannel, Warning); - P4VFS_ENUM_TO_STRING_RETURN(value, LogChannel, Error); - return AString(); + P4VFS_ENUM_TO_STRING_RETURN(String, value, LogChannel, Verbose); + P4VFS_ENUM_TO_STRING_RETURN(String, value, LogChannel, Debug); + P4VFS_ENUM_TO_STRING_RETURN(String, value, LogChannel, Info); + P4VFS_ENUM_TO_STRING_RETURN(String, value, LogChannel, Warning); + P4VFS_ENUM_TO_STRING_RETURN(String, value, LogChannel, Error); + return String(); } -LogChannel::Enum LogChannel::FromString(const AString& value) +LogChannel::Enum LogChannel::FromString(const String& value) { P4VFS_STRING_TO_ENUM_RETURN(value, LogChannel, Verbose); P4VFS_STRING_TO_ENUM_RETURN(value, LogChannel, Debug); @@ -129,7 +129,7 @@ void LogDeviceFile::WriteInternal(time_t time, LogChannel::Enum channel, const S { String line = StringInfo::Format(L"-%s::<%s> - %s\n", CSTR_ATOW(P4::DepotDateTime(time).ToDisplayString()), - CSTR_ATOW(LogChannel::ToString(channel)), + LogChannel::ToString(channel).c_str(), StringInfo::TrimRight(text.c_str(), L"\n").c_str()); if (m_RemoteFilePath.empty() == false && SettingManager::StaticInstance().RemoteLogging.GetValue()) @@ -440,7 +440,7 @@ void LogSystem::WriteLogElement(const LogElement& element) bool LogSystem::IsChannelEnabled(LogChannel::Enum channel) { - return channel >= LogChannel::FromString(StringInfo::ToAnsi(SettingManager::StaticInstance().Verbosity.GetValue())); + return channel >= LogChannel::FromString(SettingManager::StaticInstance().Verbosity.GetValue()); } DWORD LogSystem::WriteThreadEntry(void* data) diff --git a/source/P4VFS.Core/Source/SettingManager.cpp b/source/P4VFS.Core/Source/SettingManager.cpp index 993f022..f060e21 100644 --- a/source/P4VFS.Core/Source/SettingManager.cpp +++ b/source/P4VFS.Core/Source/SettingManager.cpp @@ -227,5 +227,9 @@ bool SettingManager::GetProperties(PropertyMap& propertyMap) const return true; } +#define SETTING_MANAGER_DECLARE_PROP(type, name, value) type SettingManager::Default::name() { return value; } +SETTING_MANAGER_PROPERTIES(SETTING_MANAGER_DECLARE_PROP) +#undef SETTING_MANAGER_DECLARE_PROP + }}} diff --git a/source/P4VFS.CoreInterop/Include/SettingManagerInterop.h b/source/P4VFS.CoreInterop/Include/SettingManagerInterop.h index 97bf5da..5d7956b 100644 --- a/source/P4VFS.CoreInterop/Include/SettingManagerInterop.h +++ b/source/P4VFS.CoreInterop/Include/SettingManagerInterop.h @@ -102,6 +102,14 @@ namespace SettingTraits { typedef System::Boolean ManagedType; + static ManagedType + ToManagedType( + bool value + ) + { + return value; + } + static ManagedType ToManagedType( SettingNode^ n, @@ -124,6 +132,14 @@ namespace SettingTraits { typedef System::Int32 ManagedType; + static ManagedType + ToManagedType( + int32_t value + ) + { + return value; + } + static ManagedType ToManagedType( SettingNode^ n, @@ -146,6 +162,14 @@ namespace SettingTraits { typedef System::String^ ManagedType; + static ManagedType + ToManagedType( + const wchar_t* value + ) + { + return Marshal::FromNativeWide(value); + } + static ManagedType ToManagedType( SettingNode^ n, @@ -209,6 +233,22 @@ public ref class SettingManager abstract sealed }; SETTING_MANAGER_PROPERTIES(SETTING_MANAGER_DECLARE_PROP) #undef SETTING_MANAGER_DECLARE_PROP + + ref class Default abstract sealed + { + public: + + #define SETTING_MANAGER_DECLARE_PROP(type, name, value) \ + static property SettingTraits::Value::ManagedType name \ + { \ + SettingTraits::Value::ManagedType get() \ + { \ + return SettingTraits::Value::ToManagedType(value); \ + } \ + }; + SETTING_MANAGER_PROPERTIES(SETTING_MANAGER_DECLARE_PROP) + #undef SETTING_MANAGER_DECLARE_PROP + }; }; }}} diff --git a/source/P4VFS.UnitTest/Source/UnitTestBase.cs b/source/P4VFS.UnitTest/Source/UnitTestBase.cs index 503dc8f..1264ae2 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestBase.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestBase.cs @@ -279,9 +279,13 @@ public void WorkspaceReset(DepotConfig config = null) AssertRetry(() => VirtualFileSystem.IsDriverLoaded(), message:"IsDriverLoaded"); AssertRetry(() => VirtualFileSystem.IsDriverReady(), message:"IsDriverReady"); AssertRetry(() => VirtualFileSystem.IsVirtualFileSystemAvailable(), message:"IsVirtualFileSystemAvailable"); - ServiceSettings.Reset(); - InteractiveOverridePasswd = null; + + if (ServiceSettingScope.IsOverriding == false) + { + ServiceSettings.Reset(); + } + InteractiveOverridePasswd = null; if (String.IsNullOrEmpty(config.Passwd)) { config.Passwd = UnitTestServer.GetUserP4Passwd(config.User); @@ -343,7 +347,7 @@ public void WorkspaceReset(DepotConfig config = null) Extensions.SocketModel.SocketModelClient service = new Extensions.SocketModel.SocketModelClient(); Assert(service.GarbageCollect()); - Assert(service.GetServiceSetting(nameof(SettingManager.ExcludedProcessNames)).ToString() == ExcludedProcessNames); + Assert(service.GetServiceSetting(nameof(SettingManager.ExcludedProcessNames)).ToString() == SettingManager.Default.ExcludedProcessNames); } public void ServiceRestart() @@ -648,32 +652,44 @@ public IEnumerable EnumerateCommonPrimaryDepotSyncFlags(bool ver foreach (DepotSyncFlags primarySyncFlags in new[]{ DepotSyncFlags.Normal, DepotSyncFlags.Quiet, DepotSyncFlags.IgnoreOutput }) { if (verbose) + { VirtualFileSystemLog.Info("EnumerateCommonPrimaryDepotSyncFlags: {0}", primarySyncFlags); + } yield return primarySyncFlags; } } - public IEnumerable EnumerateCommonServicePopulateSettings(bool verbose = true) + public IEnumerable EnumerateCommonServiceSettings(bool verbose = true) { - foreach (FilePopulateMethod populateMethod in new[]{ FilePopulateMethod.Copy, FilePopulateMethod.Stream }) + ServiceSettingsScope[] items = ServiceSettingsScope.Combined(Enumerable.Empty>() + .Concat( + new[]{ FilePopulateMethod.Copy, FilePopulateMethod.Stream } + .Select(v => new KeyValuePair(nameof(SettingManager.PopulateMethod), v.ToString()))) + .Concat( + new[]{ -1, 0, 10, SettingManager.Default.MaxDiff2StatFileCount } + .Select(v => new KeyValuePair(nameof(SettingManager.MaxDiff2StatFileCount), v.ToString())))); + + foreach (ServiceSettingsScope item in items) { - ServiceSettingsScope item = new ServiceSettingsScope(); - item.ServiceSettings.Add(new ServiceSettingScope("PopulateMethod", populateMethod.ToString())); if (verbose) + { VirtualFileSystemLog.Info("EnumerateCommonServicePopulateSettings: {0}", item); + } yield return item; } } public IEnumerable EnumerateCommonServiceSyncSettings(bool verbose = true) { - foreach (ServiceSettingsScope item in EnumerateCommonServicePopulateSettings(false)) + foreach (ServiceSettingsScope item in EnumerateCommonServiceSettings(false)) { foreach (DepotSyncFlags primarySyncFlags in EnumerateCommonPrimaryDepotSyncFlags(false)) { item.SyncFlags = primarySyncFlags; if (verbose) + { VirtualFileSystemLog.Info("EnumerateCommonServiceSyncSettings: {0}", item); + } yield return item; } } @@ -687,7 +703,9 @@ public IEnumerable EnumerateCommonServiceSyncLineEndSettin { item.LineEnd = lineEnd; if (verbose) + { VirtualFileSystemLog.Info("EnumerateCommonServiceSyncLineEndSettings: {0}", item); + } yield return item; } } @@ -813,41 +831,27 @@ public struct FileDifferenceSummary public uint LinesTotal { get { return LinesAdded + LinesDeleted + LinesChanged; } } } - public class LocalSettingScope : IDisposable + public class DisposableAction : IDisposable { - public string Name { get; private set; } - public string Value { get; private set; } - public SettingNode PreviousValue { get; private set; } + private Action m_Action; - public LocalSettingScope(string name, string value) + public DisposableAction(Action action) { - Name = name; - UnitTestBase.Assert(String.IsNullOrEmpty(Name) == false); - Value = value; - UnitTestBase.Assert(Value != null); - - PreviousValue = ServiceSettings.GetProperty(Name); - UnitTestBase.Assert(PreviousValue.ToString() != null); - UnitTestBase.Assert(ServiceSettings.SetProperty(SettingNode.FromString(Value), Name)); - UnitTestBase.Assert(ServiceSettings.GetProperty(Name).ToString() == Value); + m_Action = action; } public void Dispose() { - UnitTestBase.Assert(ServiceSettings.SetProperty(PreviousValue, Name)); - } - - public override string ToString() - { - return String.Format("{0}={1}", Name, Value); + UnitTestBase.Assert(m_Action != null); + m_Action.Invoke(); + m_Action = null; } } - public class ServiceSettingScope : IDisposable + public class ServiceSettingScope { public string Name { get; private set; } public string Value { get; private set; } - public SettingNode PreviousValue { get; private set; } public ServiceSettingScope(string name, string value) { @@ -855,22 +859,39 @@ public ServiceSettingScope(string name, string value) UnitTestBase.Assert(String.IsNullOrEmpty(Name) == false); Value = value; UnitTestBase.Assert(Value != null); - - Extensions.SocketModel.SocketModelClient serviceClient = new Extensions.SocketModel.SocketModelClient(); - PreviousValue = serviceClient.GetServiceSetting(Name); - UnitTestBase.Assert(PreviousValue.ToString() != null); - UnitTestBase.Assert(serviceClient.SetServiceSetting(Name, SettingNode.FromString(Value))); - UnitTestBase.Assert(serviceClient.GetServiceSetting(Name).ToString() == Value); } public ServiceSettingScope(string name) : this(name, ServiceSettings.GetProperty(name).ToString()) { } - public void Dispose() + public DisposableAction CreateDisposable() { + UnitTestBase.Assert(System.Threading.Interlocked.Increment(ref m_Depth) > 0); + Extensions.SocketModel.SocketModelClient serviceClient = new Extensions.SocketModel.SocketModelClient(); - UnitTestBase.Assert(serviceClient.SetServiceSetting(Name, PreviousValue)); + SettingNode PreviousServiceValue = serviceClient.GetServiceSetting(Name); + UnitTestBase.Assert(PreviousServiceValue.ToString() != null); + UnitTestBase.Assert(serviceClient.SetServiceSetting(Name, SettingNode.FromString(Value))); + UnitTestBase.Assert(serviceClient.GetServiceSetting(Name).ToString() == Value); + + SettingNode PreviousClientValue = ServiceSettings.GetProperty(Name); + UnitTestBase.Assert(PreviousClientValue.ToString() != null); + UnitTestBase.Assert(ServiceSettings.SetProperty(SettingNode.FromString(Value), Name)); + UnitTestBase.Assert(ServiceSettings.GetProperty(Name).ToString() == Value); + + return new DisposableAction(() => + { + UnitTestBase.Assert(serviceClient.GetServiceSetting(Name).ToString() == Value, $"Service setting {Name} expected to be overriden to {Value}"); + UnitTestBase.Assert(serviceClient.SetServiceSetting(Name, PreviousServiceValue)); + UnitTestBase.Assert(serviceClient.GetServiceSetting(Name).ToString() == PreviousServiceValue.ToString()); + + UnitTestBase.Assert(ServiceSettings.GetProperty(Name).ToString() == Value, $"Client setting {Name} expected to be overriden to {Value}"); + UnitTestBase.Assert(ServiceSettings.SetProperty(PreviousClientValue, Name)); + UnitTestBase.Assert(ServiceSettings.GetProperty(Name).ToString() == PreviousClientValue.ToString()); + + UnitTestBase.Assert(System.Threading.Interlocked.Decrement(ref m_Depth) >= 0); + }); } public override string ToString() @@ -878,40 +899,68 @@ public override string ToString() return String.Format("{0}={1}", Name, Value); } - public void ApplyGlobal() + private static volatile int m_Depth; + public static bool IsOverriding { - UnitTestBase.Assert(ServiceSettings.SetProperty(new SettingNode(Value), Name)); - UnitTestBase.Assert(ServiceSettings.GetPropertyJson(Name).ToString() == ServiceSettings.SettingNodeToJson(new SettingNode(Value)).ToString()); + get { return m_Depth > 0; } } } - public class ServiceSettingsScope : IDisposable + public class ServiceSettingsScope { public List ServiceSettings = new List(); public DepotSyncFlags? SyncFlags; public string LineEnd; - public void Dispose() + public static ServiceSettingsScope[] Combined(IEnumerable> keyValues) { - foreach (ServiceSettingScope setting in ServiceSettings) - setting.Dispose(); + KeyValuePair[] groups = keyValues + .GroupBy(selector => selector.Key) + .Select(grouping => new KeyValuePair(grouping.Key, grouping.Select(kvp => kvp.Value).Distinct().ToArray())) + .ToArray(); + + return CreateCombinations(groups, 0).ToArray(); + } + + private static IEnumerable CreateCombinations(KeyValuePair[] groups, int index) + { + if (index >= groups.Length) + { + yield return new ServiceSettingsScope(); + yield break; + } + + foreach (string value in groups[index].Value) + { + foreach (ServiceSettingsScope combined in CreateCombinations(groups, index + 1)) + { + combined.ServiceSettings.Add(new ServiceSettingScope(groups[index].Key, value)); + yield return combined; + } + } + } + + public DisposableAction CreateDisposable() + { + List actions = ServiceSettings + .Select(scope => scope.CreateDisposable()) + .ToList(); + + return new DisposableAction(() => + { + actions.ForEach(action => action.Dispose()); + }); } public override string ToString() { - string result = String.Format("ServiceSettings=[{0}]", String.Join(",", ServiceSettings)); + string result = String.Format("ServiceSettings={{{0}}}", String.Join(", ", ServiceSettings)); if (SyncFlags.HasValue) - result += String.Format(", SyncFlags=[{0}]", SyncFlags.Value); + result += String.Format(", SyncFlags={0}", SyncFlags.Value); if (LineEnd != null) result += String.Format(", LineEnd={0}", LineEnd); return result; } - - public void ApplyGlobal() - { - foreach (ServiceSettingScope setting in ServiceSettings) - setting.ApplyGlobal(); - } } public class DepotTunableScope : IDisposable diff --git a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs index 5c90ba3..381acbf 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs @@ -25,7 +25,7 @@ public void BasicWorkspaceTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -92,7 +92,7 @@ public void MakeFileResidentTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -148,7 +148,7 @@ public void MakeDirectoryResidentTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -191,7 +191,7 @@ public void WritableClientAndFileTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncLineEndSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -269,7 +269,7 @@ public void OpenedFileSyncTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -539,7 +539,7 @@ public void MakeResidentRaceTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { WorkspaceReset(); @@ -872,7 +872,7 @@ public void DepotClientSymlinkSyncTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncLineEndSettings().Where(s => s.SyncFlags == DepotSyncFlags.Normal)) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { WorkspaceReset(); @@ -1040,6 +1040,12 @@ public void DepotServerConfigTest() ServiceSettings.Reset(); AssertCurrentServiceSettings(); + // Simple test of expected values of SettingManager.Default + Assert(SettingManager.ExcludedProcessNames == SettingManager.Default.ExcludedProcessNames); + Assert(SettingManager.ExcludedProcessNames == "MsSense.exe;MsMpEng.exe;SenseCE.exe;SenseIR.exe;SearchProtocolHost.exe;MpDlpService.exe"); + Assert(SettingManager.PopulateMethod == SettingManager.Default.PopulateMethod); + Assert(SettingManager.PopulateMethod == "Stream"); + // Test DepotServerConfig serialization DepotServerConfig config0 = new DepotServerConfig{ Servers = new[]{ new DepotServerConfigEntry{ Pattern = "MyHost*", Address = "127.0.0.1:1666" }}}; SettingNode configNode = config0.ToNode(); @@ -1077,16 +1083,15 @@ public void DepotServerConfigTest() Assert(DepotOperations.ResolveDepotServerName("CTO-P4MAIN") == "CTO-P4MAIN"); // Test the service DepotServerConfig pattern matching of the host name resolving - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { WorkspaceReset(); Assert(depotClient.Connect(_P4Port, _P4Client, _P4User)); string clientRoot = GetClientRoot(depotClient); - settings.ApplyGlobal(); SettingManagerExtensions.DepotServerConfig = depotServerConfig; Assert(JsonConvert.SerializeObject(SettingManagerExtensions.DepotServerConfig) == JsonConvert.SerializeObject(depotServerConfig)); AssertCurrentServiceSettings(); @@ -1206,9 +1211,9 @@ public void DepotServerConfigTest() [TestMethod, Priority(21), TestRemote] public void FileCopyTest() { - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { WorkspaceReset(); @@ -1303,9 +1308,9 @@ public void ServiceSettingNodeTest() [TestMethod, Priority(23), TestRemote] public void SparseUnderflowCopyTest() { - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { WorkspaceReset(); @@ -1676,7 +1681,7 @@ public void LongPathSyncTest() { foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -1876,8 +1881,8 @@ public void DepotClientCacheIdleTimeoutTest() Assert(GetServiceIdleConnectionCount() == 1); // Write a temporary PublicSettingsFilePath settings file with very short GC timeout - using (new LocalSettingScope(nameof(SettingManager.GarbageCollectPeriodMs), "100")) { - using (new LocalSettingScope(nameof(SettingManager.DepotClientCacheIdleTimeoutMs), "5000")) { + using (new ServiceSettingScope(nameof(SettingManager.GarbageCollectPeriodMs), "100").CreateDisposable()) { + using (new ServiceSettingScope(nameof(SettingManager.DepotClientCacheIdleTimeoutMs), "5000").CreateDisposable()) { using (new DepotTempFile(VirtualFileSystem.PublicSettingsFilePath)) { Assert(ServiceSettings.SaveToFile(VirtualFileSystem.PublicSettingsFilePath)); @@ -2006,7 +2011,7 @@ public void NonAsciiFilePathTest() foreach (ServiceSettingsScope settings in EnumerateCommonServiceSyncSettings()) { - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { DepotSyncFlags syncFlags = settings.SyncFlags.Value; @@ -2035,7 +2040,7 @@ public void SyncClientSizeTest() settings.SyncFlags = settings.SyncFlags.Value | DepotSyncFlags.ClientSize; VirtualFileSystemLog.Info("EnumerateCommonServiceSyncLineEndSettings: {0}", settings); - using (settings) { + using (settings.CreateDisposable()) { using (DepotClient depotClient = new DepotClient()) { WorkspaceReset(); diff --git a/source/P4VFS.UnitTest/Source/UnitTestUtilities.cs b/source/P4VFS.UnitTest/Source/UnitTestUtilities.cs index ed2101c..4b02a31 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestUtilities.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestUtilities.cs @@ -20,9 +20,9 @@ public class UnitTestUtilities : UnitTestBase [TestMethod, Priority(0)] public void FileAttributeTest() { - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { WorkspaceReset(); string clientRoot = GetClientRoot(); diff --git a/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs b/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs index f3c70b7..ede692e 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs @@ -21,9 +21,9 @@ public class UnitTestWorkflow : UnitTestBase [TestMethod, Priority(0)] public void ReconciledTest() { - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { WorkspaceReset(); string[] fileSpecs = new string[]{ @@ -52,9 +52,9 @@ public void ReconciledTest() [TestMethod, Priority(1)] public void CurrentWorkingDirectoryTest() { - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { foreach (string syncOption in EnumerateCommonConsoleSyncOptions()) { WorkspaceReset(); @@ -165,9 +165,9 @@ public void ServiceInstallUninstallTest() [TestMethod, Priority(5), TestRemote] public void ResidentCommandTest() { - foreach (ServiceSettingsScope settings in EnumerateCommonServicePopulateSettings()) + foreach (ServiceSettingsScope settings in EnumerateCommonServiceSettings()) { - using (settings) { + using (settings.CreateDisposable()) { WorkspaceReset(); string clientRoot = GetClientRoot(); From 7bd5ab8cc95b717e9fe15011c16af071024a4dbc Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Mon, 5 Jan 2026 17:57:13 -0800 Subject: [PATCH 08/10] update solution paths for latest SDKs --- source/P4VFS.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/P4VFS.props b/source/P4VFS.props index 661097d..791de78 100644 --- a/source/P4VFS.props +++ b/source/P4VFS.props @@ -65,7 +65,7 @@ $(P4VFSBuildDir)/$(P4VFSUnitTestName) - 2024.1 + 2025.2 $(P4VFSConfiguration) $(P4VFSExternalDir)/P4API/$(PerforceApiVersion) $(PerforceApiDir)/include @@ -73,7 +73,7 @@ libclient.lib;libp4api.lib;libp4script.lib;libp4script_c.lib;libp4script_curl.lib;libp4script_sqlite.lib;librpc.lib;libsupp.lib - 3.3.3 + 3.0.18 $(P4VFSConfiguration) $(P4VFSExternalDir)/OpenSSL/$(OpenSSLApiVersion) $(OpenSSLApiDir)/include From 231b7b25725f285170c5a765ff541c3121c61cef Mon Sep 17 00:00:00 2001 From: Jess Kube <44536565+jessk-msft@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:40:33 -0800 Subject: [PATCH 09/10] Update OS version in GitHub Actions workflow --- .github/workflows/p4vfs-verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/p4vfs-verify.yml b/.github/workflows/p4vfs-verify.yml index 597ff45..476306f 100644 --- a/.github/workflows/p4vfs-verify.yml +++ b/.github/workflows/p4vfs-verify.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - os: [windows-latest] + os: [windows-2022] configuration: [DebugDev, ReleaseDev] steps: From f47688eb4fc3d2117cb99189bc5f6593f40547aa Mon Sep 17 00:00:00 2001 From: Jess Kube Date: Tue, 6 Jan 2026 12:43:37 -0800 Subject: [PATCH 10/10] removing duplicate test ExcludedProcessNames --- source/P4VFS.UnitTest/Source/UnitTestBase.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/source/P4VFS.UnitTest/Source/UnitTestBase.cs b/source/P4VFS.UnitTest/Source/UnitTestBase.cs index 1264ae2..8f7cb35 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestBase.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestBase.cs @@ -233,22 +233,6 @@ public static void WaitForAttachedDebugger() System.Threading.Thread.Sleep(100); } - public string ExcludedProcessNames - { - get - { - return String.Join(";", new[] - { - "MsSense.exe", - "MsMpEng.exe", - "SenseCE.exe", - "SenseIR.exe", - "SearchProtocolHost.exe", - "MpDlpService.exe", - }); - } - } - public void WorkspaceReset(string port, string client, string user) { WorkspaceReset(new DepotConfig{ Port=port??_P4Port, Client=client??_P4Client, User=user??_P4User });