winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

RepositorySource.cpp (41661B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ISource.h"
      5 #include "CompositeSource.h"
      6 #include "SourceFactory.h"
      7 #include "SourceList.h"
      8 #include "SourcePolicy.h"
      9 #include "Microsoft/PredefinedInstalledSourceFactory.h"
     10 #include "Microsoft/PredefinedWriteableSourceFactory.h"
     11 #include "Microsoft/PreIndexedPackageSourceFactory.h"
     12 #include "Rest/RestSourceFactory.h"
     13 #include "PackageTrackingCatalogSourceFactory.h"
     14 #include "SourceUpdateChecks.h"
     15 
     16 #ifndef AICLI_DISABLE_TEST_HOOKS
     17 #include "Microsoft/ConfigurableTestSourceFactory.h"
     18 #endif
     19 
     20 #include <winget/GroupPolicy.h>
     21 
     22 using namespace AppInstaller::Settings;
     23 using namespace std::chrono_literals;
     24 using namespace AppInstaller::Utility::literals;
     25 
     26 namespace AppInstaller::Repository
     27 {
     28     namespace
     29     {
     30 #ifndef AICLI_DISABLE_TEST_HOOKS
     31         static std::map<std::string, std::function<std::unique_ptr<ISourceFactory>()>> s_Sources_TestHook_SourceFactories;
     32 #endif
     33 
     34         std::shared_ptr<ISourceReference> CreateSourceFromDetails(const SourceDetails& details)
     35         {
     36             return ISourceFactory::GetForType(details.Type)->Create(details);
     37         }
     38 
     39         std::chrono::milliseconds GetMillisecondsToWait(std::chrono::seconds retryAfter, size_t randomMultiplier = 1)
     40         {
     41             if (retryAfter != 0s)
     42             {
     43                 return std::chrono::duration_cast<std::chrono::milliseconds>(retryAfter);
     44             }
     45             else
     46             {
     47                 // Add a bit of randomness to the retry wait time
     48                 std::default_random_engine randomEngine(std::random_device{}());
     49                 std::uniform_int_distribution<long long> distribution(2000, 10000);
     50 
     51                 return std::chrono::milliseconds(distribution(randomEngine) * randomMultiplier);
     52             }
     53         }
     54 
     55         struct AddOrUpdateResult
     56         {
     57             bool UpdateChecked = false;
     58             bool MetadataWritten = false;
     59         };
     60 
     61         template <typename MemberFunc>
     62         AddOrUpdateResult AddOrUpdateFromDetails(SourceDetails& details, MemberFunc member, IProgressCallback& progress)
     63         {
     64             AddOrUpdateResult result;
     65 
     66             auto factory = ISourceFactory::GetForType(details.Type);
     67 
     68             // If we are instructed to wait longer than this, just fail rather than retrying.
     69             constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s;
     70             std::chrono::seconds waitSecondsForRetry = 0s;
     71 
     72             // Attempt; if it fails, wait a short time and retry.
     73             try
     74             {
     75                 result.UpdateChecked = (factory.get()->*member)(details, progress);
     76                 if (result.UpdateChecked)
     77                 {
     78                     details.LastUpdateTime = std::chrono::system_clock::now();
     79                     result.MetadataWritten = true;
     80                 }
     81                 return result;
     82             }
     83             catch (const Utility::ServiceUnavailableException& sue)
     84             {
     85                 waitSecondsForRetry = sue.RetryAfter();
     86 
     87                 // Do not retry if the server tell us to wait more than the max time allowed.
     88                 if (waitSecondsForRetry > maximumWaitTimeAllowed)
     89                 {
     90                     details.DoNotUpdateBefore = std::chrono::system_clock::now() + waitSecondsForRetry;
     91                     AICLI_LOG(Repo, Info, << "Source `" << details.Name << "` unavailable first try, setting DoNotUpdateBefore to " << details.DoNotUpdateBefore);
     92                     result.MetadataWritten = true;
     93                     return result;
     94                 }
     95             }
     96             CATCH_LOG();
     97 
     98             std::chrono::milliseconds millisecondsToWait = GetMillisecondsToWait(waitSecondsForRetry);
     99 
    100             AICLI_LOG(Repo, Info, << "Source add/update failed, waiting " << millisecondsToWait.count() << " milliseconds and retrying: " << details.Name);
    101 
    102             if (!ProgressCallback::Wait(progress, millisecondsToWait))
    103             {
    104                 AICLI_LOG(Repo, Info, << "Source second try cancelled.");
    105                 return {};
    106             }
    107 
    108             try
    109             {
    110                 // If this one fails, maybe the problem is persistent.
    111                 result.UpdateChecked = (factory.get()->*member)(details, progress);
    112                 if (result.UpdateChecked)
    113                 {
    114                     details.LastUpdateTime = std::chrono::system_clock::now();
    115                     result.MetadataWritten = true;
    116                 }
    117             }
    118             catch (const Utility::ServiceUnavailableException& sue)
    119             {
    120                 details.DoNotUpdateBefore = std::chrono::system_clock::now() + GetMillisecondsToWait(sue.RetryAfter(), 3);
    121                 AICLI_LOG(Repo, Info, << "Source `" << details.Name << "` unavailable second try, setting DoNotUpdateBefore to " << details.DoNotUpdateBefore);
    122                 result.MetadataWritten = true;
    123             }
    124 
    125             return result;
    126         }
    127 
    128         AddOrUpdateResult AddSourceFromDetails(SourceDetails& details, IProgressCallback& progress)
    129         {
    130             return AddOrUpdateFromDetails(details, &ISourceFactory::Add, progress);
    131         }
    132 
    133         AddOrUpdateResult UpdateSourceFromDetails(SourceDetails& details, IProgressCallback& progress)
    134         {
    135             return AddOrUpdateFromDetails(details, &ISourceFactory::Update, progress);
    136         }
    137 
    138         AddOrUpdateResult BackgroundUpdateSourceFromDetails(SourceDetails& details, IProgressCallback& progress)
    139         {
    140             return AddOrUpdateFromDetails(details, &ISourceFactory::BackgroundUpdate, progress);
    141         }
    142 
    143         bool RemoveSourceFromDetails(const SourceDetails& details, IProgressCallback& progress)
    144         {
    145             auto factory = ISourceFactory::GetForType(details.Type);
    146 
    147             return factory->Remove(details, progress);
    148         }
    149 
    150         bool ContainsAvailablePackagesInternal(SourceOrigin origin)
    151         {
    152             return (origin == SourceOrigin::Default || origin == SourceOrigin::GroupPolicy || origin == SourceOrigin::User);
    153         }
    154 
    155         SourceDetails GetPredefinedSourceDetails(PredefinedSource source)
    156         {
    157             SourceDetails details;
    158             details.Origin = SourceOrigin::Predefined;
    159 
    160             switch (source)
    161             {
    162             case PredefinedSource::Installed:
    163                 details.Type = Microsoft::PredefinedInstalledSourceFactory::Type();
    164                 details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::None);
    165                 return details;
    166             case PredefinedSource::InstalledForceCacheUpdate:
    167                 details.Type = Microsoft::PredefinedInstalledSourceFactory::Type();
    168                 details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate);
    169                 return details;
    170             case PredefinedSource::InstalledUser:
    171                 details.Type = Microsoft::PredefinedInstalledSourceFactory::Type();
    172                 details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::User);
    173                 return details;
    174             case PredefinedSource::InstalledMachine:
    175                 details.Type = Microsoft::PredefinedInstalledSourceFactory::Type();
    176                 details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::Machine);
    177                 return details;
    178             case PredefinedSource::ARP:
    179                 details.Type = Microsoft::PredefinedInstalledSourceFactory::Type();
    180                 details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::ARP);
    181                 return details;
    182             case PredefinedSource::MSIX:
    183                 details.Type = Microsoft::PredefinedInstalledSourceFactory::Type();
    184                 details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX);
    185                 return details;
    186             case PredefinedSource::Installing:
    187                 details.Type = Microsoft::PredefinedWriteableSourceFactory::Type();
    188                 // As long as there is only one type this is not particularly needed, but Arg is exposed publicly
    189                 // so this is used here for consistency with other predefined sources.
    190                 details.Arg = Microsoft::PredefinedWriteableSourceFactory::TypeToString(Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing);
    191                 return details;
    192             }
    193 
    194             THROW_HR(E_UNEXPECTED);
    195         }
    196 
    197         // Carries the exception from an OpenSource call and presents it back at search time.
    198         struct OpenExceptionProxy : public ISource, std::enable_shared_from_this<OpenExceptionProxy>
    199         {
    200             static constexpr ISourceType SourceType = ISourceType::OpenExceptionProxy;
    201 
    202             OpenExceptionProxy(const SourceDetails& details, std::exception_ptr exception) :
    203                 m_details(details), m_exception(std::move(exception)) {}
    204 
    205             const SourceDetails& GetDetails() const override { return m_details; }
    206 
    207             const std::string& GetIdentifier() const override { return m_details.Identifier; }
    208 
    209             SearchResult Search(const SearchRequest&) const override
    210             {
    211                 SearchResult result;
    212                 result.Failures.emplace_back(SearchResult::Failure{ GetDetails().Name, m_exception });
    213                 return result;
    214             }
    215 
    216             void* CastTo(ISourceType type) override
    217             {
    218                 if (type == SourceType)
    219                 {
    220                     return this;
    221                 }
    222 
    223                 return nullptr;
    224             }
    225 
    226         private:
    227             SourceDetails m_details;
    228             std::exception_ptr m_exception;
    229         };
    230 
    231         // A wrapper that doesn't actually forward the search requests.
    232         struct TrackingOnlySourceWrapper : public ISource
    233         {
    234             TrackingOnlySourceWrapper(std::shared_ptr<ISourceReference> wrapped) : m_wrapped(std::move(wrapped))
    235             {
    236                 m_identifier = m_wrapped->GetIdentifier();
    237             }
    238 
    239             const std::string& GetIdentifier() const override { return m_identifier; }
    240 
    241             SourceDetails& GetDetails() const override { return m_wrapped->GetDetails(); }
    242 
    243             SourceInformation GetInformation() const override { return m_wrapped->GetInformation(); }
    244 
    245             SearchResult Search(const SearchRequest&) const override { return {}; }
    246 
    247             void* CastTo(ISourceType) override { return nullptr; }
    248 
    249         private:
    250             std::shared_ptr<ISourceReference> m_wrapped;
    251             std::string m_identifier;
    252         };
    253 
    254         // A wrapper to create another wrapper.
    255         struct TrackingOnlyReferenceWrapper : public ISourceReference
    256         {
    257             TrackingOnlyReferenceWrapper(std::shared_ptr<ISourceReference> wrapped) : m_wrapped(std::move(wrapped)) {}
    258 
    259             std::string GetIdentifier() override { return m_wrapped->GetIdentifier(); }
    260 
    261             SourceDetails& GetDetails() override { return m_wrapped->GetDetails(); }
    262 
    263             SourceInformation GetInformation() override { return m_wrapped->GetInformation(); }
    264 
    265             bool SetCustomHeader(std::optional<std::string>) override { return false; }
    266 
    267             void SetCaller(std::string caller) override { m_wrapped->SetCaller(std::move(caller)); }
    268 
    269             std::shared_ptr<ISource> Open(IProgressCallback&) override
    270             {
    271                 return std::make_shared<TrackingOnlySourceWrapper>(m_wrapped);
    272             }
    273 
    274         private:
    275             std::shared_ptr<ISourceReference> m_wrapped;
    276         };
    277     }
    278 
    279     std::unique_ptr<ISourceFactory> ISourceFactory::GetForType(std::string_view type)
    280     {
    281 #ifndef AICLI_DISABLE_TEST_HOOKS
    282         // Tests can ensure case matching
    283         auto itr = s_Sources_TestHook_SourceFactories.find(std::string(type));
    284         if (itr != s_Sources_TestHook_SourceFactories.end())
    285         {
    286             return itr->second();
    287         }
    288 
    289         if (Utility::CaseInsensitiveEquals(Microsoft::ConfigurableTestSourceFactory::Type(), type))
    290         {
    291             return Microsoft::ConfigurableTestSourceFactory::Create();
    292         }
    293 #endif
    294 
    295         // For now, enable an empty type to represent the only one we have.
    296         if (type.empty() ||
    297             Utility::CaseInsensitiveEquals(Microsoft::PreIndexedPackageSourceFactory::Type(), type))
    298         {
    299             return Microsoft::PreIndexedPackageSourceFactory::Create();
    300         }
    301         // Should always come from code, so no need for case insensitivity
    302         else if (Microsoft::PredefinedInstalledSourceFactory::Type() == type)
    303         {
    304             return Microsoft::PredefinedInstalledSourceFactory::Create();
    305         }
    306         // Should always come from code, so no need for case insensitivity
    307         else if (Microsoft::PredefinedWriteableSourceFactory::Type() == type)
    308         {
    309             return Microsoft::PredefinedWriteableSourceFactory::Create();
    310         }
    311         // Should always come from code, so no need for case insensitivity
    312         else if (PackageTrackingCatalogSourceFactory::Type() == type)
    313         {
    314             return PackageTrackingCatalogSourceFactory::Create();
    315         }
    316         else if (Utility::CaseInsensitiveEquals(Rest::RestSourceFactory::Type(), type))
    317         {
    318             return Rest::RestSourceFactory::Create();
    319         }
    320 
    321         THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE);
    322     }
    323 
    324     SourceTrustLevel ConvertToSourceTrustLevelEnum(std::string_view trustLevel)
    325     {
    326         std::string lowerTrustLevel = Utility::ToLower(trustLevel);
    327 
    328         if (lowerTrustLevel == "storeorigin")
    329         {
    330             return SourceTrustLevel::StoreOrigin;
    331         }
    332         else if (lowerTrustLevel == "trusted")
    333         {
    334             return SourceTrustLevel::Trusted;
    335         }
    336         else if (lowerTrustLevel == "none")
    337         {
    338             return SourceTrustLevel::None;
    339         }
    340         else
    341         {
    342             THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    343         }
    344     }
    345 
    346     std::string_view SourceTrustLevelEnumToString(SourceTrustLevel trustLevel)
    347     {
    348         switch (trustLevel)
    349         {
    350         case SourceTrustLevel::StoreOrigin:
    351             return "StoreOrigin"sv;
    352         case SourceTrustLevel::Trusted:
    353             return "Trusted"sv;
    354         case SourceTrustLevel::None:
    355             return "None"sv;
    356         }
    357 
    358         return "Unknown"sv;
    359     }
    360 
    361     SourceTrustLevel ConvertToSourceTrustLevelFlag(std::vector<std::string> trustLevels)
    362     {
    363         Repository::SourceTrustLevel result = Repository::SourceTrustLevel::None;
    364         for (auto& trustLevel : trustLevels)
    365         {
    366             Repository::SourceTrustLevel trustLevelEnum = ConvertToSourceTrustLevelEnum(trustLevel);
    367             if (trustLevelEnum == Repository::SourceTrustLevel::None)
    368             {
    369                 return Repository::SourceTrustLevel::None;
    370             }
    371             else if (trustLevelEnum == Repository::SourceTrustLevel::Trusted)
    372             {
    373                 WI_SetFlag(result, Repository::SourceTrustLevel::Trusted);
    374             }
    375             else if (trustLevelEnum == Repository::SourceTrustLevel::StoreOrigin)
    376             {
    377                 WI_SetFlag(result, Repository::SourceTrustLevel::StoreOrigin);
    378             }
    379             else
    380             {
    381                 THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Invalid source trust level.");
    382             }
    383         }
    384 
    385         return result;
    386     }
    387 
    388     std::vector<std::string_view> SourceTrustLevelFlagToList(SourceTrustLevel trustLevel)
    389     {
    390         std::vector<std::string_view> result;
    391 
    392         if (WI_IsFlagSet(trustLevel, Repository::SourceTrustLevel::Trusted))
    393         {
    394             result.emplace_back(Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::Trusted));
    395         }
    396         if (WI_IsFlagSet(trustLevel, Repository::SourceTrustLevel::StoreOrigin))
    397         {
    398             result.emplace_back(Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::StoreOrigin));
    399         }
    400 
    401         return result;
    402     }
    403 
    404     std::string GetSourceTrustLevelForDisplay(SourceTrustLevel trustLevel)
    405     {
    406         std::vector<std::string_view> trustLevelList = Repository::SourceTrustLevelFlagToList(trustLevel);
    407         std::vector<Utility::LocIndString> locIndList(trustLevelList.begin(), trustLevelList.end());
    408         return Utility::Join("|"_liv, locIndList);
    409     }
    410 
    411     std::string_view ToString(SourceOrigin origin)
    412     {
    413         switch (origin)
    414         {
    415         case SourceOrigin::Default:
    416             return "Default"sv;
    417         case SourceOrigin::User:
    418             return "User"sv;
    419         case SourceOrigin::Predefined:
    420             return "Predefined"sv;
    421         case SourceOrigin::GroupPolicy:
    422             return "GroupPolicy"sv;
    423         case SourceOrigin::Metadata:
    424             return "Metadata"sv;
    425         default:
    426             THROW_HR(E_UNEXPECTED);
    427         }
    428     }
    429 
    430     std::optional<WellKnownSource> CheckForWellKnownSource(const SourceDetails& sourceDetails)
    431     {
    432         return CheckForWellKnownSourceMatch(sourceDetails.Name, sourceDetails.Arg, sourceDetails.Type);
    433     }
    434 
    435     Source::Source() {}
    436 
    437     Source::Source(std::string_view name)
    438     {
    439         InitializeSourceReference(name);
    440     }
    441 
    442     Source::Source(PredefinedSource source)
    443     {
    444         SourceDetails details = GetPredefinedSourceDetails(source);
    445         m_sourceReferences.emplace_back(CreateSourceFromDetails(details));
    446     }
    447 
    448     Source::Source(WellKnownSource source)
    449     {
    450         THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !IsWellKnownSourceEnabled(source));
    451 
    452         auto details = GetWellKnownSourceDetailsInternal(source);
    453 
    454         // Populate metadata
    455         SourceList sourceList;
    456         auto sourceDetailsWithMetadata = sourceList.GetSource(details.Name);
    457         if (sourceDetailsWithMetadata)
    458         {
    459             sourceDetailsWithMetadata->CopyMetadataFieldsTo(details);
    460         }
    461 
    462         m_sourceReferences.emplace_back(CreateSourceFromDetails(details));
    463     }
    464 
    465     Source::Source(std::string_view name, std::string_view arg, std::string_view type, SourceTrustLevel trustLevel, bool isExplicit)
    466     {
    467         m_isSourceToBeAdded = true;
    468         SourceDetails details;
    469 
    470         std::optional<WellKnownSource> wellKnownSourceCheck = CheckForWellKnownSourceMatch(name, arg, type);
    471 
    472         if (wellKnownSourceCheck)
    473         {
    474             details = GetWellKnownSourceDetailsInternal(wellKnownSourceCheck.value());
    475         }
    476         else
    477         {
    478             details.Name = name;
    479             details.Arg = arg;
    480             details.Type = type;
    481             details.TrustLevel = trustLevel;
    482             details.Explicit = isExplicit;
    483         }
    484 
    485         m_sourceReferences.emplace_back(CreateSourceFromDetails(details));
    486     }
    487 
    488     Source::Source(const std::vector<Source>& availableSources)
    489     {
    490         std::shared_ptr<CompositeSource> compositeSource = std::make_shared<CompositeSource>("*CompositeSource");
    491 
    492         for (const auto& availableSource : availableSources)
    493         {
    494             THROW_HR_IF(E_INVALIDARG, !availableSource.m_source || availableSource.IsComposite());
    495             compositeSource->AddAvailableSource(availableSource.m_source);
    496         }
    497 
    498         m_source = compositeSource;
    499         m_isComposite = true;
    500     }
    501 
    502     Source::Source(const Source& installedSource, const Source& availableSource, CompositeSearchBehavior searchBehavior)
    503     {
    504         THROW_HR_IF(E_INVALIDARG, !installedSource.m_source || installedSource.m_isComposite || !availableSource.m_source);
    505 
    506         std::shared_ptr<CompositeSource> compositeSource = SourceCast<CompositeSource>(availableSource.m_source);
    507 
    508         if (!compositeSource)
    509         {
    510             compositeSource = std::make_shared<CompositeSource>("*CompositeSource");
    511             compositeSource->AddAvailableSource(availableSource.m_source);
    512         }
    513 
    514         compositeSource->SetInstalledSource(installedSource, searchBehavior);
    515 
    516         m_source = compositeSource;
    517         m_isComposite = true;
    518     }
    519 
    520     Source::Source(std::shared_ptr<ISource> source) : m_source(std::move(source)) {}
    521 
    522     Source::operator bool() const
    523     {
    524         return !m_sourceReferences.empty() || m_source != nullptr;
    525     }
    526 
    527     void Source::InitializeSourceReference(std::string_view name)
    528     {
    529         SourceList sourceList;
    530 
    531         if (name.empty())
    532         {
    533             auto currentSources = sourceList.GetCurrentSourceRefs();
    534             if (currentSources.empty())
    535             {
    536                 AICLI_LOG(Repo, Info, << "Default source requested, but no sources configured");
    537             }
    538             else if (currentSources.size() == 1)
    539             {
    540                 if (!currentSources[0].get().Explicit)
    541                 {
    542                     AICLI_LOG(Repo, Info, << "Default source requested, only 1 source available, using the only source: " << currentSources[0].get().Name);
    543                     InitializeSourceReference(currentSources[0].get().Name);
    544                 }
    545                 else
    546                 {
    547                     AICLI_LOG(Repo, Info, << "Skipping explicit source reference " << currentSources[0].get().Name);
    548                 }
    549             }
    550             else
    551             {
    552                 AICLI_LOG(Repo, Info, << "Default source requested, multiple sources available, adding all to source references.");
    553 
    554                 for (auto& source : currentSources)
    555                 {
    556                     if (!source.get().Explicit)
    557                     {
    558                         AICLI_LOG(Repo, Info, << "Adding to source references " << source.get().Name);
    559                         m_sourceReferences.emplace_back(CreateSourceFromDetails(source));
    560                     }
    561                     else
    562                     {
    563                         AICLI_LOG(Repo, Info, << "Skipping explicit source reference " << source.get().Name);
    564                     }
    565                 }
    566 
    567                 m_isComposite = true;
    568             }
    569         }
    570         else
    571         {
    572             auto source = sourceList.GetCurrentSource(name);
    573             if (!source)
    574             {
    575                 AICLI_LOG(Repo, Info, << "Named source requested, but not found: " << name);
    576             }
    577             else
    578             {
    579                 AICLI_LOG(Repo, Info, << "Named source requested, found: " << source->Name);
    580                 m_sourceReferences.emplace_back(CreateSourceFromDetails(*source));
    581             }
    582         }
    583     }
    584 
    585     bool Source::operator==(const Source& other) const
    586     {
    587         SourceDetails thisDetails = GetDetails();
    588         SourceDetails otherDetails = other.GetDetails();
    589 
    590         return (thisDetails.Type == otherDetails.Type && thisDetails.Identifier == otherDetails.Identifier);
    591     }
    592 
    593     bool Source::operator!=(const Source& other) const
    594     {
    595         return !operator==(other);
    596     }
    597 
    598     std::string Source::GetIdentifier() const
    599     {
    600         if (m_source)
    601         {
    602             return m_source->GetIdentifier();
    603         }
    604         else if (m_sourceReferences.size() == 1)
    605         {
    606             return m_sourceReferences[0]->GetIdentifier();
    607         }
    608         else
    609         {
    610             THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
    611         }
    612     }
    613 
    614     SourceDetails Source::GetDetails() const
    615     {
    616         if (m_source)
    617         {
    618             return m_source->GetDetails();
    619         }
    620         else if (m_sourceReferences.size() == 1)
    621         {
    622             return m_sourceReferences[0]->GetDetails();
    623         }
    624         else
    625         {
    626             THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
    627         }
    628     }
    629 
    630     SourceInformation Source::GetInformation() const
    631     {
    632         if (m_source && !m_isComposite)
    633         {
    634             return m_source->GetInformation();
    635         }
    636         else if (m_sourceReferences.size() == 1)
    637         {
    638             return m_sourceReferences[0]->GetInformation();
    639         }
    640         else
    641         {
    642             THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
    643         }
    644     }
    645 
    646     bool Source::QueryFeatureFlag(SourceFeatureFlag flag) const
    647     {
    648         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source);
    649         return m_source->QueryFeatureFlag(flag);
    650     }
    651 
    652     bool Source::ContainsAvailablePackages() const
    653     {
    654         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), IsComposite());
    655         return ContainsAvailablePackagesInternal(GetDetails().Origin);
    656     }
    657 
    658     bool Source::SetCustomHeader(std::optional<std::string> header)
    659     {
    660         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_sourceReferences.size() != 1);
    661         return m_sourceReferences[0]->SetCustomHeader(header);
    662     }
    663 
    664     void Source::SetCaller(std::string caller)
    665     {
    666         for (auto& sourceReference : m_sourceReferences)
    667         {
    668             sourceReference->SetCaller(caller);
    669         }
    670     }
    671 
    672     void Source::SetAuthenticationArguments(Authentication::AuthenticationArguments args)
    673     {
    674         for (auto& sourceReference : m_sourceReferences)
    675         {
    676             sourceReference->SetAuthenticationArguments(args);
    677         }
    678     }
    679 
    680     void Source::SetBackgroundUpdateInterval(TimeSpan interval)
    681     {
    682         m_backgroundUpdateInterval = interval;
    683     }
    684 
    685     void Source::InstalledPackageInformationOnly(bool value)
    686     {
    687         m_installedPackageInformationOnly = value;
    688     }
    689 
    690     bool Source::IsWellKnownSource(WellKnownSource wellKnownSource)
    691     {
    692         SourceDetails details = GetDetails();
    693         auto wellKnown = CheckForWellKnownSourceMatch(details.Name, details.Arg, details.Type);
    694         return wellKnown && wellKnown.value() == wellKnownSource;
    695     }
    696 
    697     SearchResult Source::Search(const SearchRequest& request) const
    698     {
    699         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source);
    700         return m_source->Search(request);
    701     }
    702 
    703     ImplicitAgreementFieldEnum Source::GetAgreementFieldsFromSourceInformation() const
    704     {
    705         ImplicitAgreementFieldEnum result = ImplicitAgreementFieldEnum::None;
    706 
    707         auto info = GetInformation();
    708         if (info.RequiredPackageMatchFields.end() != std::find_if(info.RequiredPackageMatchFields.begin(), info.RequiredPackageMatchFields.end(), [&](const auto& field) { return Utility::CaseInsensitiveEquals(field, "market"); }) ||
    709             info.RequiredQueryParameters.end() != std::find_if(info.RequiredQueryParameters.begin(), info.RequiredQueryParameters.end(), [&](const auto& param) { return Utility::CaseInsensitiveEquals(param, "market"); }))
    710         {
    711             WI_SetFlag(result, ImplicitAgreementFieldEnum::Market);
    712         }
    713 
    714         return result;
    715     }
    716 
    717     bool Source::CheckSourceAgreements() const
    718     {
    719         auto sourceName = GetDetails().Name;
    720         auto agreementFields = GetAgreementFieldsFromSourceInformation();
    721         auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier;
    722 
    723         SourceList sourceList;
    724         return sourceList.CheckSourceAgreements(sourceName, agreementsIdentifier, agreementFields);
    725     }
    726 
    727     void Source::SaveAcceptedSourceAgreements() const
    728     {
    729         auto sourceName = GetDetails().Name;
    730         auto agreementFields = GetAgreementFieldsFromSourceInformation();
    731         auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier;
    732 
    733         SourceList sourceList;
    734         return sourceList.SaveAcceptedSourceAgreements(sourceName, agreementsIdentifier, agreementFields);
    735     }
    736 
    737     bool Source::IsComposite() const
    738     {
    739         return m_isComposite;
    740     }
    741 
    742     std::vector<Source> Source::GetAvailableSources() const
    743     {
    744         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source || !m_isComposite);
    745 
    746         auto compositeSource = SourceCast<CompositeSource>(m_source);
    747         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !compositeSource);
    748 
    749         return compositeSource->GetAvailableSources();
    750     }
    751 
    752     void Source::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath)
    753     {
    754         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source);
    755         auto writableSource = SourceCast<IMutablePackageSource>(m_source);
    756         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource);
    757         writableSource->AddPackageVersion(manifest, relativePath);
    758     }
    759 
    760     void Source::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath)
    761     {
    762         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source);
    763         auto writableSource = SourceCast<IMutablePackageSource>(m_source);
    764         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource);
    765         writableSource->RemovePackageVersion(manifest, relativePath);
    766     }
    767 
    768     std::vector<SourceDetails> Source::Open(IProgressCallback& progress)
    769     {
    770         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.empty());
    771 
    772         std::vector<SourceDetails> result;
    773 
    774         if (!m_source)
    775         {
    776             std::vector<std::shared_ptr<ISourceReference>>* sourceReferencesToOpen = nullptr;
    777             std::vector<std::shared_ptr<ISourceReference>> sourceReferencesForTrackingOnly;
    778             std::unique_ptr<SourceList> sourceList;
    779 
    780             if (m_installedPackageInformationOnly)
    781             {
    782                 sourceReferencesToOpen = &sourceReferencesForTrackingOnly;
    783 
    784                 // Create a wrapper for each reference
    785                 for (auto& sourceReference : m_sourceReferences)
    786                 {
    787                     sourceReferencesForTrackingOnly.emplace_back(std::make_shared<TrackingOnlyReferenceWrapper>(sourceReference));
    788                 }
    789             }
    790             else
    791             {
    792                 // Check for updates before opening.
    793                 for (auto& sourceReference : m_sourceReferences)
    794                 {
    795                     if (ShouldUpdateBeforeOpen(sourceReference.get(), m_backgroundUpdateInterval))
    796                     {
    797                         auto& details = sourceReference->GetDetails();
    798 
    799                         try
    800                         {
    801                             // TODO: Consider adding a context callback to indicate we are doing the same action
    802                             // to avoid the progress bar fill up multiple times.
    803                             AddOrUpdateResult updateResult = BackgroundUpdateSourceFromDetails(details, progress);
    804 
    805                             if (updateResult.MetadataWritten)
    806                             {
    807                                 if (sourceList == nullptr)
    808                                 {
    809                                     sourceList = std::make_unique<SourceList>();
    810                                 }
    811 
    812                                 auto detailsInternal = sourceList->GetSource(details.Name);
    813                                 detailsInternal->CopyMetadataFieldsFrom(details);
    814                                 sourceList->SaveMetadata(*detailsInternal);
    815                             }
    816 
    817                             if (!updateResult.UpdateChecked)
    818                             {
    819                                 AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name);
    820                                 result.emplace_back(details);
    821                             }
    822                         }
    823                         catch (...)
    824                         {
    825                             LOG_CAUGHT_EXCEPTION();
    826                             AICLI_LOG(Repo, Warning, << "Failed to update source: " << details.Name);
    827                             result.emplace_back(details);
    828                         }
    829                     }
    830                 }
    831 
    832                 sourceReferencesToOpen = &m_sourceReferences;
    833             }
    834 
    835             if (sourceReferencesToOpen->size() > 1)
    836             {
    837                 AICLI_LOG(Repo, Info, << "Multiple sources available, creating aggregated source.");
    838                 auto aggregatedSource = std::make_shared<CompositeSource>("*DefaultSource");
    839                 std::vector<std::shared_ptr<OpenExceptionProxy>> openExceptionProxies;
    840 
    841                 for (auto& sourceReference : *sourceReferencesToOpen)
    842                 {
    843                     AICLI_LOG(Repo, Info, << "Adding to aggregated source: " << sourceReference->GetDetails().Name);
    844 
    845                     try
    846 
    847                     {
    848                         aggregatedSource->AddAvailableSource(sourceReference->Open(progress));
    849                     }
    850                     catch (...)
    851                     {
    852                         LOG_CAUGHT_EXCEPTION();
    853                         AICLI_LOG(Repo, Warning, << "Failed to open available source: " << sourceReference->GetDetails().Name);
    854                         openExceptionProxies.emplace_back(std::make_shared<OpenExceptionProxy>(sourceReference->GetDetails(), std::current_exception()));
    855                     }
    856                 }
    857 
    858                 // If all sources failed to open, then throw an exception that is specific to this case.
    859                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, !aggregatedSource->HasAvailableSource());
    860 
    861                 // Place all of the proxies into the source to be searched later
    862                 for (auto& proxy : openExceptionProxies)
    863                 {
    864                     aggregatedSource->AddAvailableSource(Source{ std::move(proxy) });
    865                 }
    866 
    867                 m_source = aggregatedSource;
    868             }
    869             else
    870             {
    871                 m_source = (*sourceReferencesToOpen)[0]->Open(progress);
    872             }
    873         }
    874 
    875         return result;
    876     }
    877 
    878     bool Source::Add(IProgressCallback& progress)
    879     {
    880         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isSourceToBeAdded || m_sourceReferences.size() != 1);
    881 
    882         auto& sourceDetails = m_sourceReferences[0]->GetDetails();
    883 
    884         // If the source type is empty, use a default.
    885         // AddSourceForDetails will also check for empty, but we need the actual type before that for validation.
    886         if (sourceDetails.Type.empty())
    887         {
    888             sourceDetails.Type = GetDefaultSourceType();
    889         }
    890 
    891         AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]");
    892 
    893         // Check all sources for the given name.
    894         SourceList sourceList;
    895 
    896         auto source = sourceList.GetSource(sourceDetails.Name);
    897         THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, source != nullptr && source->Origin != SourceOrigin::Metadata && !source->IsTombstone);
    898 
    899         // Check sources allowed by group policy
    900         auto blockingPolicy = GetPolicyBlockingUserSource(sourceDetails.Name, sourceDetails.Type, sourceDetails.Arg, false);
    901         if (blockingPolicy != TogglePolicy::Policy::None)
    902         {
    903             throw GroupPolicyException(blockingPolicy);
    904         }
    905 
    906         sourceDetails.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(0);
    907 
    908         // Allow the origin to stay as Default if the incoming details match a well known value
    909         if (!(sourceDetails.Origin == SourceOrigin::Default && CheckForWellKnownSourceMatch(sourceDetails.Name, sourceDetails.Arg, sourceDetails.Type)))
    910         {
    911             sourceDetails.Origin = SourceOrigin::User;
    912         }
    913 
    914         bool result = AddSourceFromDetails(sourceDetails, progress).UpdateChecked;
    915         if (result)
    916         {
    917             sourceList.AddSource(sourceDetails);
    918             SaveAcceptedSourceAgreements();
    919             m_isSourceToBeAdded = false;
    920             AICLI_LOG(Repo, Info, << "Source created with extra data: " << sourceDetails.Data);
    921         }
    922 
    923         return result;
    924     }
    925 
    926     std::vector<SourceDetails> Source::Update(IProgressCallback& progress)
    927     {
    928         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_source || m_sourceReferences.empty());
    929 
    930         SourceList sourceList;
    931         std::vector<SourceDetails> result;
    932 
    933         for (auto& sourceReference : m_sourceReferences)
    934         {
    935             THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !ContainsAvailablePackagesInternal(sourceReference->GetDetails().Origin));
    936 
    937             auto& details = sourceReference->GetDetails();
    938             AICLI_LOG(Repo, Info, << "Named source to be updated, found: " << details.Name);
    939 
    940             try
    941             {
    942                 // TODO: Consider adding a context callback to indicate we are doing the same action
    943                 // to avoid the progress bar fill up multiple times.
    944                 AddOrUpdateResult updateResult = UpdateSourceFromDetails(details, progress);
    945 
    946                 if (updateResult.MetadataWritten)
    947                 {
    948                     auto detailsInternal = sourceList.GetSource(details.Name);
    949                     detailsInternal->CopyMetadataFieldsFrom(details);
    950                     sourceList.SaveMetadata(*detailsInternal);
    951                 }
    952 
    953                 if (!updateResult.UpdateChecked)
    954                 {
    955                     AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name);
    956                     result.emplace_back(details);
    957                 }
    958             }
    959             catch (...)
    960             {
    961                 LOG_CAUGHT_EXCEPTION();
    962                 AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name);
    963                 result.emplace_back(details);
    964             }
    965         }
    966 
    967         return result;
    968     }
    969 
    970     bool Source::Remove(IProgressCallback& progress)
    971     {
    972         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.size() != 1 || m_source);
    973 
    974         const auto& details = m_sourceReferences[0]->GetDetails();
    975         AICLI_LOG(Repo, Info, << "Named source to be removed, found: " << details.Name << " [" << ToString(details.Origin) << ']');
    976 
    977         EnsureSourceIsRemovable(details);
    978 
    979         bool result = RemoveSourceFromDetails(details, progress);
    980         if (result)
    981         {
    982             SourceList sourceList;
    983             sourceList.RemoveSource(details);
    984         }
    985 
    986         return result;
    987     }
    988 
    989     PackageTrackingCatalog Source::GetTrackingCatalog() const
    990     {
    991         // With C++20, consider removing the shared_ptr here and making the one inside PackageTrackingCatalog atomic.
    992         std::shared_ptr<PackageTrackingCatalog> currentTrackingCatalog = std::atomic_load(&m_trackingCatalog);
    993         if (!currentTrackingCatalog)
    994         {
    995             std::shared_ptr<PackageTrackingCatalog> newTrackingCatalog = std::make_shared<PackageTrackingCatalog>(PackageTrackingCatalog::CreateForSource(*this));
    996 
    997             if (std::atomic_compare_exchange_strong(&m_trackingCatalog, &currentTrackingCatalog, newTrackingCatalog))
    998             {
    999                 currentTrackingCatalog = newTrackingCatalog;
   1000             }
   1001         }
   1002 
   1003         return *currentTrackingCatalog;
   1004     }
   1005 
   1006     std::vector<SourceDetails> Source::GetCurrentSources()
   1007     {
   1008         SourceList sourceList;
   1009 
   1010         std::vector<SourceDetails> result;
   1011         for (auto&& source : sourceList.GetCurrentSourceRefs())
   1012         {
   1013             result.emplace_back(std::move(source));
   1014         }
   1015 
   1016         return result;
   1017     }
   1018 
   1019     bool Source::DropSource(std::string_view name)
   1020     {
   1021         if (name.empty())
   1022         {
   1023             SourceList::RemoveSettingsStreams();
   1024             return true;
   1025         }
   1026         else
   1027         {
   1028             SourceList sourceList;
   1029 
   1030             auto source = sourceList.GetCurrentSource(name);
   1031             if (!source)
   1032             {
   1033                 AICLI_LOG(Repo, Info, << "Named source to be dropped, but not found: " << name);
   1034                 return false;
   1035             }
   1036             else
   1037             {
   1038                 AICLI_LOG(Repo, Info, << "Named source to be dropped, found: " << source->Name);
   1039 
   1040                 EnsureSourceIsRemovable(*source);
   1041                 sourceList.RemoveSource(*source);
   1042 
   1043                 return true;
   1044             }
   1045         }
   1046     }
   1047 
   1048     std::string_view Source::GetDefaultSourceType()
   1049     {
   1050         return ISourceFactory::GetForType("")->TypeName();
   1051     }
   1052 
   1053 #ifndef AICLI_DISABLE_TEST_HOOKS
   1054     void TestHook_SetSourceFactoryOverride(const std::string& type, std::function<std::unique_ptr<ISourceFactory>()>&& factory)
   1055     {
   1056         s_Sources_TestHook_SourceFactories[type] = std::move(factory);
   1057     }
   1058 
   1059     void TestHook_ClearSourceFactoryOverrides()
   1060     {
   1061         s_Sources_TestHook_SourceFactories.clear();
   1062     }
   1063 #endif
   1064 }