Add support for setting subdirectories

Allows user to specify subdirectories for games, installers, extras, patches, language packs and dlc
This commit is contained in:
Sude 2014-09-19 22:46:03 +03:00
parent 810506b74b
commit 5635909e20
6 changed files with 124 additions and 34 deletions

View File

@ -54,6 +54,12 @@ class Config
std::string sOrphanRegex; std::string sOrphanRegex;
std::string sCoverList; std::string sCoverList;
std::string sReportFilePath; std::string sReportFilePath;
std::string sInstallersSubdir;
std::string sExtrasSubdir;
std::string sPatchesSubdir;
std::string sLanguagePackSubdir;
std::string sDLCSubdir;
std::string sGameSubdir;
unsigned int iInstallerType; unsigned int iInstallerType;
unsigned int iInstallerLanguage; unsigned int iInstallerLanguage;
int iRetries; int iRetries;

View File

@ -7,6 +7,8 @@
#ifndef UTIL_H #ifndef UTIL_H
#define UTIL_H #define UTIL_H
#include "globalconstants.h"
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -24,12 +26,14 @@ struct gameSpecificConfig
namespace Util namespace Util
{ {
std::string makeFilepath(const std::string& directory, const std::string& path, const std::string& gamename, std::string subdirectory = ""); std::string makeFilepath(const std::string& directory, const std::string& path, const std::string& gamename, std::string subdirectory = "", const unsigned int& platformId = 0, const std::string& dlcname = "");
std::string makeRelativeFilepath(const std::string& path, const std::string& gamename, std::string subdirectory = ""); std::string makeRelativeFilepath(const std::string& path, const std::string& gamename, std::string subdirectory = "");
std::string getFileHash(const std::string& filename, unsigned hash_id); std::string getFileHash(const std::string& filename, unsigned hash_id);
std::string getChunkHash(unsigned char* chunk, size_t chunk_size, unsigned hash_id); std::string getChunkHash(unsigned char* chunk, size_t chunk_size, unsigned hash_id);
int createXML(std::string filepath, size_t chunk_size, std::string xml_dir = std::string()); int createXML(std::string filepath, size_t chunk_size, std::string xml_dir = std::string());
int getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf, std::string directory = std::string()); int getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf, std::string directory = std::string());
int replaceString(std::string& str, const std::string& to_replace, const std::string& replace_with);
void filepathReplaceReservedStrings(std::string& str, const std::string& gamename, const unsigned int& platformId = 0, const std::string& dlcname = "");
} }
#endif // UTIL_H #endif // UTIL_H

View File

@ -59,6 +59,9 @@ int main(int argc, char *argv[])
std::string orphans_regex_default = ".*\\.(zip|exe|bin|dmg|old|deb|tar\\.gz|pkg)$"; // Limit to files with these extensions (".old" is for renamed older version files) std::string orphans_regex_default = ".*\\.(zip|exe|bin|dmg|old|deb|tar\\.gz|pkg)$"; // Limit to files with these extensions (".old" is for renamed older version files)
std::string check_orphans_text = "Check for orphaned files (files found on local filesystem that are not found on GOG servers). Sets regular expression filter (Perl syntax) for files to check. If no argument is given then the regex defaults to '" + orphans_regex_default + "'"; std::string check_orphans_text = "Check for orphaned files (files found on local filesystem that are not found on GOG servers). Sets regular expression filter (Perl syntax) for files to check. If no argument is given then the regex defaults to '" + orphans_regex_default + "'";
// Help text for subdir options
std::string subdir_help_text = "\nTemplates:\n- %platform%\n- %gamename%\n- %dlcname%";
std::vector<std::string> unrecognized_options_cfg; std::vector<std::string> unrecognized_options_cfg;
bpo::variables_map vm; bpo::variables_map vm;
bpo::options_description options_cli_all("Options"); bpo::options_description options_cli_all("Options");
@ -104,7 +107,7 @@ int main(int argc, char *argv[])
; ;
// Commandline options (config file) // Commandline options (config file)
options_cli_cfg.add_options() options_cli_cfg.add_options()
("directory", bpo::value<std::string>(&config.sDirectory)->default_value(""), "Set download directory") ("directory", bpo::value<std::string>(&config.sDirectory)->default_value("."), "Set download directory")
("limit-rate", bpo::value<curl_off_t>(&config.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited") ("limit-rate", bpo::value<curl_off_t>(&config.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited")
("xml-directory", bpo::value<std::string>(&config.sXMLDirectory), "Set directory for GOG XML files") ("xml-directory", bpo::value<std::string>(&config.sXMLDirectory), "Set directory for GOG XML files")
("chunk-size", bpo::value<size_t>(&config.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML") ("chunk-size", bpo::value<size_t>(&config.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML")
@ -129,6 +132,12 @@ int main(int argc, char *argv[])
("retries", bpo::value<int>(&config.iRetries)->default_value(3), "Set maximum number of retries on failed download") ("retries", bpo::value<int>(&config.iRetries)->default_value(3), "Set maximum number of retries on failed download")
("wait", bpo::value<int>(&config.iWait)->default_value(0), "Time to wait between requests (milliseconds)") ("wait", bpo::value<int>(&config.iWait)->default_value(0), "Time to wait between requests (milliseconds)")
("cover-list", bpo::value<std::string>(&config.sCoverList)->default_value("https://sites.google.com/site/gogdownloader/covers.xml"), "Set URL for cover list") ("cover-list", bpo::value<std::string>(&config.sCoverList)->default_value("https://sites.google.com/site/gogdownloader/covers.xml"), "Set URL for cover list")
("subdir-installers", bpo::value<std::string>(&config.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str())
("subdir-extras", bpo::value<std::string>(&config.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str())
("subdir-patches", bpo::value<std::string>(&config.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str())
("subdir-language-packs", bpo::value<std::string>(&config.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str())
("subdir-dlc", bpo::value<std::string>(&config.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str())
("subdir-game", bpo::value<std::string>(&config.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str())
; ;
// Options read from config file // Options read from config file
options_cfg_only.add_options() options_cfg_only.add_options()

View File

@ -2232,40 +2232,70 @@ void Downloader::checkOrphans()
for (unsigned int i = 0; i < games.size(); ++i) for (unsigned int i = 0; i < games.size(); ++i)
{ {
std::cout << "Checking for orphaned files " << i+1 << " / " << games.size() << "\r" << std::flush; std::cout << "Checking for orphaned files " << i+1 << " / " << games.size() << "\r" << std::flush;
boost::filesystem::path path (config.sDirectory + games[i].gamename);
std::vector<boost::filesystem::path> filepath_vector; std::vector<boost::filesystem::path> filepath_vector;
std::size_t pathlen = config.sDirectory.length();
try try
{ {
if (boost::filesystem::exists(path)) std::vector<boost::filesystem::path> paths;
std::vector<unsigned int> platformIds;
platformIds.push_back(0);
for (unsigned int j = 0; j < GlobalConstants::PLATFORMS.size(); ++j)
{ {
if (boost::filesystem::is_directory(path)) platformIds.push_back(GlobalConstants::PLATFORMS[j].platformId);
}
for (unsigned int j = 0; j < platformIds.size(); ++j)
{
std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
Util::filepathReplaceReservedStrings(directory, games[i].gamename, platformIds[j]);
boost::filesystem::path path (directory);
if (boost::filesystem::exists(path))
{ {
// Recursively iterate over files in directory bool bDuplicate = false;
boost::filesystem::recursive_directory_iterator end_iter; for (unsigned int k = 0; k < paths.size(); ++k)
boost::filesystem::recursive_directory_iterator dir_iter(path);
while (dir_iter != end_iter)
{ {
if (boost::filesystem::is_regular_file(dir_iter->status())) if (path == paths[k])
{ {
std::string filepath = dir_iter->path().string(); bDuplicate = true;
if (config.blacklist.isBlacklisted(filepath.substr(pathlen))) { break;
if (config.bVerbose)
std::cout << "skipped blacklisted file " << filepath << std::endl;
} else {
boost::regex expression(config.sOrphanRegex); // Limit to files matching the regex
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(filepath, what, expression))
filepath_vector.push_back(dir_iter->path());
}
} }
dir_iter++;
} }
if (!bDuplicate)
paths.push_back(path);
} }
} }
else
std::cout << path << " does not exist" << std::endl; for (unsigned int j = 0; j < paths.size(); ++j)
{
std::size_t pathlen = config.sDirectory.length();
if (boost::filesystem::exists(paths[j]))
{
if (boost::filesystem::is_directory(paths[j]))
{
// Recursively iterate over files in directory
boost::filesystem::recursive_directory_iterator end_iter;
boost::filesystem::recursive_directory_iterator dir_iter(paths[j]);
while (dir_iter != end_iter)
{
if (boost::filesystem::is_regular_file(dir_iter->status()))
{
std::string filepath = dir_iter->path().string();
if (config.blacklist.isBlacklisted(filepath.substr(pathlen))) {
if (config.bVerbose)
std::cout << "skipped blacklisted file " << filepath << std::endl;
} else {
boost::regex expression(config.sOrphanRegex); // Limit to files matching the regex
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(filepath, what, expression))
filepath_vector.push_back(dir_iter->path());
}
}
dir_iter++;
}
}
}
else
std::cout << paths[j] << " does not exist" << std::endl;
}
} }
catch (const boost::filesystem::filesystem_error& ex) catch (const boost::filesystem::filesystem_error& ex)
{ {

View File

@ -14,47 +14,56 @@ gameDetails::~gameDetails()
void gameDetails::makeFilepaths(const Config& config) void gameDetails::makeFilepaths(const Config& config)
{ {
std::string filepath; std::string filepath;
std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
std::string subdir;
for (unsigned int i = 0; i < this->installers.size(); ++i) for (unsigned int i = 0; i < this->installers.size(); ++i)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->installers[i].path, this->gamename); subdir = config.bSubDirectories ? config.sInstallersSubdir : "";
filepath = Util::makeFilepath(directory, this->installers[i].path, this->gamename, subdir, this->installers[i].platform);
this->installers[i].setFilepath(filepath); this->installers[i].setFilepath(filepath);
} }
for (unsigned int i = 0; i < this->extras.size(); ++i) for (unsigned int i = 0; i < this->extras.size(); ++i)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->extras[i].path, this->gamename, config.bSubDirectories ? "extras" : ""); subdir = config.bSubDirectories ? config.sExtrasSubdir : "";
filepath = Util::makeFilepath(directory, this->extras[i].path, this->gamename, subdir, 0);
this->extras[i].setFilepath(filepath); this->extras[i].setFilepath(filepath);
} }
for (unsigned int i = 0; i < this->patches.size(); ++i) for (unsigned int i = 0; i < this->patches.size(); ++i)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->patches[i].path, this->gamename, config.bSubDirectories ? "patches" : ""); subdir = config.bSubDirectories ? config.sPatchesSubdir : "";
filepath = Util::makeFilepath(directory, this->patches[i].path, this->gamename, subdir, this->patches[i].platform);
this->patches[i].setFilepath(filepath); this->patches[i].setFilepath(filepath);
} }
for (unsigned int i = 0; i < this->languagepacks.size(); ++i) for (unsigned int i = 0; i < this->languagepacks.size(); ++i)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->languagepacks[i].path, this->gamename, config.bSubDirectories ? "languagepacks" : ""); subdir = config.bSubDirectories ? config.sLanguagePackSubdir : "";
filepath = Util::makeFilepath(directory, this->languagepacks[i].path, this->gamename, subdir, 0);
} }
for (unsigned int i = 0; i < this->dlcs.size(); ++i) for (unsigned int i = 0; i < this->dlcs.size(); ++i)
{ {
for (unsigned int j = 0; j < this->dlcs[i].installers.size(); ++j) for (unsigned int j = 0; j < this->dlcs[i].installers.size(); ++j)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->dlcs[i].installers[j].path, this->gamename, config.bSubDirectories ? "dlc/" + this->dlcs[i].gamename : ""); subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sInstallersSubdir : "";
filepath = Util::makeFilepath(directory, this->dlcs[i].installers[j].path, this->gamename, subdir, this->dlcs[i].installers[j].platform, this->dlcs[i].gamename);
this->dlcs[i].installers[j].setFilepath(filepath); this->dlcs[i].installers[j].setFilepath(filepath);
} }
for (unsigned int j = 0; j < this->dlcs[i].patches.size(); ++j) for (unsigned int j = 0; j < this->dlcs[i].patches.size(); ++j)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->dlcs[i].patches[j].path, this->gamename, config.bSubDirectories ? "dlc/" + this->dlcs[i].gamename + "/patches" : ""); subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sPatchesSubdir : "";
filepath = Util::makeFilepath(directory, this->dlcs[i].patches[j].path, this->gamename, subdir, this->dlcs[i].patches[j].platform, this->dlcs[i].gamename);
this->dlcs[i].patches[j].setFilepath(filepath); this->dlcs[i].patches[j].setFilepath(filepath);
} }
for (unsigned int j = 0; j < this->dlcs[i].extras.size(); ++j) for (unsigned int j = 0; j < this->dlcs[i].extras.size(); ++j)
{ {
filepath = Util::makeFilepath(config.sDirectory, this->dlcs[i].extras[j].path, this->gamename, config.bSubDirectories ? "dlc/" + this->dlcs[i].gamename + "/extras" : ""); subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sExtrasSubdir : "";
filepath = Util::makeFilepath(directory, this->dlcs[i].extras[j].path, this->gamename, subdir, 0, this->dlcs[i].gamename);
this->dlcs[i].extras[j].setFilepath(filepath); this->dlcs[i].extras[j].setFilepath(filepath);
} }
} }

View File

@ -7,6 +7,7 @@
#include "util.h" #include "util.h"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <tinyxml.h> #include <tinyxml.h>
#include <jsoncpp/json/json.h> #include <jsoncpp/json/json.h>
#include <fstream> #include <fstream>
@ -16,9 +17,11 @@
Remove the leading slash from path if needed Remove the leading slash from path if needed
Use gamename as base directory if specified Use gamename as base directory if specified
*/ */
std::string Util::makeFilepath(const std::string& directory, const std::string& path, const std::string& gamename, std::string subdirectory) std::string Util::makeFilepath(const std::string& directory, const std::string& path, const std::string& gamename, std::string subdirectory, const unsigned int& platformId, const std::string& dlcname)
{ {
return directory + makeRelativeFilepath(path, gamename, subdirectory); std::string dir = directory + makeRelativeFilepath(path, gamename, subdirectory);
Util::filepathReplaceReservedStrings(dir, gamename, platformId, dlcname);
return dir;
} }
/* Create filepath relative to download base directory specified in config. /* Create filepath relative to download base directory specified in config.
@ -46,7 +49,7 @@ std::string Util::makeRelativeFilepath(const std::string& path, const std::strin
{ {
subdirectory = "/" + subdirectory; subdirectory = "/" + subdirectory;
} }
filepath = gamename + subdirectory + "/" + filename; filepath = subdirectory + "/" + filename;
} }
return filepath; return filepath;
@ -275,3 +278,32 @@ int Util::getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf,
return res; return res;
} }
int Util::replaceString(std::string& str, const std::string& to_replace, const std::string& replace_with)
{
size_t pos = str.find(to_replace);
if (pos == std::string::npos)
{
return 0;
}
str.replace(str.begin()+pos, str.begin()+pos+to_replace.length(), replace_with);
return 1;
}
void Util::filepathReplaceReservedStrings(std::string& str, const std::string& gamename, const unsigned int& platformId, const std::string& dlcname)
{
std::string platform;
while (Util::replaceString(str, "%gamename%", gamename));
while (Util::replaceString(str, "%dlcname%", dlcname));
for (unsigned int i = 0; i < GlobalConstants::PLATFORMS.size(); ++i)
{
if ((platformId & GlobalConstants::PLATFORMS[i].platformId) == GlobalConstants::PLATFORMS[i].platformId)
{
platform = boost::algorithm::to_lower_copy(GlobalConstants::PLATFORMS[i].platformString);
break;
}
}
while (Util::replaceString(str, "%platform%", platform));
while (Util::replaceString(str, "//", "/")); // Replace any double slashes with single slash
}