/* * 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, unsigned int min, const gchar *(*user_dir)(void)) { file->f_file = NULL; file->f_name = name; file->f_subdir = subdir; file->f_mode = CLOSED; file->f_version = OCARINA_MINOR_VERSION; file->f_prev = 0; file->f_min = min; file->f_user_dir = user_dir; } static bool __file_open(struct file *file, enum open_mode mode) { gchar *cmode, *path; if (mode == OPEN_READ || mode == OPEN_READ_BINARY) { cmode = "r"; path = file_path(file); } else { cmode = "w"; path = file_write_path(file); } file->f_file = g_fopen(path, cmode); if (!file->f_file) REPORT_ERRNO(path); g_free(path); return file->f_file != NULL; } static bool __file_mkdir(struct file *file) { gchar *dir = g_build_filename(file->f_user_dir(), OCARINA_NAME, file->f_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 *file) { gchar *path = file_path(file); 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, min, g_get_user_data_dir); } void file_init_cache(struct file *file, const gchar *subdir, const gchar *name) { __file_init_common(file, subdir, name, 0, 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 *file_write_path(struct file *file) { gchar *tmp, *res; if (string_length(file->f_name) == 0) return g_strdup(""); tmp = g_strdup_printf(".%s.tmp", file->f_name); res = g_build_filename(file->f_user_dir(), OCARINA_NAME, file->f_subdir, tmp, NULL); g_free(tmp); return res; } const unsigned int file_version(struct file *file) { if (file->f_file && (file->f_mode == OPEN_READ)) return file->f_prev; return file->f_version; } bool file_exists(struct file *file) { gchar *path = file_path(file); bool ret = g_file_test(path, G_FILE_TEST_EXISTS); g_free(path); return ret; } static bool __file_open_read(struct file *file, enum open_mode mode) { if (!file_exists(file)) return false; if (!__file_open(file, mode)) return false; file->f_mode = mode; if (mode == OPEN_READ_BINARY) return true; file->f_prev = file_readu(file); if (file->f_prev < file->f_min) { REPORT_ERROR(file->f_name, "File too old to be upgraded."); file_close(file); exit(1); } if (file->f_prev > file->f_version) { REPORT_ERROR(file->f_name, "File too new to be opened."); file_close(file); exit(1); } return true; } static bool __file_open_write(struct file *file, enum open_mode mode) { if (!__file_mkdir(file)) return false; if (!__file_can_write(file)) return false; if (!__file_open(file, OPEN_WRITE)) return false; file->f_mode = mode; if (mode == OPEN_WRITE_BINARY) return true; return file_writef(file, "%d\n", file->f_version) > 0; } bool file_open(struct file *file, enum open_mode mode) { if ((string_length(file->f_name) == 0) || (file->f_file != NULL)) return false; if (mode == CLOSED) return false; if (mode == OPEN_READ || mode == OPEN_READ_BINARY) return __file_open_read(file, mode); return __file_open_write(file, mode); } void file_close(struct file *file) { gchar *path = file_path(file); gchar *tmp = file_write_path(file); if (file->f_file) { fclose(file->f_file); if (file->f_mode == OPEN_WRITE || file->f_mode == OPEN_WRITE_BINARY) g_rename(tmp, path); } file->f_file = NULL; file->f_mode = CLOSED; g_free(path); g_free(tmp); } gchar *file_readw(struct file *file) { gchar *s; return fscanf(file->f_file, "%ms%*c", &s) ? s : g_strdup(""); } gchar *file_readl(struct file *file) { gchar *s = NULL; size_t len = 0; return getline(&s, &len, file->f_file) ? g_strchomp(s) : g_strdup(""); } unsigned int file_readu(struct file *file) { unsigned int u; return fscanf(file->f_file, "%u%*c", &u) ? u : 0; } int file_writef(struct file *file, const char *fmt, ...) { va_list argp; int ret; va_start(argp, fmt); ret = g_vfprintf(file->f_file, fmt, argp); va_end(argp); if (ret < 0) REPORT_ERRNO(file->f_name); return ret; } gchar *file_read(struct file *file) { int fd = fileno(file->f_file); struct stat st; gchar *buf; if (fstat(fd, &st) < 0) return NULL; buf = g_malloc0(st.st_size + 1); if (fread(buf, st.st_size, 1, file->f_file) == 1) return buf; g_free(buf); return NULL; } int file_write(struct file *file, const void *data, size_t len) { if (fwrite(data, len, 1, file->f_file) == 1) return len; return -1; } bool file_import(struct file *file, const gchar *srcpath) { gchar *contents = NULL; gsize length = 0; if (!file->f_file || !srcpath) return false; if (!g_file_get_contents(srcpath, &contents, &length, NULL)) return false; file_write(file, contents, length); return true; } bool file_remove(struct file *file) { gchar *path, *dir; int ret = -1; if (!file->f_file) { path = file_path(file); ret = g_unlink(path); dir = g_path_get_dirname(path); if (string_length(file->f_subdir) > 0) g_rmdir(dir); g_free(path); g_free(dir); } return ret == 0; }