winget-cli

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

PinFlow.cpp (13443B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Resources.h"
      5 #include "PinFlow.h"
      6 #include "TableOutput.h"
      7 #include <winget/PinningData.h>
      8 #include <winget/RepositorySearch.h>
      9 #include <winget/PackageVersionSelection.h>
     10 
     11 using namespace AppInstaller::Repository;
     12 
     13 namespace AppInstaller::CLI::Workflow
     14 {
     15     namespace
     16     {
     17         // Creates a Pin appropriate for the context based on the arguments provided
     18         Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey)
     19         {
     20             if (context.Args.Contains(Execution::Args::Type::GatedVersion))
     21             {
     22                 return Pinning::Pin::CreateGatingPin(pinKey, context.Args.GetArg(Execution::Args::Type::GatedVersion));
     23             }
     24             else if (context.Args.Contains(Execution::Args::Type::BlockingPin))
     25             {
     26                 return Pinning::Pin::CreateBlockingPin(pinKey);
     27             }
     28             else
     29             {
     30                 return Pinning::Pin::CreatePinningPin(pinKey);
     31             }
     32         }
     33 
     34         void GetPinKeysForInstalled(const std::shared_ptr<IPackageVersion>& installedVersion, std::set<Pinning::PinKey>& pinKeys)
     35         {
     36             auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]);
     37             std::vector<Utility::LocIndString> propertyStrings;
     38 
     39             if (Manifest::DoesInstallerTypeUsePackageFamilyName(installedType))
     40             {
     41                 propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName);
     42             }
     43             else if (Manifest::DoesInstallerTypeUseProductCode(installedType))
     44             {
     45                 propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode);
     46             }
     47 
     48             for (const auto& value : propertyStrings)
     49             {
     50                 pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(value));
     51             }
     52         }
     53 
     54         std::set<Pinning::PinKey> GetPinKeysForPackage(Execution::Context& context)
     55         {
     56             auto package = context.Get<Execution::Data::Package>();
     57 
     58             std::set<Pinning::PinKey> pinKeys;
     59 
     60             if (context.Args.Contains(Execution::Args::Type::PinInstalled))
     61             {
     62                 auto installedVersion = GetInstalledVersion(package);
     63                 if (installedVersion)
     64                 {
     65                     GetPinKeysForInstalled(installedVersion, pinKeys);
     66                 }
     67             }
     68             else
     69             {
     70                 auto availablePackages = package->GetAvailable();
     71                 for (const auto& availablePackage : availablePackages)
     72                 {
     73                     pinKeys.emplace(
     74                         availablePackage->GetProperty(PackageProperty::Id).get(),
     75                         availablePackage->GetSource().GetIdentifier());
     76                 }
     77             }
     78 
     79             return pinKeys;
     80         }
     81 
     82         // Gets a search request that can be used to find the installed package that corresponds with a pin.
     83         SearchRequest GetSearchRequestForPin(const Pinning::PinKey& pinKey)
     84         {
     85             SearchRequest searchRequest;
     86             if (pinKey.IsForInstalled())
     87             {
     88                 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pinKey.PackageId));
     89                 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pinKey.PackageId));
     90             }
     91             else
     92             {
     93                 searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pinKey.PackageId);
     94             }
     95 
     96             return searchRequest;
     97         }
     98     }
     99 
    100     void OpenPinningIndex::operator()(Execution::Context& context) const
    101     {
    102         auto pinningData = Pinning::PinningData{ m_readOnly ? Pinning::PinningData::Disposition::ReadOnly : Pinning::PinningData::Disposition::ReadWrite };
    103         if (!m_readOnly && !pinningData)
    104         {
    105             AICLI_LOG(CLI, Error, << "Unable to open pinning index.");
    106             context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl;
    107             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX);
    108         }
    109 
    110         context.Add<Execution::Data::PinningData>(std::move(pinningData));
    111     }
    112 
    113     void GetAllPins(Execution::Context& context)
    114     {
    115         AICLI_LOG(CLI, Info, << "Getting all existing pins");
    116         context.Add<Execution::Data::Pins>(context.Get<Execution::Data::PinningData>().GetAllPins());
    117     }
    118 
    119     void SearchPin(Execution::Context& context)
    120     {
    121         auto pinKeys = GetPinKeysForPackage(context);
    122 
    123         auto package = context.Get<Execution::Data::Package>();
    124         auto pinningData = context.Get<Execution::Data::PinningData>();
    125 
    126         std::vector<Pinning::Pin> pins;
    127         for (const auto& pinKey : pinKeys)
    128         {
    129             auto pin = pinningData.GetPin(pinKey);
    130             if (pin)
    131             {
    132                 pins.emplace_back(std::move(pin.value()));
    133             }
    134         }
    135 
    136         context.Add<Execution::Data::Pins>(std::move(pins));
    137     }
    138 
    139     void AddPin(Execution::Context& context)
    140     {
    141         auto pinKeys = GetPinKeysForPackage(context);
    142 
    143         auto package = context.Get<Execution::Data::Package>();
    144         auto pinningData = context.Get<Execution::Data::PinningData>();
    145         auto installedVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    146 
    147         std::vector<Pinning::Pin> pinsToAddOrUpdate;
    148         for (const auto& pinKey : pinKeys)
    149         {
    150             auto pin = CreatePin(context, pinKey);
    151             AICLI_LOG(CLI, Info, << "Evaluating Pin " << pin.ToString());
    152 
    153             auto existingPin = pinningData.GetPin(pinKey);
    154             if (existingPin)
    155             {
    156                 Utility::LocIndString packageNameToReport;
    157                 if (pinKey.IsForInstalled() && installedVersion)
    158                 {
    159                     packageNameToReport = installedVersion->GetProperty(PackageVersionProperty::Name);
    160                 }
    161                 else
    162                 {
    163                     auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion();
    164                     if (availableVersion)
    165                     {
    166                         packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name);
    167                     }
    168                 }
    169 
    170                 // Pin already exists.
    171                 // If it is the same, we do nothing. If it is different, check for the --force arg
    172                 if (pin == existingPin)
    173                 {
    174                     AICLI_LOG(CLI, Info, << "Pin already exists");
    175                     context.Reporter.Info() << Resource::String::PinAlreadyExists(packageNameToReport) << std::endl;
    176                     continue;
    177                 }
    178 
    179                 AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId);
    180                 if (context.Args.Contains(Execution::Args::Type::Force))
    181                 {
    182                     AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument");
    183                     context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageNameToReport) << std::endl;
    184                     pinsToAddOrUpdate.push_back(std::move(pin));
    185                 }
    186                 else
    187                 {
    188                     context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageNameToReport) << std::endl;
    189                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS);
    190                 }
    191             }
    192             else
    193             {
    194                 pinsToAddOrUpdate.push_back(std::move(pin));
    195             }
    196         }
    197 
    198         if (!pinsToAddOrUpdate.empty())
    199         {
    200             for (const auto& pin : pinsToAddOrUpdate)
    201 
    202             {
    203                 pinningData.AddOrUpdatePin(pin);
    204             }
    205 
    206             context.Reporter.Info() << Resource::String::PinAdded << std::endl;
    207         }
    208     }
    209 
    210     void RemovePin(Execution::Context& context)
    211     {
    212         auto package = context.Get<Execution::Data::Package>();
    213         auto pins = context.Get<Execution::Data::Pins>();
    214 
    215         auto pinningData = context.Get<Execution::Data::PinningData>();
    216         bool pinExists = false;
    217 
    218         // Note that if a source was specified in the command line,
    219         // that will be the only one we get version keys from.
    220         // So, we remove pins from all sources unless one was provided.
    221         for (const auto& pin : pins)
    222         {
    223             AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString());
    224             pinningData.RemovePin(pin.GetKey());
    225             pinExists = true;
    226         }
    227 
    228         if (!pinExists)
    229         {
    230             AICLI_LOG(CLI, Warning, << "Pin does not exist");
    231             context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl;
    232             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST);
    233         }
    234 
    235         context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl;
    236     }
    237 
    238     void ReportPins(Execution::Context& context)
    239     {
    240         const auto& pins = context.Get<Execution::Data::Pins>();
    241         if (pins.empty())
    242         {
    243             context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl;
    244             return;
    245         }
    246 
    247         Execution::TableOutput<6> table(context.Reporter,
    248             {
    249                 Resource::String::SearchName,
    250                 Resource::String::SearchId,
    251                 Resource::String::SearchVersion,
    252                 Resource::String::SearchSource,
    253                 Resource::String::PinType,
    254                 Resource::String::PinVersion,
    255             });
    256 
    257         const auto& source = context.Get<Execution::Data::Source>();
    258         for (const auto& pin : pins)
    259         {
    260             const auto& pinKey = pin.GetKey();
    261             auto searchRequest = GetSearchRequestForPin(pin.GetKey());
    262             auto searchResult = source.Search(searchRequest);
    263             for (const auto& match : searchResult.Matches)
    264             {
    265                 Utility::LocIndString packageName;
    266                 Utility::LocIndString sourceName;
    267                 Utility::LocIndString version;
    268 
    269                 if (pinKey.IsForInstalled())
    270                 {
    271                     sourceName = Resource::LocString{ Resource::String::PinInstalledSource };
    272                 }
    273                 else
    274                 {
    275                     // This ensures we get the info from the right source if it exists on multiple
    276                     auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId);
    277                     if (availablePackage)
    278                     {
    279                         auto availableVersion = availablePackage->GetLatestVersion();
    280                         if (availableVersion)
    281                         {
    282                             packageName = availableVersion->GetProperty(PackageVersionProperty::Name);
    283                             sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName);
    284                         }
    285                     }
    286                 }
    287 
    288                 auto installedVersion = GetInstalledVersion(match.Package);
    289                 if (installedVersion)
    290                 {
    291                     packageName = installedVersion->GetProperty(PackageVersionProperty::Name);
    292                     version = installedVersion->GetProperty(PackageVersionProperty::Version);
    293                 }
    294 
    295                 table.OutputLine({
    296                     packageName,
    297                     pinKey.PackageId,
    298                     version,
    299                     sourceName,
    300                     std::string{ ToString(pin.GetType()) },
    301                     pin.GetGatedVersion().ToString(),
    302                 });
    303             }
    304         }
    305 
    306         table.Complete();
    307     }
    308 
    309     void ResetAllPins(Execution::Context& context)
    310     {
    311         AICLI_LOG(CLI, Info, << "Resetting all pins");
    312         context.Reporter.Info() << Resource::String::PinResettingAll << std::endl;
    313 
    314         std::string sourceId;
    315         if (context.Args.Contains(Execution::Args::Type::Source))
    316         {
    317             auto sourceName = context.Args.GetArg(Execution::Args::Type::Source);
    318             auto sources = Source::GetCurrentSources();
    319             for (const auto& source : sources)
    320             {
    321                 if (Utility::CaseInsensitiveEquals(source.Name, sourceName))
    322                 {
    323                     sourceId = source.Identifier;
    324                     break;
    325                 }
    326             }
    327         }
    328 
    329         if (context.Get<Execution::Data::PinningData>().ResetAllPins(sourceId))
    330         {
    331             context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl;
    332         }
    333         else
    334         {
    335             context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl;
    336         }
    337     }
    338 }