winget-cli

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

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 }