Drop plain database backend (fixes #389)

The sqlite backend will now import the data from the plain backend.
This commit is contained in:
Sebastian Ramacher 2024-03-03 17:11:01 +01:00
parent 5f1efd18f3
commit 1e69a18129
7 changed files with 306 additions and 965 deletions

View File

@ -605,8 +605,13 @@ zathura
*database*
Defines the database backend to use for bookmarks and input history. Possible
values are "plain", "sqlite" (if built with sqlite support) and "null". If
"null" is used, bookmarks and input history will not be stored.
values are "plain", "sqlite" and "null". If "null" is used, bookmarks and
input history will not be stored.
Note that the "plain" backend is deprecated. If selected, the "sqlite" backend
will import old history from the "plain" database and operation will continue
with the "sqlite" backend. After the first import, the setting can safely be
changed to "sqlite". The default will change after a release of Debian trixie.
* Value type: String
* Default value: plain

View File

@ -119,7 +119,6 @@ sources = files(
'zathura/config.c',
'zathura/content-type.c',
'zathura/database.c',
'zathura/database-plain.c',
'zathura/database-sqlite.c',
'zathura/dbus-interface.c',
'zathura/document.c',

View File

@ -1,913 +0,0 @@
/* SPDX-License-Identifier: Zlib */
#define _POSIX_SOURCE
#define _XOPEN_SOURCE 500
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <girara/utils.h>
#include <girara/datastructures.h>
#include <girara/input-history.h>
#include <time.h>
#include "database-plain.h"
#include "utils.h"
#define BOOKMARKS "bookmarks"
#define HISTORY "history"
#define INPUT_HISTORY "input-history"
#define KEY_PAGE "page"
#define KEY_OFFSET "offset"
#define KEY_ZOOM "zoom"
#define KEY_ROTATE "rotate"
#define KEY_PAGES_PER_ROW "pages-per-row"
#define KEY_PAGE_RIGHT_TO_LEFT "page-right-to-left"
#define KEY_FIRST_PAGE_COLUMN "first-page-column"
#define KEY_POSITION_X "position-x"
#define KEY_POSITION_Y "position-y"
#define KEY_JUMPLIST "jumplist"
#define KEY_TIME "time"
#ifdef __GNU__
#include <sys/file.h>
#define FILE_LOCK_WRITE LOCK_EX
#define FILE_LOCK_READ LOCK_SH
static int
file_lock_set(int fd, int cmd)
{
return flock(fd, cmd);
}
#else
#define FILE_LOCK_WRITE F_WRLCK
#define FILE_LOCK_READ F_RDLCK
static int
file_lock_set(int fd, short cmd)
{
struct flock lock = { .l_type = cmd, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0};
return fcntl(fd, F_SETLKW, &lock);
}
#endif
static void zathura_database_interface_init(ZathuraDatabaseInterface* iface);
static void io_interface_init(GiraraInputHistoryIOInterface* iface);
typedef struct zathura_plaindatabase_private_s {
char* bookmark_path;
GKeyFile* bookmarks;
GFileMonitor* bookmark_monitor;
char* history_path;
GKeyFile* history;
GFileMonitor* history_monitor;
char* input_history_path;
} ZathuraPlainDatabasePrivate;
G_DEFINE_TYPE_WITH_CODE(ZathuraPlainDatabase, zathura_plaindatabase, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(ZATHURA_TYPE_DATABASE, zathura_database_interface_init)
G_IMPLEMENT_INTERFACE(GIRARA_TYPE_INPUT_HISTORY_IO, io_interface_init)
G_ADD_PRIVATE(ZathuraPlainDatabase))
enum {
PROP_0,
PROP_PATH
};
static char*
prepare_filename(const char* file)
{
if (file == NULL) {
return NULL;
}
if (strchr(file, '[') == NULL && strchr(file, ']') == NULL) {
return g_strdup(file);
}
return g_base64_encode((const guchar*) file, strlen(file));
}
static char*
prepare_hash_key(const uint8_t* hash_sha256)
{
return g_base64_encode(hash_sha256, 32);
}
static bool
zathura_db_check_file(const char* path)
{
if (path == NULL) {
return false;
}
if (g_file_test(path, G_FILE_TEST_EXISTS) == false) {
FILE* file = fopen(path, "w");
if (file != NULL) {
fclose(file);
} else {
return false;
}
} else if (g_file_test(path, G_FILE_TEST_IS_REGULAR) == false) {
return false;
}
return true;
}
static GKeyFile*
zathura_db_read_key_file_from_file(const char* path)
{
if (path == NULL) {
return NULL;
}
/* open file */
FILE* file = fopen(path, "r+");
if (file == NULL) {
return NULL;
}
/* and lock it */
if (file_lock_set(fileno(file), FILE_LOCK_WRITE) != 0) {
fclose(file);
return NULL;
}
GKeyFile* key_file = g_key_file_new();
if (key_file == NULL) {
fclose(file);
return NULL;
}
/* read config file */
char* content = girara_file_read2(file);
fclose(file);
if (content == NULL) {
g_key_file_free(key_file);
return NULL;
}
/* parse config file */
size_t contentlen = strlen(content);
if (contentlen == 0) {
static const char dummy_content[] = "# nothing";
static const size_t dummy_len = sizeof(dummy_content) - 1;
g_free(content);
content = g_malloc(sizeof(char) * (dummy_len + 1));
if (content == NULL) {
g_key_file_free(key_file);
return NULL;
}
strcpy(content, dummy_content);
contentlen = dummy_len;
}
GError* error = NULL;
if (g_key_file_load_from_data(key_file, content, contentlen,
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error) ==
FALSE) {
if (error->code != 1) { /* ignore empty file */
g_free(content);
g_key_file_free(key_file);
g_error_free(error);
return NULL;
}
g_error_free(error);
}
g_free(content);
return key_file;
}
static void
zathura_db_write_key_file_to_file(const char* file, GKeyFile* key_file)
{
if (file == NULL || key_file == NULL) {
return;
}
gchar* content = g_key_file_to_data(key_file, NULL, NULL);
if (content == NULL) {
return;
}
/* open file */
int fd = open(file, O_RDWR | O_TRUNC);
if (fd == -1) {
g_free(content);
return;
}
if (file_lock_set(fd, FILE_LOCK_READ) != 0 || write(fd, content, strlen(content)) == 0) {
girara_error("Failed to write to %s", file);
}
close(fd);
g_free(content);
}
zathura_database_t*
zathura_plaindatabase_new(const char* path)
{
g_return_val_if_fail(path != NULL && strlen(path) != 0, NULL);
zathura_database_t* db = g_object_new(ZATHURA_TYPE_PLAINDATABASE, "path", path, NULL);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(ZATHURA_PLAINDATABASE(db));
if (priv->bookmark_path == NULL) {
g_object_unref(db);
return NULL;
}
return db;
}
static void
cb_zathura_db_watch_file(GFileMonitor* UNUSED(monitor), GFile* file, GFile* UNUSED(other_file),
GFileMonitorEvent event, zathura_database_t* database)
{
if (event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT || database == NULL) {
return;
}
char* path = g_file_get_path(file);
if (path == NULL) {
return;
}
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(database);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
if (priv->bookmark_path && strcmp(priv->bookmark_path, path) == 0) {
if (priv->bookmarks != NULL) {
g_key_file_free(priv->bookmarks);
}
priv->bookmarks = zathura_db_read_key_file_from_file(priv->bookmark_path);
} else if (priv->history_path && strcmp(priv->history_path, path) == 0) {
if (priv->history != NULL) {
g_key_file_free(priv->history);
}
priv->history = zathura_db_read_key_file_from_file(priv->history_path);
}
g_free(path);
}
static void
plain_db_init(ZathuraPlainDatabase* db, const char* dir)
{
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
/* bookmarks */
priv->bookmark_path = g_build_filename(dir, BOOKMARKS, NULL);
if (zathura_db_check_file(priv->bookmark_path) == false) {
goto error_free;
}
GFile* bookmark_file = g_file_new_for_path(priv->bookmark_path);
if (bookmark_file != NULL) {
priv->bookmark_monitor = g_file_monitor(bookmark_file, G_FILE_MONITOR_NONE, NULL, NULL);
} else {
goto error_free;
}
g_object_unref(bookmark_file);
g_signal_connect(
G_OBJECT(priv->bookmark_monitor),
"changed",
G_CALLBACK(cb_zathura_db_watch_file),
db
);
priv->bookmarks = zathura_db_read_key_file_from_file(priv->bookmark_path);
if (priv->bookmarks == NULL) {
goto error_free;
}
/* history */
priv->history_path = g_build_filename(dir, HISTORY, NULL);
if (zathura_db_check_file(priv->history_path) == false) {
goto error_free;
}
GFile* history_file = g_file_new_for_path(priv->history_path);
if (history_file != NULL) {
priv->history_monitor = g_file_monitor(history_file, G_FILE_MONITOR_NONE, NULL, NULL);
} else {
goto error_free;
}
g_object_unref(history_file);
g_signal_connect(
G_OBJECT(priv->history_monitor),
"changed",
G_CALLBACK(cb_zathura_db_watch_file),
db
);
priv->history = zathura_db_read_key_file_from_file(priv->history_path);
if (priv->history == NULL) {
goto error_free;
}
/* input history */
priv->input_history_path = g_build_filename(dir, INPUT_HISTORY, NULL);
if (zathura_db_check_file(priv->input_history_path) == false) {
goto error_free;
}
return;
error_free:
/* bookmarks */
g_free(priv->bookmark_path);
priv->bookmark_path = NULL;
g_clear_object(&priv->bookmark_monitor);
if (priv->bookmarks != NULL) {
g_key_file_free(priv->bookmarks);
priv->bookmarks = NULL;
}
/* history */
g_free(priv->history_path);
priv->history_path = NULL;
g_clear_object(&priv->history_monitor);
if (priv->history != NULL) {
g_key_file_free(priv->history);
priv->history = NULL;
}
/* input history */
g_free(priv->input_history_path);
priv->input_history_path = NULL;
}
static void
plain_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
{
ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
switch (prop_id) {
case PROP_PATH:
plain_db_init(db, g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
}
}
static void
plain_dispose(GObject* object)
{
ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
g_clear_object(&priv->bookmark_monitor);
g_clear_object(&priv->history_monitor);
G_OBJECT_CLASS(zathura_plaindatabase_parent_class)->dispose(object);
}
static void
plain_finalize(GObject* object)
{
ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
/* bookmarks */
g_free(priv->bookmark_path);
if (priv->bookmarks != NULL) {
g_key_file_free(priv->bookmarks);
}
/* history */
g_free(priv->history_path);
if (priv->history != NULL) {
g_key_file_free(priv->history);
}
/* input history */
g_free(priv->input_history_path);
G_OBJECT_CLASS(zathura_plaindatabase_parent_class)->finalize(object);
}
static bool
plain_add_bookmark(zathura_database_t* db, const char* file,
zathura_bookmark_t* bookmark)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
if (priv->bookmarks == NULL || priv->bookmark_path == NULL ||
bookmark->id == NULL) {
return false;
}
char* name = prepare_filename(file);
char* val_list[] = {
g_strdup_printf("%d", bookmark->page),
g_try_malloc0(G_ASCII_DTOSTR_BUF_SIZE),
g_try_malloc0(G_ASCII_DTOSTR_BUF_SIZE)
};
if (name == NULL || val_list[1] == NULL || val_list[2] == NULL) {
g_free(name);
for (unsigned int i = 0; i < LENGTH(val_list); ++i) {
g_free(val_list[i]);
}
return false;
}
g_ascii_dtostr(val_list[1], G_ASCII_DTOSTR_BUF_SIZE, bookmark->x);
g_ascii_dtostr(val_list[2], G_ASCII_DTOSTR_BUF_SIZE, bookmark->y);
g_key_file_set_string_list(priv->bookmarks, name, bookmark->id, (const char**)val_list, LENGTH(val_list));
for (unsigned int i = 0; i < LENGTH(val_list); ++i) {
g_free(val_list[i]);
}
g_free(name);
zathura_db_write_key_file_to_file(priv->bookmark_path, priv->bookmarks);
return true;
}
static bool
plain_remove_bookmark(zathura_database_t* db, const char* file, const char* id)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
if (priv->bookmarks == NULL || priv->bookmark_path == NULL) {
return false;
}
char* name = prepare_filename(file);
if (g_key_file_has_group(priv->bookmarks, name) == TRUE && g_key_file_remove_key(priv->bookmarks, name, id, NULL) == TRUE) {
zathura_db_write_key_file_to_file(priv->bookmark_path, priv->bookmarks);
g_free(name);
return true;
}
g_free(name);
return false;
}
static girara_list_t*
plain_load_bookmarks(zathura_database_t* db, const char* file)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
if (priv->bookmarks == NULL) {
return NULL;
}
char* name = prepare_filename(file);
if (g_key_file_has_group(priv->bookmarks, name) == FALSE) {
g_free(name);
return NULL;
}
girara_list_t* result = bookmarks_list_new();
gsize num_keys;
char** keys = g_key_file_get_keys(priv->bookmarks, name, &num_keys, NULL);
if (keys == NULL) {
girara_list_free(result);
g_free(name);
return NULL;
}
gsize num_vals = 0;
for (gsize i = 0; i < num_keys; i++) {
zathura_bookmark_t* bookmark = g_try_malloc0(sizeof(zathura_bookmark_t));
if (bookmark == NULL) {
continue;
}
bookmark->id = g_strdup(keys[i]);
char** val_list = g_key_file_get_string_list(priv->bookmarks, name, keys[i],
&num_vals, NULL);
if (num_vals != 1 && num_vals != 3) {
girara_error("Unexpected number of values.");
g_free(bookmark);
g_strfreev(val_list);
continue;
}
bookmark->page = atoi(val_list[0]);
if (num_vals == 3) {
bookmark->x = g_ascii_strtod(val_list[1], NULL);
bookmark->y = g_ascii_strtod(val_list[2], NULL);
} else if (num_vals == 1) {
bookmark->x = DBL_MIN;
bookmark->y = DBL_MIN;
}
girara_list_append(result, bookmark);
g_strfreev(val_list);
}
g_free(name);
g_strfreev(keys);
return result;
}
static girara_list_t*
get_jumplist_from_str(const char* str)
{
g_return_val_if_fail(str != NULL, NULL);
if (*str == '\0') {
return girara_list_new2(g_free);
}
girara_list_t* result = girara_list_new2(g_free);
char* copy = g_strdup(str);
char* saveptr = NULL;
char* token = strtok_r(copy, " ", &saveptr);
while (token != NULL) {
zathura_jump_t* jump = g_try_malloc0(sizeof(zathura_jump_t));
if (jump == NULL) {
continue;
}
jump->page = strtoul(token, NULL, 0);
token = strtok_r(NULL, " ", &saveptr);
if (token == NULL) {
girara_warning("Could not parse jumplist information.");
g_free(jump);
break;
}
jump->x = g_ascii_strtod(token, NULL);
token = strtok_r(NULL, " ", &saveptr);
if (token == NULL) {
girara_warning("Could not parse jumplist information.");
g_free(jump);
break;
}
jump->y = g_ascii_strtod(token, NULL);
girara_list_append(result, jump);
token = strtok_r(NULL, " ", &saveptr);
}
g_free(copy);
return result;
}
static girara_list_t*
plain_load_jumplist(zathura_database_t* db, const char* file)
{
g_return_val_if_fail(db != NULL && file != NULL, NULL);
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
char* str_value = g_key_file_get_string(priv->history, file, KEY_JUMPLIST, NULL);
if (str_value == NULL) {
return girara_list_new2(g_free);
}
girara_list_t* list = get_jumplist_from_str(str_value);
g_free(str_value);
return list;
}
static void
jump_to_str(void* data, void* userdata)
{
const zathura_jump_t* jump = data;
GString* str_val = userdata;
char buffer[G_ASCII_DTOSTR_BUF_SIZE] = { '\0' };
g_string_append_printf(str_val, "%d ", jump->page);
g_string_append(str_val, g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, jump->x));
g_string_append_c(str_val, ' ');
g_string_append(str_val, g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, jump->y));
g_string_append_c(str_val, ' ');
}
static bool
plain_save_jumplist(zathura_database_t* db, const char* file, girara_list_t* jumplist)
{
g_return_val_if_fail(db != NULL && file != NULL && jumplist != NULL, false);
GString* str_val = g_string_new(NULL);
girara_list_foreach(jumplist, jump_to_str, str_val);
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
g_key_file_set_string(priv->history, file, KEY_JUMPLIST, str_val->str);
zathura_db_write_key_file_to_file(priv->history_path, priv->history);
g_string_free(str_val, TRUE);
return true;
}
static bool
plain_set_fileinfo(zathura_database_t* db, const char* file, const uint8_t* hash_sha256,
zathura_fileinfo_t* file_info)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
if (priv->history == NULL || file_info == NULL || hash_sha256 == NULL || file == NULL) {
return false;
}
char* name = prepare_filename(file);
g_key_file_set_integer(priv->history, name, KEY_PAGE, file_info->current_page);
g_key_file_set_integer(priv->history, name, KEY_OFFSET, file_info->page_offset);
g_key_file_set_double (priv->history, name, KEY_ZOOM, file_info->zoom);
g_key_file_set_integer(priv->history, name, KEY_ROTATE, file_info->rotation);
g_key_file_set_integer(priv->history, name, KEY_PAGES_PER_ROW, file_info->pages_per_row);
g_key_file_set_string (priv->history, name, KEY_FIRST_PAGE_COLUMN, file_info->first_page_column_list);
g_key_file_set_boolean(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, file_info->page_right_to_left);
g_key_file_set_double (priv->history, name, KEY_POSITION_X, file_info->position_x);
g_key_file_set_double (priv->history, name, KEY_POSITION_Y, file_info->position_y);
g_key_file_set_integer(priv->history, name, KEY_TIME, time(NULL));
g_free(name);
name = prepare_hash_key(hash_sha256);
g_key_file_set_integer(priv->history, name, KEY_PAGE, file_info->current_page);
g_key_file_set_integer(priv->history, name, KEY_OFFSET, file_info->page_offset);
g_key_file_set_double (priv->history, name, KEY_ZOOM, file_info->zoom);
g_key_file_set_integer(priv->history, name, KEY_ROTATE, file_info->rotation);
g_key_file_set_integer(priv->history, name, KEY_PAGES_PER_ROW, file_info->pages_per_row);
g_key_file_set_string (priv->history, name, KEY_FIRST_PAGE_COLUMN, file_info->first_page_column_list);
g_key_file_set_boolean(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, file_info->page_right_to_left);
g_key_file_set_double (priv->history, name, KEY_POSITION_X, file_info->position_x);
g_key_file_set_double (priv->history, name, KEY_POSITION_Y, file_info->position_y);
g_key_file_set_integer(priv->history, name, KEY_TIME, time(NULL));
g_free(name);
zathura_db_write_key_file_to_file(priv->history_path, priv->history);
return true;
}
static bool
plain_get_fileinfo(zathura_database_t* db, const char* file, const uint8_t* hash_sha256,
zathura_fileinfo_t* file_info)
{
if (db == NULL || file == NULL || hash_sha256 == NULL || file_info == NULL) {
return false;
}
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
if (priv->history == NULL) {
return false;
}
char* name = prepare_filename(file);
if (g_key_file_has_group(priv->history, name) == FALSE) {
g_free(name);
name = prepare_hash_key(hash_sha256);
if (g_key_file_has_group(priv->history, name) == FALSE) {
g_free(name);
return false;
}
}
file_info->current_page = g_key_file_get_integer(priv->history, name, KEY_PAGE, NULL);
file_info->page_offset = g_key_file_get_integer(priv->history, name, KEY_OFFSET, NULL);
file_info->zoom = g_key_file_get_double (priv->history, name, KEY_ZOOM, NULL);
file_info->rotation = g_key_file_get_integer(priv->history, name, KEY_ROTATE, NULL);
/* the following flags got introduced at a later point */
if (g_key_file_has_key(priv->history, name, KEY_PAGES_PER_ROW, NULL) == TRUE) {
file_info->pages_per_row = g_key_file_get_integer(priv->history, name, KEY_PAGES_PER_ROW, NULL);
}
if (g_key_file_has_key(priv->history, name, KEY_FIRST_PAGE_COLUMN, NULL) == TRUE) {
file_info->first_page_column_list = g_key_file_get_string(priv->history, name, KEY_FIRST_PAGE_COLUMN, NULL);
}
if (g_key_file_has_key(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, NULL) == TRUE) {
file_info->page_right_to_left = g_key_file_get_boolean(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, NULL);
}
if (g_key_file_has_key(priv->history, name, KEY_POSITION_X, NULL) == TRUE) {
file_info->position_x = g_key_file_get_double(priv->history, name, KEY_POSITION_X, NULL);
}
if (g_key_file_has_key(priv->history, name, KEY_POSITION_Y, NULL) == TRUE) {
file_info->position_y = g_key_file_get_double(priv->history, name, KEY_POSITION_Y, NULL);
}
g_free(name);
return true;
}
static girara_list_t*
plain_io_read(GiraraInputHistoryIO* db)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
/* open file */
FILE* file = fopen(priv->input_history_path, "r");
if (file == NULL) {
return NULL;
}
/* read input history file */
if (file_lock_set(fileno(file), FILE_LOCK_READ) != 0) {
fclose(file);
return NULL;
}
char* content = girara_file_read2(file);
fclose(file);
girara_list_t* res = girara_list_new2(g_free);
char** tmp = g_strsplit(content, "\n", 0);
for (size_t i = 0; tmp[i] != NULL; ++i) {
if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL) {
continue;
}
girara_list_append(res, g_strdup(tmp[i]));
}
g_strfreev(tmp);
g_free(content);
return res;
}
static void
plain_io_append(GiraraInputHistoryIO* db, const char* input)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
/* open file */
FILE* file = fopen(priv->input_history_path, "r+");
if (file == NULL) {
return;
}
/* read input history file */
if (file_lock_set(fileno(file), FILE_LOCK_WRITE) != 0) {
fclose(file);
return;
}
char* content = girara_file_read2(file);
rewind(file);
if (ftruncate(fileno(file), 0) != 0) {
g_free(content);
fclose(file);
return;
}
char** tmp = g_strsplit(content, "\n", 0);
g_free(content);
/* write input history file */
for (size_t i = 0; tmp[i] != NULL; ++i) {
if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL || strcmp(tmp[i], input) == 0) {
continue;
}
fprintf(file, "%s\n", tmp[i]);
}
g_strfreev(tmp);
fprintf(file, "%s\n", input);
fclose(file);
}
static int
compare_time(const void* l, const void* r, void* data)
{
const gchar* lhs = *(const gchar**) l;
const gchar* rhs = *(const gchar**) r;
GKeyFile* keyfile = data;
time_t lhs_time = 0;
time_t rhs_time = 0;
if (g_key_file_has_key(keyfile, lhs, KEY_TIME, NULL) == TRUE) {
lhs_time = g_key_file_get_uint64(keyfile, lhs, KEY_TIME, NULL);
}
if (g_key_file_has_key(keyfile, rhs, KEY_TIME, NULL) == TRUE) {
rhs_time = g_key_file_get_uint64(keyfile, rhs, KEY_TIME, NULL);
}
if (lhs_time < rhs_time) {
return 1;
} else if (lhs_time > rhs_time) {
return -1;
}
return 0;
}
static girara_list_t*
plain_get_recent_files(zathura_database_t* db, int max, const char* basepath)
{
ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
girara_list_t* result = girara_list_new2(g_free);
if (result == NULL) {
return NULL;
}
gsize groups_size = 0;
gchar** groups = g_key_file_get_groups(priv->history, &groups_size);
if (groups_size > 0) {
g_qsort_with_data(groups, groups_size, sizeof(gchar*), compare_time, priv->history);
}
const size_t basepath_len = basepath != NULL ? strlen(basepath) : 0;
for (gsize s = 0; s != groups_size && max != 0; ++s) {
if (basepath != NULL && strncmp(groups[s], basepath, basepath_len) != 0) {
continue;
}
girara_list_append(result, g_strdup(groups[s]));
--max;
}
g_strfreev(groups);
return result;
}
static void
zathura_database_interface_init(ZathuraDatabaseInterface* iface)
{
/* initialize interface */
iface->add_bookmark = plain_add_bookmark;
iface->remove_bookmark = plain_remove_bookmark;
iface->load_bookmarks = plain_load_bookmarks;
iface->load_jumplist = plain_load_jumplist;
iface->save_jumplist = plain_save_jumplist;
iface->set_fileinfo = plain_set_fileinfo;
iface->get_fileinfo = plain_get_fileinfo;
iface->get_recent_files = plain_get_recent_files;
}
static void
io_interface_init(GiraraInputHistoryIOInterface* iface)
{
/* initialize interface */
iface->append = plain_io_append;
iface->read = plain_io_read;
}
static void
zathura_plaindatabase_class_init(ZathuraPlainDatabaseClass* class)
{
/* override methods */
GObjectClass* object_class = G_OBJECT_CLASS(class);
object_class->dispose = plain_dispose;
object_class->finalize = plain_finalize;
object_class->set_property = plain_set_property;
g_object_class_install_property(object_class, PROP_PATH,
g_param_spec_string("path", "path", "path to directory where the bookmarks and history are located",
NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
static void
zathura_plaindatabase_init(ZathuraPlainDatabase* db)
{
ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
priv->bookmark_path = NULL;
priv->bookmark_monitor = NULL;
priv->bookmarks = NULL;
priv->history_path = NULL;
priv->history_monitor = NULL;
priv->history = NULL;
priv->input_history_path = NULL;
}

View File

@ -1,43 +0,0 @@
/* SPDX-License-Identifier: Zlib */
#ifndef ZATHURA_DATABASE_PLAIN_H
#define ZATHURA_DATABASE_PLAIN_H
#include "database.h"
#define ZATHURA_TYPE_PLAINDATABASE \
(zathura_plaindatabase_get_type())
#define ZATHURA_PLAINDATABASE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), ZATHURA_TYPE_PLAINDATABASE, ZathuraPlainDatabase))
#define ZATHURA_IS_PLAINDATABASE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ZATHURA_TYPE_PLAINDATABASE))
#define ZATHURA_PLAINDATABASE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), ZATHURA_TYPE_PLAINDATABASE, ZathuraPlainDatabaseClass))
#define ZATHURA_IS_PLAINDATABASE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), ZATHURA_TYPE_PLAINDATABASE))
#define ZATHURA_PLAINDATABASE_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), ZATHURA_TYPE_PLAINDATABASE, ZathuraPlainDatabaseClass))
typedef struct _ZathuraPlainDatabase ZathuraPlainDatabase;
typedef struct _ZathuraPlainDatabaseClass ZathuraPlainDatabaseClass;
struct _ZathuraPlainDatabase
{
GObject parent_instance;
};
struct _ZathuraPlainDatabaseClass
{
GObjectClass parent_class;
};
GType zathura_plaindatabase_get_type(void) G_GNUC_CONST;
/**
* Initialize database system.
*
* @param dir Path to the directory where the database file should be located.
* @return A valid zathura_database_t instance or NULL on failure
*/
zathura_database_t* zathura_plaindatabase_new(const char* dir);
#endif

View File

@ -882,9 +882,292 @@ zathura_sqldatabase_class_init(ZathuraSQLDatabaseClass* class)
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
static void
zathura_sqldatabase_init(ZathuraSQLDatabase* db)
{
static void zathura_sqldatabase_init(ZathuraSQLDatabase* db) {
ZathuraSQLDatabasePrivate* priv = zathura_sqldatabase_get_instance_private(db);
priv->session = NULL;
priv->session = NULL;
}
/* functions to import plain database into sqlite database */
static GKeyFile* db_read_key_file_from_file(const char* path) {
if (path == NULL) {
return NULL;
}
/* open file */
GKeyFile* key_file = g_key_file_new();
if (key_file == NULL) {
return NULL;
}
GError* error = NULL;
if (g_key_file_load_from_file(key_file, path, 0, &error) == FALSE) {
girara_debug("Unable to parse key file: %s", path);
g_key_file_free(key_file);
g_error_free(error);
return NULL;
}
return key_file;
}
static char* unprepare_filename(const char* file) {
if (g_path_is_absolute(file)) {
return g_strdup(file);
}
gsize length = 0;
guchar* decoded = g_base64_decode(file, &length);
if (length == 0) {
return NULL;
}
gchar* ret = g_malloc0(length + 1);
memcpy(ret, decoded, length);
g_free(decoded);
/* deocuded string not a path */
if (g_path_is_absolute(ret)) {
return ret;
}
g_free(ret);
return NULL;
}
#define BOOKMARKS "bookmarks"
#define HISTORY "history"
#define INPUT_HISTORY "input-history"
#define KEY_PAGE "page"
#define KEY_OFFSET "offset"
#define KEY_ZOOM "zoom"
#define KEY_ROTATE "rotate"
#define KEY_PAGES_PER_ROW "pages-per-row"
#define KEY_PAGE_RIGHT_TO_LEFT "page-right-to-left"
#define KEY_FIRST_PAGE_COLUMN "first-page-column"
#define KEY_POSITION_X "position-x"
#define KEY_POSITION_Y "position-y"
#define KEY_JUMPLIST "jumplist"
#define KEY_TIME "time"
static void import_bookmarks(zathura_database_t* db, const char* plain_dir) {
gchar* bookmark_path = g_build_filename(plain_dir, BOOKMARKS, NULL);
GKeyFile* key_file = db_read_key_file_from_file(bookmark_path);
g_free(bookmark_path);
if (key_file == NULL) {
return;
}
gsize groups_length = 0;
gchar** groups = g_key_file_get_groups(key_file, &groups_length);
for (gsize idx = 0; idx != groups_length; ++idx) {
gchar* group = groups[idx];
char* path = unprepare_filename(group);
if (path == NULL) {
girara_debug("Unable to decode file path: %s", group);
continue;
}
gsize keys_length = 0;
char** keys = g_key_file_get_keys(key_file, group, &keys_length, NULL);
if (keys == NULL) {
girara_error("No bookmarks");
continue;
}
for (gsize key_index = 0; key_index != keys_length; ++key_index) {
gchar* key = keys[key_index];
gsize num_vals = 0;
char** val_list = g_key_file_get_string_list(key_file, group, key, &num_vals, NULL);
if (num_vals != 1 && num_vals != 3) {
girara_error("Unexpected number of values.");
g_strfreev(val_list);
continue;
}
zathura_bookmark_t bookmark;
bookmark.id = key;
bookmark.page = atoi(val_list[0]);
if (num_vals == 3) {
bookmark.x = g_ascii_strtod(val_list[1], NULL);
bookmark.y = g_ascii_strtod(val_list[2], NULL);
} else if (num_vals == 1) {
bookmark.x = DBL_MIN;
bookmark.y = DBL_MIN;
}
zathura_db_add_bookmark(db, path, &bookmark);
g_strfreev(val_list);
}
g_strfreev(keys);
g_free(path);
}
g_strfreev(groups);
g_key_file_free(key_file);
}
static void import_history(zathura_database_t* db, const char* plain_dir) {
gchar* history_path = g_build_filename(plain_dir, HISTORY, NULL);
GKeyFile* key_file = db_read_key_file_from_file(history_path);
g_free(history_path);
if (key_file == NULL) {
return;
}
gsize groups_length = 0;
gchar** groups = g_key_file_get_groups(key_file, &groups_length);
for (gsize idx = 0; idx != groups_length; ++idx) {
gchar* group = groups[idx];
/* if path is NULL, then group was a check sum */
char* path = unprepare_filename(group);
gsize hash_size = 0;
guchar* hash = path == NULL ? g_base64_decode(group, &hash_size) : NULL;
if (path == NULL && hash == NULL) {
girara_debug("Unable to decode group: %s", group);
continue;
}
/* load fileinf o*/
zathura_fileinfo_t file_info;
file_info.current_page = g_key_file_get_integer(key_file, group, KEY_PAGE, NULL);
file_info.page_offset = g_key_file_get_integer(key_file, group, KEY_OFFSET, NULL);
file_info.zoom = g_key_file_get_double(key_file, group, KEY_ZOOM, NULL);
file_info.rotation = g_key_file_get_integer(key_file, group, KEY_ROTATE, NULL);
/* the following flags got introduced at a later point */
if (g_key_file_has_key(key_file, group, KEY_PAGES_PER_ROW, NULL) == TRUE) {
file_info.pages_per_row = g_key_file_get_integer(key_file, group, KEY_PAGES_PER_ROW, NULL);
}
if (g_key_file_has_key(key_file, group, KEY_FIRST_PAGE_COLUMN, NULL) == TRUE) {
file_info.first_page_column_list = g_key_file_get_string(key_file, group, KEY_FIRST_PAGE_COLUMN, NULL);
}
if (g_key_file_has_key(key_file, group, KEY_PAGE_RIGHT_TO_LEFT, NULL) == TRUE) {
file_info.page_right_to_left = g_key_file_get_boolean(key_file, group, KEY_PAGE_RIGHT_TO_LEFT, NULL);
}
if (g_key_file_has_key(key_file, group, KEY_POSITION_X, NULL) == TRUE) {
file_info.position_x = g_key_file_get_double(key_file, group, KEY_POSITION_X, NULL);
}
if (g_key_file_has_key(key_file, group, KEY_POSITION_Y, NULL) == TRUE) {
file_info.position_y = g_key_file_get_double(key_file, group, KEY_POSITION_Y, NULL);
}
char* temporary_path = NULL;
if (path == NULL) {
/* unique path if imported via checksum */
temporary_path = g_build_filename("/IMPORTED", group, NULL);
}
static const uint8_t zero_digest[32] = {0};
sqlite_set_fileinfo(db, path != NULL ? path : temporary_path, path != NULL ? zero_digest : hash, &file_info);
g_free(temporary_path);
/* load jumplist */
char* jumplist_str = g_key_file_get_string(key_file, group, KEY_JUMPLIST, NULL);
if (path != NULL && jumplist_str != NULL) {
girara_list_t* jumplist = girara_list_new2(g_free);
char* copy = g_strdup(jumplist_str);
char* saveptr = NULL;
char* token = strtok_r(copy, " ", &saveptr);
while (token != NULL) {
zathura_jump_t* jump = g_try_malloc0(sizeof(zathura_jump_t));
if (jump == NULL) {
continue;
}
jump->page = strtoul(token, NULL, 0);
token = strtok_r(NULL, " ", &saveptr);
if (token == NULL) {
girara_warning("Could not parse jumplist information.");
g_free(jump);
break;
}
jump->x = g_ascii_strtod(token, NULL);
token = strtok_r(NULL, " ", &saveptr);
if (token == NULL) {
girara_warning("Could not parse jumplist information.");
g_free(jump);
break;
}
jump->y = g_ascii_strtod(token, NULL);
girara_list_append(jumplist, jump);
token = strtok_r(NULL, " ", &saveptr);
}
g_free(copy);
zathura_db_save_jumplist(db, path, jumplist);
girara_list_free(jumplist);
}
g_free(hash);
g_free(path);
}
g_strfreev(groups);
g_key_file_free(key_file);
}
static void import_input_history(GiraraInputHistoryIO* ih, const char* plain_dir) {
gchar* input_history_path = g_build_filename(plain_dir, INPUT_HISTORY, NULL);
char* input_history_content = girara_file_read(input_history_path);
if (input_history_content != NULL) {
char** tmp = g_strsplit(input_history_content, "\n", 0);
for (size_t i = 0; tmp[i] != NULL; ++i) {
if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL) {
continue;
}
girara_input_history_io_append(ih, tmp[i]);
}
g_strfreev(tmp);
g_free(input_history_content);
}
g_free(input_history_path);
}
zathura_database_t* zathura_sqldatabase_new_from_plain(const char* sqlite_path, const char* plain_dir) {
g_return_val_if_fail(sqlite_path != NULL && strlen(sqlite_path) != 0, NULL);
g_return_val_if_fail(plain_dir != NULL && strlen(plain_dir) != 0, NULL);
girara_info("Opening plain database via sqlite backend.");
if (g_file_test(sqlite_path, G_FILE_TEST_EXISTS) == true) {
girara_warning("sqlite database already exists. Set your database backend to sqlite");
return zathura_sqldatabase_new(sqlite_path);
}
girara_debug("Migrating plain database.");
zathura_database_t* db = g_object_new(ZATHURA_TYPE_SQLDATABASE, "path", sqlite_path, NULL);
ZathuraSQLDatabasePrivate* priv = zathura_sqldatabase_get_instance_private(ZATHURA_SQLDATABASE(db));
if (priv->session == NULL) {
g_object_unref(G_OBJECT(db));
return NULL;
}
/* bookmarks */
girara_debug("Migrating bookmarks from plain database.");
import_bookmarks(db, plain_dir);
/* history */
girara_debug("Migrating file infrom from plain database.");
import_history(db, plain_dir);
/* input history */
girara_debug("Migrating input history from plain database.");
import_input_history(GIRARA_INPUT_HISTORY_IO(db), plain_dir);
girara_info("Database migration done. Set your database backend to sqlite.");
return db;
}

View File

@ -41,4 +41,13 @@ GType zathura_sqldatabase_get_type(void) G_GNUC_CONST;
*/
zathura_database_t* zathura_sqldatabase_new(const char* path);
/**
* Initialize database system from an old plain database.
*
* @param sqlite_path Path to the sqlite database.
* @param plain_dir Path to the old plain database.
* @return A valid zathura_database_t instance or NULL on failure
*/
zathura_database_t* zathura_sqldatabase_new_from_plain(const char* sqlite_path, const char* plain_dir);
#endif

View File

@ -29,7 +29,6 @@
#include "config.h"
#include "commands.h"
#include "database-sqlite.h"
#include "database-plain.h"
#include "document.h"
#include "shortcuts.h"
#include "zathura.h"
@ -379,7 +378,9 @@ static void init_database(zathura_t* zathura) {
if (g_strcmp0(database, "plain") == 0) {
girara_debug("Using plain database backend.");
zathura->database = zathura_plaindatabase_new(zathura->config.data_dir);
char* tmp = g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL);
zathura->database = zathura_sqldatabase_new_from_plain(tmp, zathura->config.data_dir);
g_free(tmp);
} else if (g_strcmp0(database, "sqlite") == 0) {
girara_debug("Using sqlite database backend.");
char* tmp = g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL);