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 }