diff --git a/doc/man/zathurarc.5.rst b/doc/man/zathurarc.5.rst index ab702e5..35fa499 100644 --- a/doc/man/zathurarc.5.rst +++ b/doc/man/zathurarc.5.rst @@ -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 diff --git a/meson.build b/meson.build index f07151a..29d32f7 100644 --- a/meson.build +++ b/meson.build @@ -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', diff --git a/zathura/database-plain.c b/zathura/database-plain.c deleted file mode 100644 index 13b3488..0000000 --- a/zathura/database-plain.c +++ /dev/null @@ -1,913 +0,0 @@ -/* SPDX-License-Identifier: Zlib */ - -#define _POSIX_SOURCE -#define _XOPEN_SOURCE 500 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 - -#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; -} diff --git a/zathura/database-plain.h b/zathura/database-plain.h deleted file mode 100644 index 8485a38..0000000 --- a/zathura/database-plain.h +++ /dev/null @@ -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 diff --git a/zathura/database-sqlite.c b/zathura/database-sqlite.c index 0eaaadf..b33b19a 100644 --- a/zathura/database-sqlite.c +++ b/zathura/database-sqlite.c @@ -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; } diff --git a/zathura/database-sqlite.h b/zathura/database-sqlite.h index d6fb8c3..d446a16 100644 --- a/zathura/database-sqlite.h +++ b/zathura/database-sqlite.h @@ -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 diff --git a/zathura/zathura.c b/zathura/zathura.c index fd863be..a1dd7ab 100644 --- a/zathura/zathura.c +++ b/zathura/zathura.c @@ -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);