#include "../include/Cache.hpp" #include "../include/env.hpp" #include "../include/ResourceCache.hpp" #include #include #include #include #if OS_FAMILY == OS_FAMILY_POSIX #include #else #include #endif using namespace std; using namespace TinyProcessLib; namespace dchat { unordered_map imageUrlCache; boost::filesystem::path getHomeDir() { #if OS_FAMILY == OS_FAMILY_POSIX const char *homeDir = getenv("HOME"); if(!homeDir) { passwd *pw = getpwuid(getuid()); homeDir = pw->pw_dir; } return boost::filesystem::path(homeDir); #elif OS_FAMILY == OS_FAMILY_WINDOWS BOOL ret; HANDLE hToken; std::wstring homeDir; DWORD homeDirLen = MAX_PATH; homeDir.resize(homeDirLen); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) return Result::Err("Failed to open process token"); if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen)) { CloseHandle(hToken); return Result::Err("Failed to get home directory"); } CloseHandle(hToken); homeDir.resize(wcslen(homeDir.c_str())); return boost::filesystem::path(homeDir); #endif } boost::filesystem::path Cache::getDchatDir() { boost::filesystem::path dchatHomeDir = getHomeDir() / ".dchat"; boost::filesystem::create_directories(dchatHomeDir); return dchatHomeDir; } Cache::Cache() { downloadWaitThread = thread([this] { while(true) { imageDownloadMutex.lock(); for(vector::iterator it = imageDownloadProcesses.begin(); it != imageDownloadProcesses.end();) { int exitStatus; if(it->process->try_get_exit_status(exitStatus)) { bool failed = exitStatus != 0; if(!failed) { boost::filesystem::path filepath = getDchatDir(); odhtdb::Hash urlHash(it->url.data(), it->url.size()); filepath /= urlHash.toString(); try { const sf::Texture *texture = ResourceCache::getTexture(filepath.string()); ImageByUrlResult &imageByUrlResult = imageUrlCache[it->url]; imageByUrlResult.texture = texture; imageByUrlResult.type = ImageByUrlResult::Type::CACHED; } catch(FailedToLoadResourceException &e) { fprintf(stderr, "%s\n", e.what()); failed = true; } } if(failed) { imageUrlCache[it->url].type = ImageByUrlResult::Type::FAILED_DOWNLOAD; fprintf(stderr, "Image download failed for url: %s\n", it->url.c_str()); } it = imageDownloadProcesses.erase(it); } else ++it; } imageDownloadMutex.unlock(); while(imageDownloadProcesses.empty()) this_thread::sleep_for(chrono::milliseconds(20)); } }); downloadWaitThread.detach(); } const ImageByUrlResult Cache::getImageByUrl(const string &url, int downloadLimitBytes) { auto it = imageUrlCache.find(url); if(it != imageUrlCache.end()) return it->second; // TODO: Verify hashed url is not too long for filepath on windows boost::filesystem::path filepath = getDchatDir(); odhtdb::Hash urlHash(url.data(), url.size()); filepath /= urlHash.toString(); // Check if file exists because we dont want sfml spam with "Failed to load image""... if(boost::filesystem::exists(filepath)) { try { const sf::Texture *texture = ResourceCache::getTexture(filepath.string()); lock_guard lock(imageDownloadMutex); ImageByUrlResult result { texture, ImageByUrlResult::Type::CACHED }; imageUrlCache[url] = result; return result; } catch(FailedToLoadResourceException &e) { } } lock_guard lock(imageDownloadMutex); ImageByUrlResult result { nullptr, ImageByUrlResult::Type::DOWNLOADING }; imageUrlCache[url] = result; string downloadLimitBytesStr = to_string(downloadLimitBytes); Process::string_type cmd = "curl -L --silent -o '"; cmd += filepath.native(); cmd += "' --max-filesize " + downloadLimitBytesStr + " --range 0-" + downloadLimitBytesStr + " --url '" + url + "'"; // certutil.exe -urlcache -split -f "https://url/to/file" path/and/name/to/save/as/file Process *process = new Process(cmd, "", nullptr, nullptr, false); ImageDownloadInfo imageDownloadInfo { process, url }; imageDownloadProcesses.emplace_back(imageDownloadInfo); return result; } }