#include "../include/Storage.hpp" #include "../include/env.hpp" #include "../include/StringUtils.hpp" #include #include #include #include #include #include #include #include #if OS_FAMILY == OS_FAMILY_POSIX #include #include #include #include #include #endif static int makedir(const char *path) { return mkdir(path, S_IRWXU); } namespace QuickMedia { Path get_home_dir() { #if OS_FAMILY == OS_FAMILY_POSIX const char *homeDir = getenv("HOME"); if(!homeDir) { passwd *pw = getpwuid(getuid()); homeDir = pw->pw_dir; } if(!homeDir) { fprintf(stderr, "Failed to get home directory of user!\n"); abort(); } return 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)) throw std::runtime_error("Failed to open process token"); if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen)) { CloseHandle(hToken); throw std::runtime_error("Failed to get home directory"); } CloseHandle(hToken); homeDir.resize(wcslen(homeDir.c_str())); return boost::filesystem::path(homeDir); #endif } Path get_storage_dir() { return get_home_dir().join(".config").join("quickmedia"); } Path get_cache_dir() { return get_home_dir().join(".cache").join("quickmedia"); } int get_cookies_filepath(Path &path, const std::string &plugin_name) { Path cookies_dir = get_storage_dir().join("cookies"); int res = create_directory_recursive(cookies_dir); if(res != 0) return res; path = cookies_dir; path.join(plugin_name).append(".txt"); return 0; } int create_directory_recursive(const Path &path) { size_t index = 0; while(true) { index = path.data.find('/', index); // Skips first '/', we don't want to try and create the root directory if(index == 0) { ++index; continue; } std::string path_component = path.data.substr(0, index); int err = makedir(path_component.c_str()); if(err == -1 && errno != EEXIST) return err; if(index == std::string::npos) break; else ++index; } return 0; } FileType get_file_type(const Path &path) { struct stat file_stat; if(stat(path.data.c_str(), &file_stat) == 0) return S_ISREG(file_stat.st_mode) ? FileType::REGULAR : FileType::DIRECTORY; return FileType::FILE_NOT_FOUND; } int file_get_content(const Path &path, std::string &result) { FILE *file = fopen(path.data.c_str(), "rb"); if(!file) return -errno; fseek(file, 0, SEEK_END); long file_size = ftell(file); if(file_size == -1) { fprintf(stderr, "Error: attempted to read directory %s as a file\n", path.data.c_str()); fclose(file); return -1; } fseek(file, 0, SEEK_SET); result.resize(file_size); fread(&result[0], 1, file_size, file); fclose(file); return 0; } int file_get_size(const Path &path, size_t *size) { struct stat file_stat; if(stat(path.data.c_str(), &file_stat) == 0 && S_ISREG(file_stat.st_mode)) { *size = file_stat.st_size; return 0; } *size = 0; return -1; } static int file_overwrite(const Path &path, const char *str, size_t size) { FILE *file = fopen(path.data.c_str(), "wb"); if(!file) return -1; if(fwrite(str, 1, size, file) != size) { fclose(file); return -1; } return fclose(file); } int file_overwrite(const Path &path, const std::string &data) { return file_overwrite(path, data.c_str(), data.size()); } void for_files_in_dir(const Path &path, FileIteratorCallback callback) { try { for(auto &p : std::filesystem::directory_iterator(path.data)) { if(!callback(p.path())) break; } } catch(const std::filesystem::filesystem_error &err) { fprintf(stderr, "Failed to list files in directory %s, error: %s\n", path.data.c_str(), err.what()); return; } } static std::filesystem::file_time_type file_get_filetime_or(const std::filesystem::directory_entry &path, std::filesystem::file_time_type default_value) { try { return path.last_write_time(); } catch(const std::filesystem::filesystem_error &err) { return default_value; } } void for_files_in_dir_sort_last_modified(const Path &path, FileIteratorCallback callback) { std::vector paths; try { for(auto &p : std::filesystem::directory_iterator(path.data)) { paths.push_back(p); } } catch(const std::filesystem::filesystem_error &err) { fprintf(stderr, "Failed to list files in directory %s, error: %s\n", path.data.c_str(), err.what()); return; } std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) { return file_get_filetime_or(path1, std::filesystem::file_time_type::min()) > file_get_filetime_or(path2, std::filesystem::file_time_type::min()); }); for(auto &p : paths) { if(!callback(p.path())) break; } } bool read_file_as_json(const Path &filepath, Json::Value &result) { std::string file_content; if(file_get_content(filepath, file_content) != 0) { fprintf(stderr, "Failed to get content of file: %s\n", filepath.data.c_str()); return false; } Json::CharReaderBuilder json_builder; std::unique_ptr json_reader(json_builder.newCharReader()); std::string json_errors; if(!json_reader->parse(file_content.data(), file_content.data() + file_content.size(), &result, &json_errors)) { fprintf(stderr, "Failed to read file %s as json, error: %s\n", filepath.data.c_str(), json_errors.c_str()); return false; } return true; } bool save_json_to_file_atomic(const Path &path, const Json::Value &json) { Path tmp_path = path; tmp_path.append(".tmp"); Json::StreamWriterBuilder json_builder; if(file_overwrite(tmp_path, Json::writeString(json_builder, json)) != 0) return false; // Rename is atomic under posix! if(rename(tmp_path.data.c_str(), path.data.c_str()) != 0) { perror("save_json_to_file_atomic rename"); return false; } return true; } bool save_json_to_file_atomic(const Path &path, const rapidjson::Value &json) { Path tmp_path = path; tmp_path.append(".tmp"); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); json.Accept(writer); Json::StreamWriterBuilder json_builder; if(file_overwrite(tmp_path, buffer.GetString(), buffer.GetSize()) != 0) return false; // Rename is atomic under posix! if(rename(tmp_path.data.c_str(), path.data.c_str()) != 0) { perror("save_json_to_file_atomic rename"); return false; } return true; } bool is_program_executable_by_name(const char *name) { // TODO: Implement for Windows. Windows also uses semicolon instead of colon as a separator char *env = getenv("PATH"); std::unordered_set paths; string_split(env, ':', [&paths](const char *str, size_t size) { paths.insert(std::string(str, size)); return true; }); for(const std::string &path_str : paths) { Path path(path_str); path.join(name); if(get_file_type(path) == FileType::REGULAR) return true; } return false; } }