1 /* SPDX-License-Identifier: Zlib */
2
3 #define _POSIX_SOURCE
4 #define _XOPEN_SOURCE 500
5
6 #include <glib.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/stat.h>
10 #include <fcntl.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <girara/utils.h>
14 #include <girara/datastructures.h>
15 #include <girara/input-history.h>
16 #include <time.h>
17
18 #include "database-plain.h"
19 #include "utils.h"
20
21 #define BOOKMARKS "bookmarks"
22 #define HISTORY "history"
23 #define INPUT_HISTORY "input-history"
24
25 #define KEY_PAGE "page"
26 #define KEY_OFFSET "offset"
27 #define KEY_ZOOM "zoom"
28 #define KEY_ROTATE "rotate"
29 #define KEY_PAGES_PER_ROW "pages-per-row"
30 #define KEY_PAGE_RIGHT_TO_LEFT "page-right-to-left"
31 #define KEY_FIRST_PAGE_COLUMN "first-page-column"
32 #define KEY_POSITION_X "position-x"
33 #define KEY_POSITION_Y "position-y"
34 #define KEY_JUMPLIST "jumplist"
35 #define KEY_TIME "time"
36
37 #ifdef __GNU__
38 #include <sys/file.h>
39
40 #define FILE_LOCK_WRITE LOCK_EX
41 #define FILE_LOCK_READ LOCK_SH
42
43 static int
file_lock_set(int fd,int cmd)44 file_lock_set(int fd, int cmd)
45 {
46 return flock(fd, cmd);
47 }
48 #else
49 #define FILE_LOCK_WRITE F_WRLCK
50 #define FILE_LOCK_READ F_RDLCK
51
52 static int
file_lock_set(int fd,short cmd)53 file_lock_set(int fd, short cmd)
54 {
55 struct flock lock = { .l_type = cmd, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0};
56 return fcntl(fd, F_SETLKW, &lock);
57 }
58 #endif
59
60 static void zathura_database_interface_init(ZathuraDatabaseInterface* iface);
61 static void io_interface_init(GiraraInputHistoryIOInterface* iface);
62
63 typedef struct zathura_plaindatabase_private_s {
64 char* bookmark_path;
65 GKeyFile* bookmarks;
66 GFileMonitor* bookmark_monitor;
67
68 char* history_path;
69 GKeyFile* history;
70 GFileMonitor* history_monitor;
71
72 char* input_history_path;
73 } ZathuraPlainDatabasePrivate;
74
75 G_DEFINE_TYPE_WITH_CODE(ZathuraPlainDatabase, zathura_plaindatabase, G_TYPE_OBJECT,
76 G_IMPLEMENT_INTERFACE(ZATHURA_TYPE_DATABASE, zathura_database_interface_init)
77 G_IMPLEMENT_INTERFACE(GIRARA_TYPE_INPUT_HISTORY_IO, io_interface_init)
78 G_ADD_PRIVATE(ZathuraPlainDatabase))
79
80 enum {
81 PROP_0,
82 PROP_PATH
83 };
84
85 static char*
prepare_filename(const char * file)86 prepare_filename(const char* file)
87 {
88 if (file == NULL) {
89 return NULL;
90 }
91
92 if (strchr(file, '[') == NULL && strchr(file, ']') == NULL) {
93 return g_strdup(file);
94 }
95
96 return g_base64_encode((const guchar*) file, strlen(file));
97 }
98
99 static char*
prepare_hash_key(const uint8_t * hash_sha256)100 prepare_hash_key(const uint8_t* hash_sha256)
101 {
102 return g_base64_encode(hash_sha256, 32);
103 }
104
105 static bool
zathura_db_check_file(const char * path)106 zathura_db_check_file(const char* path)
107 {
108 if (path == NULL) {
109 return false;
110 }
111
112 if (g_file_test(path, G_FILE_TEST_EXISTS) == false) {
113 FILE* file = fopen(path, "w");
114 if (file != NULL) {
115 fclose(file);
116 } else {
117 return false;
118 }
119 } else if (g_file_test(path, G_FILE_TEST_IS_REGULAR) == false) {
120 return false;
121 }
122
123 return true;
124 }
125
126 static GKeyFile*
zathura_db_read_key_file_from_file(const char * path)127 zathura_db_read_key_file_from_file(const char* path)
128 {
129 if (path == NULL) {
130 return NULL;
131 }
132
133 /* open file */
134 FILE* file = fopen(path, "r+");
135 if (file == NULL) {
136 return NULL;
137 }
138 /* and lock it */
139 if (file_lock_set(fileno(file), FILE_LOCK_WRITE) != 0) {
140 fclose(file);
141 return NULL;
142 }
143
144 GKeyFile* key_file = g_key_file_new();
145 if (key_file == NULL) {
146 fclose(file);
147 return NULL;
148 }
149
150 /* read config file */
151 char* content = girara_file_read2(file);
152 fclose(file);
153 if (content == NULL) {
154 g_key_file_free(key_file);
155 return NULL;
156 }
157
158 /* parse config file */
159 size_t contentlen = strlen(content);
160 if (contentlen == 0) {
161 static const char dummy_content[] = "# nothing";
162 static const size_t dummy_len = sizeof(dummy_content) - 1;
163
164 free(content);
165 content = malloc(sizeof(char) * (dummy_len + 1));
166 if (content == NULL)
167 {
168 g_key_file_free(key_file);
169 return NULL;
170 }
171 g_strlcat(content, dummy_content, dummy_len + 1);
172 contentlen = dummy_len;
173 }
174
175 GError* error = NULL;
176 if (g_key_file_load_from_data(key_file, content, contentlen,
177 G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error) ==
178 FALSE) {
179 if (error->code != 1) { /* ignore empty file */
180 free(content);
181 g_key_file_free(key_file);
182 g_error_free(error);
183 return NULL;
184 }
185
186 g_error_free(error);
187 }
188
189 free(content);
190
191 return key_file;
192 }
193
194 static void
zathura_db_write_key_file_to_file(const char * file,GKeyFile * key_file)195 zathura_db_write_key_file_to_file(const char* file, GKeyFile* key_file)
196 {
197 if (file == NULL || key_file == NULL) {
198 return;
199 }
200
201 gchar* content = g_key_file_to_data(key_file, NULL, NULL);
202 if (content == NULL) {
203 return;
204 }
205
206 /* open file */
207 int fd = open(file, O_RDWR | O_TRUNC);
208 if (fd == -1) {
209 g_free(content);
210 return;
211 }
212
213 if (file_lock_set(fd, FILE_LOCK_READ) != 0 || write(fd, content, strlen(content)) == 0) {
214 girara_error("Failed to write to %s", file);
215 }
216 close(fd);
217
218 g_free(content);
219 }
220
221 zathura_database_t*
zathura_plaindatabase_new(const char * path)222 zathura_plaindatabase_new(const char* path)
223 {
224 g_return_val_if_fail(path != NULL && strlen(path) != 0, NULL);
225
226 zathura_database_t* db = g_object_new(ZATHURA_TYPE_PLAINDATABASE, "path", path, NULL);
227 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(ZATHURA_PLAINDATABASE(db));
228 if (priv->bookmark_path == NULL) {
229 g_object_unref(db);
230 return NULL;
231 }
232
233 return db;
234 }
235
236 static void
cb_zathura_db_watch_file(GFileMonitor * UNUSED (monitor),GFile * file,GFile * UNUSED (other_file),GFileMonitorEvent event,zathura_database_t * database)237 cb_zathura_db_watch_file(GFileMonitor* UNUSED(monitor), GFile* file, GFile* UNUSED(other_file),
238 GFileMonitorEvent event, zathura_database_t* database)
239 {
240 if (event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT || database == NULL) {
241 return;
242 }
243
244 char* path = g_file_get_path(file);
245 if (path == NULL) {
246 return;
247 }
248
249 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(database);
250 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
251 if (priv->bookmark_path && strcmp(priv->bookmark_path, path) == 0) {
252 if (priv->bookmarks != NULL) {
253 g_key_file_free(priv->bookmarks);
254 }
255
256 priv->bookmarks = zathura_db_read_key_file_from_file(priv->bookmark_path);
257 } else if (priv->history_path && strcmp(priv->history_path, path) == 0) {
258 if (priv->history != NULL) {
259 g_key_file_free(priv->history);
260 }
261
262 priv->history = zathura_db_read_key_file_from_file(priv->history_path);
263 }
264
265 g_free(path);
266 }
267
268 static void
plain_db_init(ZathuraPlainDatabase * db,const char * dir)269 plain_db_init(ZathuraPlainDatabase* db, const char* dir)
270 {
271 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
272
273 /* bookmarks */
274 priv->bookmark_path = g_build_filename(dir, BOOKMARKS, NULL);
275 if (zathura_db_check_file(priv->bookmark_path) == false) {
276 goto error_free;
277 }
278
279 GFile* bookmark_file = g_file_new_for_path(priv->bookmark_path);
280 if (bookmark_file != NULL) {
281 priv->bookmark_monitor = g_file_monitor(bookmark_file, G_FILE_MONITOR_NONE, NULL, NULL);
282 } else {
283 goto error_free;
284 }
285
286 g_object_unref(bookmark_file);
287
288 g_signal_connect(
289 G_OBJECT(priv->bookmark_monitor),
290 "changed",
291 G_CALLBACK(cb_zathura_db_watch_file),
292 db
293 );
294
295 priv->bookmarks = zathura_db_read_key_file_from_file(priv->bookmark_path);
296 if (priv->bookmarks == NULL) {
297 goto error_free;
298 }
299
300 /* history */
301 priv->history_path = g_build_filename(dir, HISTORY, NULL);
302 if (zathura_db_check_file(priv->history_path) == false) {
303 goto error_free;
304 }
305
306 GFile* history_file = g_file_new_for_path(priv->history_path);
307 if (history_file != NULL) {
308 priv->history_monitor = g_file_monitor(history_file, G_FILE_MONITOR_NONE, NULL, NULL);
309 } else {
310 goto error_free;
311 }
312
313 g_object_unref(history_file);
314
315 g_signal_connect(
316 G_OBJECT(priv->history_monitor),
317 "changed",
318 G_CALLBACK(cb_zathura_db_watch_file),
319 db
320 );
321
322 priv->history = zathura_db_read_key_file_from_file(priv->history_path);
323 if (priv->history == NULL) {
324 goto error_free;
325 }
326
327 /* input history */
328 priv->input_history_path = g_build_filename(dir, INPUT_HISTORY, NULL);
329 if (zathura_db_check_file(priv->input_history_path) == false) {
330 goto error_free;
331 }
332
333 return;
334
335 error_free:
336
337 /* bookmarks */
338 g_free(priv->bookmark_path);
339 priv->bookmark_path = NULL;
340
341 g_clear_object(&priv->bookmark_monitor);
342
343 if (priv->bookmarks != NULL) {
344 g_key_file_free(priv->bookmarks);
345 priv->bookmarks = NULL;
346 }
347
348 /* history */
349 g_free(priv->history_path);
350 priv->history_path = NULL;
351
352 g_clear_object(&priv->history_monitor);
353
354 if (priv->history != NULL) {
355 g_key_file_free(priv->history);
356 priv->history = NULL;
357 }
358
359 /* input history */
360 g_free(priv->input_history_path);
361 priv->input_history_path = NULL;
362 }
363
364 static void
plain_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)365 plain_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
366 {
367 ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
368
369 switch (prop_id) {
370 case PROP_PATH:
371 plain_db_init(db, g_value_get_string(value));
372 break;
373 default:
374 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
375 }
376 }
377
378 static void
plain_dispose(GObject * object)379 plain_dispose(GObject* object)
380 {
381 ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
382 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
383
384 g_clear_object(&priv->bookmark_monitor);
385 g_clear_object(&priv->history_monitor);
386
387 G_OBJECT_CLASS(zathura_plaindatabase_parent_class)->dispose(object);
388 }
389
390 static void
plain_finalize(GObject * object)391 plain_finalize(GObject* object)
392 {
393 ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
394 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
395
396 /* bookmarks */
397 g_free(priv->bookmark_path);
398
399 if (priv->bookmarks != NULL) {
400 g_key_file_free(priv->bookmarks);
401 }
402
403 /* history */
404 g_free(priv->history_path);
405
406 if (priv->history != NULL) {
407 g_key_file_free(priv->history);
408 }
409
410 /* input history */
411 g_free(priv->input_history_path);
412
413 G_OBJECT_CLASS(zathura_plaindatabase_parent_class)->finalize(object);
414 }
415
416 static bool
plain_add_bookmark(zathura_database_t * db,const char * file,zathura_bookmark_t * bookmark)417 plain_add_bookmark(zathura_database_t* db, const char* file,
418 zathura_bookmark_t* bookmark)
419 {
420 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
421 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
422 if (priv->bookmarks == NULL || priv->bookmark_path == NULL ||
423 bookmark->id == NULL) {
424 return false;
425 }
426
427 char* name = prepare_filename(file);
428 char* val_list[] = {
429 g_strdup_printf("%d", bookmark->page),
430 g_try_malloc0(G_ASCII_DTOSTR_BUF_SIZE),
431 g_try_malloc0(G_ASCII_DTOSTR_BUF_SIZE)
432 };
433 if (name == NULL || val_list[1] == NULL || val_list[2] == NULL) {
434 g_free(name);
435 for (unsigned int i = 0; i < LENGTH(val_list); ++i) {
436 g_free(val_list[i]);
437 }
438 return false;
439 }
440
441 g_ascii_dtostr(val_list[1], G_ASCII_DTOSTR_BUF_SIZE, bookmark->x);
442 g_ascii_dtostr(val_list[2], G_ASCII_DTOSTR_BUF_SIZE, bookmark->y);
443
444 g_key_file_set_string_list(priv->bookmarks, name, bookmark->id, (const char**)val_list, LENGTH(val_list));
445
446 for (unsigned int i = 0; i < LENGTH(val_list); ++i) {
447 g_free(val_list[i]);
448 }
449 g_free(name);
450
451 zathura_db_write_key_file_to_file(priv->bookmark_path, priv->bookmarks);
452
453 return true;
454 }
455
456 static bool
plain_remove_bookmark(zathura_database_t * db,const char * file,const char * id)457 plain_remove_bookmark(zathura_database_t* db, const char* file, const char* id)
458 {
459 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
460 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
461 if (priv->bookmarks == NULL || priv->bookmark_path == NULL) {
462 return false;
463 }
464
465 char* name = prepare_filename(file);
466 if (g_key_file_has_group(priv->bookmarks, name) == TRUE) {
467 if (g_key_file_remove_key(priv->bookmarks, name, id, NULL) == TRUE) {
468
469 zathura_db_write_key_file_to_file(priv->bookmark_path, priv->bookmarks);
470 g_free(name);
471
472 return true;
473 }
474 }
475 g_free(name);
476
477 return false;
478 }
479
480 static girara_list_t*
plain_load_bookmarks(zathura_database_t * db,const char * file)481 plain_load_bookmarks(zathura_database_t* db, const char* file)
482 {
483 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
484 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
485 if (priv->bookmarks == NULL) {
486 return NULL;
487 }
488
489 char* name = prepare_filename(file);
490 if (g_key_file_has_group(priv->bookmarks, name) == FALSE) {
491 g_free(name);
492 return NULL;
493 }
494
495 girara_list_t* result = bookmarks_list_new();
496
497 gsize num_keys;
498 char** keys = g_key_file_get_keys(priv->bookmarks, name, &num_keys, NULL);
499 if (keys == NULL) {
500 girara_list_free(result);
501 g_free(name);
502 return NULL;
503 }
504
505 gsize num_vals = 0;
506
507 for (gsize i = 0; i < num_keys; i++) {
508 zathura_bookmark_t* bookmark = g_try_malloc0(sizeof(zathura_bookmark_t));
509 if (bookmark == NULL) {
510 continue;
511 }
512
513 bookmark->id = g_strdup(keys[i]);
514 char** val_list = g_key_file_get_string_list(priv->bookmarks, name, keys[i],
515 &num_vals, NULL);
516
517 if (num_vals != 1 && num_vals != 3) {
518 girara_error("Unexpected number of values.");
519 g_free(bookmark);
520 g_strfreev(val_list);
521 continue;
522 }
523
524 bookmark->page = atoi(val_list[0]);
525
526 if (num_vals == 3) {
527 bookmark->x = g_ascii_strtod(val_list[1], NULL);
528 bookmark->y = g_ascii_strtod(val_list[2], NULL);
529 } else if (num_vals == 1) {
530 bookmark->x = DBL_MIN;
531 bookmark->y = DBL_MIN;
532 }
533
534 girara_list_append(result, bookmark);
535 g_strfreev(val_list);
536 }
537
538 g_free(name);
539 g_strfreev(keys);
540
541 return result;
542 }
543
544 static girara_list_t*
get_jumplist_from_str(const char * str)545 get_jumplist_from_str(const char* str)
546 {
547 g_return_val_if_fail(str != NULL, NULL);
548
549 if (*str == '\0') {
550 return girara_list_new2(g_free);
551 }
552
553 girara_list_t* result = girara_list_new2(g_free);
554 char* copy = g_strdup(str);
555 char* saveptr = NULL;
556 char* token = strtok_r(copy, " ", &saveptr);
557
558 while (token != NULL) {
559 zathura_jump_t* jump = g_try_malloc0(sizeof(zathura_jump_t));
560 if (jump == NULL) {
561 continue;
562 }
563
564 jump->page = strtoul(token, NULL, 0);
565 token = strtok_r(NULL, " ", &saveptr);
566 if (token == NULL) {
567 girara_warning("Could not parse jumplist information.");
568 g_free(jump);
569 break;
570 }
571 jump->x = g_ascii_strtod(token, NULL);
572
573 token = strtok_r(NULL, " ", &saveptr);
574 if (token == NULL) {
575 girara_warning("Could not parse jumplist information.");
576 g_free(jump);
577 break;
578 }
579 jump->y = g_ascii_strtod(token, NULL);
580
581 girara_list_append(result, jump);
582 token = strtok_r(NULL, " ", &saveptr);
583 }
584
585 g_free(copy);
586
587 return result;
588 }
589
590 static girara_list_t*
plain_load_jumplist(zathura_database_t * db,const char * file)591 plain_load_jumplist(zathura_database_t* db, const char* file)
592 {
593 g_return_val_if_fail(db != NULL && file != NULL, NULL);
594
595 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
596 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
597
598 char* str_value = g_key_file_get_string(priv->history, file, KEY_JUMPLIST, NULL);
599 if (str_value == NULL) {
600 return girara_list_new2(g_free);
601 }
602
603 girara_list_t* list = get_jumplist_from_str(str_value);
604 g_free(str_value);
605 return list;
606 }
607
608 static void
jump_to_str(void * data,void * userdata)609 jump_to_str(void* data, void* userdata)
610 {
611 const zathura_jump_t* jump = data;
612 GString* str_val = userdata;
613
614 char buffer[G_ASCII_DTOSTR_BUF_SIZE] = { '\0' };
615
616 g_string_append_printf(str_val, "%d ", jump->page);
617 g_string_append(str_val, g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, jump->x));
618 g_string_append_c(str_val, ' ');
619 g_string_append(str_val, g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, jump->y));
620 g_string_append_c(str_val, ' ');
621 }
622
623 static bool
plain_save_jumplist(zathura_database_t * db,const char * file,girara_list_t * jumplist)624 plain_save_jumplist(zathura_database_t* db, const char* file, girara_list_t* jumplist)
625 {
626 g_return_val_if_fail(db != NULL && file != NULL && jumplist != NULL, false);
627
628 GString* str_val = g_string_new(NULL);
629 girara_list_foreach(jumplist, jump_to_str, str_val);
630
631 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
632 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
633
634 g_key_file_set_string(priv->history, file, KEY_JUMPLIST, str_val->str);
635 zathura_db_write_key_file_to_file(priv->history_path, priv->history);
636 g_string_free(str_val, TRUE);
637
638 return true;
639 }
640
641 static bool
plain_set_fileinfo(zathura_database_t * db,const char * file,const uint8_t * hash_sha256,zathura_fileinfo_t * file_info)642 plain_set_fileinfo(zathura_database_t* db, const char* file, const uint8_t* hash_sha256,
643 zathura_fileinfo_t* file_info)
644 {
645 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
646 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
647 if (priv->history == NULL || file_info == NULL || hash_sha256 == NULL || file == NULL) {
648 return false;
649 }
650
651 char* name = prepare_filename(file);
652
653 g_key_file_set_integer(priv->history, name, KEY_PAGE, file_info->current_page);
654 g_key_file_set_integer(priv->history, name, KEY_OFFSET, file_info->page_offset);
655 g_key_file_set_double (priv->history, name, KEY_ZOOM, file_info->zoom);
656 g_key_file_set_integer(priv->history, name, KEY_ROTATE, file_info->rotation);
657 g_key_file_set_integer(priv->history, name, KEY_PAGES_PER_ROW, file_info->pages_per_row);
658 g_key_file_set_string (priv->history, name, KEY_FIRST_PAGE_COLUMN, file_info->first_page_column_list);
659 g_key_file_set_boolean(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, file_info->page_right_to_left);
660 g_key_file_set_double (priv->history, name, KEY_POSITION_X, file_info->position_x);
661 g_key_file_set_double (priv->history, name, KEY_POSITION_Y, file_info->position_y);
662 g_key_file_set_integer(priv->history, name, KEY_TIME, time(NULL));
663
664 g_free(name);
665 name = prepare_hash_key(hash_sha256);
666
667 g_key_file_set_integer(priv->history, name, KEY_PAGE, file_info->current_page);
668 g_key_file_set_integer(priv->history, name, KEY_OFFSET, file_info->page_offset);
669 g_key_file_set_double (priv->history, name, KEY_ZOOM, file_info->zoom);
670 g_key_file_set_integer(priv->history, name, KEY_ROTATE, file_info->rotation);
671 g_key_file_set_integer(priv->history, name, KEY_PAGES_PER_ROW, file_info->pages_per_row);
672 g_key_file_set_string (priv->history, name, KEY_FIRST_PAGE_COLUMN, file_info->first_page_column_list);
673 g_key_file_set_boolean(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, file_info->page_right_to_left);
674 g_key_file_set_double (priv->history, name, KEY_POSITION_X, file_info->position_x);
675 g_key_file_set_double (priv->history, name, KEY_POSITION_Y, file_info->position_y);
676 g_key_file_set_integer(priv->history, name, KEY_TIME, time(NULL));
677
678 g_free(name);
679
680 zathura_db_write_key_file_to_file(priv->history_path, priv->history);
681
682 return true;
683 }
684
685 static bool
plain_get_fileinfo(zathura_database_t * db,const char * file,const uint8_t * hash_sha256,zathura_fileinfo_t * file_info)686 plain_get_fileinfo(zathura_database_t* db, const char* file, const uint8_t* hash_sha256,
687 zathura_fileinfo_t* file_info)
688 {
689 if (db == NULL || file == NULL || hash_sha256 == NULL || file_info == NULL) {
690 return false;
691 }
692
693 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
694 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
695 if (priv->history == NULL) {
696 return false;
697 }
698
699 char* name = prepare_filename(file);
700 if (g_key_file_has_group(priv->history, name) == FALSE) {
701 g_free(name);
702 name = prepare_hash_key(hash_sha256);
703 if (g_key_file_has_group(priv->history, name) == FALSE) {
704 g_free(name);
705 return false;
706 }
707 }
708
709 file_info->current_page = g_key_file_get_integer(priv->history, name, KEY_PAGE, NULL);
710 file_info->page_offset = g_key_file_get_integer(priv->history, name, KEY_OFFSET, NULL);
711 file_info->zoom = g_key_file_get_double (priv->history, name, KEY_ZOOM, NULL);
712 file_info->rotation = g_key_file_get_integer(priv->history, name, KEY_ROTATE, NULL);
713
714 /* the following flags got introduced at a later point */
715 if (g_key_file_has_key(priv->history, name, KEY_PAGES_PER_ROW, NULL) == TRUE) {
716 file_info->pages_per_row = g_key_file_get_integer(priv->history, name, KEY_PAGES_PER_ROW, NULL);
717 }
718 if (g_key_file_has_key(priv->history, name, KEY_FIRST_PAGE_COLUMN, NULL) == TRUE) {
719 file_info->first_page_column_list = g_key_file_get_string(priv->history, name, KEY_FIRST_PAGE_COLUMN, NULL);
720 }
721 if (g_key_file_has_key(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, NULL) == TRUE) {
722 file_info->page_right_to_left = g_key_file_get_boolean(priv->history, name, KEY_PAGE_RIGHT_TO_LEFT, NULL);
723 }
724 if (g_key_file_has_key(priv->history, name, KEY_POSITION_X, NULL) == TRUE) {
725 file_info->position_x = g_key_file_get_double(priv->history, name, KEY_POSITION_X, NULL);
726 }
727 if (g_key_file_has_key(priv->history, name, KEY_POSITION_Y, NULL) == TRUE) {
728 file_info->position_y = g_key_file_get_double(priv->history, name, KEY_POSITION_Y, NULL);
729 }
730
731 g_free(name);
732
733 return true;
734 }
735
736 static girara_list_t*
plain_io_read(GiraraInputHistoryIO * db)737 plain_io_read(GiraraInputHistoryIO* db)
738 {
739 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
740 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
741
742 /* open file */
743 FILE* file = fopen(priv->input_history_path, "r");
744 if (file == NULL) {
745 return NULL;
746 }
747
748 /* read input history file */
749 if (file_lock_set(fileno(file), FILE_LOCK_READ) != 0) {
750 fclose(file);
751 return NULL;
752 }
753 char* content = girara_file_read2(file);
754 fclose(file);
755
756 girara_list_t* res = girara_list_new2(g_free);
757 char** tmp = g_strsplit(content, "\n", 0);
758 for (size_t i = 0; tmp[i] != NULL; ++i) {
759 if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL) {
760 continue;
761 }
762 girara_list_append(res, g_strdup(tmp[i]));
763 }
764 g_strfreev(tmp);
765 free(content);
766
767 return res;
768 }
769
770 static void
plain_io_append(GiraraInputHistoryIO * db,const char * input)771 plain_io_append(GiraraInputHistoryIO* db, const char* input)
772 {
773 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
774 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
775
776 /* open file */
777 FILE* file = fopen(priv->input_history_path, "r+");
778 if (file == NULL) {
779 return;
780 }
781
782 /* read input history file */
783 if (file_lock_set(fileno(file), FILE_LOCK_WRITE) != 0) {
784 fclose(file);
785 return;
786 }
787 char* content = girara_file_read2(file);
788
789 rewind(file);
790 if (ftruncate(fileno(file), 0) != 0) {
791 free(content);
792 fclose(file);
793 return;
794 }
795
796 char** tmp = g_strsplit(content, "\n", 0);
797 free(content);
798
799 /* write input history file */
800 for (size_t i = 0; tmp[i] != NULL; ++i) {
801 if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL || strcmp(tmp[i], input) == 0) {
802 continue;
803 }
804 fprintf(file, "%s\n", tmp[i]);
805 }
806 g_strfreev(tmp);
807 fprintf(file, "%s\n", input);
808 fclose(file);
809 }
810
811 static int
compare_time(const void * l,const void * r,void * data)812 compare_time(const void* l, const void* r, void* data)
813 {
814 const gchar* lhs = *(const gchar**) l;
815 const gchar* rhs = *(const gchar**) r;
816 GKeyFile* keyfile = data;
817
818 time_t lhs_time = 0;
819 time_t rhs_time = 0;
820
821 if (g_key_file_has_key(keyfile, lhs, KEY_TIME, NULL) == TRUE) {
822 lhs_time = g_key_file_get_uint64(keyfile, lhs, KEY_TIME, NULL);
823 }
824 if (g_key_file_has_key(keyfile, rhs, KEY_TIME, NULL) == TRUE) {
825 rhs_time = g_key_file_get_uint64(keyfile, rhs, KEY_TIME, NULL);
826 }
827
828 if (lhs_time < rhs_time) {
829 return 1;
830 } else if (lhs_time > rhs_time) {
831 return -1;
832 }
833 return 0;
834 }
835
836 static girara_list_t*
plain_get_recent_files(zathura_database_t * db,int max,const char * basepath)837 plain_get_recent_files(zathura_database_t* db, int max, const char* basepath)
838 {
839 ZathuraPlainDatabase* plaindb = ZATHURA_PLAINDATABASE(db);
840 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(plaindb);
841
842 girara_list_t* result = girara_list_new2(g_free);
843 if (result == NULL) {
844 return NULL;
845 }
846
847 gsize groups_size = 0;
848 gchar** groups = g_key_file_get_groups(priv->history, &groups_size);
849
850 if (groups_size > 0) {
851 g_qsort_with_data(groups, groups_size, sizeof(gchar*), compare_time, priv->history);
852 }
853
854 const size_t basepath_len = basepath != NULL ? strlen(basepath) : 0;
855
856 for (gsize s = 0; s != groups_size && max != 0; ++s) {
857 if (basepath != NULL && strncmp(groups[s], basepath, basepath_len) != 0) {
858 continue;
859 }
860
861 girara_list_append(result, g_strdup(groups[s]));
862 --max;
863 }
864 g_strfreev(groups);
865
866 return result;
867 }
868
869 static void
zathura_database_interface_init(ZathuraDatabaseInterface * iface)870 zathura_database_interface_init(ZathuraDatabaseInterface* iface)
871 {
872 /* initialize interface */
873 iface->add_bookmark = plain_add_bookmark;
874 iface->remove_bookmark = plain_remove_bookmark;
875 iface->load_bookmarks = plain_load_bookmarks;
876 iface->load_jumplist = plain_load_jumplist;
877 iface->save_jumplist = plain_save_jumplist;
878 iface->set_fileinfo = plain_set_fileinfo;
879 iface->get_fileinfo = plain_get_fileinfo;
880 iface->get_recent_files = plain_get_recent_files;
881 }
882
883 static void
io_interface_init(GiraraInputHistoryIOInterface * iface)884 io_interface_init(GiraraInputHistoryIOInterface* iface)
885 {
886 /* initialize interface */
887 iface->append = plain_io_append;
888 iface->read = plain_io_read;
889 }
890
891 static void
zathura_plaindatabase_class_init(ZathuraPlainDatabaseClass * class)892 zathura_plaindatabase_class_init(ZathuraPlainDatabaseClass* class)
893 {
894 /* override methods */
895 GObjectClass* object_class = G_OBJECT_CLASS(class);
896 object_class->dispose = plain_dispose;
897 object_class->finalize = plain_finalize;
898 object_class->set_property = plain_set_property;
899
900 g_object_class_install_property(object_class, PROP_PATH,
901 g_param_spec_string("path", "path", "path to directory where the bookmarks and history are locates",
902 NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
903 }
904
905 static void
zathura_plaindatabase_init(ZathuraPlainDatabase * db)906 zathura_plaindatabase_init(ZathuraPlainDatabase* db)
907 {
908 ZathuraPlainDatabasePrivate* priv = zathura_plaindatabase_get_instance_private(db);
909
910 priv->bookmark_path = NULL;
911 priv->bookmark_monitor = NULL;
912 priv->bookmarks = NULL;
913 priv->history_path = NULL;
914 priv->history_monitor = NULL;
915 priv->history = NULL;
916 priv->input_history_path = NULL;
917 }
918