/* This program is free software. It comes without any warranty, to * the extent permitted by applicable law. You can redistribute it * and/or modify it under the terms of the Do What The Fuck You Want * To Public License, Version 2, as published by Sam Hocevar. See * http://www.wtfpl.net/ for more details. */ #include "util.h" #include #include #include #include #include #include #include #include #include #include /* Create filepath from specified directory and path Remove the leading slash from path if needed 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, const unsigned int& platformId, const std::string& dlcname) { 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. */ std::string Util::makeRelativeFilepath(const std::string& path, const std::string& gamename, std::string subdirectory) { std::string filepath; if (gamename.empty()) { if (path.at(0)=='/') { std::string tmp_path = path.substr(1,path.length()); filepath = tmp_path; } else { filepath = path; } } else { std::string filename = path.substr(path.find_last_of("/")+1, path.length()); if (!subdirectory.empty()) { subdirectory = "/" + subdirectory; } filepath = subdirectory + "/" + filename; } return filepath; } std::string Util::getFileHash(const std::string& filename, unsigned hash_id) { unsigned char digest[rhash_get_digest_size(hash_id)]; char result[rhash_get_hash_length(hash_id) + 1]; int i = rhash_file(hash_id, filename.c_str(), digest); if (i < 0) std::cerr << "LibRHash error: " << strerror(errno) << std::endl; else rhash_print_bytes(result, digest, rhash_get_digest_size(hash_id), RHPR_HEX); return result; } std::string Util::getFileHashRange(const std::string& filepath, unsigned hash_id, off_t range_start, off_t range_end) { char result[rhash_get_hash_length(hash_id) + 1]; if (!boost::filesystem::exists(filepath)) return result; off_t filesize = boost::filesystem::file_size(filepath); if (range_end == 0 || range_end > filesize) range_end = filesize; if (range_end < range_start) { off_t tmp = range_start; range_start = range_end; range_end = tmp; } off_t chunk_size = 10 << 20; // 10MB off_t rangesize = range_end - range_start; off_t remaining = rangesize % chunk_size; int chunks = (remaining == 0) ? rangesize/chunk_size : (rangesize/chunk_size)+1; rhash rhash_context; rhash_context = rhash_init(hash_id); FILE *infile = fopen(filepath.c_str(), "r"); for (int i = 0; i < chunks; i++) { off_t chunk_begin = range_start + i*chunk_size; fseek(infile, chunk_begin, SEEK_SET); if ((i == chunks-1) && (remaining != 0)) chunk_size = remaining; unsigned char *chunk = (unsigned char *) malloc(chunk_size * sizeof(unsigned char *)); if (chunk == NULL) { std::cerr << "Memory error" << std::endl; fclose(infile); return result; } off_t size = fread(chunk, 1, chunk_size, infile); if (size != chunk_size) { std::cerr << "Read error" << std::endl; free(chunk); fclose(infile); return result; } rhash_update(rhash_context, chunk, chunk_size); free(chunk); } fclose(infile); rhash_final(rhash_context, NULL); rhash_print(result, rhash_context, hash_id, RHPR_HEX); rhash_free(rhash_context); return result; } std::string Util::getChunkHash(unsigned char *chunk, uintmax_t chunk_size, unsigned hash_id) { unsigned char digest[rhash_get_digest_size(hash_id)]; char result[rhash_get_hash_length(hash_id) + 1]; int i = rhash_msg(hash_id, chunk, chunk_size, digest); if (i < 0) std::cerr << "LibRHash error: " << strerror(errno) << std::endl; else rhash_print_bytes(result, digest, rhash_get_digest_size(hash_id), RHPR_HEX); return result; } // Create GOG XML int Util::createXML(std::string filepath, uintmax_t chunk_size, std::string xml_dir) { int res = 0; FILE *infile; FILE *xmlfile; uintmax_t filesize, size; int chunks, i; if (xml_dir.empty()) { xml_dir = Util::getCacheHome() + "/lgogdownloader/xml"; } // Make sure directory exists boost::filesystem::path path = xml_dir; if (!boost::filesystem::exists(path)) { if (!boost::filesystem::create_directories(path)) { std::cerr << "Failed to create directory: " << path << std::endl; return res; } } if ((infile=fopen(filepath.c_str(), "r"))!=NULL) { //File exists fseek(infile, 0, SEEK_END); filesize = ftell(infile); rewind(infile); } else { std::cerr << filepath << " doesn't exist" << std::endl; return res; } // Get filename boost::filesystem::path pathname = filepath; std::string filename = pathname.filename().string(); std::string filenameXML = xml_dir + "/" + filename + ".xml"; std::cout << filename << std::endl; //Determine number of chunks int remaining = filesize % chunk_size; chunks = (remaining == 0) ? filesize/chunk_size : (filesize/chunk_size)+1; std::cout << "Filesize: " << filesize << " bytes" << std::endl << "Chunks: " << chunks << std::endl << "Chunk size: " << (chunk_size >> 20) << " MB" << std::endl; tinyxml2::XMLDocument xml; tinyxml2::XMLElement *fileElem = xml.NewElement("file"); fileElem->SetAttribute("name", filename.c_str()); fileElem->SetAttribute("chunks", chunks); fileElem->SetAttribute("total_size", std::to_string(filesize).c_str()); std::cout << "Getting MD5 for chunks" << std::endl; rhash rhash_context; rhash_context = rhash_init(RHASH_MD5); if(!rhash_context) { std::cerr << "error: couldn't initialize rhash context" << std::endl; fclose(infile); return res; } char rhash_result[rhash_get_hash_length(RHASH_MD5) + 1]; for (i = 0; i < chunks; i++) { uintmax_t range_begin = i*chunk_size; fseek(infile, range_begin, SEEK_SET); if ((i == chunks-1) && (remaining != 0)) chunk_size = remaining; uintmax_t range_end = range_begin + chunk_size - 1; unsigned char *chunk = (unsigned char *) malloc(chunk_size * sizeof(unsigned char *)); if (chunk == NULL) { std::cerr << "Memory error" << std::endl; fclose(infile); return res; } size = fread(chunk, 1, chunk_size, infile); if (size != chunk_size) { std::cerr << "Read error" << std::endl; free(chunk); fclose(infile); return res; } std::string hash = Util::getChunkHash(chunk, chunk_size, RHASH_MD5); rhash_update(rhash_context, chunk, chunk_size); // Update hash for the whole file free(chunk); tinyxml2::XMLElement *chunkElem = xml.NewElement("chunk"); chunkElem->SetAttribute("id", i); chunkElem->SetAttribute("from", std::to_string(range_begin).c_str()); chunkElem->SetAttribute("to", std::to_string(range_end).c_str()); chunkElem->SetAttribute("method", "md5"); tinyxml2::XMLText *text = xml.NewText(hash.c_str()); chunkElem->LinkEndChild(text); fileElem->LinkEndChild(chunkElem); std::cout << "Chunks hashed " << (i+1) << " / " << chunks << "\r" << std::flush; } fclose(infile); rhash_final(rhash_context, NULL); rhash_print(rhash_result, rhash_context, RHASH_MD5, RHPR_HEX); rhash_free(rhash_context); std::cout << std::endl << "MD5: " << rhash_result << std::endl; fileElem->SetAttribute("md5", rhash_result); xml.LinkEndChild(fileElem); std::cout << "Writing XML: " << filenameXML << std::endl; if ((xmlfile=fopen(filenameXML.c_str(), "w"))!=NULL) { tinyxml2::XMLPrinter printer(xmlfile); xml.Print(&printer); fclose(xmlfile); res = 1; } else { std::cerr << "Can't create " << filenameXML << std::endl; return res; } return res; } /* Overrides global settings with game specific settings returns 0 if fails returns number of changed settings if succesful */ int Util::getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf, std::string directory) { int res = 0; if (directory.empty()) { directory = Util::getConfigHome() + "/lgogdownloader/gamespecific"; } std::string filepath = directory + "/" + gamename + ".conf"; // Make sure file exists boost::filesystem::path path = filepath; if (!boost::filesystem::exists(path)) { return res; } std::ifstream json(filepath, std::ifstream::binary); Json::Value root; try { json >> root; } catch (const Json::Exception& exc) { std::cerr << "Failed to parse game specific config " << filepath << std::endl; std::cerr << exc.what() << std::endl; return res; } if (root.isMember("language")) { if (root["language"].isInt()) conf->dlConf.iInstallerLanguage = root["language"].asUInt(); else { Util::parseOptionString(root["language"].asString(), conf->dlConf.vLanguagePriority, conf->dlConf.iInstallerLanguage, GlobalConstants::LANGUAGES); } res++; } if (root.isMember("platform")) { if (root["platform"].isInt()) conf->dlConf.iInstallerPlatform = root["platform"].asUInt(); else { Util::parseOptionString(root["platform"].asString(), conf->dlConf.vPlatformPriority, conf->dlConf.iInstallerPlatform, GlobalConstants::PLATFORMS); } res++; } // Warn about deprecated option if (root.isMember("dlc")) { std::cerr << filepath << " contains deprecated option \"dlc\" which will be ignored, use \"include\" instead" << std::endl; } if (root.isMember("include")) { conf->dlConf.iInclude = 0; std::vector vInclude = Util::tokenize(root["include"].asString(), ","); for (std::vector::iterator it = vInclude.begin(); it != vInclude.end(); it++) { conf->dlConf.iInclude |= Util::getOptionValue(*it, GlobalConstants::INCLUDE_OPTIONS); } res++; } if (root.isMember("ignore-dlc-count")) { conf->dlConf.bIgnoreDLCCount = root["ignore-dlc-count"].asBool(); res++; } if (root.isMember("subdirectories")) { conf->dirConf.bSubDirectories = root["subdirectories"].asBool(); res++; } if (root.isMember("directory")) { conf->dirConf.sDirectory = root["directory"].asString(); res++; } if (root.isMember("subdir-game")) { conf->dirConf.sGameSubdir = root["subdir-game"].asString(); res++; } if (root.isMember("subdir-installers")) { conf->dirConf.sInstallersSubdir = root["subdir-installers"].asString(); res++; } if (root.isMember("subdir-extras")) { conf->dirConf.sExtrasSubdir = root["subdir-extras"].asString(); res++; } if (root.isMember("subdir-patches")) { conf->dirConf.sPatchesSubdir = root["subdir-patches"].asString(); res++; } if (root.isMember("subdir-language-packs")) { conf->dirConf.sLanguagePackSubdir = root["subdir-language-packs"].asString(); res++; } if (root.isMember("subdir-dlc")) { conf->dirConf.sDLCSubdir = root["subdir-dlc"].asString(); 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; } int Util::replaceAllString(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; } do { str.replace(str.begin()+pos, str.begin()+pos+to_replace.length(), replace_with); pos = str.find(to_replace, pos + to_replace.length()); } while(pos != std::string::npos); return 1; } void Util::filepathReplaceReservedStrings(std::string& str, const std::string& gamename, const unsigned int& platformId, const std::string& dlcname) { std::string platform; for (unsigned int i = 0; i < GlobalConstants::PLATFORMS.size(); ++i) { if ((platformId & GlobalConstants::PLATFORMS[i].id) == GlobalConstants::PLATFORMS[i].id) { platform = boost::algorithm::to_lower_copy(GlobalConstants::PLATFORMS[i].str); break; } } if (platform.empty()) { if (str.find("%gamename%/%platform%") != std::string::npos) platform = ""; else platform = "no_platform"; } // Don't save certain files in "no_platform" folder if ( str.rfind("/icon.png") != std::string::npos || str.rfind("/logo.jpg") != std::string::npos || str.rfind("/product.json") != std::string::npos ) platform = ""; std::string gamename_firstletter; if (!gamename.empty()) { if (std::isdigit(gamename.front())) gamename_firstletter = "0"; else gamename_firstletter = gamename.front(); } if (str.find("%gamename_transformed%") != std::string::npos || str.find("%gamename_transformed_firstletter%") != std::string::npos) { std::string gamename_transformed = transformGamename(gamename); std::string gamename_transformed_firstletter; if (!gamename_transformed.empty()) { if (std::isdigit(gamename_transformed.front())) gamename_transformed_firstletter = "0"; else gamename_transformed_firstletter = gamename_transformed.front(); } while (Util::replaceString(str, "%gamename_transformed%", gamename_transformed)); while (Util::replaceString(str, "%gamename_transformed_firstletter%", gamename_transformed_firstletter)); } while (Util::replaceString(str, "%gamename_firstletter%", gamename_firstletter)); while (Util::replaceString(str, "%gamename%", gamename)); while (Util::replaceString(str, "%dlcname%", dlcname)); while (Util::replaceString(str, "%platform%", platform)); while (Util::replaceString(str, "//", "/")); // Replace any double slashes with single slash } void Util::setFilePermissions(const boost::filesystem::path& path, const boost::filesystem::perms& permissions) { if (boost::filesystem::exists(path)) { if (boost::filesystem::is_regular_file(path)) { boost::filesystem::file_status s = boost::filesystem::status(path); if (s.permissions() != permissions) { boost::system::error_code ec; boost::filesystem::permissions(path, permissions, ec); if (ec) { std::cerr << "Failed to set file permissions for " << path.string() << std::endl; } } } } } int Util::getTerminalWidth() { int width; if(isatty(STDOUT_FILENO)) { struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); width = static_cast(w.ws_col); } else width = 10000;//Something sufficiently big return width; } void Util::getManualUrlsFromJSON(const Json::Value &root, std::vector &urls) { if(root.size() > 0) { for(Json::ValueConstIterator it = root.begin() ; it != root.end() ; ++it) { if (it.key() == "manualUrl") { Json::Value url = *it; urls.push_back(url.asString()); } else getManualUrlsFromJSON(*it, urls); } } return; } std::vector Util::getDLCNamesFromJSON(const Json::Value &root) { std::vector urls, dlcnames; getManualUrlsFromJSON(root, urls); for (unsigned int i = 0; i < urls.size(); ++i) { std::string gamename; std::string url_prefix = "/downloads/"; gamename.assign(urls[i].begin()+urls[i].find(url_prefix)+url_prefix.length(), urls[i].begin()+urls[i].find_last_of("/")); bool bDuplicate = false; for (unsigned int j = 0; j < dlcnames.size(); ++j) { if (gamename == dlcnames[j]) { bDuplicate = true; break; } } if (!bDuplicate) dlcnames.push_back(gamename); } return dlcnames; } std::string Util::getHomeDir() { return (std::string)getenv("HOME"); } std::string Util::getConfigHome() { std::string configHome; char *xdgconfig = getenv("XDG_CONFIG_HOME"); if (xdgconfig) configHome = (std::string)xdgconfig; else configHome = Util::getHomeDir() + "/.config"; return configHome; } std::string Util::getCacheHome() { std::string cacheHome; char *xdgcache = getenv("XDG_CACHE_HOME"); if (xdgcache) cacheHome = (std::string)xdgcache; else cacheHome = Util::getHomeDir() + "/.cache"; return cacheHome; } std::vector Util::tokenize(const std::string& str, const std::string& separator) { std::vector tokens; std::string token; size_t idx = 0, found; while ((found = str.find(separator, idx)) != std::string::npos) { token = str.substr(idx, found - idx); if (!token.empty()) tokens.push_back(token); idx = found + separator.length(); } token = str.substr(idx); if (!token.empty()) tokens.push_back(token); return tokens; } unsigned int Util::getOptionValue(const std::string& str, const std::vector& options, const bool& bAllowStringToIntConversion) { unsigned int value = 0; boost::regex expression("^[+-]?\\d+$", boost::regex::perl); boost::match_results what; if (str == "all") { for (unsigned int i = 0; i < options.size(); ++i) value |= options[i].id; } else if (boost::regex_search(str, what, expression) && bAllowStringToIntConversion) { value = std::stoi(str); } else { for (unsigned int i = 0; i < options.size(); ++i) { if (!options[i].regexp.empty()) { boost::regex expr("^(" + options[i].regexp + ")$", boost::regex::perl | boost::regex::icase); if (boost::regex_search(str, what, expr)) { value = options[i].id; break; } } if (str == options[i].code) { value = options[i].id; break; } } } return value; } std::string Util::getOptionNameString(const unsigned int& value, const std::vector& options) { std::string str; for (unsigned int i = 0; i < options.size(); ++i) { if ((value & options[i].id) == options[i].id) str += (str.empty() ? "" : ", ")+options[i].str; } return str; } // Parse the options string void Util::parseOptionString(const std::string &option_string, std::vector &priority, unsigned int &type, const std::vector& options) { type = 0; priority.clear(); std::vector tokens_priority = Util::tokenize(option_string, ","); for (std::vector::iterator it_priority = tokens_priority.begin(); it_priority != tokens_priority.end(); it_priority++) { unsigned int value = 0; std::vector tokens_value = Util::tokenize(*it_priority, "+"); for (std::vector::iterator it_value = tokens_value.begin(); it_value != tokens_value.end(); it_value++) { value |= Util::getOptionValue(*it_value, options); } priority.push_back(value); type |= value; } } std::string Util::getLocalFileHash(const std::string& xml_dir, const std::string& filepath, const std::string& gamename, const bool& useFastCheck) { std::string localHash; boost::filesystem::path path = filepath; boost::filesystem::path local_xml_file; if (!gamename.empty()) local_xml_file = xml_dir + "/" + gamename + "/" + path.filename().string() + ".xml"; else local_xml_file = xml_dir + "/" + path.filename().string() + ".xml"; if (boost::filesystem::exists(local_xml_file) && useFastCheck) { tinyxml2::XMLDocument local_xml; local_xml.LoadFile(local_xml_file.string().c_str()); tinyxml2::XMLElement *fileElem = local_xml.FirstChildElement("file"); if (fileElem) { localHash = fileElem->Attribute("md5"); } } else if (boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path)) { localHash = Util::getFileHash(path.string(), RHASH_MD5); } return localHash; } void Util::shortenStringToTerminalWidth(std::string& str) { int iStrLen = static_cast(str.length()); int iTermWidth = Util::getTerminalWidth(); if (iStrLen >= iTermWidth) { size_t chars_to_remove = (iStrLen - iTermWidth) + 4; size_t middle = iStrLen / 2; size_t pos1 = middle - (chars_to_remove / 2); size_t pos2 = middle + (chars_to_remove / 2); str.replace(str.begin()+pos1, str.begin()+pos2, "..."); } } std::string Util::getJsonUIntValueAsString(const Json::Value& json_value) { std::string value; try { value = json_value.asString(); } catch (...) { try { uintmax_t value_uint = json_value.asLargestUInt(); value = std::to_string(value_uint); } catch (...) { value = ""; } } return value; } std::string Util::getStrippedString(std::string str) { str.erase( std::remove_if(str.begin(), str.end(), [](unsigned char c) { bool bIsValid = false; bIsValid = (std::isspace(c) && std::isprint(c)) || std::isalnum(c); std::vector validChars = { '-', '_', '.', '(', ')', '[', ']', '{', '}' }; if (std::any_of(validChars.begin(), validChars.end(), [c](unsigned char x){return x == c;})) { bIsValid = true; } return !bIsValid; } ), str.end() ); return str; } std::string Util::makeEtaString(const unsigned long long& iBytesRemaining, const double& dlRate) { boost::posix_time::time_duration duration(boost::posix_time::seconds((long)(iBytesRemaining / dlRate))); return Util::makeEtaString(duration); } std::string Util::makeEtaString(const boost::posix_time::time_duration& duration) { std::string etastr; std::stringstream eta_ss; if (duration.hours() > 23) { eta_ss << duration.hours() / 24 << "d " << std::setfill('0') << std::setw(2) << duration.hours() % 24 << "h " << std::setfill('0') << std::setw(2) << duration.minutes() << "m " << std::setfill('0') << std::setw(2) << duration.seconds() << "s"; } else if (duration.hours() > 0) { eta_ss << duration.hours() << "h " << std::setfill('0') << std::setw(2) << duration.minutes() << "m " << std::setfill('0') << std::setw(2) << duration.seconds() << "s"; } else if (duration.minutes() > 0) { eta_ss << duration.minutes() << "m " << std::setfill('0') << std::setw(2) << duration.seconds() << "s"; } else { eta_ss << duration.seconds() << "s"; } etastr = eta_ss.str(); return etastr; } void Util::CurlHandleSetDefaultOptions(CURL* curlhandle, const CurlConfig& conf) { curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, conf.sUserAgent.c_str()); curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, conf.iTimeout); curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true); curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, conf.sCookiePath.c_str()); curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, conf.bVerifyPeer); curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, conf.bVerbose); curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, conf.iDownloadRate); // Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, conf.iLowSpeedTimeout); curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, conf.iLowSpeedTimeoutRate); if (!conf.sCACertPath.empty()) curl_easy_setopt(curlhandle, CURLOPT_CAINFO, conf.sCACertPath.c_str()); if (!conf.sInterface.empty()) { curl_easy_setopt(curlhandle, CURLOPT_DNS_INTERFACE, conf.sInterface.c_str()); curl_easy_setopt(curlhandle, CURLOPT_INTERFACE, conf.sInterface.c_str()); } } std::string Util::CurlHandleGetInfoString(CURL* curlhandle, CURLINFO info) { char* str; return (curl_easy_getinfo(curlhandle, info, &str) == CURLE_OK) ? str : ""; } CURLcode Util::CurlGetResponse(const std::string& url, std::string& response, int max_retries) { CURLcode result; CURL *handle = curl_easy_init(); Util::CurlHandleSetDefaultOptions(handle, Globals::globalConfig.curlConf); curl_easy_setopt(handle, CURLOPT_URL, url.c_str()); result = Util::CurlHandleGetResponse(handle, response, max_retries); curl_easy_cleanup(handle); return result; } CURLcode Util::CurlHandleGetResponse(CURL* curlhandle, std::string& response, int max_retries) { CURLcode result; int retries = 0; std::ostringstream memory; bool bShouldRetry = false; long int response_code = 0; if (max_retries < 0) max_retries = Globals::globalConfig.iRetries; curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Util::CurlWriteMemoryCallback); curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory); do { if (bShouldRetry) retries++; if (Globals::globalConfig.iWait > 0) usleep(Globals::globalConfig.iWait); // Delay the request by specified time result = curl_easy_perform(curlhandle); response = memory.str(); memory.str(std::string()); switch (result) { // Retry on these errors case CURLE_PARTIAL_FILE: case CURLE_OPERATION_TIMEDOUT: case CURLE_RECV_ERROR: bShouldRetry = true; break; // Retry on CURLE_HTTP_RETURNED_ERROR if response code is not "404 Not Found" case CURLE_HTTP_RETURNED_ERROR: curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code); if (response_code == 404 || response_code == 403) { bShouldRetry = false; } else bShouldRetry = true; break; default: bShouldRetry = false; break; } if (retries >= max_retries) bShouldRetry = false; } while (bShouldRetry); return result; } curl_off_t Util::CurlWriteMemoryCallback(char *ptr, curl_off_t size, curl_off_t nmemb, void *userp) { std::ostringstream *stream = (std::ostringstream*)userp; std::streamsize count = (std::streamsize) size * nmemb; stream->write(ptr, count); return count; } curl_off_t Util::CurlWriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp) { curl_off_t realsize = size * nmemb; ChunkMemoryStruct *mem = (ChunkMemoryStruct *)userp; mem->memory = (char *) realloc(mem->memory, mem->size + realsize + 1); if(mem->memory == NULL) { std::cout << "Not enough memory (realloc returned NULL)" << std::endl; return 0; } memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; 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; } std::string Util::makeSizeString(const unsigned long long& iSizeInBytes) { auto units = { "B", "kB", "MB", "GB", "TB", "PB" }; std::string size_unit = "B"; double iSize = static_cast(iSizeInBytes); for (auto unit : units) { size_unit = unit; if (iSize < 1024) break; iSize /= 1024; } return formattedString("%0.2f %s", iSize, size_unit.c_str()); } Json::Value Util::readJsonFile(const std::string& path) { Json::Value json; std::ifstream ifs(path, std::ifstream::binary); if (ifs) { try { ifs >> json; } catch (const Json::Exception& exc) { std::cerr << "Failed to parse " << path << std::endl; std::cerr << exc.what() << std::endl; } ifs.close(); } else { std::cerr << "Failed to open " << path << std::endl; } return json; } std::string Util::transformGamename(const std::string& gamename) { std::string gamename_transformed = gamename; for (auto transformMatch : Globals::globalConfig.transformationsJSON.getMemberNames()) { boost::regex expression(transformMatch); boost::match_results what; if (boost::regex_search(gamename_transformed, what, expression)) { // Get list of exceptions std::vector vExceptions; if (Globals::globalConfig.transformationsJSON[transformMatch].isMember("exceptions")) { if (Globals::globalConfig.transformationsJSON[transformMatch]["exceptions"].isArray()) { for (auto exception : Globals::globalConfig.transformationsJSON[transformMatch]["exceptions"]) vExceptions.push_back(exception.asString()); } else { vExceptions.push_back(Globals::globalConfig.transformationsJSON[transformMatch]["exceptions"].asString()); } } // Skip if gamename matches exception if (std::any_of(vExceptions.begin(), vExceptions.end(), [gamename](std::string exception){return exception == gamename;})) continue; boost::regex transformRegex(Globals::globalConfig.transformationsJSON[transformMatch]["regex"].asString()); std::string transformReplacement = Globals::globalConfig.transformationsJSON[transformMatch]["replacement"].asString(); gamename_transformed = boost::regex_replace(gamename_transformed, transformRegex, transformReplacement); } } return gamename_transformed; } std::string Util::htmlToXhtml(const std::string& html) { std::string xhtml; TidyBuffer buffer; int rc = -1; TidyDoc doc = tidyCreate(); tidyBufInit(&buffer); tidyOptSetBool(doc, TidyXhtmlOut, yes); tidyOptSetBool(doc, TidyForceOutput, yes); tidyOptSetBool(doc, TidyShowInfo, no); tidyOptSetBool(doc, TidyShowWarnings, no); tidyOptSetInt(doc, TidyWrapLen, 0); rc = tidyParseString(doc, html.c_str()); if ( rc >= 0 ) rc = tidyCleanAndRepair(doc); if ( rc >= 0 ) rc = tidySaveBuffer(doc, &buffer); if (rc >= 0) { if (buffer.size > 0) xhtml = std::string((char*)buffer.bp, buffer.size); } else { std::cerr << "Severe error occured: " << std::string(strerror(rc)) << std::endl; } tidyBufFree(&buffer); tidyRelease(doc); return xhtml; } tinyxml2::XMLNode* Util::nextXMLNode(tinyxml2::XMLNode* node) { if (node->FirstChildElement()) // Has child element, go to first child node = node->FirstChildElement(); else if (node->NextSiblingElement()) // Has sibling element, go to first sibling node = node->NextSiblingElement(); else { // Go to parent node until it has sibling while(node->Parent() && !node->Parent()->NextSiblingElement()) node = node->Parent(); if(node->Parent() && node->Parent()->NextSiblingElement()) node = node->Parent()->NextSiblingElement(); else // Reached the end node = nullptr; } return node; }