winget-cli

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

IntegrationDOClient.cpp (9989B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 #include <do_download.h>
      5 #include <sfsclient/SFSClient.h>
      6 
      7 #include <filesystem>
      8 #include <iomanip>
      9 #include <iostream>
     10 #include <sstream>
     11 #include <time.h>
     12 #include <vector>
     13 
     14 #ifdef _WIN32
     15 #include <combaseapi.h>
     16 #endif
     17 
     18 namespace
     19 {
     20 void DisplayUsage()
     21 {
     22     std::cout << "Usage: ExampleIntegrationDOClient --product <identifier> [options]" << std::endl
     23               << std::endl
     24               << "Required:" << std::endl
     25               << "  --product <identifier>\t\tName or GUID of the product to be retrieved" << std::endl
     26               << std::endl
     27               << "Options:" << std::endl
     28               << "  -h, --help\t\t\tDisplay this help message" << std::endl
     29               << "  --accountId <id>\t\tAccount ID of the SFS service, used to identify the caller" << std::endl
     30               << "  --outDir <id>\t\tLocation where download will be placed. Defaults to a tmp dir" << std::endl
     31               << std::endl
     32               << std::endl
     33               << "Example:" << std::endl
     34               << "  ExampleIntegrationDOClient --product \"msedge-stable-win-x64\" --accountId msedge" << std::endl;
     35 }
     36 
     37 void DisplayHelp()
     38 {
     39     std::cout
     40         << "ExampleIntegrationDOClient" << std::endl
     41         << "Copyright (c) Microsoft Corporation. All rights reserved." << std::endl
     42         << std::endl
     43         << "Use to interact with the SFS service through the SFS Client library and download using the Delivery Optimization SDK."
     44         << std::endl
     45         << std::endl;
     46     DisplayUsage();
     47 }
     48 
     49 const std::string c_boldRedStart = "\033[1;31m";
     50 const std::string c_cyanStart = "\033[0;36m";
     51 const std::string c_darkGreyStart = "\033[0;90m";
     52 const std::string c_colorEnd = "\033[0m";
     53 
     54 void PrintError(std::string_view message)
     55 {
     56     std::cout << c_boldRedStart << message << c_colorEnd << std::endl;
     57 }
     58 
     59 void PrintLog(std::string_view message)
     60 {
     61     std::cout << c_cyanStart << message << c_colorEnd << std::endl;
     62 }
     63 
     64 struct Settings
     65 {
     66     bool displayHelp{true};
     67     std::string product;
     68     std::string accountId;
     69     std::string outDir;
     70 };
     71 
     72 int ParseArguments(const std::vector<std::string_view>& args, Settings& settings)
     73 {
     74     settings = {};
     75     settings.displayHelp = args.size() == 1;
     76 
     77     const size_t argsSize = args.size();
     78     auto validateArg =
     79         [&argsSize](const size_t index, const std::string& switchName, const std::string_view& argValue) -> bool {
     80         if (argsSize <= index + 1)
     81         {
     82             PrintError("Missing argument of --" + switchName + ".");
     83             return false;
     84         }
     85         if (!argValue.empty())
     86         {
     87             PrintError("--" + switchName + " can only be specified once.");
     88             return false;
     89         }
     90         return true;
     91     };
     92 
     93     for (size_t i = 1; i < args.size(); ++i)
     94     {
     95         if (args[i].compare("-h") == 0 || args[i].compare("--help") == 0)
     96         {
     97             settings.displayHelp = true;
     98         }
     99         else if (args[i].compare("--product") == 0)
    100         {
    101             if (!validateArg(i, "product", settings.product))
    102             {
    103                 return 1;
    104             }
    105             settings.product = args[++i];
    106         }
    107         else if (args[i].compare("--accountId") == 0)
    108         {
    109             if (!validateArg(i, "accountId", settings.accountId))
    110             {
    111                 return 1;
    112             }
    113             settings.accountId = args[++i];
    114         }
    115         else if (args[i].compare("--outDir") == 0)
    116         {
    117             if (!validateArg(i, "outDir", settings.outDir))
    118             {
    119                 return 1;
    120             }
    121             settings.outDir = args[++i];
    122         }
    123         else
    124         {
    125             PrintError("Unknown option " + std::string(args[i]) + ".\n");
    126             return 1;
    127         }
    128     }
    129 
    130     if (settings.product.empty())
    131     {
    132         return 1;
    133     }
    134 
    135     return 0;
    136 }
    137 
    138 void LogResult(const SFS::Result& result)
    139 {
    140     std::cout << "  Result code: " << ToString(result.GetCode());
    141     if (!result.GetMsg().empty())
    142     {
    143         std::cout << ". Message: " << result.GetMsg();
    144     }
    145     std::cout << std::endl;
    146 }
    147 
    148 std::string TimestampToString(std::chrono::time_point<std::chrono::system_clock> time)
    149 {
    150     using namespace std::chrono;
    151 
    152     // get number of milliseconds for the current second
    153     // (remainder after division into seconds)
    154     auto ms = duration_cast<milliseconds>(time.time_since_epoch()) % 1000;
    155 
    156     auto timer = system_clock::to_time_t(time);
    157 
    158     std::stringstream timeStream;
    159     struct tm gmTime;
    160 #ifdef _WIN32
    161     gmtime_s(&gmTime, &timer); // gmtime_s is the safe version of gmtime, not available on Linux
    162 #else
    163     gmTime = (*std::gmtime(&timer));
    164 #endif
    165     timeStream << std::put_time(&gmTime, "%F %X"); // yyyy-mm-dd HH:MM:SS
    166     timeStream << '.' << std::setfill('0') << std::setw(3) << ms.count();
    167 
    168     return timeStream.str();
    169 }
    170 
    171 void LoggingCallback(const SFS::LogData& logData)
    172 {
    173     std::cout << c_darkGreyStart << "Log: " << TimestampToString(logData.time) << " [" << ToString(logData.severity)
    174               << "]"
    175               << " " << std::filesystem::path(logData.file).filename().string() << ":" << logData.line << " "
    176               << logData.message << c_colorEnd << std::endl;
    177 }
    178 
    179 std::filesystem::path GetOutDir(const std::string& outDir)
    180 {
    181     if (outDir.empty())
    182     {
    183         auto tmpDir = std::filesystem::temp_directory_path();
    184         auto outDir = tmpDir / "SFSDownload";
    185         std::filesystem::create_directories(outDir);
    186 
    187         // Set write permissions to the download dir since the download happens from a different process, but only in
    188         // non-Windows
    189 #ifndef _WIN32
    190         std::filesystem::permissions(outDir,
    191                                      std::filesystem::perms::owner_all | std::filesystem::perms::group_all |
    192                                          std::filesystem::perms::others_all,
    193                                      std::filesystem::perm_options::add);
    194 #endif
    195         return outDir.string();
    196     }
    197     return outDir;
    198 }
    199 
    200 std::vector<SFS::Content> GetLatestDownloadInfo(const Settings& settings)
    201 {
    202     // Initialize SFSClient
    203     PrintLog("Initializing SFSClient with accountId: " + settings.accountId);
    204 
    205     SFS::ClientConfig config;
    206     config.accountId = settings.accountId;
    207     config.logCallbackFn = LoggingCallback;
    208 
    209     std::unique_ptr<SFS::SFSClient> sfsClient;
    210     auto result = SFS::SFSClient::Make(config, sfsClient);
    211     if (!result)
    212     {
    213         PrintError("Failed to initialize SFSClient.");
    214         LogResult(result);
    215         return {};
    216     }
    217 
    218     // Perform operations using SFSClient
    219     PrintLog("Getting latest download info for product: " + settings.product);
    220     std::vector<SFS::Content> contents;
    221     SFS::RequestParams params;
    222     params.productRequests = {{settings.product, {}}};
    223     result = sfsClient->GetLatestDownloadInfo(params, contents);
    224     if (!result)
    225     {
    226         PrintError("Failed to get latest download info.");
    227         LogResult(result);
    228         return {};
    229     }
    230 
    231     return contents;
    232 }
    233 
    234 #ifdef _WIN32
    235 class CoInitializeWrapper
    236 {
    237   public:
    238     CoInitializeWrapper()
    239     {
    240         CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    241     }
    242 
    243     ~CoInitializeWrapper()
    244     {
    245         CoUninitialize();
    246     }
    247 };
    248 #endif
    249 
    250 std::string ErrorCodeToHexString(std::error_code error)
    251 {
    252     std::stringstream stream;
    253     stream << "0x" << std::hex << error.value();
    254     return stream.str();
    255 }
    256 
    257 int Download(const SFS::Content& content, const std::string& baseOutDir)
    258 {
    259     PrintLog("Found content: " + content.GetContentId().GetNameSpace() + "/" + content.GetContentId().GetName() + "/" +
    260              content.GetContentId().GetVersion());
    261 
    262     const auto outDir = GetOutDir(baseOutDir);
    263     PrintLog("Downloading files to: " + outDir.string());
    264 
    265     for (const auto& file : content.GetFiles())
    266     {
    267         PrintLog("Downloading file " + file.GetFileId() + " from " + file.GetUrl());
    268         auto outFilePath = outDir / file.GetFileId();
    269 
    270         if (std::filesystem::exists(outFilePath))
    271         {
    272             // In a real implementation, one would check the file sha1 or sha256 hash to see if the file is the same as
    273             // the current one
    274             PrintLog("File already exists, cannot download again. Skipping.");
    275             continue;
    276         }
    277 
    278         // Download the file using Delivery Optimization SDK
    279         std::unique_ptr<microsoft::deliveryoptimization::download> download;
    280         auto error = microsoft::deliveryoptimization::download::make(file.GetUrl(), outFilePath.string(), download);
    281         if (error)
    282         {
    283             PrintError("Failed to create download object with error " + ErrorCodeToHexString(error));
    284             return 1;
    285         }
    286 
    287         error = download->start_and_wait_until_completion();
    288         if (error)
    289         {
    290             PrintError("Failed to download file with error " + ErrorCodeToHexString(error));
    291             return 1;
    292         }
    293 
    294         PrintLog("File successfully downloaded");
    295     }
    296 
    297     return 0;
    298 }
    299 } // namespace
    300 
    301 int main(int argc, char* argv[])
    302 {
    303     Settings settings;
    304     if (ParseArguments(std::vector<std::string_view>(argv, argv + argc), settings) != 0)
    305     {
    306         DisplayUsage();
    307         return 1;
    308     }
    309 
    310     if (settings.displayHelp)
    311     {
    312         DisplayHelp();
    313         return 0;
    314     }
    315 
    316     auto contents = GetLatestDownloadInfo(settings);
    317     if (contents.size() != 1)
    318     {
    319         return 1;
    320     }
    321 
    322     // In Windows the DO SDK uses COM, so we must call CoInitializeEx before using it
    323 #ifdef _WIN32
    324     CoInitializeWrapper coInitWrapper;
    325 #endif
    326 
    327     if (Download(contents[0], settings.outDir) != 0)
    328     {
    329         return 1;
    330     }
    331 
    332     return 0;
    333 }