database-sqlite.c 24.5 KB
Newer Older
1 2 3
/* See LICENSE file for license and copyright information */

#include <sqlite3.h>
4 5
#include <girara/utils.h>
#include <girara/datastructures.h>
6
#include <girara/input-history.h>
7
#include <string.h>
8
#include <strings.h>
9

10
#include "database-sqlite.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
11
#include "utils.h"
12

13 14 15 16 17 18
static char*
sqlite3_column_text_dup(sqlite3_stmt* stmt, int col)
{
  return g_strdup((const char*) sqlite3_column_text(stmt, col));
}

19
static void zathura_database_interface_init(ZathuraDatabaseInterface* iface);
20
static void io_interface_init(GiraraInputHistoryIOInterface* iface);
21 22

G_DEFINE_TYPE_WITH_CODE(ZathuraSQLDatabase, zathura_sqldatabase, G_TYPE_OBJECT,
23 24
                        G_IMPLEMENT_INTERFACE(ZATHURA_TYPE_DATABASE, zathura_database_interface_init)
                        G_IMPLEMENT_INTERFACE(GIRARA_TYPE_INPUT_HISTORY_IO, io_interface_init))
25

26
static bool           check_column(sqlite3* session, const char* table, const char* col, bool* result);
27
static bool           check_column_type(sqlite3* session, const char* table, const char* col, const char* type, bool* result);
28 29 30 31 32 33 34 35 36 37
static void           sqlite_finalize(GObject* object);
static bool           sqlite_add_bookmark(zathura_database_t* db, const char* file, zathura_bookmark_t* bookmark);
static bool           sqlite_remove_bookmark(zathura_database_t* db, const char* file, const char* id);
static girara_list_t* sqlite_load_bookmarks(zathura_database_t* db, const char* file);
static girara_list_t* sqlite_load_jumplist(zathura_database_t* db, const char* file);
static bool           sqlite_save_jumplist(zathura_database_t* db, const char* file, girara_list_t* jumplist);
static bool           sqlite_set_fileinfo(zathura_database_t* db, const char* file, zathura_fileinfo_t* file_info);
static bool           sqlite_get_fileinfo(zathura_database_t* db, const char* file, zathura_fileinfo_t* file_info);
static void           sqlite_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec);
static void           sqlite_io_append(GiraraInputHistoryIO* db, const char*);
38
static girara_list_t* sqlite_io_read(GiraraInputHistoryIO* db);
39
static girara_list_t* sqlite_get_recent_files(zathura_database_t* db, int max, const char* basepath);
40 41

typedef struct zathura_sqldatabase_private_s {
42
  sqlite3* session;
43 44 45 46 47
} zathura_sqldatabase_private_t;

#define ZATHURA_SQLDATABASE_GET_PRIVATE(obj) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ZATHURA_TYPE_SQLDATABASE, zathura_sqldatabase_private_t))

Moritz Lipp's avatar
Moritz Lipp committed
48
enum {
49 50
  PROP_0,
  PROP_PATH
51 52
};

53 54 55 56
static void
zathura_database_interface_init(ZathuraDatabaseInterface* iface)
{
  /* initialize interface */
57 58 59 60 61 62 63 64
  iface->add_bookmark     = sqlite_add_bookmark;
  iface->remove_bookmark  = sqlite_remove_bookmark;
  iface->load_bookmarks   = sqlite_load_bookmarks;
  iface->load_jumplist    = sqlite_load_jumplist;
  iface->save_jumplist    = sqlite_save_jumplist;
  iface->set_fileinfo     = sqlite_set_fileinfo;
  iface->get_fileinfo     = sqlite_get_fileinfo;
  iface->get_recent_files = sqlite_get_recent_files;
65 66
}

67 68 69 70 71 72 73 74
static void
io_interface_init(GiraraInputHistoryIOInterface* iface)
{
  /* initialize interface */
  iface->append = sqlite_io_append;
  iface->read = sqlite_io_read;
}

75 76 77 78 79 80 81 82
static void
zathura_sqldatabase_class_init(ZathuraSQLDatabaseClass* class)
{
  /* add private members */
  g_type_class_add_private(class, sizeof(zathura_sqldatabase_private_t));

  /* override methods */
  GObjectClass* object_class = G_OBJECT_CLASS(class);
Moritz Lipp's avatar
Moritz Lipp committed
83
  object_class->finalize     = sqlite_finalize;
84 85 86
  object_class->set_property = sqlite_set_property;

  g_object_class_install_property(object_class, PROP_PATH,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
87 88
    g_param_spec_string("path", "path", "path to the database", NULL,
      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
89 90 91 92 93 94 95 96 97
}

static void
zathura_sqldatabase_init(ZathuraSQLDatabase* db)
{
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  priv->session = NULL;
}

98
zathura_database_t*
99
zathura_sqldatabase_new(const char* path)
Pavel Borzenkov's avatar
Pavel Borzenkov committed
100
{
101 102 103 104 105
  g_return_val_if_fail(path != NULL && strlen(path) != 0, NULL);

  zathura_database_t* db = g_object_new(ZATHURA_TYPE_SQLDATABASE, "path", path, NULL);
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  if (priv->session == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
106
    g_object_unref(G_OBJECT(db));
107
    return NULL;
Moritz Lipp's avatar
Moritz Lipp committed
108
  }
Moritz Lipp's avatar
Moritz Lipp committed
109

110 111
  return db;
}
Moritz Lipp's avatar
Moritz Lipp committed
112

113 114 115 116 117 118
static void
sqlite_finalize(GObject* object)
{
  ZathuraSQLDatabase* db = ZATHURA_SQLDATABASE(object);
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  if (priv->session) {
119
    sqlite3_exec(priv->session, "VACUUM;", NULL, 0, NULL);
120
    sqlite3_close(priv->session);
121 122
  }

123 124 125 126 127 128 129
  G_OBJECT_CLASS(zathura_sqldatabase_parent_class)->finalize(object);
}

static void
sqlite_db_init(ZathuraSQLDatabase* db, const char* path)
{
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
130

131
  /* create bookmarks table */
132 133
  static const char SQL_BOOKMARK_INIT[] =
    "CREATE TABLE IF NOT EXISTS bookmarks ("
Moritz Lipp's avatar
Moritz Lipp committed
134 135 136
    "file TEXT,"
    "id TEXT,"
    "page INTEGER,"
137 138
    "hadj_ratio FLOAT,"
    "vadj_ratio FLOAT,"
Moritz Lipp's avatar
Moritz Lipp committed
139
    "PRIMARY KEY(file, id));";
140

Sebastian Ramacher's avatar
Sebastian Ramacher committed
141
  /* create jumplist table */
142 143 144 145 146 147 148 149 150
  static const char SQL_JUMPLIST_INIT[] =
    "CREATE TABLE IF NOT EXISTS jumplist ("
    "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    "file TEXT,"
    "page INTEGER,"
    "hadj_ratio FLOAT,"
    "vadj_ratio FLOAT"
    ");";

151
  /* create fileinfo table */
152 153
  static const char SQL_FILEINFO_INIT[] =
    "CREATE TABLE IF NOT EXISTS fileinfo ("
Moritz Lipp's avatar
Moritz Lipp committed
154 155 156
    "file TEXT PRIMARY KEY,"
    "page INTEGER,"
    "offset INTEGER,"
157
    "zoom FLOAT,"
Moritz Lipp's avatar
Moritz Lipp committed
158 159
    "rotation INTEGER,"
    "pages_per_row INTEGER,"
160
    "first_page_column TEXT,"
Moritz Lipp's avatar
Moritz Lipp committed
161
    "position_x FLOAT,"
162 163
    "position_y FLOAT,"
    "time TIMESTAMP"
Moritz Lipp's avatar
Moritz Lipp committed
164
    ");";
165

166 167 168 169 170 171 172
  /* create history table */
  static const char SQL_HISTORY_INIT[] =
    "CREATE TABLE IF NOT EXISTS history ("
    "time TIMESTAMP,"
    "line TEXT,"
    "PRIMARY KEY(line));";

Sebastian Ramacher's avatar
Sebastian Ramacher committed
173 174 175 176 177 178 179
  static const char* ALL_INIT[] = {
    SQL_BOOKMARK_INIT,
    SQL_JUMPLIST_INIT,
    SQL_FILEINFO_INIT,
    SQL_HISTORY_INIT
  };

180
  /* update fileinfo table (part 1) */
181 182 183 184 185
  static const char SQL_FILEINFO_ALTER[] =
    "ALTER TABLE fileinfo ADD COLUMN pages_per_row INTEGER;"
    "ALTER TABLE fileinfo ADD COLUMN position_x FLOAT;"
    "ALTER TABLE fileinfo ADD COLUMN position_y FLOAT;";

186
  /* update fileinfo table (part 2) */
187
  static const char SQL_FILEINFO_ALTER2[] =
188
    "ALTER TABLE fileinfo ADD COLUMN first_page_column TEXT;";
189

190
  /* update fileinfo table (part 3) */
191 192 193
  static const char SQL_FILEINFO_ALTER3[] =
    "ALTER TABLE fileinfo ADD COLUMN time TIMESTAMP;";

194 195 196 197
  /* update fileinfo table (part 4) */
  static const char SQL_FILEINFO_ALTER4[] =
    "ALTER TABLE fileinfo ADD COLUMN zoom FLOAT;";

198
  /* update bookmark table */
199 200 201 202
  static const char SQL_BOOKMARK_ALTER[] =
    "ALTER TABLE bookmarks ADD COLUMN hadj_ratio FLOAT;"
    "ALTER TABLE bookmarks ADD COLUMN vadj_ratio FLOAT;";

203 204
  sqlite3* session = NULL;
  if (sqlite3_open(path, &session) != SQLITE_OK) {
205
    girara_error("Could not open database: %s\n", path);
206
    return;
207 208
  }

209
  /* create tables if they don't exist */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
210 211 212 213 214 215
  for (size_t s = 0; s < LENGTH(ALL_INIT); ++s) {
    if (sqlite3_exec(session, ALL_INIT[s], NULL, 0, NULL) != SQLITE_OK) {
      girara_error("Failed to initialize database: %s\n", path);
      sqlite3_close(session);
      return;
    }
216 217
  }

218
  /* check existing tables for missing columns */
219 220 221 222 223
  bool res1, res2, ret1, ret2;

  ret1 = check_column(session, "fileinfo", "pages_per_row", &res1);

  if (ret1 == true && res1 == false) {
224 225 226 227 228 229
    girara_debug("old database table layout detected; updating ...");
    if (sqlite3_exec(session, SQL_FILEINFO_ALTER, NULL, 0, NULL) != SQLITE_OK) {
      girara_warning("failed to update database table layout");
    }
  }

230 231 232
  ret1 = check_column(session, "fileinfo", "first_page_column", &res1);

  if (ret1 == true && res1 == false) {
233 234 235 236 237 238
    girara_debug("old database table layout detected; updating ...");
    if (sqlite3_exec(session, SQL_FILEINFO_ALTER2, NULL, 0, NULL) != SQLITE_OK) {
      girara_warning("failed to update database table layout");
    }
  }

239 240 241 242 243 244 245 246 247
  ret1 = check_column(session, "fileinfo", "time", &res1);

  if (ret1 == true && res1 == false) {
    girara_debug("old database table layout detected; updating ...");
    if (sqlite3_exec(session, SQL_FILEINFO_ALTER3, NULL, 0, NULL) != SQLITE_OK) {
      girara_warning("failed to update database table layout");
    }
  }

248 249 250 251 252 253 254 255 256
  ret1 = check_column(session, "fileinfo", "zoom", &res1);

  if (ret1 == true && res1 == false) {
    girara_debug("old database table layout detected; updating ...");
    if (sqlite3_exec(session, SQL_FILEINFO_ALTER4, NULL, 0, NULL) != SQLITE_OK) {
      girara_warning("failed to update database table layout");
    }
  }

257 258 259 260
  ret1 = check_column(session, "bookmarks", "hadj_ratio", &res1);
  ret2 = check_column(session, "bookmarks", "vadj_ratio", &res2);

  if (ret1 == true && ret2 == true && res1 == false && res2 == false) {
261 262 263 264 265 266
    girara_debug("old database table layout detected; updating ...");
    if (sqlite3_exec(session, SQL_BOOKMARK_ALTER, NULL, 0, NULL) != SQLITE_OK) {
      girara_warning("failed to update database table layout");
    }
  }

267 268 269 270 271 272 273
  /* check existing tables for correct column types */
  ret1 = check_column_type(session, "fileinfo", "first_page_column", "TEXT", &res1);

  if (ret1 == true && res1 == false) {
    girara_debug("old database table layout detected; updating ...");

    /* prepare transaction */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
274 275 276 277 278 279 280
    static const char tx_begin[] =
      "BEGIN TRANSACTION;"
      "ALTER TABLE fileinfo RENAME TO tmp;";
    static const char tx_end[] =
      "INSERT INTO fileinfo SELECT * FROM tmp;"
      "DROP TABLE tmp;"
      "COMMIT;";
281 282

    /* assemble transaction */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
283 284 285 286
    char transaction[sizeof(tx_begin) + sizeof(SQL_FILEINFO_INIT) + sizeof(tx_end) - 2] = { '\0' };
    g_strlcat(transaction, tx_begin, sizeof(transaction));
    g_strlcat(transaction, SQL_FILEINFO_INIT, sizeof(transaction));
    g_strlcat(transaction, tx_end, sizeof(transaction));
287 288 289 290 291 292

    if (sqlite3_exec(session, transaction, NULL, 0, NULL) != SQLITE_OK) {
      girara_warning("failed to update database table layout");
    }
  }

293
  priv->session = session;
294 295
}

296 297
static void
sqlite_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
Pavel Borzenkov's avatar
Pavel Borzenkov committed
298
{
299 300 301 302 303 304 305 306 307 308
  ZathuraSQLDatabase* db = ZATHURA_SQLDATABASE(object);
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);

  switch (prop_id) {
    case PROP_PATH:
      g_return_if_fail(priv->session == NULL);
      sqlite_db_init(db, g_value_get_string(value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
309
  }
310 311 312 313 314 315 316 317 318 319 320 321
}

static sqlite3_stmt*
prepare_statement(sqlite3* session, const char* statement)
{
  if (session == NULL || statement == NULL) {
    return NULL;
  }

  const char* pz_tail   = NULL;
  sqlite3_stmt* pp_stmt = NULL;

322
  if (sqlite3_prepare_v2(session, statement, -1, &pp_stmt, &pz_tail) != SQLITE_OK) {
323 324 325 326 327 328 329 330 331 332 333 334
    girara_error("Failed to prepare query: %s", statement);
    sqlite3_finalize(pp_stmt);
    return NULL;
  } else if (pz_tail && *pz_tail != '\0') {
    girara_error("Unused portion of statement: %s", pz_tail);
    sqlite3_finalize(pp_stmt);
    return NULL;
  }

  return pp_stmt;
}

335 336 337
static bool
check_column(sqlite3* session, const char* table, const char* col, bool* res)
{
338 339 340 341 342 343
  /* we can't actually bind the argument with sqlite3_bind_text because
   * sqlite3_prepare_v2 fails with "PRAGMA table_info(?);" */
  char* query = sqlite3_mprintf("PRAGMA table_info(%Q);", table);
  if (query == NULL) {
    return false;
  }
344

345
  sqlite3_stmt* stmt = prepare_statement(session, query);
346 347 348 349 350 351 352 353 354 355 356 357 358 359
  if (stmt == NULL) {
    return false;
  }

  *res = false;

  while (sqlite3_step(stmt) == SQLITE_ROW) {
    if (strcmp((const char*) sqlite3_column_text(stmt, 1), col) == 0) {
      *res = true;
      break;
    }
  }

  if (*res == false) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
360
    girara_debug("Column '%s' in table '%s' NOT found.", col, table);
361 362 363
  }

  sqlite3_finalize(stmt);
364
  sqlite3_free(query);
365 366 367 368

  return true;
}

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
static bool
check_column_type(sqlite3* session, const char* table, const char* col, const char* type, bool* res)
{
  /* we can't actually bind the argument with sqlite3_bind_text because
   * sqlite3_prepare_v2 fails with "PRAGMA table_info(?);" */
  char* query = sqlite3_mprintf("PRAGMA table_info(%Q);", table);
  if (query == NULL) {
    return false;
  }

  sqlite3_stmt* stmt = prepare_statement(session, query);
  if (stmt == NULL) {
    return false;
  }

  *res = false;

  while (sqlite3_step(stmt) == SQLITE_ROW) {
    if (strcmp((const char*) sqlite3_column_text(stmt, 1), col) == 0) {
      if (strcmp((const char*) sqlite3_column_text(stmt, 2), type) == 0) {
        *res = true;
        break;
      }
    }
  }

  if (*res == false) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
396
    girara_debug("Column '%s' in table '%s' has wrong type.", col, table);
397 398 399 400 401 402 403 404
  }

  sqlite3_finalize(stmt);
  sqlite3_free(query);

  return true;
}

405 406
static bool
sqlite_add_bookmark(zathura_database_t* db, const char* file,
Moritz Lipp's avatar
Moritz Lipp committed
407
                    zathura_bookmark_t* bookmark)
408
{
409
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
410 411

  static const char SQL_BOOKMARK_ADD[] =
412
    "REPLACE INTO bookmarks (file, id, page, hadj_ratio, vadj_ratio) VALUES (?, ?, ?, ?, ?);";
413

414
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_BOOKMARK_ADD);
415 416 417 418 419 420
  if (stmt == NULL) {
    return false;
  }

  if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK ||
      sqlite3_bind_text(stmt, 2, bookmark->id, -1, NULL) != SQLITE_OK ||
421 422 423
      sqlite3_bind_int(stmt, 3, bookmark->page) != SQLITE_OK ||
      sqlite3_bind_double(stmt, 4, bookmark->x) != SQLITE_OK ||
      sqlite3_bind_double(stmt, 5, bookmark->y) != SQLITE_OK) {
424 425 426 427 428 429 430
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return false;
  }

  int res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);
Moritz Lipp's avatar
Moritz Lipp committed
431 432

  return (res == SQLITE_DONE) ? true : false;
433 434
}

435 436
static bool
sqlite_remove_bookmark(zathura_database_t* db, const char* file, const char*
Moritz Lipp's avatar
Moritz Lipp committed
437
                       id)
438
{
439
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
440 441

  static const char SQL_BOOKMARK_ADD[] =
442
    "DELETE FROM bookmarks WHERE file = ? AND id = ?;";
443

444
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_BOOKMARK_ADD);
445 446 447 448 449 450 451 452 453 454 455 456 457
  if (stmt == NULL) {
    return false;
  }

  if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK ||
      sqlite3_bind_text(stmt, 2, id, -1, NULL) != SQLITE_OK) {
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return false;
  }

  int res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);
Moritz Lipp's avatar
Moritz Lipp committed
458 459

  return (res == SQLITE_DONE) ? true : false;
460 461
}

462 463
static girara_list_t*
sqlite_load_bookmarks(zathura_database_t* db, const char* file)
464
{
465
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
466 467

  static const char SQL_BOOKMARK_SELECT[] =
468
    "SELECT id, page, hadj_ratio, vadj_ratio FROM bookmarks WHERE file = ?;";
469

470
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_BOOKMARK_SELECT);
471 472 473 474 475 476 477 478 479 480
  if (stmt == NULL) {
    return NULL;
  }

  if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK) {
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return NULL;
  }

481
  girara_list_t* result = bookmarks_list_new();
Sebastian Ramacher's avatar
Sebastian Ramacher committed
482
  if (result == NULL) {
483 484 485
    sqlite3_finalize(stmt);
    return NULL;
  }
Moritz Lipp's avatar
Moritz Lipp committed
486

487
  while (sqlite3_step(stmt) == SQLITE_ROW) {
488 489 490 491
    zathura_bookmark_t* bookmark = g_try_malloc0(sizeof(zathura_bookmark_t));
    if (bookmark == NULL) {
      continue;
    }
Moritz Lipp's avatar
Moritz Lipp committed
492

493
    bookmark->id   = sqlite3_column_text_dup(stmt, 0);
494
    bookmark->page = sqlite3_column_int(stmt, 1);
495 496
    bookmark->x    = sqlite3_column_double(stmt, 2);
    bookmark->y    = sqlite3_column_double(stmt, 3);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
497 498
    bookmark->x    = MAX(DBL_MIN, bookmark->x);
    bookmark->y    = MAX(DBL_MIN, bookmark->y);
499

500 501
    girara_list_append(result, bookmark);
  }
Moritz Lipp's avatar
Moritz Lipp committed
502

503
  sqlite3_finalize(stmt);
Moritz Lipp's avatar
Moritz Lipp committed
504

505 506 507
  return result;
}

508 509 510 511 512
static bool
sqlite_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);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
513
  static const char SQL_INSERT_JUMP[]     = "INSERT INTO jumplist (file, page, hadj_ratio, vadj_ratio) VALUES (?, ?, ?, ?);";
514
  static const char SQL_REMOVE_JUMPLIST[] = "DELETE FROM jumplist WHERE file = ?;";
Sebastian Ramacher's avatar
Sebastian Ramacher committed
515 516 517 518

  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  sqlite3_stmt* stmt                  = NULL;
  int res                             = 0;
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551

  if (sqlite3_exec(priv->session, "BEGIN;", NULL, 0, NULL) != SQLITE_OK) {
    return false;
  }

  stmt = prepare_statement(priv->session, SQL_REMOVE_JUMPLIST);

  if (stmt == NULL) {
    sqlite3_exec(priv->session, "ROLLBACK;", NULL, 0, NULL);
    return false;
  }

  if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK) {
    sqlite3_finalize(stmt);
    sqlite3_exec(priv->session, "ROLLBACK;", NULL, 0, NULL);
    girara_error("Failed to bind arguments.");
    return false;
  }

  res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  if (res != SQLITE_DONE) {
    sqlite3_exec(priv->session, "ROLLBACK;", NULL, 0, NULL);
    return false;
  }

  if (girara_list_size(jumplist) == 0) {
    sqlite3_exec(priv->session, "COMMIT;", NULL, 0, NULL);
    return true;
  }

  bool status = true;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
552
  GIRARA_LIST_FOREACH_BODY(jumplist, zathura_jump_t*, jump,
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
    stmt = prepare_statement(priv->session, SQL_INSERT_JUMP);
    if (stmt == NULL) {
      status = false;
      break;
    }

    if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK ||
        sqlite3_bind_int(stmt, 2, jump->page)      != SQLITE_OK ||
        sqlite3_bind_double(stmt, 3, jump->x)      != SQLITE_OK ||
        sqlite3_bind_double(stmt, 4, jump->y)      != SQLITE_OK) {
      sqlite3_finalize(stmt);
      girara_error("Failed to bind arguments.");
      status = false;
      break;
    }

    res = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if (res != SQLITE_DONE) {
      status = false;
      break;
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
576
  );
Sebastian Ramacher's avatar
Sebastian Ramacher committed
577

578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
  if (status == false) {
    sqlite3_exec(priv->session, "ROLLBACK;", NULL, 0, NULL);
    return false;
  } else {
    sqlite3_exec(priv->session, "COMMIT;", NULL, 0, NULL);
    return true;
  }
}

static girara_list_t*
sqlite_load_jumplist(zathura_database_t* db, const char* file)
{
  g_return_val_if_fail(db != NULL && file != NULL, NULL);

  static const char SQL_GET_JUMPLIST[] = "SELECT page, hadj_ratio, vadj_ratio FROM jumplist WHERE file = ? ORDER BY id ASC;";
Sebastian Ramacher's avatar
Sebastian Ramacher committed
593 594 595

  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  sqlite3_stmt* stmt                  = prepare_statement(priv->session, SQL_GET_JUMPLIST);
596 597 598 599 600 601 602 603 604 605 606 607 608

  if (stmt == NULL) {
    return NULL;
  }

  if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK) {
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");

    return NULL;
  }

  girara_list_t* jumplist = girara_list_new2(g_free);
609 610 611 612
  if (jumplist == NULL) {
    sqlite3_finalize(stmt);
    return NULL;
  }
613

614
  int res = 0;
615
  while ((res = sqlite3_step(stmt)) == SQLITE_ROW) {
616 617 618 619
    zathura_jump_t* jump = g_try_malloc0(sizeof(zathura_jump_t));
    if (jump == NULL) {
      continue;
    }
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637

    jump->page = sqlite3_column_int(stmt, 0);
    jump->x    = sqlite3_column_double(stmt, 1);
    jump->y    = sqlite3_column_double(stmt, 2);
    girara_list_append(jumplist, jump);
  }

  sqlite3_finalize(stmt);

  if (res != SQLITE_DONE) {
    girara_list_free(jumplist);

    return NULL;
  }

  return jumplist;
}

638
static bool
639
sqlite_set_fileinfo(zathura_database_t* db, const char* file,
Moritz Lipp's avatar
Moritz Lipp committed
640
                    zathura_fileinfo_t* file_info)
641
{
642
  if (db == NULL || file == NULL || file_info == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
643 644
    return false;
  }
645

646
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
647 648

  static const char SQL_FILEINFO_SET[] =
649
    "REPLACE INTO fileinfo (file, page, offset, zoom, rotation, pages_per_row, first_page_column, position_x, position_y, time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, DATETIME('now'));";
650

651
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_FILEINFO_SET);
652 653 654 655
  if (stmt == NULL) {
    return false;
  }

656 657 658
  if (sqlite3_bind_text(stmt,   1, file, -1, NULL)               != SQLITE_OK ||
      sqlite3_bind_int(stmt,    2, file_info->current_page)      != SQLITE_OK ||
      sqlite3_bind_int(stmt,    3, file_info->page_offset)       != SQLITE_OK ||
659
      sqlite3_bind_double(stmt, 4, file_info->zoom)              != SQLITE_OK ||
660 661
      sqlite3_bind_int(stmt,    5, file_info->rotation)          != SQLITE_OK ||
      sqlite3_bind_int(stmt,    6, file_info->pages_per_row)     != SQLITE_OK ||
662 663
      sqlite3_bind_text(stmt,   7, file_info->first_page_column_list, -1, NULL)
                                                                 != SQLITE_OK ||
664 665
      sqlite3_bind_double(stmt, 8, file_info->position_x)        != SQLITE_OK ||
      sqlite3_bind_double(stmt, 9, file_info->position_y)        != SQLITE_OK) {
666 667 668 669 670 671 672
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return false;
  }

  int res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);
Moritz Lipp's avatar
Moritz Lipp committed
673 674

  return (res == SQLITE_DONE) ? true : false;
675
}
676

677
static bool
678
sqlite_get_fileinfo(zathura_database_t* db, const char* file,
Moritz Lipp's avatar
Moritz Lipp committed
679
                    zathura_fileinfo_t* file_info)
680
{
Moritz Lipp's avatar
Moritz Lipp committed
681 682 683
  if (db == NULL || file == NULL || file_info == NULL) {
    return false;
  }
684

685
  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
686 687

  static const char SQL_FILEINFO_GET[] =
688
    "SELECT page, offset, zoom, rotation, pages_per_row, first_page_column, position_x, position_y FROM fileinfo WHERE file = ?;";
689

690
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_FILEINFO_GET);
691
  if (stmt == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
692
    return false;
693 694
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
695
  if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK) {
696 697 698 699 700 701 702
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return false;
  }

  if (sqlite3_step(stmt) != SQLITE_ROW) {
    sqlite3_finalize(stmt);
703
    girara_debug("No info for file %s available.", file);
704 705 706
    return false;
  }

707 708
  file_info->current_page           = sqlite3_column_int(stmt, 0);
  file_info->page_offset            = sqlite3_column_int(stmt, 1);
709
  file_info->zoom                   = sqlite3_column_double(stmt, 2);
710 711
  file_info->rotation               = sqlite3_column_int(stmt, 3);
  file_info->pages_per_row          = sqlite3_column_int(stmt, 4);
712
  file_info->first_page_column_list = sqlite3_column_text_dup(stmt, 5);
713 714
  file_info->position_x             = sqlite3_column_double(stmt, 6);
  file_info->position_y             = sqlite3_column_double(stmt, 7);
715

716
  sqlite3_finalize(stmt);
Moritz Lipp's avatar
Moritz Lipp committed
717

718 719
  return true;
}
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754

static void
sqlite_io_append(GiraraInputHistoryIO* db, const char* input)
{
  static const char SQL_HISTORY_SET[] =
    "REPLACE INTO history (line, time) VALUES (?, DATETIME('now'));";

  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_HISTORY_SET);
  if (stmt == NULL) {
    return;
  }

  if (sqlite3_bind_text(stmt, 1, input, -1, NULL) != SQLITE_OK) {
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return;
  }

  sqlite3_step(stmt);
  sqlite3_finalize(stmt);
}

static girara_list_t*
sqlite_io_read(GiraraInputHistoryIO* db)
{
  static const char SQL_HISTORY_GET[] =
    "SELECT line FROM history ORDER BY time";

  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
  sqlite3_stmt* stmt = prepare_statement(priv->session, SQL_HISTORY_GET);
  if (stmt == NULL) {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
755
  girara_list_t* list = girara_list_new2(g_free);
756 757 758 759 760 761
  if (list == NULL) {
    sqlite3_finalize(stmt);
    return NULL;
  }

  while (sqlite3_step(stmt) == SQLITE_ROW) {
762
    girara_list_append(list, sqlite3_column_text_dup(stmt, 0));
763 764 765 766 767
  }

  sqlite3_finalize(stmt);
  return list;
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
768

769
static girara_list_t*
770
sqlite_get_recent_files(zathura_database_t* db, int max, const char* basepath)
771 772
{
  static const char SQL_HISTORY_GET[] =
773
    "SELECT file FROM fileinfo ORDER BY time DESC LIMIT ?";
774
  static const char SQL_HISTORY_GET_WITH_BASEPATH[] =
Sebastian Ramacher's avatar
Sebastian Ramacher committed
775
    "SELECT file FROM fileinfo WHERE file LIKE ? || '%' ORDER BY time DESC LIMIT ?";
776 777

  zathura_sqldatabase_private_t* priv = ZATHURA_SQLDATABASE_GET_PRIVATE(db);
778
  sqlite3_stmt* stmt = prepare_statement(priv->session, basepath == NULL ? SQL_HISTORY_GET : SQL_HISTORY_GET_WITH_BASEPATH);
779 780 781 782
  if (stmt == NULL) {
    return NULL;
  }

783 784 785 786
  if (max < 0) {
    max = INT_MAX;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
787 788 789 790 791 792 793 794
  bool failed = false;
  if (basepath != NULL) {
    failed = sqlite3_bind_int(stmt, 2, max) != SQLITE_OK || sqlite3_bind_text(stmt, 1, basepath, -1, NULL) != SQLITE_OK;
  } else  {
    failed = sqlite3_bind_int(stmt, 1, max) != SQLITE_OK;
  }

  if (failed == true) {
795 796 797 798 799
    sqlite3_finalize(stmt);
    girara_error("Failed to bind arguments.");
    return false;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
800
  girara_list_t* list = girara_list_new2(g_free);
801 802 803 804 805 806
  if (list == NULL) {
    sqlite3_finalize(stmt);
    return NULL;
  }

  while (sqlite3_step(stmt) == SQLITE_ROW) {
807
    girara_list_append(list, sqlite3_column_text_dup(stmt, 0));
808 809 810 811 812
  }

  sqlite3_finalize(stmt);
  return list;
}