PackageVersionSelection.cpp (12276B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/winget/PackageVersionSelection.h" 5 #include "Public/winget/RepositorySource.h" 6 #include "Public/winget/PinningData.h" 7 8 9 namespace AppInstaller::Repository 10 { 11 namespace 12 { 13 std::shared_ptr<IPackage> GetAvailablePackageFromSource(const std::vector<std::shared_ptr<IPackage>>& packages, const std::string_view sourceIdentifier) 14 { 15 for (const std::shared_ptr<IPackage>& package : packages) 16 { 17 if (sourceIdentifier == package->GetSource().GetIdentifier()) 18 { 19 return package; 20 } 21 } 22 23 return {}; 24 } 25 26 struct AvailablePackageVersionCollection : public IPackageVersionCollection 27 { 28 AvailablePackageVersionCollection(const std::shared_ptr<ICompositePackage>& composite, const std::shared_ptr<IPackageVersion>& installedVersion) : 29 m_packages(composite->GetAvailable()) 30 { 31 if (!installedVersion) 32 { 33 return; 34 } 35 36 m_channel = installedVersion->GetProperty(PackageVersionProperty::Channel); 37 38 // Remove the packages that are not from the installed source. 39 Source installedVersionSource = installedVersion->GetSource(); 40 if (installedVersionSource && installedVersionSource.ContainsAvailablePackages()) 41 { 42 m_packages.erase(std::remove_if(m_packages.begin(), m_packages.end(), [&](const std::shared_ptr<IPackage>& p) { return installedVersionSource != p->GetSource(); }), m_packages.end()); 43 } 44 } 45 46 std::vector<PackageVersionKey> GetVersionKeys() const override 47 { 48 std::vector<PackageVersionKey> result; 49 50 for (const std::shared_ptr<IPackage>& package : m_packages) 51 { 52 std::vector<PackageVersionKey> versionKeys = package->GetVersionKeys(); 53 std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); 54 } 55 56 // Remove all elements whose channel does not match the installed package. 57 if (m_channel) 58 { 59 result.erase( 60 std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, m_channel.value()); }), 61 result.end()); 62 } 63 64 // Put latest versions at the front; for versions available from multiple sources maintain the order they were added in 65 std::stable_sort(result.begin(), result.end()); 66 67 return result; 68 } 69 70 std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const override 71 { 72 // If there is a specific source, just use that package's result 73 std::shared_ptr<IPackage> package; 74 75 if (!versionKey.SourceId.empty()) 76 { 77 package = GetAvailablePackageFromSource(m_packages, versionKey.SourceId); 78 } 79 else if (m_packages.size() == 1) 80 { 81 package = m_packages[0]; 82 } 83 else if (versionKey.IsDefaultLatest()) 84 { 85 std::shared_ptr<IPackageVersion> result; 86 Utility::Version resultVersion; 87 88 for (const auto& p : m_packages) 89 { 90 std::shared_ptr<IPackageVersion> latest = p->GetLatestVersion(); 91 Utility::Version version { latest->GetProperty(PackageVersionProperty::Version) }; 92 93 if (!result || resultVersion < version) 94 { 95 result = std::move(latest); 96 resultVersion = std::move(version); 97 } 98 } 99 100 return result; 101 } 102 else 103 { 104 // Otherwise, find the first version that matches 105 std::vector<PackageVersionKey> versions = GetVersionKeys(); 106 107 for (const PackageVersionKey& key : versions) 108 { 109 if (key.IsMatch(versionKey)) 110 { 111 package = GetAvailablePackageFromSource(m_packages, key.SourceId); 112 break; 113 } 114 } 115 } 116 117 return package ? package->GetVersion(versionKey) : nullptr; 118 } 119 120 std::shared_ptr<IPackageVersion> GetLatestVersion() const override 121 { 122 return GetVersion({ "", "", m_channel.value_or("") }); 123 } 124 125 private: 126 std::optional<std::string> m_channel; 127 std::vector<std::shared_ptr<IPackage>> m_packages; 128 }; 129 } 130 131 std::shared_ptr<IPackageVersionCollection> GetAvailableVersionsForInstalledVersion(const std::shared_ptr<ICompositePackage>& composite) 132 { 133 return std::make_shared<AvailablePackageVersionCollection>(composite, GetInstalledVersion(composite)); 134 } 135 136 std::shared_ptr<IPackageVersionCollection> GetAvailableVersionsForInstalledVersion( 137 const std::shared_ptr<ICompositePackage>& composite, 138 const std::shared_ptr<IPackageVersion>& installedVersion) 139 { 140 return std::make_shared<AvailablePackageVersionCollection>(composite, installedVersion); 141 } 142 143 std::shared_ptr<IPackageVersionCollection> GetAllAvailableVersions(const std::shared_ptr<ICompositePackage>& composite) 144 { 145 return GetAvailableVersionsForInstalledVersion(composite, nullptr); 146 } 147 148 std::shared_ptr<IPackageVersion> GetInstalledVersion(const std::shared_ptr<ICompositePackage>& composite) 149 { 150 auto installedPackage = composite->GetInstalled(); 151 return installedPackage ? installedPackage->GetLatestVersion() : nullptr; 152 } 153 154 std::shared_ptr<IPackage> GetAvailablePackageFromSource(const std::shared_ptr<ICompositePackage>& composite, const std::string_view sourceIdentifier) 155 { 156 return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); 157 } 158 159 LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr<ICompositePackage>& composite) 160 { 161 using namespace AppInstaller::Pinning; 162 163 LatestApplicableVersionData result; 164 165 auto installedVersion = AppInstaller::Repository::GetInstalledVersion(composite); 166 auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(composite, installedVersion); 167 168 PinningData pinningData{ PinningData::Disposition::ReadOnly }; 169 auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, installedVersion); 170 171 AppInstaller::Manifest::ManifestComparator::Options options; 172 if (installedVersion) 173 { 174 GetManifestComparatorOptionsFromMetadata(options, installedVersion->GetMetadata()); 175 } 176 AppInstaller::Manifest::ManifestComparator manifestComparator{ options }; 177 178 auto availableVersionKeys = availableVersions->GetVersionKeys(); 179 for (const auto& availableVersionKey : availableVersionKeys) 180 { 181 auto availableVersion = availableVersions->GetVersion(availableVersionKey); 182 183 if (installedVersion && !evaluator.IsUpdate(availableVersion)) 184 { 185 // Version too low or different channel for upgrade 186 continue; 187 } 188 189 if (evaluator.EvaluatePinType(availableVersion) != AppInstaller::Pinning::PinType::Unknown) 190 { 191 // Pinned 192 continue; 193 } 194 195 auto manifestComparatorResult = manifestComparator.GetPreferredInstaller(availableVersion->GetManifest()); 196 if (!manifestComparatorResult.installer.has_value()) 197 { 198 // No applicable installer 199 continue; 200 } 201 202 result.LatestApplicableVersion = availableVersion; 203 if (installedVersion) 204 { 205 result.UpdateAvailable = true; 206 } 207 208 break; 209 } 210 211 return result; 212 } 213 214 void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures) 215 { 216 auto installedTypeItr = metadata.find(Repository::PackageVersionMetadata::InstalledType); 217 if (installedTypeItr != metadata.end()) 218 { 219 options.CurrentlyInstalledType = Manifest::ConvertToInstallerTypeEnum(installedTypeItr->second); 220 } 221 222 auto installedScopeItr = metadata.find(Repository::PackageVersionMetadata::InstalledScope); 223 if (installedScopeItr != metadata.end()) 224 { 225 options.CurrentlyInstalledScope = Manifest::ConvertToScopeEnum(installedScopeItr->second); 226 } 227 228 auto userIntentLocaleItr = metadata.find(Repository::PackageVersionMetadata::UserIntentLocale); 229 if (userIntentLocaleItr != metadata.end()) 230 { 231 options.PreviousUserIntentLocale = userIntentLocaleItr->second; 232 } 233 234 auto installedLocaleItr = metadata.find(Repository::PackageVersionMetadata::InstalledLocale); 235 if (installedLocaleItr != metadata.end()) 236 { 237 options.CurrentlyInstalledLocale = installedLocaleItr->second; 238 } 239 240 if (includeAllowedArchitectures) 241 { 242 auto userIntentItr = metadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); 243 if (userIntentItr != metadata.end()) 244 { 245 // For upgrade, user intent from previous install is considered requirement 246 options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(userIntentItr->second)); 247 } 248 else 249 { 250 auto installedItr = metadata.find(Repository::PackageVersionMetadata::InstalledArchitecture); 251 if (installedItr != metadata.end()) 252 { 253 // For upgrade, previous installed architecture should be considered first preference and is always allowed. 254 // Then check settings requirements and preferences. 255 options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(installedItr->second)); 256 } 257 258 std::vector<Utility::Architecture> requiredArchitectures = Settings::User().Get<Settings::Setting::InstallArchitectureRequirement>(); 259 std::vector<Utility::Architecture> optionalArchitectures = Settings::User().Get<Settings::Setting::InstallArchitecturePreference>(); 260 261 if (!requiredArchitectures.empty()) 262 { 263 // Required architecture list from settings if applicable 264 options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), requiredArchitectures.begin(), requiredArchitectures.end()); 265 } 266 else 267 { 268 // Preferred architecture list from settings if applicable, add Unknown to indicate allowing remaining applicable 269 if (!optionalArchitectures.empty()) 270 { 271 options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), optionalArchitectures.begin(), optionalArchitectures.end()); 272 } 273 274 options.AllowedArchitectures.emplace_back(Utility::Architecture::Unknown); 275 } 276 } 277 } 278 } 279 }