mirror of
https://github.com/Sude-/lgogdownloader.git
synced 2024-11-20 11:49:17 +01:00
Upload local saves to the cloud
This commit is contained in:
parent
829d263bc7
commit
33ec0434aa
@ -102,6 +102,9 @@ class Downloader
|
|||||||
void repair();
|
void repair();
|
||||||
void download();
|
void download();
|
||||||
void downloadCloudSaves(const std::string& product_id, int build_index = -1);
|
void downloadCloudSaves(const std::string& product_id, int build_index = -1);
|
||||||
|
void downloadCloudSavesById(const std::string& product_id, int build_index = -1);
|
||||||
|
void uploadCloudSaves(const std::string& product_id, int build_index = -1);
|
||||||
|
void uploadCloudSavesById(const std::string& product_id, int build_index = -1);
|
||||||
void checkOrphans();
|
void checkOrphans();
|
||||||
void checkStatus();
|
void checkStatus();
|
||||||
void updateCache();
|
void updateCache();
|
||||||
@ -144,6 +147,7 @@ class Downloader
|
|||||||
void saveChangelog(const std::string& changelog, const std::string& filepath);
|
void saveChangelog(const std::string& changelog, const std::string& filepath);
|
||||||
static void processDownloadQueue(Config conf, const unsigned int& tid);
|
static void processDownloadQueue(Config conf, const unsigned int& tid);
|
||||||
static void processCloudSaveDownloadQueue(Config conf, const unsigned int& tid);
|
static void processCloudSaveDownloadQueue(Config conf, const unsigned int& tid);
|
||||||
|
static void processCloudSaveUploadQueue(Config conf, const unsigned int& tid);
|
||||||
static int progressCallbackForThread(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
|
static int progressCallbackForThread(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
|
||||||
template <typename T> void printProgress(const ThreadSafeQueue<T>& download_queue);
|
template <typename T> void printProgress(const ThreadSafeQueue<T>& download_queue);
|
||||||
static void getGameDetailsThread(Config config, const unsigned int& tid);
|
static void getGameDetailsThread(Config config, const unsigned int& tid);
|
||||||
|
@ -91,6 +91,7 @@ namespace Util
|
|||||||
CURLcode CurlHandleGetResponse(CURL* curlhandle, std::string& response, int max_retries = -1);
|
CURLcode CurlHandleGetResponse(CURL* curlhandle, std::string& response, int max_retries = -1);
|
||||||
curl_off_t CurlWriteMemoryCallback(char *ptr, curl_off_t size, curl_off_t nmemb, void *userp);
|
curl_off_t CurlWriteMemoryCallback(char *ptr, curl_off_t size, curl_off_t nmemb, void *userp);
|
||||||
curl_off_t CurlWriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp);
|
curl_off_t CurlWriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp);
|
||||||
|
curl_off_t CurlReadChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, ChunkMemoryStruct *userp);
|
||||||
|
|
||||||
template<typename ... Args> std::string formattedString(const std::string& format, Args ... args)
|
template<typename ... Args> std::string formattedString(const std::string& format, Args ... args)
|
||||||
{
|
{
|
||||||
|
12
main.cpp
12
main.cpp
@ -166,6 +166,7 @@ int main(int argc, char *argv[])
|
|||||||
std::string galaxy_product_id_show_cloud_paths;
|
std::string galaxy_product_id_show_cloud_paths;
|
||||||
std::string galaxy_product_id_show_local_cloud_paths;
|
std::string galaxy_product_id_show_local_cloud_paths;
|
||||||
std::string galaxy_product_cloud_saves;
|
std::string galaxy_product_cloud_saves;
|
||||||
|
std::string galaxy_upload_product_cloud_saves;
|
||||||
std::string tags;
|
std::string tags;
|
||||||
|
|
||||||
std::vector<std::string> vFileIdStrings;
|
std::vector<std::string> vFileIdStrings;
|
||||||
@ -288,6 +289,7 @@ int main(int argc, char *argv[])
|
|||||||
("galaxy-install", bpo::value<std::string>(&galaxy_product_id_install)->default_value(""), "Install game using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
("galaxy-install", bpo::value<std::string>(&galaxy_product_id_install)->default_value(""), "Install game using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
||||||
("galaxy-show-builds", bpo::value<std::string>(&galaxy_product_id_show_builds)->default_value(""), "Show game builds using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
("galaxy-show-builds", bpo::value<std::string>(&galaxy_product_id_show_builds)->default_value(""), "Show game builds using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
||||||
("galaxy-download-cloud-saves", bpo::value<std::string>(&galaxy_product_cloud_saves)->default_value(""), "Download cloud saves using product-id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
("galaxy-download-cloud-saves", bpo::value<std::string>(&galaxy_product_cloud_saves)->default_value(""), "Download cloud saves using product-id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
||||||
|
("galaxy-upload-cloud-saves", bpo::value<std::string>(&galaxy_upload_product_cloud_saves)->default_value(""), "Upload cloud saves using product-id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
||||||
("galaxy-show-cloud-saves", bpo::value<std::string>(&galaxy_product_id_show_cloud_paths)->default_value(""), "Show game cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
("galaxy-show-cloud-saves", bpo::value<std::string>(&galaxy_product_id_show_cloud_paths)->default_value(""), "Show game cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
||||||
("galaxy-show-local-cloud-saves", bpo::value<std::string>(&galaxy_product_id_show_local_cloud_paths)->default_value(""), "Show local cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
("galaxy-show-local-cloud-saves", bpo::value<std::string>(&galaxy_product_id_show_local_cloud_paths)->default_value(""), "Show local cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
|
||||||
("galaxy-platform", bpo::value<std::string>(&sGalaxyPlatform)->default_value("w"), galaxy_platform_text.c_str())
|
("galaxy-platform", bpo::value<std::string>(&sGalaxyPlatform)->default_value("w"), galaxy_platform_text.c_str())
|
||||||
@ -832,6 +834,16 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
downloader.downloadCloudSaves(product_id, build_index);
|
downloader.downloadCloudSaves(product_id, build_index);
|
||||||
}
|
}
|
||||||
|
else if (!galaxy_upload_product_cloud_saves.empty()) {
|
||||||
|
int build_index = -1;
|
||||||
|
std::vector<std::string> tokens = Util::tokenize(galaxy_upload_product_cloud_saves, "/");
|
||||||
|
std::string product_id = tokens[0];
|
||||||
|
if (tokens.size() == 2)
|
||||||
|
{
|
||||||
|
build_index = std::stoi(tokens[1]);
|
||||||
|
}
|
||||||
|
downloader.uploadCloudSaves(product_id, build_index);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!Globals::globalConfig.bLogin)
|
if (!Globals::globalConfig.bLogin)
|
||||||
|
@ -64,6 +64,25 @@ std::string username() {
|
|||||||
return user ? user : std::string();
|
return user ? user : std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dirForEachHelper(const boost::filesystem::path &location, std::function<void(boost::filesystem::directory_iterator)> &f) {
|
||||||
|
boost::filesystem::directory_iterator begin { location };
|
||||||
|
boost::filesystem::directory_iterator end;
|
||||||
|
|
||||||
|
for(boost::filesystem::directory_iterator curr_dir { begin }; curr_dir != end; ++curr_dir) {
|
||||||
|
if(boost::filesystem::is_directory(*curr_dir)) {
|
||||||
|
|
||||||
|
dirForEachHelper(*curr_dir, f);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
f(curr_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dirForEach(const std::string &location, std::function<void(boost::filesystem::directory_iterator)> &&f) {
|
||||||
|
dirForEachHelper(location, f);
|
||||||
|
}
|
||||||
|
|
||||||
Downloader::Downloader()
|
Downloader::Downloader()
|
||||||
{
|
{
|
||||||
if (Globals::globalConfig.bLogin)
|
if (Globals::globalConfig.bLogin)
|
||||||
@ -2530,6 +2549,149 @@ void Downloader::showWishlist()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Downloader::processCloudSaveUploadQueue(Config conf, const unsigned int& tid) {
|
||||||
|
std::string msg_prefix = "[Thread #" + std::to_string(tid) + "]";
|
||||||
|
|
||||||
|
std::unique_ptr<galaxyAPI> galaxy { new galaxyAPI(Globals::globalConfig.curlConf) };
|
||||||
|
if (!galaxy->init())
|
||||||
|
{
|
||||||
|
if (!galaxy->refreshLogin())
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Galaxy API failed to refresh login", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
vDownloadInfo[tid].setStatus(DLSTATUS_FINISHED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CURL* dlhandle = curl_easy_init();
|
||||||
|
|
||||||
|
Util::CurlHandleSetDefaultOptions(dlhandle, conf.curlConf);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_NOPROGRESS, 0);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_READFUNCTION, Util::CurlReadChunkMemoryCallback);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_FILETIME, 1L);
|
||||||
|
|
||||||
|
xferInfo xferinfo;
|
||||||
|
xferinfo.tid = tid;
|
||||||
|
xferinfo.curlhandle = dlhandle;
|
||||||
|
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_XFERINFOFUNCTION, Downloader::progressCallbackForThread);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_XFERINFODATA, &xferinfo);
|
||||||
|
|
||||||
|
cloudSaveFile csf;
|
||||||
|
|
||||||
|
std::string access_token;
|
||||||
|
if (!Globals::galaxyConf.isExpired()) {
|
||||||
|
access_token = Globals::galaxyConf.getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access_token.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string bearer = "Authorization: Bearer " + access_token;
|
||||||
|
|
||||||
|
while(dlCloudSaveQueue.try_pop(csf)) {
|
||||||
|
CURLcode result = CURLE_RECV_ERROR; // assume network error
|
||||||
|
int iRetryCount = 0;
|
||||||
|
|
||||||
|
iTotalRemainingBytes.fetch_sub(csf.fileSize);
|
||||||
|
|
||||||
|
vDownloadInfo[tid].setFilename(csf.location);
|
||||||
|
|
||||||
|
std::string filecontents;
|
||||||
|
{
|
||||||
|
std::ifstream in { csf.location, std::ios_base::in | std::ios_base::binary };
|
||||||
|
|
||||||
|
in >> filecontents;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkMemoryStruct cms {
|
||||||
|
&filecontents[0],
|
||||||
|
(curl_off_t)filecontents.size()
|
||||||
|
};
|
||||||
|
|
||||||
|
auto md5 = Util::getChunkHash((std::uint8_t*)filecontents.data(), filecontents.size(), RHASH_MD5);
|
||||||
|
|
||||||
|
auto url = "https://cloudstorage.gog.com/v1/" + Globals::galaxyConf.getUserId() + '/' + Globals::galaxyConf.getClientId() + '/' + csf.path;
|
||||||
|
|
||||||
|
curl_slist *header = nullptr;
|
||||||
|
header = curl_slist_append(header, bearer.c_str());
|
||||||
|
header = curl_slist_append(header, ("X-Object-Meta-LocalLastModified: " + boost::posix_time::to_iso_extended_string(csf.lastModified)).c_str());
|
||||||
|
header = curl_slist_append(header, ("Etag: " + md5).c_str());
|
||||||
|
header = curl_slist_append(header, "Content-Type: Octet-Stream");
|
||||||
|
header = curl_slist_append(header, ("Content-Length: " + std::to_string(filecontents.size())).c_str());
|
||||||
|
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_PUT, 1L);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_HTTPHEADER, header);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_READDATA, &cms);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_URL, url.c_str());
|
||||||
|
|
||||||
|
msgQueue.push(Message("Begin upload: " + csf.location, MSGTYPE_INFO, msg_prefix));
|
||||||
|
|
||||||
|
bool bShouldRetry = false;
|
||||||
|
long int response_code = 0;
|
||||||
|
std::string retry_reason;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (conf.iWait > 0)
|
||||||
|
usleep(conf.iWait); // Wait before continuing
|
||||||
|
|
||||||
|
response_code = 0; // Make sure that response code is reset
|
||||||
|
|
||||||
|
if (iRetryCount != 0)
|
||||||
|
{
|
||||||
|
std::string retry_msg = "Retry " + std::to_string(iRetryCount) + "/" + std::to_string(conf.iRetries) + ": " + boost::filesystem::path(csf.location).filename().string();
|
||||||
|
if (!retry_reason.empty())
|
||||||
|
retry_msg += " (" + retry_reason + ")";
|
||||||
|
msgQueue.push(Message(retry_msg, MSGTYPE_INFO, msg_prefix));
|
||||||
|
}
|
||||||
|
retry_reason = ""; // reset retry reason
|
||||||
|
|
||||||
|
xferinfo.offset = 0;
|
||||||
|
xferinfo.timer.reset();
|
||||||
|
xferinfo.TimeAndSize.clear();
|
||||||
|
result = curl_easy_perform(dlhandle);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
// Retry on these errors
|
||||||
|
case CURLE_PARTIAL_FILE:
|
||||||
|
case CURLE_OPERATION_TIMEDOUT:
|
||||||
|
case CURLE_RECV_ERROR:
|
||||||
|
case CURLE_SSL_CONNECT_ERROR:
|
||||||
|
bShouldRetry = true;
|
||||||
|
break;
|
||||||
|
// Retry on CURLE_HTTP_RETURNED_ERROR if response code is not "416 Range Not Satisfiable"
|
||||||
|
case CURLE_HTTP_RETURNED_ERROR:
|
||||||
|
curl_easy_getinfo(dlhandle, CURLINFO_RESPONSE_CODE, &response_code);
|
||||||
|
if (response_code == 416 || response_code == 422 || response_code == 400 || response_code == 422) {
|
||||||
|
msgQueue.push(Message(std::to_string(response_code) + ": " + curl_easy_strerror(result)));
|
||||||
|
bShouldRetry = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
bShouldRetry = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bShouldRetry = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShouldRetry) {
|
||||||
|
iRetryCount++;
|
||||||
|
retry_reason = std::to_string(response_code) + ": " + curl_easy_strerror(result);
|
||||||
|
}
|
||||||
|
} while (bShouldRetry && (iRetryCount <= conf.iRetries));
|
||||||
|
|
||||||
|
curl_slist_free_all(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_cleanup(dlhandle);
|
||||||
|
|
||||||
|
vDownloadInfo[tid].setStatus(DLSTATUS_FINISHED);
|
||||||
|
msgQueue.push(Message("Finished all tasks", MSGTYPE_INFO, msg_prefix));
|
||||||
|
}
|
||||||
|
|
||||||
void Downloader::processCloudSaveDownloadQueue(Config conf, const unsigned int& tid) {
|
void Downloader::processCloudSaveDownloadQueue(Config conf, const unsigned int& tid) {
|
||||||
std::string msg_prefix = "[Thread #" + std::to_string(tid) + "]";
|
std::string msg_prefix = "[Thread #" + std::to_string(tid) + "]";
|
||||||
|
|
||||||
@ -2621,7 +2783,6 @@ void Downloader::processCloudSaveDownloadQueue(Config conf, const unsigned int&
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto url = "https://cloudstorage.gog.com/v1/" + Globals::galaxyConf.getUserId() + '/' + Globals::galaxyConf.getClientId() + '/' + csf.path;
|
auto url = "https://cloudstorage.gog.com/v1/" + Globals::galaxyConf.getUserId() + '/' + Globals::galaxyConf.getClientId() + '/' + csf.path;
|
||||||
msgQueue.push(Message(url));
|
|
||||||
curl_easy_setopt(dlhandle, CURLOPT_HTTPHEADER, header);
|
curl_easy_setopt(dlhandle, CURLOPT_HTTPHEADER, header);
|
||||||
curl_easy_setopt(dlhandle, CURLOPT_URL, url.c_str());
|
curl_easy_setopt(dlhandle, CURLOPT_URL, url.c_str());
|
||||||
long int response_code = 0;
|
long int response_code = 0;
|
||||||
@ -4400,6 +4561,26 @@ std::pair<std::string::const_iterator, std::string::const_iterator> getline(std:
|
|||||||
return { end, end };
|
return { end, end };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Downloader::uploadCloudSaves(const std::string& product_id, int build_index)
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
if(this->galaxySelectProductIdHelper(product_id, id))
|
||||||
|
{
|
||||||
|
if (!id.empty())
|
||||||
|
this->uploadCloudSavesById(id, build_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Downloader::downloadCloudSaves(const std::string& product_id, int build_index)
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
if(this->galaxySelectProductIdHelper(product_id, id))
|
||||||
|
{
|
||||||
|
if (!id.empty())
|
||||||
|
this->downloadCloudSavesById(id, build_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Downloader::galaxyShowCloudSaves(const std::string& product_id, int build_index)
|
void Downloader::galaxyShowCloudSaves(const std::string& product_id, int build_index)
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
@ -4533,7 +4714,96 @@ int Downloader::cloudSaveListByIdForEach(const std::string& product_id, int buil
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downloader::downloadCloudSaves(const std::string& product_id, int build_index)
|
void Downloader::uploadCloudSavesById(const std::string& product_id, int build_index)
|
||||||
|
{
|
||||||
|
auto name_to_locations = cloudSaveLocations(product_id, build_index);
|
||||||
|
|
||||||
|
if(name_to_locations.empty()) {
|
||||||
|
std::cout << "Cloud saves not supported for this game" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, cloudSaveFile> path_to_cloudSaveFile;
|
||||||
|
for(auto &name_to_location : name_to_locations) {
|
||||||
|
auto &name = name_to_location.first;
|
||||||
|
auto &location = name_to_location.second;
|
||||||
|
|
||||||
|
if(!boost::filesystem::exists(location) || !boost::filesystem::is_directory(location)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirForEach(location, [&](boost::filesystem::directory_iterator file) {
|
||||||
|
cloudSaveFile csf {
|
||||||
|
boost::posix_time::from_time_t(boost::filesystem::last_write_time(*file) - 1),
|
||||||
|
boost::filesystem::file_size(*file),
|
||||||
|
(name / boost::filesystem::relative(*file, location)).string(),
|
||||||
|
file->path().string()
|
||||||
|
};
|
||||||
|
|
||||||
|
path_to_cloudSaveFile.insert(std::make_pair(csf.path, std::move(csf)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path_to_cloudSaveFile.empty()) {
|
||||||
|
std::cout << "No local cloud saves found" << std::endl;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = this->cloudSaveListByIdForEach(product_id, build_index, [&](cloudSaveFile &csf) {
|
||||||
|
auto it = path_to_cloudSaveFile.find(csf.path);
|
||||||
|
|
||||||
|
//If remote save is not locally stored, skip
|
||||||
|
if(it == std::end(path_to_cloudSaveFile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudSaveFile local_csf { std::move(it->second) };
|
||||||
|
path_to_cloudSaveFile.erase(it);
|
||||||
|
|
||||||
|
if(csf.lastModified < local_csf.lastModified || boost::filesystem::path(csf.location).filename().string() == "test.txt") {
|
||||||
|
iTotalRemainingBytes.fetch_add(local_csf.fileSize);
|
||||||
|
|
||||||
|
dlCloudSaveQueue.push(local_csf);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for(auto &path_csf : path_to_cloudSaveFile) {
|
||||||
|
auto &csf = path_csf.second;
|
||||||
|
|
||||||
|
iTotalRemainingBytes.fetch_add(csf.fileSize);
|
||||||
|
|
||||||
|
dlCloudSaveQueue.push(csf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(res || dlCloudSaveQueue.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit thread count to number of items in upload queue
|
||||||
|
unsigned int iThreads = std::min(Globals::globalConfig.iThreads, static_cast<unsigned int>(dlCloudSaveQueue.size()));
|
||||||
|
|
||||||
|
// Create download threads
|
||||||
|
std::vector<std::thread> vThreads;
|
||||||
|
for (unsigned int i = 0; i < iThreads; ++i)
|
||||||
|
{
|
||||||
|
DownloadInfo dlInfo;
|
||||||
|
dlInfo.setStatus(DLSTATUS_NOTSTARTED);
|
||||||
|
vDownloadInfo.push_back(dlInfo);
|
||||||
|
vThreads.push_back(std::thread(Downloader::processCloudSaveUploadQueue, Globals::globalConfig, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->printProgress(dlCloudSaveQueue);
|
||||||
|
|
||||||
|
// Join threads
|
||||||
|
for (unsigned int i = 0; i < vThreads.size(); ++i) {
|
||||||
|
vThreads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
vThreads.clear();
|
||||||
|
vDownloadInfo.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Downloader::downloadCloudSavesById(const std::string& product_id, int build_index)
|
||||||
{
|
{
|
||||||
auto res = this->cloudSaveListByIdForEach(product_id, build_index, [](cloudSaveFile &csf) {
|
auto res = this->cloudSaveListByIdForEach(product_id, build_index, [](cloudSaveFile &csf) {
|
||||||
iTotalRemainingBytes.fetch_add(csf.fileSize);
|
iTotalRemainingBytes.fetch_add(csf.fileSize);
|
||||||
@ -4558,7 +4828,7 @@ void Downloader::downloadCloudSaves(const std::string& product_id, int build_ind
|
|||||||
vThreads.push_back(std::thread(Downloader::processCloudSaveDownloadQueue, Globals::globalConfig, i));
|
vThreads.push_back(std::thread(Downloader::processCloudSaveDownloadQueue, Globals::globalConfig, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
this->printProgress(dlQueue);
|
this->printProgress(dlCloudSaveQueue);
|
||||||
|
|
||||||
// Join threads
|
// Join threads
|
||||||
for (unsigned int i = 0; i < vThreads.size(); ++i) {
|
for (unsigned int i = 0; i < vThreads.size(); ++i) {
|
||||||
@ -4597,25 +4867,6 @@ void Downloader::galaxyShowCloudSavesById(const std::string& product_id, int bui
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void dirForEachHelper(const boost::filesystem::path &location, std::function<void(boost::filesystem::directory_iterator)> &f) {
|
|
||||||
boost::filesystem::directory_iterator begin { location };
|
|
||||||
boost::filesystem::directory_iterator end;
|
|
||||||
|
|
||||||
for(boost::filesystem::directory_iterator curr_dir { begin }; curr_dir != end; ++curr_dir) {
|
|
||||||
if(boost::filesystem::is_directory(*curr_dir)) {
|
|
||||||
|
|
||||||
dirForEachHelper(*curr_dir, f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
f(curr_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dirForEach(const std::string &location, std::function<void(boost::filesystem::directory_iterator)> &&f) {
|
|
||||||
dirForEachHelper(location, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Downloader::galaxyShowLocalCloudSavesById(const std::string& product_id, int build_index) {
|
void Downloader::galaxyShowLocalCloudSavesById(const std::string& product_id, int build_index) {
|
||||||
auto name_to_locations = cloudSaveLocations(product_id, build_index);
|
auto name_to_locations = cloudSaveLocations(product_id, build_index);
|
||||||
|
|
||||||
|
15
src/util.cpp
15
src/util.cpp
@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/algorithm/string/case_conv.hpp>
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
#include <boost/iostreams/filter/gzip.hpp>
|
||||||
|
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||||
|
#include <boost/iostreams/copy.hpp>
|
||||||
#include <tinyxml2.h>
|
#include <tinyxml2.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -765,7 +768,6 @@ void Util::CurlHandleSetDefaultOptions(CURL* curlhandle, const CurlConfig& conf)
|
|||||||
curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, conf.sUserAgent.c_str());
|
curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, conf.sUserAgent.c_str());
|
||||||
curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
|
curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
|
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
|
||||||
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
|
|
||||||
curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
|
curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
|
||||||
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, conf.iTimeout);
|
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, conf.iTimeout);
|
||||||
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
|
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
|
||||||
@ -882,3 +884,14 @@ curl_off_t Util::CurlWriteChunkMemoryCallback(void *contents, curl_off_t size, c
|
|||||||
|
|
||||||
return realsize;
|
return realsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curl_off_t Util::CurlReadChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, ChunkMemoryStruct *mem) {
|
||||||
|
curl_off_t realsize = std::min(size * nmemb, mem->size);
|
||||||
|
|
||||||
|
std::copy(mem->memory, mem->memory + realsize, (char*)contents);
|
||||||
|
|
||||||
|
mem->size -= realsize;
|
||||||
|
mem->memory += realsize;
|
||||||
|
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user