/* * Copyright 2013 (c) Anna Schumaker. */ #include #include #include #include #include #include #define REPORT_ERROR(fname, error) \ g_printerr("%s (%s:%d): %s: %s\n", __func__, __FILE__, __LINE__, fname, error) #define REPORT_ERRNO(fname) REPORT_ERROR(fname, strerror(errno)) static void __file_init_common(struct file *file, const gchar *subdir, const gchar *name, const gchar *(*user_dir)(void)) { file->f_file = NULL; file->f_name = name; file->f_subdir = subdir; file->f_user_dir = user_dir; } static gchar *__file_path(const gchar *base, const gchar *dir, const gchar *name) { return g_build_filename(base, OCARINA_NAME, dir ? dir : "", name, NULL); } static gchar *__file_build_tmp(const gchar *base, const gchar *dir, const gchar *name) { gchar *tmp, *res; if (string_length(name) == 0) return g_strdup(""); tmp = g_strdup_printf(".%s.tmp", name); res = __file_path(base, dir, tmp); g_free(tmp); return res; } static bool __file_exists(gchar *path) { bool ret = g_file_test(path, G_FILE_TEST_EXISTS); g_free(path); return ret; } static FILE *__file_open(gchar *path, const gchar *mode) { FILE *ret = g_fopen(path, mode); if (!ret) REPORT_ERRNO(path); g_free(path); return ret; } static void __file_close(FILE *file, gchar *path, gchar *tmp) { if (file) { fclose(file); if (path && tmp) g_rename(tmp, path); } g_free(path); g_free(tmp); } static bool __file_mkdir(const gchar *basedir, const gchar *subdir) { gchar *dir = __file_path(basedir, subdir, NULL); int ret = g_mkdir_with_parents(dir, 0755); if (ret != 0) REPORT_ERRNO(dir); g_free(dir); return ret == 0; } static bool __file_can_write(struct file *data) { gchar *path = file_path(data); bool ret = true; if (g_access(path, F_OK) == 0 && g_access(path, W_OK) < 0) ret = false; g_free(path); return ret; } void file_init_data(struct file *file, const gchar *subdir, const gchar *name, unsigned int min) { __file_init_common(file, subdir, name, g_get_user_data_dir); file->f_data.f_mode = OPEN_READ; file->f_data.f_version = OCARINA_MINOR_VERSION; file->f_data.f_prev = 0; file->f_data.f_min = min; } void file_init_cache(struct file *file, const gchar *subdir, const gchar *name) { __file_init_common(file, subdir, name, g_get_user_cache_dir); } gchar *file_path(struct file *file) { if (string_length(file->f_name) == 0) return g_strdup(""); return g_build_filename(file->f_user_dir(), OCARINA_NAME, file->f_subdir, file->f_name, NULL); } gchar *data_file_write_path(struct file *data) { return __file_build_tmp(g_get_user_data_dir(), NULL, data->f_name); } gchar *cache_file_write_path(struct file *cache) { return __file_build_tmp(g_get_user_cache_dir(), cache->f_subdir, cache->f_name); } const unsigned int data_file_version(struct file *data) { struct data_file *file = &data->f_data; if (data->f_file && (file->f_mode == OPEN_READ)) return file->f_prev; return file->f_version; } bool data_file_exists(struct file *file) { return __file_exists(file_path(file)); } bool cache_file_exists(struct file *file) { return __file_exists(file_path(file)); } static bool __file_open_read(struct file *data) { struct data_file *file = &data->f_data; if (!data_file_exists(data)) return false; data->f_file = __file_open(file_path(data), "r"); if (!data->f_file) return false; file->f_mode = OPEN_READ; if (data_file_readf(data, "%u\n", &file->f_prev) != 1) return false; if (file->f_prev < file->f_min) { REPORT_ERROR(data->f_name, "File too old to be upgraded."); data_file_close(data); exit(1); } if (file->f_prev > file->f_version) { REPORT_ERROR(data->f_name, "File too new to be opened."); data_file_close(data); exit(1); } return true; } static bool __file_open_write(struct file *data) { struct data_file *file = &data->f_data; if (!__file_mkdir(g_get_user_data_dir(), NULL)) return false; if (!__file_can_write(data)) return false; data->f_file = __file_open(data_file_write_path(data), "w"); if (!data->f_file) return false; file->f_mode = OPEN_WRITE; return data_file_writef(data, "%d\n", file->f_version) > 0; } bool data_file_open(struct file *data, enum open_mode mode) { if ((string_length(data->f_name) == 0) || (data->f_file != NULL)) return false; if (mode == OPEN_READ) return __file_open_read(data); return __file_open_write(data); } bool cache_file_open(struct file *cache, enum open_mode mode) { if (mode == OPEN_READ) return false; if ((string_length(cache->f_name) == 0) || (cache->f_file != NULL)) return false; if (!__file_mkdir(g_get_user_cache_dir(), cache->f_subdir)) return false; cache->f_file = __file_open(cache_file_write_path(cache), "wb"); return cache->f_file != NULL; } void data_file_close(struct file *data) { struct data_file *file = &data->f_data; __file_close(data->f_file, file->f_mode == OPEN_WRITE ? file_path(data) : NULL, file->f_mode == OPEN_WRITE ? data_file_write_path(data) : NULL); data->f_file = NULL; } void cache_file_close(struct file *cache) { __file_close(cache->f_file, file_path(cache), cache_file_write_path(cache)); cache->f_file = NULL; } int data_file_readf(struct file *data, const char *fmt, ...) { va_list argp; int ret; va_start(argp, fmt); ret = vfscanf(data->f_file, fmt, argp); va_end(argp); return ret; } gchar *data_file_readl(struct file *data) { gchar *res; if (data_file_readf(data, "%m[^\n]\n", &res) == 0) return g_strdup(""); g_strstrip(res); return res; } int data_file_writef(struct file *data, const char *fmt, ...) { va_list argp; int ret; va_start(argp, fmt); ret = g_vfprintf(data->f_file, fmt, argp); va_end(argp); if (ret < 0) REPORT_ERRNO(data->f_name); return ret; } int cache_file_write(struct file *cache, const void *data, size_t len) { if (fwrite(data, len, 1, cache->f_file) == 1) return len; return -1; } bool cache_file_import(struct file *cache, const gchar *srcpath) { gchar *contents = NULL; gsize length = 0; if (!cache->f_file || !srcpath) return false; if (!g_file_get_contents(srcpath, &contents, &length, NULL)) return false; cache_file_write(cache, contents, length); return true; } bool data_file_remove(struct file *data) { int ret = -1; gchar *path; if (!data->f_file) { path = file_path(data); ret = g_unlink(path); g_free(path); } return ret == 0; }