#include "../include/Storage.hpp" #include "../include/StringUtils.hpp" #include "../include/Notification.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EINTR_RETRY(ret, expr) \ do { \ errno = 0; \ while(true) { \ ret = (expr); \ if(ret != -1 || errno != EINTR) \ break; \ } \ } while(0) static int makedir(const char *path) { return mkdir(path, S_IRWXU); } namespace QuickMedia { static FILE* fopen_eintr(const char *filename, const char *modes) { errno = 0; while(true) { FILE *f = fopen(filename, modes); if(f || errno != EINTR) return f; } } static size_t fwrite_eintr(const char *data, size_t size, FILE *f) { errno = 0; while(true) { const size_t r = fwrite(data, 1, size, f); if(r == size || errno != EINTR) return r; } } static size_t fread_eintr(char *data, size_t size, FILE *f) { errno = 0; while(true) { const size_t r = fread(data, 1, size, f); if(r == size || errno != EINTR) return r; } } static size_t fseek_eintr(FILE *f, long offset, int whence) { errno = 0; while(true) { const int r = fseek(f, offset, whence); if(r == 0 || errno != EINTR) return r; } } Path get_home_dir() { const char *homeDir = getenv("HOME"); if(!homeDir) { passwd *pw = getpwuid(getuid()); homeDir = pw->pw_dir; } if(!homeDir) { show_notification("QuickMedia", "Failed to get home directory of user", Urgency::CRITICAL); abort(); } return homeDir; } static Path xdg_config_home; static Path xdg_cache_home; Path get_storage_dir() { if(xdg_config_home.data.empty()) { const char *xdg_config_home_p = getenv("XDG_CONFIG_HOME"); if(xdg_config_home_p) xdg_config_home = xdg_config_home_p; else xdg_config_home = get_home_dir().join(".config"); } Path storage_dir = xdg_config_home; storage_dir.join("quickmedia"); return storage_dir; } Path get_cache_dir() { if(xdg_cache_home.data.empty()) { const char *xdg_cache_home_p = getenv("XDG_CACHE_HOME"); if(xdg_cache_home_p) xdg_cache_home = xdg_cache_home_p; else xdg_cache_home = get_home_dir().join(".cache"); } Path cache_dir = xdg_cache_home; cache_dir.join("quickmedia"); return cache_dir; } 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; memset(&file_stat, 0, sizeof(file_stat)); int ret; EINTR_RETRY(ret, stat(path.data.c_str(), &file_stat)); if(ret == 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_eintr(path.data.c_str(), "rb"); if(!file) return -errno; int fd = fileno(file); struct stat s; if(fstat(fd, &s) == -1 || !S_ISREG(s.st_mode)) { fclose(file); return -1; } fseek_eintr(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_eintr(file, 0, SEEK_SET); result.resize(file_size); if(fread_eintr(&result[0], file_size, file) != (size_t)file_size) { fclose(file); return -1; } fclose(file); return 0; } int file_get_size(const Path &path, int64_t *size) { struct stat64 file_stat; memset(&file_stat, 0, sizeof(file_stat)); int ret; EINTR_RETRY(ret, stat64(path.data.c_str(), &file_stat)); if(ret == 0 && S_ISREG(file_stat.st_mode)) { *size = file_stat.st_size; return 0; } *size = 0; return -1; } bool file_get_last_modified_time_seconds(const char *path, time_t *result) { struct stat file_stat; memset(&file_stat, 0, sizeof(file_stat)); int ret; EINTR_RETRY(ret, stat(path, &file_stat)); if(ret == 0) { *result = file_stat.st_mtim.tv_sec; return true; } return false; } static int file_overwrite(const Path &path, const char *str, size_t size) { FILE *file = fopen_eintr(path.data.c_str(), "wb"); if(!file) { perror(path.data.c_str()); return -1; } if(fwrite_eintr(str, 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()); } int file_overwrite_atomic(const Path &path, const std::string &data) { Path tmp_path = path; tmp_path.append(".tmp"); int res = file_overwrite(tmp_path, data.c_str(), data.size()); if(res != 0) return res; return rename_atomic(tmp_path.data.c_str(), path.data.c_str()); } bool file_append(const Path &path, const std::string &data) { FILE *file = fopen_eintr(path.data.c_str(), "ab"); if(!file) { perror(path.data.c_str()); return false; } if(fwrite_eintr(data.data(), data.size(), file) != data.size()) { fclose(file); return false; } return fclose(file) == 0; } void for_files_in_dir(const Path &path, FileIteratorCallback callback) { try { for(auto &p : std::filesystem::directory_iterator(path.data)) { std::error_code ec; const FileType file_type = p.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; if(!callback(p.path().string(), file_type, 0)) 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; } } void for_files_in_dir_sort_last_modified(const Path &path, FileIteratorCallback callback, FileSortDirection sort_dir) { std::vector> paths_with_last_modified; try { for(auto &p : std::filesystem::directory_iterator(path.data)) { time_t last_modified = 0; file_get_last_modified_time_seconds(p.path().c_str(), &last_modified); paths_with_last_modified.push_back(std::make_pair(p, last_modified)); } } 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; } if(sort_dir == FileSortDirection::ASC) { std::sort(paths_with_last_modified.begin(), paths_with_last_modified.end(), [](const auto &path1, const auto &path2) { return path1.second > path2.second; }); } else { std::sort(paths_with_last_modified.begin(), paths_with_last_modified.end(), [](const auto &path1, const auto &path2) { return path1.second < path2.second; }); } for(auto &p : paths_with_last_modified) { std::error_code ec; const FileType file_type = p.first.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; if(!callback(p.first.path().string(), file_type, p.second)) break; } } void for_files_in_dir_sort_name(const Path &path, FileIteratorCallback callback, FileSortDirection sort_dir) { 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; } if(sort_dir == FileSortDirection::ASC) { std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) { return path1.path().filename() < path2.path().filename(); }); } else { std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) { return path1.path().filename() > path2.path().filename(); }); } for(auto &p : paths) { std::error_code ec; const FileType file_type = p.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; if(!callback(p.path().string(), file_type, 0)) 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) { Json::StreamWriterBuilder json_builder; return file_overwrite_atomic(path, Json::writeString(json_builder, json)) == 0; } 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); if(file_overwrite(tmp_path, buffer.GetString(), buffer.GetSize()) != 0) return false; return rename_atomic(tmp_path.data.c_str(), path.data.c_str()) == 0; } int rename_atomic(const char *oldpath, const char *newpath) { int fd = open(oldpath, O_RDONLY); if(fd == -1) { perror(oldpath); return -1; } if(fsync(fd) == -1) { perror(oldpath); close(fd); return -1; } close(fd); return rename(oldpath, newpath); } bool is_program_executable_by_name(const char *name) { char *env = getenv("PATH"); if(!env) return false; std::unordered_set paths; string_split(env, ':', [&paths](const char *str, size_t size) { if(size > 0) 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; } std::string file_size_to_human_readable_string(int64_t bytes) { double kb = (double)bytes / 1024.0; double mb = (double)bytes / 1024.0 / 1024.0; double gb = (double)bytes / 1024.0 / 1024.0 / 1024.0; char result[32]; if(gb >= 1.0) snprintf(result, sizeof(result), "%.1f GiB", gb); else if(mb >= 1.0) snprintf(result, sizeof(result), "%.1f MiB", mb); else if(kb >= 1.0) snprintf(result, sizeof(result), "%.1f KiB", kb); else snprintf(result, sizeof(result), "%zu bytes", bytes); return result; } }