PackageTrackingCatalog.cpp (12802B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "winget/PackageTrackingCatalog.h" 5 #include "PackageTrackingCatalogSourceFactory.h" 6 #include "winget/Pin.h" 7 #include "winget/RepositorySource.h" 8 #include "Microsoft/SQLiteIndexSource.h" 9 #include "AppInstallerDateTime.h" 10 11 using namespace std::string_literals; 12 using namespace AppInstaller::Repository::Microsoft; 13 14 15 namespace AppInstaller::Repository 16 { 17 namespace 18 { 19 constexpr std::string_view c_PackageTrackingFileName = "installed.db"; 20 constexpr std::string_view c_PackageTrackingCorruptedFileName = "installed-corrupted.db"; 21 22 std::string CreateNameForCPL(const std::string& pathName) 23 { 24 return "PackageTrackingCPL_"s + pathName; 25 } 26 27 std::filesystem::path GetPackageTrackingFilePath(const std::string& pathName) 28 { 29 std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); 30 result /= pathName; 31 result /= c_PackageTrackingFileName; 32 return result; 33 } 34 35 // Call while holding the CrossProcessLock 36 SQLiteIndex CreateOnlyTrackingIndex(const std::filesystem::path& trackingDB) 37 { 38 return SQLiteIndex::CreateNew(trackingDB.u8string(), SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); 39 } 40 41 // Call while holding the CrossProcessLock 42 SQLiteIndex CreateOrOpenTrackingIndex(const std::filesystem::path& trackingDB) 43 { 44 if (!std::filesystem::exists(trackingDB)) 45 { 46 std::filesystem::create_directories(trackingDB.parent_path()); 47 return CreateOnlyTrackingIndex(trackingDB); 48 } 49 else 50 { 51 try 52 { 53 // TODO: Check schema version and upgrade as necessary when there is a relevant new schema. 54 // Could write this all now but it will be better tested when there is a new schema. 55 return SQLiteIndex::Open(trackingDB.u8string(), SQLiteIndex::OpenDisposition::ReadWrite); 56 } 57 catch(...) 58 { 59 LOG_CAUGHT_EXCEPTION_MSG("Exception opening tracking catalog"); 60 } 61 62 // Move existing database and create a new one 63 std::filesystem::path destination{ trackingDB }; 64 destination.replace_filename(c_PackageTrackingCorruptedFileName); 65 SQLite::SQLiteStorageBase::RenameSQLiteDatabase(trackingDB, destination, true); 66 67 return CreateOnlyTrackingIndex(trackingDB); 68 } 69 } 70 71 struct PackageTrackingCatalogSourceReference : public ISourceReference 72 { 73 PackageTrackingCatalogSourceReference(const SourceDetails& details) : m_details(details) {} 74 75 SourceDetails& GetDetails() override 76 { 77 return m_details; 78 } 79 80 std::string GetIdentifier() override 81 { 82 return m_details.Identifier; 83 } 84 85 std::shared_ptr<ISource> Open(IProgressCallback& callback) override 86 { 87 m_details.Arg = Utility::MakeSuitablePathPart(m_details.Data); 88 std::filesystem::path trackingDB = GetPackageTrackingFilePath(m_details.Arg); 89 90 Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details.Arg)); 91 if (!lock.Acquire(callback)) 92 { 93 return {}; 94 } 95 96 return std::make_shared<SQLiteIndexSource>(m_details, CreateOrOpenTrackingIndex(trackingDB)); 97 } 98 99 private: 100 // Store the identifier of the source in the Data field. 101 SourceDetails m_details; 102 }; 103 104 struct PackageTrackingCatalogSourceFactoryImpl : public ISourceFactory 105 { 106 std::string_view TypeName() const override final 107 { 108 return PackageTrackingCatalogSourceFactory::Type(); 109 } 110 111 std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final 112 { 113 THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, PackageTrackingCatalogSourceFactory::Type())); 114 115 return std::make_shared<PackageTrackingCatalogSourceReference>(details); 116 } 117 118 bool Add(SourceDetails&, IProgressCallback&) override final 119 { 120 THROW_HR(E_NOTIMPL); 121 } 122 123 bool Update(const SourceDetails&, IProgressCallback&) override final 124 { 125 THROW_HR(E_NOTIMPL); 126 } 127 128 bool Remove(const SourceDetails& details, IProgressCallback& progress) override final 129 { 130 THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, PackageTrackingCatalogSourceFactory::Type())); 131 132 std::string pathName = Utility::MakeSuitablePathPart(details.Data); 133 134 Synchronization::CrossProcessLock lock(CreateNameForCPL(pathName)); 135 if (!lock.Acquire(progress)) 136 { 137 return false; 138 } 139 140 std::filesystem::path trackingDB = GetPackageTrackingFilePath(pathName); 141 142 if (std::filesystem::exists(trackingDB)) 143 { 144 std::filesystem::remove(trackingDB); 145 } 146 147 return true; 148 } 149 }; 150 } 151 152 struct PackageTrackingCatalog::implementation 153 { 154 std::shared_ptr<Microsoft::SQLiteIndexSource> Source; 155 }; 156 157 PackageTrackingCatalog::PackageTrackingCatalog() = default; 158 PackageTrackingCatalog::PackageTrackingCatalog(const PackageTrackingCatalog&) = default; 159 PackageTrackingCatalog& PackageTrackingCatalog::operator=(const PackageTrackingCatalog&) = default; 160 PackageTrackingCatalog::PackageTrackingCatalog(PackageTrackingCatalog&&) noexcept = default; 161 PackageTrackingCatalog& PackageTrackingCatalog::operator=(PackageTrackingCatalog&&) noexcept = default; 162 PackageTrackingCatalog::~PackageTrackingCatalog() = default; 163 164 PackageTrackingCatalog PackageTrackingCatalog::CreateForSource(const Source& source) 165 { 166 // Not a valid source for tracking 167 const std::string sourceIdentifier = source.GetIdentifier(); 168 if (sourceIdentifier.empty() || !source.ContainsAvailablePackages()) 169 { 170 THROW_HR(E_INVALIDARG); 171 } 172 173 // Create fake details for the source while stashing some information that might be helpful for debugging 174 SourceDetails details; 175 details.Type = PackageTrackingCatalogSourceFactory::Type(); 176 details.Identifier = "*Tracking"; 177 details.Name = "Tracking for "s + source.GetDetails().Name; 178 details.Origin = SourceOrigin::PackageTracking; 179 details.Data = sourceIdentifier; 180 181 ProgressCallback dummyProgress; 182 183 PackageTrackingCatalog result; 184 result.m_implementation = std::make_shared<PackageTrackingCatalog::implementation>(); 185 result.m_implementation->Source = SourceCast<SQLiteIndexSource>(ISourceFactory::GetForType(details.Type)->Create(details)->Open(dummyProgress)); 186 187 return result; 188 } 189 190 void PackageTrackingCatalog::RemoveForSource(const std::string& identifier) 191 { 192 if (identifier.empty()) 193 { 194 THROW_HR(E_INVALIDARG); 195 } 196 197 // Create details to pass to the factory; the identifier of the source is passed in the Data field. 198 SourceDetails dummyDetails; 199 dummyDetails.Type = PackageTrackingCatalogSourceFactory::Type(); 200 dummyDetails.Data = identifier; 201 202 ProgressCallback dummyProgress; 203 204 ISourceFactory::GetForType(dummyDetails.Type)->Remove(dummyDetails, dummyProgress); 205 } 206 207 PackageTrackingCatalog::operator bool() const 208 { 209 return static_cast<bool>(m_implementation); 210 } 211 212 SearchResult PackageTrackingCatalog::Search(const SearchRequest& request) const 213 { 214 return m_implementation->Source->Search(request); 215 } 216 217 struct PackageTrackingCatalog::Version::implementation 218 { 219 SQLiteIndex::IdType Id; 220 }; 221 222 PackageTrackingCatalog::Version::Version(const Version&) = default; 223 PackageTrackingCatalog::Version& PackageTrackingCatalog::Version::operator=(const Version&) = default; 224 PackageTrackingCatalog::Version::Version(Version&&) noexcept = default; 225 PackageTrackingCatalog::Version& PackageTrackingCatalog::Version::operator=(Version&&) noexcept = default; 226 PackageTrackingCatalog::Version::~Version() = default; 227 228 PackageTrackingCatalog::Version::Version(PackageTrackingCatalog& catalog, std::shared_ptr<implementation>&& value) : 229 m_catalog(&catalog), m_implementation(std::move(value)) {} 230 231 void PackageTrackingCatalog::Version::SetMetadata(PackageVersionMetadata metadata, const Utility::NormalizedString& value) 232 { 233 auto& index = m_catalog->m_implementation->Source->GetIndex(); 234 index.SetMetadataByManifestId(m_implementation->Id, metadata, value); 235 } 236 237 PackageTrackingCatalog::Version PackageTrackingCatalog::RecordInstall( 238 Manifest::Manifest& manifest, 239 const Manifest::ManifestInstaller& installer, 240 bool isUpgrade) 241 { 242 // TODO: Store additional information from these if needed 243 UNREFERENCED_PARAMETER(isUpgrade); 244 245 auto& index = m_implementation->Source->GetIndex(); 246 247 // Strip ARP version information from the manifest if it is present 248 for (auto& arpRangeRemovedInstaller : manifest.Installers) 249 { 250 for (auto& arpRangeRemovedEntry : arpRangeRemovedInstaller.AppsAndFeaturesEntries) 251 { 252 arpRangeRemovedEntry.DisplayVersion.clear(); 253 } 254 } 255 256 // Check for an existing manifest that matches this one (could be reinstalling) 257 auto manifestIdOpt = index.GetManifestIdByManifest(manifest); 258 259 if (manifestIdOpt) 260 { 261 index.UpdateManifest(manifest); 262 } 263 else 264 { 265 manifestIdOpt = index.AddManifest(manifest); 266 } 267 268 SQLiteIndex::IdType manifestId = manifestIdOpt.value(); 269 270 // Write additional metadata for package tracking 271 std::ostringstream strstr; 272 strstr << Utility::GetCurrentUnixEpoch(); 273 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::TrackingWriteTime, strstr.str()); 274 275 if (installer.RequireExplicitUpgrade) 276 { 277 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::PinnedState, ToString(Pinning::PinType::PinnedByManifest)); 278 } 279 280 // Record installed architecture and locale if applicable 281 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, ToString(installer.Arch)); 282 if (!installer.Locale.empty()) 283 { 284 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledLocale, installer.Locale); 285 } 286 287 std::shared_ptr<Version::implementation> result = std::make_shared<Version::implementation>(); 288 result->Id = manifestId; 289 return { *this, std::move(result) }; 290 } 291 292 void PackageTrackingCatalog::RecordUninstall(const Utility::LocIndString& packageIdentifier) 293 { 294 auto& index = m_implementation->Source->GetIndex(); 295 296 SearchRequest idSearch; 297 idSearch.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier.get()); 298 auto searchResult = index.Search(idSearch); 299 300 for (const auto& match : searchResult.Matches) 301 { 302 auto versions = index.GetVersionKeysById(match.first); 303 304 for (const auto& version : versions) 305 { 306 index.RemoveManifestById(version.ManifestId); 307 } 308 } 309 } 310 311 #ifndef AICLI_DISABLE_TEST_HOOKS 312 std::filesystem::path PackageTrackingCatalog::GetFilePath() const 313 { 314 return m_implementation->Source->GetIndex().GetContextData().Get<Schema::Property::DatabaseFilePath>(); 315 } 316 #endif 317 318 std::unique_ptr<ISourceFactory> PackageTrackingCatalogSourceFactory::Create() 319 { 320 return std::make_unique<PackageTrackingCatalogSourceFactoryImpl>(); 321 } 322 }