database-plain.c 22.9 KB
Newer Older
Moritz Lipp's avatar
Moritz Lipp committed
1 2
/* See LICENSE file for license and copyright information */

3
#define _POSIX_SOURCE
4
#define _XOPEN_SOURCE 500
5

6 7 8
#include <glib.h>
#include <stdlib.h>
#include <string.h>
9 10
#include <sys/stat.h>
#include <fcntl.h>
11
#include <unistd.h>
12 13
#include <girara/utils.h>
#include <girara/datastructures.h>
14
#include <girara/input-history.h>
Moritz Lipp's avatar
Moritz Lipp committed
15

16
#include "database-plain.h"
17
#include "utils.h"
Moritz Lipp's avatar
Moritz Lipp committed
18

19 20 21 22 23 24 25 26 27 28 29 30 31
#define BOOKMARKS                 "bookmarks"
#define HISTORY                   "history"
#define INPUT_HISTORY             "input-history"

#define KEY_PAGE                  "page"
#define KEY_OFFSET                "offset"
#define KEY_SCALE                 "scale"
#define KEY_ROTATE                "rotate"
#define KEY_PAGES_PER_ROW         "pages-per-row"
#define KEY_FIRST_PAGE_COLUMN     "first-page-column"
#define KEY_POSITION_X            "position-x"
#define KEY_POSITION_Y            "position-y"
#define KEY_JUMPLIST              "jumplist"
32

33 34 35 36
#ifdef __GNU__
#include <sys/file.h>
#define file_lock_set(fd, cmd) flock(fd, cmd)
#else
37 38 39
#define file_lock_set(fd, cmd) \
  { \
  struct flock lock = { .l_type = cmd, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0}; \
Sebastian Ramacher's avatar
Sebastian Ramacher committed
40
  fcntl(fd, F_SETLK, lock); \
41
  }
42
#endif
43

44
static void zathura_database_interface_init(ZathuraDatabaseInterface* iface);
45
static void io_interface_init(GiraraInputHistoryIOInterface* iface);
46 47

G_DEFINE_TYPE_WITH_CODE(ZathuraPlainDatabase, zathura_plaindatabase, G_TYPE_OBJECT,
48 49
                        G_IMPLEMENT_INTERFACE(ZATHURA_TYPE_DATABASE, zathura_database_interface_init)
                        G_IMPLEMENT_INTERFACE(GIRARA_TYPE_INPUT_HISTORY_IO, io_interface_init))
50

51
static void           plain_dispose(GObject* object);
52 53 54 55 56 57 58 59 60 61
static void           plain_finalize(GObject* object);
static bool           plain_add_bookmark(zathura_database_t* db, const char* file, zathura_bookmark_t* bookmark);
static bool           plain_remove_bookmark(zathura_database_t* db, const char* file, const char* id);
static girara_list_t* plain_load_bookmarks(zathura_database_t* db, const char* file);
static girara_list_t* plain_load_jumplist(zathura_database_t* db, const char* file);
static bool           plain_save_jumplist(zathura_database_t* db, const char* file, girara_list_t* jumplist);
static bool           plain_set_fileinfo(zathura_database_t* db, const char* file, zathura_fileinfo_t* file_info);
static bool           plain_get_fileinfo(zathura_database_t* db, const char* file, zathura_fileinfo_t* file_info);
static void           plain_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec);
static void           plain_io_append(GiraraInputHistoryIO* db, const char*);
62
static girara_list_t* plain_io_read(GiraraInputHistoryIO* db);
63

64
/* forward declaration */
65 66 67 68
static bool           zathura_db_check_file(const char* path);
static GKeyFile*      zathura_db_read_key_file_from_file(const char* path);
static void           zathura_db_write_key_file_to_file(const char* file, GKeyFile* key_file);
static void           cb_zathura_db_watch_file(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event, zathura_database_t* database);
69

70
typedef struct zathura_plaindatabase_private_s {
71
  char* bookmark_path;
72
  GKeyFile* bookmarks;
73 74 75
  GFileMonitor* bookmark_monitor;

  char* history_path;
76
  GKeyFile* history;
77
  GFileMonitor* history_monitor;
78 79

  char* input_history_path;
80 81 82 83 84
} zathura_plaindatabase_private_t;

#define ZATHURA_PLAINDATABASE_GET_PRIVATE(obj) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ZATHURA_TYPE_PLAINDATABASE, zathura_plaindatabase_private_t))

Moritz Lipp's avatar
Moritz Lipp committed
85
enum {
86 87
  PROP_0,
  PROP_PATH
Moritz Lipp's avatar
Moritz Lipp committed
88 89
};

90 91 92 93 94 95 96 97 98 99 100 101 102 103
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));
}

104 105 106 107
static void
zathura_database_interface_init(ZathuraDatabaseInterface* iface)
{
  /* initialize interface */
Moritz Lipp's avatar
Moritz Lipp committed
108
  iface->add_bookmark    = plain_add_bookmark;
109
  iface->remove_bookmark = plain_remove_bookmark;
Moritz Lipp's avatar
Moritz Lipp committed
110
  iface->load_bookmarks  = plain_load_bookmarks;
111 112
  iface->load_jumplist   = plain_load_jumplist;
  iface->save_jumplist   = plain_save_jumplist;
Moritz Lipp's avatar
Moritz Lipp committed
113 114
  iface->set_fileinfo    = plain_set_fileinfo;
  iface->get_fileinfo    = plain_get_fileinfo;
115 116
}

117 118 119 120 121 122 123 124
static void
io_interface_init(GiraraInputHistoryIOInterface* iface)
{
  /* initialize interface */
  iface->append = plain_io_append;
  iface->read = plain_io_read;
}

125 126 127 128 129 130 131 132
static void
zathura_plaindatabase_class_init(ZathuraPlainDatabaseClass* class)
{
  /* add private members */
  g_type_class_add_private(class, sizeof(zathura_plaindatabase_private_t));

  /* override methods */
  GObjectClass* object_class = G_OBJECT_CLASS(class);
133
  object_class->dispose      = plain_dispose;
Moritz Lipp's avatar
Moritz Lipp committed
134
  object_class->finalize     = plain_finalize;
135 136 137
  object_class->set_property = plain_set_property;

  g_object_class_install_property(object_class, PROP_PATH,
138 139
    g_param_spec_string("path", "path", "path to directory where the bookmarks and history are locates",
      NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
140 141 142 143 144 145
}

static void
zathura_plaindatabase_init(ZathuraPlainDatabase* db)
{
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
Moritz Lipp's avatar
Moritz Lipp committed
146

147 148 149 150 151 152 153
  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;
154 155
}

Moritz Lipp's avatar
Moritz Lipp committed
156
zathura_database_t*
157
zathura_plaindatabase_new(const char* path)
Moritz Lipp's avatar
Moritz Lipp committed
158
{
159
  g_return_val_if_fail(path != NULL && strlen(path) != 0, NULL);
160

161 162 163 164 165
  zathura_database_t* db = g_object_new(ZATHURA_TYPE_PLAINDATABASE, "path", path, NULL);
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
  if (priv->bookmark_path == NULL) {
    g_object_unref(db);
    return NULL;
166
  }
Moritz Lipp's avatar
Moritz Lipp committed
167

168 169 170 171 172 173 174
  return db;
}

static void
plain_db_init(ZathuraPlainDatabase* db, const char* dir)
{
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
175 176

  /* bookmarks */
177 178
  priv->bookmark_path = g_build_filename(dir, BOOKMARKS, NULL);
  if (zathura_db_check_file(priv->bookmark_path) == false) {
179 180 181
    goto error_free;
  }

182
  GFile* bookmark_file = g_file_new_for_path(priv->bookmark_path);
183
  if (bookmark_file != NULL) {
184
    priv->bookmark_monitor = g_file_monitor(bookmark_file, G_FILE_MONITOR_NONE, NULL, NULL);
185 186 187
  } else {
    goto error_free;
  }
Moritz Lipp's avatar
Moritz Lipp committed
188

189 190
  g_object_unref(bookmark_file);

191
  g_signal_connect(
Moritz Lipp's avatar
Moritz Lipp committed
192 193 194 195
    G_OBJECT(priv->bookmark_monitor),
    "changed",
    G_CALLBACK(cb_zathura_db_watch_file),
    db
196 197
  );

198 199
  priv->bookmarks = zathura_db_read_key_file_from_file(priv->bookmark_path);
  if (priv->bookmarks == NULL) {
200 201 202
    goto error_free;
  }

203
  /* history */
204 205
  priv->history_path = g_build_filename(dir, HISTORY, NULL);
  if (zathura_db_check_file(priv->history_path) == false) {
206 207 208
    goto error_free;
  }

209
  GFile* history_file = g_file_new_for_path(priv->history_path);
210
  if (history_file != NULL) {
211
    priv->history_monitor = g_file_monitor(history_file, G_FILE_MONITOR_NONE, NULL, NULL);
212 213 214
  } else {
    goto error_free;
  }
Moritz Lipp's avatar
Moritz Lipp committed
215

216 217
  g_object_unref(history_file);

218
  g_signal_connect(
Moritz Lipp's avatar
Moritz Lipp committed
219 220 221 222
    G_OBJECT(priv->history_monitor),
    "changed",
    G_CALLBACK(cb_zathura_db_watch_file),
    db
223 224
  );

225 226
  priv->history = zathura_db_read_key_file_from_file(priv->history_path);
  if (priv->history == NULL) {
227 228
    goto error_free;
  }
Moritz Lipp's avatar
Moritz Lipp committed
229

230 231 232 233 234 235
  /* 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;
  }

236
  return;
237 238 239

error_free:

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
  /* bookmarks */
  g_free(priv->bookmark_path);
  priv->bookmark_path = NULL;

  if (priv->bookmark_monitor != NULL) {
    g_object_unref(priv->bookmark_monitor);
    priv->bookmark_monitor = NULL;
  }

  if (priv->bookmarks != NULL) {
    g_key_file_free(priv->bookmarks);
    priv->bookmarks = NULL;
  }

  /* history */
  g_free(priv->history_path);
  priv->history_path = NULL;
257

258 259 260 261
  if (priv->history_monitor != NULL) {
    g_object_unref(priv->history_monitor);
    priv->history_monitor = NULL;
  }
262

263 264 265 266
  if (priv->history != NULL) {
    g_key_file_free(priv->history);
    priv->history = NULL;
  }
267 268 269 270

  /* input history */
  g_free(priv->input_history_path);
  priv->input_history_path = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
271 272
}

273 274
static void
plain_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
Moritz Lipp's avatar
Moritz Lipp committed
275
{
276 277 278 279 280 281 282 283
  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);
284
  }
285
}
286

287 288 289 290 291 292 293 294 295 296 297 298
static void
plain_dispose(GObject* object)
{
  ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);

  g_clear_object(&priv->bookmark_monitor);
  g_clear_object(&priv->history_monitor);

  G_OBJECT_CLASS(zathura_plaindatabase_parent_class)->dispose(object);
}

299 300 301 302 303
static void
plain_finalize(GObject* object)
{
  ZathuraPlainDatabase* db = ZATHURA_PLAINDATABASE(object);
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
Moritz Lipp's avatar
Moritz Lipp committed
304

305
  /* bookmarks */
306
  g_free(priv->bookmark_path);
307

308 309
  if (priv->bookmarks != NULL) {
    g_key_file_free(priv->bookmarks);
310
  }
311 312

  /* history */
313
  g_free(priv->history_path);
314

315 316 317 318
  if (priv->history != NULL) {
    g_key_file_free(priv->history);
  }

319 320 321
  /* input history */
  g_free(priv->input_history_path);

322
  G_OBJECT_CLASS(zathura_plaindatabase_parent_class)->finalize(object);
Moritz Lipp's avatar
Moritz Lipp committed
323 324
}

325 326
static bool
plain_add_bookmark(zathura_database_t* db, const char* file,
Moritz Lipp's avatar
Moritz Lipp committed
327
                   zathura_bookmark_t* bookmark)
Moritz Lipp's avatar
Moritz Lipp committed
328
{
329
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
Moritz Lipp's avatar
Moritz Lipp committed
330
  if (priv->bookmarks == NULL || priv->bookmark_path == NULL ||
331
      bookmark->id == NULL) {
332 333 334
    return false;
  }

335
  char* name = prepare_filename(file);
336 337
  char* val_list[] = {
    g_strdup_printf("%d", bookmark->page),
338 339
    g_try_malloc0(G_ASCII_DTOSTR_BUF_SIZE),
    g_try_malloc0(G_ASCII_DTOSTR_BUF_SIZE)
340
  };
341 342 343 344 345 346 347
  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;
  }
348

349 350
  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);
351

352
  g_key_file_set_string_list(priv->bookmarks, name, bookmark->id, (const char**)val_list, LENGTH(val_list));
353

354
  for (unsigned int i = 0; i < LENGTH(val_list); ++i) {
355 356
    g_free(val_list[i]);
  }
357
  g_free(name);
358

359
  zathura_db_write_key_file_to_file(priv->bookmark_path, priv->bookmarks);
360 361

  return true;
Moritz Lipp's avatar
Moritz Lipp committed
362 363
}

364
static bool
365
plain_remove_bookmark(zathura_database_t* db, const char* file, const char* id)
Moritz Lipp's avatar
Moritz Lipp committed
366
{
367 368
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
  if (priv->bookmarks == NULL || priv->bookmark_path == NULL) {
369 370 371
    return false;
  }

372 373
  char* name = prepare_filename(file);
  if (g_key_file_has_group(priv->bookmarks, name) == TRUE) {
374
    if (g_key_file_remove_key(priv->bookmarks, name, id, NULL) == TRUE) {
375

376 377
      zathura_db_write_key_file_to_file(priv->bookmark_path, priv->bookmarks);
      g_free(name);
378

379 380
      return true;
    }
381
  }
382
  g_free(name);
383

Moritz Lipp's avatar
Moritz Lipp committed
384 385 386
  return false;
}

387 388
static girara_list_t*
plain_load_bookmarks(zathura_database_t* db, const char* file)
Moritz Lipp's avatar
Moritz Lipp committed
389
{
390 391
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
  if (priv->bookmarks == NULL) {
392 393 394
    return NULL;
  }

395 396 397
  char* name = prepare_filename(file);
  if (g_key_file_has_group(priv->bookmarks, name) == FALSE) {
    g_free(name);
398 399 400
    return NULL;
  }

Moritz Lipp's avatar
Moritz Lipp committed
401
  girara_list_t* result = girara_sorted_list_new2((girara_compare_function_t)
Moritz Lipp's avatar
Moritz Lipp committed
402 403
                          zathura_bookmarks_compare, (girara_free_function_t)
                          zathura_bookmark_free);
404

405 406
  gsize num_keys;
  char** keys = g_key_file_get_keys(priv->bookmarks, name, &num_keys, NULL);
407 408
  if (keys == NULL) {
    girara_list_free(result);
409
    g_free(name);
410 411 412
    return NULL;
  }

413 414 415
  gsize num_vals = 0;

  for (gsize i = 0; i < num_keys; i++) {
416 417 418 419
    zathura_bookmark_t* bookmark = g_try_malloc0(sizeof(zathura_bookmark_t));
    if (bookmark == NULL) {
      continue;
    }
420

421
    bookmark->id    = g_strdup(keys[i]);
422 423 424 425 426 427 428 429 430
    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;
    }
431 432 433 434 435 436 437 438 439 440

    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;
    }
441 442

    girara_list_append(result, bookmark);
443
    g_strfreev(val_list);
444 445
  }

446
  g_free(name);
447 448
  g_strfreev(keys);

449
  return result;
Moritz Lipp's avatar
Moritz Lipp committed
450 451
}

452 453 454 455 456
static girara_list_t*
get_jumplist_from_str(const char* str)
{
  g_return_val_if_fail(str != NULL, NULL);

457
  if (*str == '\0') {
458 459 460 461 462
    return girara_list_new2(g_free);
  }

  girara_list_t* result = girara_list_new2(g_free);
  char* copy = g_strdup(str);
463 464
  char* saveptr = NULL;
  char* token = strtok_r(copy, " ", &saveptr);
465 466

  while (token != NULL) {
467 468 469 470
    zathura_jump_t* jump = g_try_malloc0(sizeof(zathura_jump_t));
    if (jump == NULL) {
      continue;
    }
471 472

    jump->page = strtoul(token, NULL, 0);
473 474 475 476 477 478
    token = strtok_r(NULL, " ", &saveptr);
    if (token == NULL) {
      girara_warning("Could not parse jumplist information.");
      g_free(jump);
      break;
    }
479
    jump->x = g_ascii_strtod(token, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
480

481
    token = strtok_r(NULL, " ", &saveptr);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
482 483 484 485 486
    if (token == NULL) {
      girara_warning("Could not parse jumplist information.");
      g_free(jump);
      break;
    }
487
    jump->y = g_ascii_strtod(token, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
488

489
    girara_list_append(result, jump);
490
    token = strtok_r(NULL, " ", &saveptr);
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
  }

  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);

  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
  char* str_value = g_key_file_get_string(priv->history, file, KEY_JUMPLIST, NULL);

  if (str_value == NULL) {
    return girara_list_new2(g_free);
  }

  return get_jumplist_from_str(str_value);
}

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, zathura_jump_t*, iter, jump)
521 522 523 524 525 526 527
    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, ' ');
528 529 530 531 532 533 534 535 536 537 538
  GIRARA_LIST_FOREACH_END(jumplist, zathura_jump_t*, iter, jump);

  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);

  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;
}

539
static bool
540
plain_set_fileinfo(zathura_database_t* db, const char* file, zathura_fileinfo_t*
Moritz Lipp's avatar
Moritz Lipp committed
541
                   file_info)
Moritz Lipp's avatar
Moritz Lipp committed
542
{
543
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
544
  if (priv->history == NULL || file_info == NULL || file == NULL) {
545 546 547
    return false;
  }

548
  char* name = prepare_filename(file);
549

550 551 552
  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_SCALE,             file_info->scale);
553 554 555
  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_integer(priv->history, name, KEY_FIRST_PAGE_COLUMN, file_info->first_page_column);
556 557
  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);
558

559 560
  g_free(name);

561
  zathura_db_write_key_file_to_file(priv->history_path, priv->history);
562

563
  return true;
Moritz Lipp's avatar
Moritz Lipp committed
564 565
}

566
static bool
567
plain_get_fileinfo(zathura_database_t* db, const char* file, zathura_fileinfo_t*
Moritz Lipp's avatar
Moritz Lipp committed
568
                   file_info)
Moritz Lipp's avatar
Moritz Lipp committed
569
{
570 571 572 573
  if (db == NULL || file == NULL || file_info == NULL) {
    return false;
  }

574 575
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);
  if (priv->history == NULL) {
576 577 578
    return false;
  }

579 580
  char* name = prepare_filename(file);
  if (g_key_file_has_group(priv->history, name) == FALSE) {
Moritz Lipp's avatar
Moritz Lipp committed
581
    g_free(name);
582
    return false;
583 584
  }

585 586
  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);
587
  file_info->scale             = g_key_file_get_double (priv->history, name, KEY_SCALE, NULL);
588
  file_info->rotation          = g_key_file_get_integer(priv->history, name, KEY_ROTATE, NULL);
589

590 591 592
  /* 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);
593
  }
594 595
  if (g_key_file_has_key(priv->history, name, KEY_FIRST_PAGE_COLUMN, NULL) == TRUE) {
    file_info->first_page_column = g_key_file_get_integer(priv->history, name, KEY_FIRST_PAGE_COLUMN, NULL);
596
  }
597 598 599 600 601
  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);
602
  }
603

604
  g_free(name);
605

606
  return true;
Moritz Lipp's avatar
Moritz Lipp committed
607
}
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629

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;
}

630
static GKeyFile*
631
zathura_db_read_key_file_from_file(const char* path)
632 633 634 635 636
{
  if (path == NULL) {
    return NULL;
  }

637
  /* open file */
638
  FILE* file = fopen(path, "rw");
639
  if (file == NULL) {
640 641 642
    return NULL;
  }

643 644
  GKeyFile* key_file = g_key_file_new();
  if (key_file == NULL) {
645
    fclose(file);
646 647 648 649
    return NULL;
  }

  /* read config file */
650 651 652 653
  file_lock_set(fileno(file), F_WRLCK);
  char* content = girara_file_read2(file);
  file_lock_set(fileno(file), F_UNLCK);
  fclose(file);
654
  if (content == NULL) {
655
    g_key_file_free(key_file);
656 657
    return NULL;
  }
658 659

  /* parse config file */
660 661 662 663 664 665 666
  size_t contentlen = strlen(content);
  if (contentlen == 0) {
    static const char dummy_content[] = "# nothing";
    static const size_t dummy_len = sizeof(dummy_content) - 1;

    free(content);
    content = malloc(sizeof(char) * (dummy_len + 1));
667
    content = memcpy(content, dummy_content, dummy_len + 1);
668 669 670
    contentlen = dummy_len;
  }

671
  GError* error = NULL;
672
  if (g_key_file_load_from_data(key_file, content, contentlen,
Moritz Lipp's avatar
Moritz Lipp committed
673
                                G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error) ==
674
      FALSE) {
Moritz Lipp's avatar
Moritz Lipp committed
675
    if (error->code != 1) { /* ignore empty file */
676
      free(content);
677 678
      g_key_file_free(key_file);
      g_error_free(error);
679
      return NULL;
680 681 682 683 684
    }

    g_error_free(error);
  }

685 686
  free(content);

687 688 689 690
  return key_file;
}

static void
691
zathura_db_write_key_file_to_file(const char* file, GKeyFile* key_file)
692
{
693
  if (file == NULL || key_file == NULL) {
694 695 696
    return;
  }

697
  gchar* content = g_key_file_to_data(key_file, NULL, NULL);
698 699 700 701
  if (content == NULL) {
    return;
  }

702
  /* open file */
703
  int fd = open(file, O_RDWR | O_TRUNC);
704
  if (fd == -1) {
705 706 707 708
    g_free(content);
    return;
  }

709
  file_lock_set(fd, F_WRLCK);
710 711 712
  if (write(fd, content, strlen(content)) == 0) {
    girara_error("Failed to write to %s", file);
  }
713 714 715 716
  file_lock_set(fd, F_UNLCK);

  close(fd);

717
  g_free(content);
718 719
}

720 721
static void
cb_zathura_db_watch_file(GFileMonitor* UNUSED(monitor), GFile* file, GFile* UNUSED(other_file),
Moritz Lipp's avatar
Moritz Lipp committed
722
                         GFileMonitorEvent event, zathura_database_t* database)
723 724 725 726 727 728 729 730 731 732
{
  if (event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT || database == NULL) {
    return;
  }

  char* path = g_file_get_path(file);
  if (path == NULL) {
    return;
  }

733 734
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(database);
  if (priv->bookmark_path && strcmp(priv->bookmark_path, path) == 0) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
735 736 737
    if (priv->bookmarks != NULL) {
      g_key_file_free(priv->bookmarks);
    }
Moritz Lipp's avatar
Moritz Lipp committed
738

739 740
    priv->bookmarks = zathura_db_read_key_file_from_file(priv->bookmark_path);
  } else if (priv->history_path && strcmp(priv->history_path, path) == 0) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
741 742 743
    if (priv->history != NULL) {
      g_key_file_free(priv->history);
    }
Moritz Lipp's avatar
Moritz Lipp committed
744

745
    priv->history = zathura_db_read_key_file_from_file(priv->history_path);
746
  }
747 748

  g_free(path);
749
}
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770

static girara_list_t*
plain_io_read(GiraraInputHistoryIO* db)
{
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);

  /* open file */
  FILE* file = fopen(priv->input_history_path, "r");
  if (file == NULL) {
    return NULL;
  }

  /* read input history file */
  file_lock_set(fileno(file), F_RDLCK);
  char* content = girara_file_read2(file);
  file_lock_set(fileno(file), F_UNLCK);
  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) {
771
    if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL) {
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
      continue;
    }
    girara_list_append(res, g_strdup(tmp[i]));
  }
  g_strfreev(tmp);
  free(content);

  return res;
}

#include <errno.h>

static void
plain_io_append(GiraraInputHistoryIO* db, const char* input)
{
  zathura_plaindatabase_private_t* priv = ZATHURA_PLAINDATABASE_GET_PRIVATE(db);

  /* open file */
  FILE* file = fopen(priv->input_history_path, "r+");
  if (file == NULL) {
    return;
  }

  /* read input history file */
  file_lock_set(fileno(file), F_WRLCK);
  char* content = girara_file_read2(file);

  rewind(file);
  if (ftruncate(fileno(file), 0) != 0) {
    free(content);
    file_lock_set(fileno(file), F_UNLCK);
    fclose(file);
    return;
  }

  char** tmp = g_strsplit(content, "\n", 0);
  free(content);

  /* write input history file */
  for (size_t i = 0; tmp[i] != NULL; ++i) {
812
    if (strlen(tmp[i]) == 0 || strchr(":/?", tmp[i][0]) == NULL || strcmp(tmp[i], input) == 0) {
813 814 815 816 817 818 819 820 821 822
      continue;
    }
    fprintf(file, "%s\n", tmp[i]);
  }
  g_strfreev(tmp);
  fprintf(file, "%s\n", input);

  file_lock_set(fileno(file), F_UNLCK);
  fclose(file);
}