[lgogdownloader] 01/03: New upstream version 3.1

Stephen Kitt skitt at moszumanska.debian.org
Mon Jan 16 18:36:27 UTC 2017


This is an automated email from the git hooks/post-receive script.

skitt pushed a commit to branch master
in repository lgogdownloader.

commit 8a534553f63f60e3c50afac96cb26a2b60491401
Author: Stephen Kitt <steve at sk2.org>
Date:   Mon Jan 16 09:10:04 2017 +0100

    New upstream version 3.1
---
 CMakeLists.txt        |   2 +-
 include/api.h         |   1 +
 include/downloader.h  |   1 +
 include/gamedetails.h |   1 +
 include/gamefile.h    |   1 +
 include/util.h        |  10 +
 main.cpp              |  69 ++++++-
 src/api.cpp           |  23 ++-
 src/downloader.cpp    | 501 ++++++++++++++++++--------------------------------
 src/gamedetails.cpp   |  22 +++
 src/util.cpp          |  13 +-
 src/website.cpp       |   2 +
 12 files changed, 307 insertions(+), 339 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e94267c..992cc90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
-project (lgogdownloader LANGUAGES C CXX VERSION 3.0)
+project (lgogdownloader LANGUAGES C CXX VERSION 3.1)
 
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG=1")
diff --git a/include/api.h b/include/api.h
index 97abe45..93cfb47 100644
--- a/include/api.h
+++ b/include/api.h
@@ -55,6 +55,7 @@ class API
 
         API(const std::string& token,const std::string& secret);
         int init();
+        bool isLoggedIn();
         int login(const std::string& email, const std::string& password);
         int getAPIConfig();
         std::string getResponse(const std::string& url);
diff --git a/include/downloader.h b/include/downloader.h
index 0aa1d45..cb0fa6b 100644
--- a/include/downloader.h
+++ b/include/downloader.h
@@ -63,6 +63,7 @@ class Downloader
     public:
         Downloader(Config &conf);
         virtual ~Downloader();
+        bool isLoggedIn();
         int init();
         int login();
         int listGames();
diff --git a/include/gamedetails.h b/include/gamedetails.h
index abda193..7009364 100644
--- a/include/gamedetails.h
+++ b/include/gamedetails.h
@@ -35,6 +35,7 @@ class gameDetails
         std::string getSerialsFilepath();
         std::string getChangelogFilepath();
         Json::Value getDetailsAsJson();
+        std::vector<gameFile> getGameFileVector();
         virtual ~gameDetails();
     protected:
         void filterListWithPriorities(std::vector<gameFile>& list, const gameSpecificConfig& config);
diff --git a/include/gamefile.h b/include/gamefile.h
index 80a37ef..90d6004 100644
--- a/include/gamefile.h
+++ b/include/gamefile.h
@@ -18,6 +18,7 @@ const unsigned int GFTYPE_INSTALLER = 1 << 0;
 const unsigned int GFTYPE_EXTRA     = 1 << 1;
 const unsigned int GFTYPE_PATCH     = 1 << 2;
 const unsigned int GFTYPE_LANGPACK  = 1 << 3;
+const unsigned int GFTYPE_DLC       = 1 << 4;
 
 class gameFile
 {
diff --git a/include/util.h b/include/util.h
index f8e4176..b30f12c 100644
--- a/include/util.h
+++ b/include/util.h
@@ -15,6 +15,7 @@
 #include <cerrno>
 #include <iostream>
 #include <sstream>
+#include <memory>
 #include <rhash.h>
 #include <boost/filesystem.hpp>
 #include <boost/regex.hpp>
@@ -49,6 +50,7 @@ struct gameItem
     std::string id;
     std::vector<std::string> dlcnames;
     Json::Value gamedetailsjson;
+    int updates = 0;
 };
 
 struct wishlistItem
@@ -90,6 +92,14 @@ namespace Util
     void parseOptionString(const std::string &option_string, std::vector<unsigned int> &priority, unsigned int &type, const std::vector<GlobalConstants::optionsStruct>& options);
     std::string getLocalFileHash(const std::string& xml_dir, const std::string& filepath, const std::string& gamename = std::string());
     void shortenStringToTerminalWidth(std::string& str);
+
+    template<typename ... Args> std::string formattedString(const std::string& format, Args ... args)
+    {
+        std::size_t sz = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; // +1 for null terminator
+        std::unique_ptr<char[]> buf(new char[sz]);
+        std::snprintf(buf.get(), sz, format.c_str(), args ...);
+        return std::string(buf.get(), buf.get() + sz - 1); // -1 because we don't want the null terminator
+    }
 }
 
 #endif // UTIL_H
diff --git a/main.cpp b/main.cpp
index 5a761fd..23b13c5 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,6 +8,7 @@
 #include "config.h"
 #include "util.h"
 #include "globalconstants.h"
+#include "ssl_thread_setup.h"
 
 #include <fstream>
 #include <boost/filesystem.hpp>
@@ -475,17 +476,43 @@ int main(int argc, char *argv[])
         std::cerr << std::endl;
     }
 
+    // Init curl globally
+    ssl_thread_setup();
+    curl_global_init(CURL_GLOBAL_ALL);
+
+    if (config.bLoginAPI)
+    {
+        config.sToken = "";
+        config.sSecret = "";
+    }
+
     Downloader downloader(config);
-    int initResult = downloader.init();
 
-    int iLoginResult = 0;
-    if (config.bLoginAPI || config.bLoginHTTP || initResult == 1)
+    int iLoginTries = 0;
+    bool bLoginOK = false;
+
+    // Login because --login, --login-api or --login-website was used
+    if (config.bLoginAPI || config.bLoginHTTP)
+        bLoginOK = downloader.login();
+
+    bool bIsLoggedin = downloader.isLoggedIn();
+
+    // Login because we are not logged in
+    while (iLoginTries++ < config.iRetries && !bIsLoggedin)
     {
-        if (!config.bLoginAPI && !config.bLoginHTTP)
-            downloader.config.bLoginAPI = true;
-        iLoginResult = downloader.login();
-        if (iLoginResult == 0)
-            return 1;
+        bLoginOK = downloader.login();
+        if (bLoginOK)
+        {
+            bIsLoggedin = downloader.isLoggedIn();
+        }
+    }
+
+    // Login failed, cleanup
+    if (!bLoginOK && !bIsLoggedin)
+    {
+        curl_global_cleanup();
+        ssl_thread_cleanup();
+        return 1;
     }
 
     // Make sure that config file and cookie file are only readable/writable by owner
@@ -495,9 +522,9 @@ int main(int argc, char *argv[])
         Util::setFilePermissions(config.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
     }
 
-    if (config.bSaveConfig || iLoginResult == 1)
+    if (config.bSaveConfig || bLoginOK)
     {
-        if (iLoginResult == 1)
+        if (bLoginOK)
         {
             set_vm_value(vm, "token", downloader.config.sToken);
             set_vm_value(vm, "secret", downloader.config.sSecret);
@@ -553,11 +580,17 @@ int main(int argc, char *argv[])
             if (!config.bRespectUmask)
                 Util::setFilePermissions(config.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
             if (config.bSaveConfig)
+            {
+                curl_global_cleanup();
+                ssl_thread_cleanup();
                 return 0;
+            }
         }
         else
         {
             std::cerr << "Failed to create config: " << config.sConfigFilePath << std::endl;
+            curl_global_cleanup();
+            ssl_thread_cleanup();
             return 1;
         }
     }
@@ -574,15 +607,28 @@ int main(int argc, char *argv[])
             ofs.close();
             if (!config.bRespectUmask)
                 Util::setFilePermissions(config.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
+
+            curl_global_cleanup();
+            ssl_thread_cleanup();
             return 0;
         }
         else
         {
             std::cerr << "Failed to create config: " << config.sConfigFilePath << std::endl;
+            curl_global_cleanup();
+            ssl_thread_cleanup();
             return 1;
         }
     }
 
+    bool bInitOK = downloader.init();
+    if (!bInitOK)
+    {
+        curl_global_cleanup();
+        ssl_thread_cleanup();
+        return 1;
+    }
+
     int res = 0;
 
     if (config.bShowWishlist)
@@ -622,5 +668,8 @@ int main(int argc, char *argv[])
     if (!config.sOrphanRegex.empty() && config.bDownload)
         downloader.checkOrphans();
 
+    curl_global_cleanup();
+    ssl_thread_cleanup();
+
     return res;
 }
diff --git a/src/api.cpp b/src/api.cpp
index 0ada213..f465ce1 100644
--- a/src/api.cpp
+++ b/src/api.cpp
@@ -34,17 +34,39 @@ API::API(const std::string& token, const std::string& secret)
     curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
     curl_easy_setopt(curlhandle, CURLOPT_PROGRESSDATA, this);
     curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
+    curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
 
     this->error = false;
     this->config.oauth_token = token;
     this->config.oauth_secret = secret;
 }
 
+/* Initialize the API
+    returns 0 if failed
+    returns 1 if successful
+*/
 int API::init()
 {
     int res = 0;
+
     this->getAPIConfig();
 
+    if (!this->getError())
+        res = 1;
+    else
+        this->clearError();
+
+    return res;
+}
+
+/* Login check
+    returns false if not logged in
+    returns true if logged in
+*/
+bool API::isLoggedIn()
+{
+    int res = 0;
+
     // Check if we already have token and secret
     if (!this->config.oauth_token.empty() && !this->config.oauth_secret.empty())
     {
@@ -55,7 +77,6 @@ int API::init()
     return res;
 }
 
-
 int API::getAPIConfig()
 {
     std::string url = "https://api.gog.com/downloader2/status/stable/"; // Stable API
diff --git a/src/downloader.cpp b/src/downloader.cpp
index 9674cc2..d0019d0 100644
--- a/src/downloader.cpp
+++ b/src/downloader.cpp
@@ -7,7 +7,6 @@
 #include "downloader.h"
 #include "util.h"
 #include "globalconstants.h"
-#include "ssl_thread_setup.h"
 #include "downloadinfo.h"
 #include "message.h"
 
@@ -28,6 +27,8 @@
 #include <htmlcxx/html/Uri.h>
 #include <termios.h>
 #include <algorithm>
+#include <thread>
+#include <mutex>
 
 namespace bptime = boost::posix_time;
 
@@ -41,45 +42,20 @@ std::mutex mtx_create_directories; // Mutex for creating directories in Download
 
 Downloader::Downloader(Config &conf)
 {
-    ssl_thread_setup();
     this->config = conf;
     if (config.bLoginHTTP && boost::filesystem::exists(config.sCookiePath))
         if (!boost::filesystem::remove(config.sCookiePath))
             std::cerr << "Failed to delete " << config.sCookiePath << std::endl;
-}
-
-Downloader::~Downloader()
-{
-    if (config.bReport)
-        if (this->report_ofs)
-            this->report_ofs.close();
-    delete progressbar;
-    delete gogAPI;
-    delete gogWebsite;
-    curl_easy_cleanup(curlhandle);
-    curl_global_cleanup();
-    ssl_thread_cleanup();
-    // Make sure that cookie file is only readable/writable by owner
-    if (!config.bRespectUmask)
-        Util::setFilePermissions(config.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
-}
 
-
-/* Initialize the downloader
-    returns 0 if successful
-    returns 1 if failed
-*/
-int Downloader::init()
-{
     this->resume_position = 0;
     this->retries = 0;
 
     // Initialize curl and set curl options
-    curl_global_init(CURL_GLOBAL_ALL);
     curlhandle = curl_easy_init();
     curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
     curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, config.sVersionString.c_str());
     curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 0);
+    curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
     curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, config.iTimeout);
     curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
     curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer);
@@ -99,7 +75,6 @@ int Downloader::init()
 
     // Create new GOG website handle
     gogWebsite = new Website(config);
-    bool bWebsiteIsLoggedIn = gogWebsite->IsLoggedIn();
 
     // Create new API handle and set curl options for the API
     gogAPI = new API(config.sToken, config.sSecret);
@@ -109,12 +84,55 @@ int Downloader::init()
     if (!config.sCACertPath.empty())
         gogAPI->curlSetOpt(CURLOPT_CAINFO, config.sCACertPath.c_str());
 
+    gogAPI->init();
+
     progressbar = new ProgressBar(config.bUnicode, config.bColor);
+}
 
-    bool bInitOK = gogAPI->init(); // Initialize the API
-    if (!bInitOK || !bWebsiteIsLoggedIn || config.bLoginHTTP || config.bLoginAPI)
-        return 1;
+Downloader::~Downloader()
+{
+    if (config.bReport)
+        if (this->report_ofs)
+            this->report_ofs.close();
+    delete progressbar;
+    delete gogAPI;
+    delete gogWebsite;
+    curl_easy_cleanup(curlhandle);
+    // Make sure that cookie file is only readable/writable by owner
+    if (!config.bRespectUmask)
+        Util::setFilePermissions(config.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
+}
+
+/* Login check
+    returns false if not logged in
+    returns true if logged in
+*/
+bool Downloader::isLoggedIn()
+{
+    bool bIsLoggedIn = false;
+    config.bLoginAPI = false;
+    config.bLoginHTTP = false;
+
+    bool bWebsiteIsLoggedIn = gogWebsite->IsLoggedIn();
+    if (!bWebsiteIsLoggedIn)
+        config.bLoginHTTP = true;
+
+    bool bIsLoggedInAPI = gogAPI->isLoggedIn();
+    if (!bIsLoggedInAPI)
+        config.bLoginAPI = true;
+
+    if (bIsLoggedInAPI && bWebsiteIsLoggedIn)
+        bIsLoggedIn = true;
 
+    return bIsLoggedIn;
+}
+
+/* Initialize the downloader
+    returns 0 if failed
+    returns 1 if successful
+*/
+int Downloader::init()
+{
     if (!config.sGameHasDLCList.empty())
     {
         if (config.gamehasdlc.empty())
@@ -126,25 +144,18 @@ int Downloader::init()
     }
     gogWebsite->setConfig(config); // Update config for website handle
 
-    if (config.bCover && config.bDownload && !config.bUpdateCheck)
-        coverXML = this->getResponse(config.sCoverList);
-
-    // updateCheck() calls getGameList() if needed
-    // getGameList() is not needed when using cache unless we want to list games from account
-    if ( !config.bUpdateCheck && (!config.bUseCache || (config.bUseCache && config.bList)) && config.sFileIdString.empty() && !config.bShowWishlist )
-        this->getGameList();
-
     if (config.bReport && (config.bDownload || config.bRepair))
     {
         this->report_ofs.open(config.sReportFilePath);
         if (!this->report_ofs)
         {
+            config.bReport = false;
             std::cerr << "Failed to create " << config.sReportFilePath << std::endl;
-            return 1;
+            return 0;
         }
     }
 
-    return 0;
+    return 1;
 }
 
 /* Login
@@ -182,6 +193,11 @@ int Downloader::login()
         // Login to website
         if (config.bLoginHTTP)
         {
+            // Delete old cookies
+            if (boost::filesystem::exists(config.sCookiePath))
+                if (!boost::filesystem::remove(config.sCookiePath))
+                    std::cerr << "Failed to delete " << config.sCookiePath << std::endl;
+
             if (!gogWebsite->Login(email, password))
             {
                 std::cerr << "HTTP: Login failed" << std::endl;
@@ -307,6 +323,9 @@ int Downloader::getGameDetails()
         }
     }
 
+    if (gameItems.empty())
+        this->getGameList();
+
     if (!gameItems.empty())
     {
         for (unsigned int i = 0; i < gameItems.size(); ++i)
@@ -315,11 +334,7 @@ int Downloader::getGameDetails()
         }
 
         // Create threads
-        unsigned int threads = config.iThreads;
-        if (gameItemQueue.size() < config.iThreads)
-        {
-            threads = gameItemQueue.size();
-        }
+        unsigned int threads = std::min(config.iThreads, static_cast<unsigned int>(gameItemQueue.size()));
         std::vector<std::thread> vThreads;
         for (unsigned int i = 0; i < threads; ++i)
         {
@@ -577,9 +592,19 @@ int Downloader::listGames()
     }
     else
     {   // List game names
+        if (gameItems.empty())
+            this->getGameList();
+
         for (unsigned int i = 0; i < gameItems.size(); ++i)
         {
-            std::cout << gameItems[i].name << std::endl;
+            std::string gamename = gameItems[i].name;
+            if (gameItems[i].updates > 0)
+            {
+                gamename += " [" + std::to_string(gameItems[i].updates) + "]";
+                if (config.bColor)
+                    gamename = "\033[32m" + gamename + "\033[0m";
+            }
+            std::cout << gamename << std::endl;
             for (unsigned int j = 0; j < gameItems[i].dlcnames.size(); ++j)
                 std::cout << "+> " << gameItems[i].dlcnames[j] << std::endl;
         }
@@ -877,6 +902,9 @@ void Downloader::download()
     if (this->games.empty())
         this->getGameDetails();
 
+    if (config.bCover && !config.bUpdateCheck)
+        coverXML = this->getResponse(config.sCoverList);
+
     for (unsigned int i = 0; i < games.size(); ++i)
     {
         if (config.bSaveSerials && !games[i].serials.empty())
@@ -981,24 +1009,30 @@ void Downloader::download()
         }
     }
 
-    // Create download threads
-    std::vector<std::thread> vThreads;
-    for (unsigned int i = 0; i < config.iThreads; ++i)
+    if (!dlQueue.empty())
     {
-        DownloadInfo dlInfo;
-        dlInfo.setStatus(DLSTATUS_NOTSTARTED);
-        vDownloadInfo.push_back(dlInfo);
-        vThreads.push_back(std::thread(Downloader::processDownloadQueue, this->config, i));
-    }
+        // Limit thread count to number of items in download queue
+        unsigned int iThreads = std::min(config.iThreads, static_cast<unsigned int>(dlQueue.size()));
 
-    this->printProgress();
+        // 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::processDownloadQueue, this->config, i));
+        }
+
+        this->printProgress();
 
-    // Join threads
-    for (unsigned int i = 0; i < vThreads.size(); ++i)
-        vThreads[i].join();
+        // Join threads
+        for (unsigned int i = 0; i < vThreads.size(); ++i)
+            vThreads[i].join();
 
-    vThreads.clear();
-    vDownloadInfo.clear();
+        vThreads.clear();
+        vDownloadInfo.clear();
+    }
 
     // Create xml data for all files in the queue
     if (!createXMLQueue.empty())
@@ -1706,8 +1740,7 @@ int Downloader::progressCallback(void *clientp, curl_off_t dltotal, curl_off_t d
         // Create progressbar
         double fraction = starting ? 0.0 : static_cast<double>(dlnow) / static_cast<double>(dltotal);
 
-        // assuming that config is provided.
-        printf("\033[0K\r%3.0f%% ", fraction * 100);
+        std::cout << Util::formattedString("\033[0K\r%3.0f%% ", fraction * 100);
 
         // Download rate unit conversion
         std::string rate_unit;
@@ -1721,9 +1754,8 @@ int Downloader::progressCallback(void *clientp, curl_off_t dltotal, curl_off_t d
             rate /= 1024;
             rate_unit = "kB/s";
         }
-        char status_text[200]; // We're probably never going to go as high as 200 characters but it's better to use too big number here than too small
-        sprintf(status_text, " %0.2f/%0.2fMB @ %0.2f%s ETA: %s\r", static_cast<double>(dlnow)/1024/1024, static_cast<double>(dltotal)/1024/1024, rate, rate_unit.c_str(), eta_ss.str().c_str());
-        int status_text_length = strlen(status_text) + 6;
+        std::string status_text = Util::formattedString(" %0.2f/%0.2fMB @ %0.2f%s ETA: %s\r", static_cast<double>(dlnow)/1024/1024, static_cast<double>(dltotal)/1024/1024, rate, rate_unit.c_str(), eta_ss.str().c_str());
+        int status_text_length = status_text.length() + 6;
 
         if ((status_text_length + bar_length) > iTermWidth)
             bar_length -= (status_text_length + bar_length) - iTermWidth;
@@ -2059,276 +2091,88 @@ void Downloader::checkStatus()
     if (this->games.empty())
         this->getGameDetails();
 
+    // Create a vector containing all game files
+    std::vector<gameFile> vGameFiles;
     for (unsigned int i = 0; i < games.size(); ++i)
     {
-        if (config.bInstallers)
-        {
-            for (unsigned int j = 0; j < games[i].installers.size(); ++j)
-            {
-                boost::filesystem::path filepath = games[i].installers[j].getFilepath();
-
-                if (config.blacklist.isBlacklisted(filepath.native()))
-                    continue;
-                std::string remoteHash;
-                std::string localHash;
-                bool bHashOK = true; // assume hash OK
-                uintmax_t filesize;
-
-                localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
-                remoteHash = this->getRemoteFileHash(games[i].gamename, games[i].installers[j].id);
-
-                if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                {
-                    filesize = boost::filesystem::file_size(filepath);
-
-                    if (remoteHash != localHash)
-                        bHashOK = false;
-                    else
-                    {
-                        // Check for incomplete file by comparing the filesizes
-                        // Remote hash was saved but download was incomplete and therefore getLocalFileHash returned the same as getRemoteFileHash
-                        uintmax_t filesize_xml = 0;
-                        boost::filesystem::path path = filepath;
-                        boost::filesystem::path local_xml_file;
-                        if (!games[i].gamename.empty())
-                            local_xml_file = config.sXMLDirectory + "/" + games[i].gamename + "/" + path.filename().string() + ".xml";
-                        else
-                            local_xml_file = config.sXMLDirectory + "/" + path.filename().string() + ".xml";
-
-                        if (boost::filesystem::exists(local_xml_file))
-                        {
-                            tinyxml2::XMLDocument local_xml;
-                            local_xml.LoadFile(local_xml_file.string().c_str());
-                            tinyxml2::XMLElement *fileElemLocal = local_xml.FirstChildElement("file");
-                            if (fileElemLocal)
-                            {
-                                std::string filesize_xml_str = fileElemLocal->Attribute("total_size");
-                                filesize_xml = std::stoull(filesize_xml_str);
-                            }
-                        }
-
-                        if (filesize_xml > 0 && filesize_xml != filesize)
-                        {
-                            localHash = Util::getFileHash(path.string(), RHASH_MD5);
-                            std::cout << "FS " << games[i].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                            continue;
-                        }
-                    }
+        std::vector<gameFile> vec = games[i].getGameFileVector();
+        vGameFiles.insert(std::end(vGameFiles), std::begin(vec), std::end(vec));
+    }
 
-                    std::cout << (bHashOK ? "OK " : "MD5 ") << games[i].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                }
-                else
-                {
-                    std::cout << "ND " << games[i].gamename << " " << filepath.filename().string() << std::endl;
-                }
-            }
-        }
+    for (unsigned int i = 0; i < vGameFiles.size(); ++i)
+    {
+        unsigned int type = vGameFiles[i].type;
+        if (!config.bDLC && (type & GFTYPE_DLC))
+            continue;
+        if (!config.bInstallers && (type & GFTYPE_INSTALLER))
+            continue;
+        if (!config.bExtras && (type & GFTYPE_EXTRA))
+            continue;
+        if (!config.bPatches && (type & GFTYPE_PATCH))
+            continue;
+        if (!config.bLanguagePacks && (type & GFTYPE_LANGPACK))
+            continue;
 
-        if (config.bExtras)
-        {
-            for (unsigned int j = 0; j < games[i].extras.size(); ++j)
-            {
-                boost::filesystem::path filepath = games[i].extras[j].getFilepath();
+        boost::filesystem::path filepath = vGameFiles[i].getFilepath();
 
-                if (config.blacklist.isBlacklisted(filepath.native()))
-                    continue;
-                std::string localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
-                uintmax_t filesize;
+        if (config.blacklist.isBlacklisted(filepath.native()))
+            continue;
 
-                if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                {
-                    filesize = boost::filesystem::file_size(filepath);
-                    std::cout << "OK " << games[i].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                }
-                else
-                {
-                    std::cout << "ND " << games[i].gamename << " " << filepath.filename().string() << std::endl;
-                }
-            }
-        }
+        std::string gamename = vGameFiles[i].gamename;
+        std::string id = vGameFiles[i].id;
 
-        if (config.bPatches)
+        if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
         {
-            for (unsigned int j = 0; j < games[i].patches.size(); ++j)
-            {
-                boost::filesystem::path filepath = games[i].patches[j].getFilepath();
+            std::string remoteHash;
+            bool bHashOK = true; // assume hash OK
+            uintmax_t filesize = boost::filesystem::file_size(filepath);
 
-                if (config.blacklist.isBlacklisted(filepath.native()))
-                    continue;
-                std::string localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
-                uintmax_t filesize;
-
-                if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                {
-                    filesize = boost::filesystem::file_size(filepath);
-                    std::cout << "OK " << games[i].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                }
-                else
-                {
-                    std::cout << "ND " << games[i].gamename << " " << filepath.filename().string() << std::endl;
-                }
-            }
-        }
+            // GOG only provides xml data for installers, patches and language packs
+            if (type & (GFTYPE_INSTALLER | GFTYPE_PATCH | GFTYPE_LANGPACK))
+                remoteHash = this->getRemoteFileHash(gamename, id);
+            std::string localHash = this->getLocalFileHash(filepath.string(), gamename);
 
-        if (config.bLanguagePacks)
-        {
-            for (unsigned int j = 0; j < games[i].languagepacks.size(); ++j)
+            if (!remoteHash.empty())
             {
-                boost::filesystem::path filepath = games[i].languagepacks[j].getFilepath();
-
-                if (config.blacklist.isBlacklisted(filepath.native()))
-                    continue;
-                std::string localHash = this->getLocalFileHash(filepath.string(), games[i].gamename);
-                uintmax_t filesize;
-
-                if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                {
-                    filesize = boost::filesystem::file_size(filepath);
-                    std::cout << "OK " << games[i].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                }
+                if (remoteHash != localHash)
+                    bHashOK = false;
                 else
                 {
-                    std::cout << "ND " << games[i].gamename << " " << filepath.filename().string() << std::endl;
-                }
-            }
-        }
-
-        if (config.bDLC)
-        {
-            for (unsigned int j = 0; j < games[i].dlcs.size(); ++j)
-            {
-                if (config.bInstallers)
-                {
-                    for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k)
-                    {
-                        boost::filesystem::path filepath = games[i].dlcs[j].installers[k].getFilepath();
-
-                        if (config.blacklist.isBlacklisted(filepath.native()))
-                            continue;
-                        std::string remoteHash;
-                        std::string localHash;
-                        bool bHashOK = true; // assume hash OK
-                        uintmax_t filesize;
-
-                        localHash = this->getLocalFileHash(filepath.string(), games[i].dlcs[j].gamename);
-                        remoteHash = this->getRemoteFileHash(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id);
-
-                        if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                        {
-                            filesize = boost::filesystem::file_size(filepath);
-
-                            if (remoteHash != localHash)
-                                bHashOK = false;
-                            else
-                            {
-                                // Check for incomplete file by comparing the filesizes
-                                // Remote hash was saved but download was incomplete and therefore getLocalFileHash returned the same as getRemoteFileHash
-                                uintmax_t filesize_xml = 0;
-                                boost::filesystem::path path = filepath;
-                                boost::filesystem::path local_xml_file;
-                                if (!games[i].dlcs[j].gamename.empty())
-                                    local_xml_file = config.sXMLDirectory + "/" + games[i].dlcs[j].gamename + "/" + path.filename().string() + ".xml";
-                                else
-                                    local_xml_file = config.sXMLDirectory + "/" + path.filename().string() + ".xml";
-
-                                if (boost::filesystem::exists(local_xml_file))
-                                {
-                                    tinyxml2::XMLDocument local_xml;
-                                    local_xml.LoadFile(local_xml_file.string().c_str());
-                                    tinyxml2::XMLElement *fileElemLocal = local_xml.FirstChildElement("file");
-                                    if (fileElemLocal)
-                                    {
-                                        std::string filesize_xml_str = fileElemLocal->Attribute("total_size");
-                                        filesize_xml = std::stoull(filesize_xml_str);
-                                    }
-                                }
-
-                                if (filesize_xml > 0 && filesize_xml != filesize)
-                                {
-                                    localHash = Util::getFileHash(path.string(), RHASH_MD5);
-                                    std::cout << "FS " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                                    continue;
-                                }
-                            }
-
-                            std::cout << (bHashOK ? "OK " : "MD5 ") << games[i].dlcs[j].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                        }
-                        else
-                        {
-                            std::cout << "ND " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << std::endl;
-                        }
-                    }
-                }
-
-                if (config.bPatches)
-                {
-                    for (unsigned int k = 0; k < games[i].dlcs[j].patches.size(); ++k)
-                    {
-                        boost::filesystem::path filepath = games[i].dlcs[j].patches[k].getFilepath();
-
-                        if (config.blacklist.isBlacklisted(filepath.native()))
-                            continue;
-                        std::string localHash = this->getLocalFileHash(filepath.string(), games[i].dlcs[j].gamename);
-                        uintmax_t filesize;
-
-                        if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                        {
-                            filesize = boost::filesystem::file_size(filepath);
-                            std::cout << "OK " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                        }
-                        else
-                        {
-                            std::cout << "ND " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << std::endl;
-                        }
-                    }
-                }
+                    // Check for incomplete file by comparing the filesizes
+                    // Remote hash was saved but download was incomplete and therefore getLocalFileHash returned the same as getRemoteFileHash
+                    uintmax_t filesize_xml = 0;
+                    boost::filesystem::path path = filepath;
+                    boost::filesystem::path local_xml_file;
+                    if (!gamename.empty())
+                        local_xml_file = config.sXMLDirectory + "/" + gamename + "/" + path.filename().string() + ".xml";
+                    else
+                        local_xml_file = config.sXMLDirectory + "/" + path.filename().string() + ".xml";
 
-                if (config.bExtras)
-                {
-                    for (unsigned int k = 0; k < games[i].dlcs[j].extras.size(); ++k)
+                    if (boost::filesystem::exists(local_xml_file))
                     {
-                        boost::filesystem::path filepath = games[i].dlcs[j].extras[k].getFilepath();
-
-                        if (config.blacklist.isBlacklisted(filepath.native()))
-                            continue;
-                        std::string localHash = this->getLocalFileHash(filepath.string(), games[i].dlcs[j].gamename);
-                        uintmax_t filesize;
-
-                        if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                        {
-                            filesize = boost::filesystem::file_size(filepath);
-                            std::cout << "OK " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                        }
-                        else
+                        tinyxml2::XMLDocument local_xml;
+                        local_xml.LoadFile(local_xml_file.string().c_str());
+                        tinyxml2::XMLElement *fileElemLocal = local_xml.FirstChildElement("file");
+                        if (fileElemLocal)
                         {
-                            std::cout << "ND " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << std::endl;
+                            std::string filesize_xml_str = fileElemLocal->Attribute("total_size");
+                            filesize_xml = std::stoull(filesize_xml_str);
                         }
                     }
-                }
 
-                if (config.bLanguagePacks)
-                {
-                    for (unsigned int k = 0; k < games[i].dlcs[j].languagepacks.size(); ++k)
+                    if (filesize_xml > 0 && filesize_xml != filesize)
                     {
-                        boost::filesystem::path filepath = games[i].dlcs[j].languagepacks[k].getFilepath();
-
-                        if (config.blacklist.isBlacklisted(filepath.native()))
-                            continue;
-                        std::string localHash = this->getLocalFileHash(filepath.string(), games[i].dlcs[j].gamename);
-                        uintmax_t filesize;
-
-                        if (boost::filesystem::exists(filepath) && boost::filesystem::is_regular_file(filepath))
-                        {
-                            filesize = boost::filesystem::file_size(filepath);
-                            std::cout << "OK " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
-                        }
-                        else
-                        {
-                            std::cout << "ND " << games[i].dlcs[j].gamename << " " << filepath.filename().string() << std::endl;
-                        }
+                        localHash = Util::getFileHash(path.string(), RHASH_MD5);
+                        std::cout << "FS " << gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
+                        continue;
                     }
                 }
             }
+            std::cout << (bHashOK ? "OK " : "MD5 ") << gamename << " " << filepath.filename().string() << " " << filesize << " " << localHash << std::endl;
+        }
+        else
+        {
+            std::cout << "ND " << gamename << " " << filepath.filename().string() << std::endl;
         }
     }
 
@@ -2805,6 +2649,7 @@ void Downloader::processDownloadQueue(Config conf, const unsigned int& tid)
     curl_easy_setopt(dlhandle, CURLOPT_FOLLOWLOCATION, 1);
     curl_easy_setopt(dlhandle, CURLOPT_USERAGENT, conf.sVersionString.c_str());
     curl_easy_setopt(dlhandle, CURLOPT_NOPROGRESS, 0);
+    curl_easy_setopt(dlhandle, CURLOPT_NOSIGNAL, 1);
 
     curl_easy_setopt(dlhandle, CURLOPT_CONNECTTIMEOUT, conf.iTimeout);
     curl_easy_setopt(dlhandle, CURLOPT_FAILONERROR, true);
@@ -2979,13 +2824,13 @@ void Downloader::processDownloadQueue(Config conf, const unsigned int& tid)
 
         // Get download url
         std::string url;
-        if (gf.type == GFTYPE_INSTALLER)
+        if (gf.type & GFTYPE_INSTALLER)
             url = api->getInstallerLink(gf.gamename, gf.id);
-        else if (gf.type == GFTYPE_PATCH)
+        else if (gf.type & GFTYPE_PATCH)
             url = api->getPatchLink(gf.gamename, gf.id);
-        else if (gf.type == GFTYPE_LANGPACK)
+        else if (gf.type & GFTYPE_LANGPACK)
             url = api->getLanguagePackLink(gf.gamename, gf.id);
-        else if (gf.type == GFTYPE_EXTRA)
+        else if (gf.type & GFTYPE_EXTRA)
             url = api->getExtraLink(gf.gamename, gf.id);
         else
             url = api->getExtraLink(gf.gamename, gf.id); // assume extra if type didn't match any of the others
@@ -3211,9 +3056,8 @@ void Downloader::printProgress()
             bool starting = ((0 == progress_info.dlnow) && (0 == progress_info.dltotal));
             double fraction = starting ? 0.0 : static_cast<double>(progress_info.dlnow) / static_cast<double>(progress_info.dltotal);
 
-            char progress_percentage_text[200];
-            sprintf(progress_percentage_text, "%3.0f%% ", fraction * 100);
-            int progress_percentage_text_length = strlen(progress_percentage_text) + 1;
+            std::string progress_percentage_text = Util::formattedString("%3.0f%% ", fraction * 100);
+            int progress_percentage_text_length = progress_percentage_text.length() + 1;
 
             bptime::time_duration eta(bptime::seconds((long)((progress_info.dltotal - progress_info.dlnow) / progress_info.rate)));
             std::stringstream eta_ss;
@@ -3252,9 +3096,8 @@ void Downloader::printProgress()
                 rate_unit = "kB/s";
             }
 
-            char progress_status_text[200]; // We're probably never going to go as high as 200 characters but it's better to use too big number here than too small
-            sprintf(progress_status_text, " %0.2f/%0.2fMB @ %0.2f%s ETA: %s", static_cast<double>(progress_info.dlnow)/1024/1024, static_cast<double>(progress_info.dltotal)/1024/1024, progress_info.rate, rate_unit.c_str(), eta_ss.str().c_str());
-            int status_text_length = strlen(progress_status_text) + 1;
+            std::string progress_status_text = Util::formattedString(" %0.2f/%0.2fMB @ %0.2f%s ETA: %s", static_cast<double>(progress_info.dlnow)/1024/1024, static_cast<double>(progress_info.dltotal)/1024/1024, progress_info.rate, rate_unit.c_str(), eta_ss.str().c_str());
+            int status_text_length = progress_status_text.length() + 1;
 
             if ((status_text_length + progress_percentage_text_length + bar_length) > iTermWidth)
                 bar_length -= (status_text_length + progress_percentage_text_length + bar_length) - iTermWidth;
@@ -3264,7 +3107,7 @@ void Downloader::printProgress()
             if (bar_length >= min_bar_length)
                 progress_bar_text = bar.createBarString(bar_length, fraction);
 
-            progress_text = std::string(progress_percentage_text) + progress_bar_text + std::string(progress_status_text);
+            progress_text = progress_percentage_text + progress_bar_text + progress_status_text;
             std::string filename_text = "#" + std::to_string(i) + " " + filename;
             Util::shortenStringToTerminalWidth(filename_text);
 
@@ -3518,6 +3361,16 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid)
                         }
                     }
 
+                    // Add DLC type to all DLC files
+                    for (unsigned int a = 0; a < dlc.installers.size(); ++a)
+                        dlc.installers[a].type |= GFTYPE_DLC;
+                    for (unsigned int a = 0; a < dlc.extras.size(); ++a)
+                        dlc.extras[a].type |= GFTYPE_DLC;
+                    for (unsigned int a = 0; a < dlc.patches.size(); ++a)
+                        dlc.patches[a].type |= GFTYPE_DLC;
+                    for (unsigned int a = 0; a < dlc.languagepacks.size(); ++a)
+                        dlc.languagepacks[a].type |= GFTYPE_DLC;
+
                     game.dlcs.push_back(dlc);
                 }
             }
diff --git a/src/gamedetails.cpp b/src/gamedetails.cpp
index d3c7530..794ca70 100644
--- a/src/gamedetails.cpp
+++ b/src/gamedetails.cpp
@@ -181,3 +181,25 @@ std::string gameDetails::getChangelogFilepath()
 {
     return this->changelogFilepath;
 }
+
+// Return vector containing all game files
+std::vector<gameFile> gameDetails::getGameFileVector()
+{
+    std::vector<gameFile> vGameFiles;
+
+    vGameFiles.insert(std::end(vGameFiles), std::begin(installers), std::end(installers));
+    vGameFiles.insert(std::end(vGameFiles), std::begin(patches), std::end(patches));
+    vGameFiles.insert(std::end(vGameFiles), std::begin(extras), std::end(extras));
+    vGameFiles.insert(std::end(vGameFiles), std::begin(languagepacks), std::end(languagepacks));
+
+    if (!dlcs.empty())
+    {
+        for (unsigned int i = 0; i < dlcs.size(); ++i)
+        {
+            std::vector<gameFile> vGameFilesDLC = dlcs[i].getGameFileVector();
+            vGameFiles.insert(std::end(vGameFiles), std::begin(vGameFilesDLC), std::end(vGameFilesDLC));
+        }
+    }
+
+    return vGameFiles;
+}
diff --git a/src/util.cpp b/src/util.cpp
index f88f26f..e90392d 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -379,9 +379,16 @@ void Util::setFilePermissions(const boost::filesystem::path& path, const boost::
 
 int Util::getTerminalWidth()
 {
-    struct winsize w;
-    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
-    return static_cast<int>(w.ws_col);
+    int width;
+    if(isatty(STDOUT_FILENO))
+    {
+        struct winsize w;
+        ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
+        width = static_cast<int>(w.ws_col);
+    }
+    else
+        width = 10000;//Something sufficiently big
+    return width;
 }
 
 
diff --git a/src/website.cpp b/src/website.cpp
index 0769968..0803432 100644
--- a/src/website.cpp
+++ b/src/website.cpp
@@ -19,6 +19,7 @@ Website::Website(Config &conf)
     curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
     curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, config.sVersionString.c_str());
     curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+    curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
     curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, config.iTimeout);
     curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
     curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, config.sCookiePath.c_str());
@@ -162,6 +163,7 @@ std::vector<gameItem> Website::getGames()
                 gameItem game;
                 game.name = product["slug"].asString();
                 game.id = product["id"].isInt() ? std::to_string(product["id"].asInt()) : product["id"].asString();
+                game.updates = product["updates"].isInt() ? product["updates"].asInt() : std::stoi(product["updates"].asString());
 
                 unsigned int platform = 0;
                 if (product["worksOn"]["Windows"].asBool())

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/lgogdownloader.git



More information about the Pkg-games-commits mailing list