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, ¤tTrackingCatalog, 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 }