ocarina/core/file.c

304 lines
6.1 KiB
C

/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/file.h>
#include <core/string.h>
#include <core/version.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#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 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_path(const gchar *base, const gchar *dir,
const gchar *name)
{
if (string_length(name) == 0)
return g_strdup("");
return __file_path(base, dir, name);
}
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 *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(struct file *file, const gchar *name,
unsigned int version, unsigned int min)
{
file->f_mode = OPEN_READ;
file->f_version = version;
file->f_prev = 0;
file->f_min = 0;
file->f_file = NULL;
file->f_name = name;
}
void cache_file_init(struct cache_file *file, gchar *subdir, gchar *name)
{
file->cf_file = NULL;
file->cf_name = name;
file->cf_subdir = subdir;
}
gchar *file_path(struct file *file)
{
return __file_build_path(g_get_user_data_dir(), NULL, file->f_name);
}
gchar *cache_file_path(struct cache_file *file)
{
return __file_build_path(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
}
gchar *file_write_path(struct file *file)
{
return __file_build_tmp(g_get_user_data_dir(), NULL, file->f_name);
}
gchar *cache_file_write_path(struct cache_file *file)
{
return __file_build_tmp(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
}
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)
{
return __file_exists(file_path(file));
}
bool cache_file_exists(struct cache_file *file)
{
return __file_exists(cache_file_path(file));
}
static bool __file_open_read(struct file *file)
{
if (!file_exists(file))
return false;
file->f_file = __file_open(file_path(file), "r");
if (!file->f_file)
return false;
file->f_mode = OPEN_READ;
if (file_readf(file, "%u\n", &file->f_prev) != 1)
return false;
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)
{
if (!__file_mkdir(g_get_user_data_dir(), NULL))
return false;
if (!__file_can_write(file))
return false;
file->f_file = __file_open(file_write_path(file), "w");
if (!file->f_file)
return false;
file->f_mode = OPEN_WRITE;
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 == OPEN_READ)
return __file_open_read(file);
return __file_open_write(file);
}
bool cache_file_open(struct cache_file *file, enum open_mode mode)
{
if (mode == OPEN_READ)
return false;
if ((string_length(file->cf_name) == 0) || (file->cf_file != NULL))
return false;
if (!__file_mkdir(g_get_user_cache_dir(), file->cf_subdir))
return false;
file->cf_file = __file_open(cache_file_write_path(file), "wb");
return file->cf_file != NULL;
}
void file_close(struct file *file)
{
__file_close(file->f_file,
file->f_mode == OPEN_WRITE ? file_path(file) : NULL,
file->f_mode == OPEN_WRITE ? file_write_path(file) : NULL);
file->f_file = NULL;
}
void cache_file_close(struct cache_file *file)
{
__file_close(file->cf_file,
cache_file_path(file),
cache_file_write_path(file));
file->cf_file = NULL;
}
int file_readf(struct file *file, const char *fmt, ...)
{
va_list argp;
int ret;
va_start(argp, fmt);
ret = vfscanf(file->f_file, fmt, argp);
va_end(argp);
return ret;
}
gchar *file_readl(struct file *file)
{
gchar *res;
if (file_readf(file, "%m[^\n]\n", &res) == 0)
return g_strdup("");
g_strstrip(res);
return res;
}
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;
}
int cache_file_write(struct cache_file *file, const void *data, size_t len)
{
if (fwrite(data, len, 1, file->cf_file) == 1)
return len;
return -1;
}
bool cache_file_import(struct cache_file *file, const gchar *srcpath)
{
gchar *contents = NULL;
gsize length = 0;
if (!file->cf_file || !srcpath)
return false;
if (!g_file_get_contents(srcpath, &contents, &length, NULL))
return false;
cache_file_write(file, contents, length);
return true;
}
bool file_remove(struct file *file)
{
int ret = -1;
gchar *path;
if (!file->f_file) {
path = file_path(file);
ret = g_unlink(path);
g_free(path);
}
return ret == 0;
}