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