1
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
4
* Copyright (C) Philip Withnall 2008–2010 <philip@tecnocode.co.uk>
6
* Almanah is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation, either version 3 of the License, or
9
* (at your option) any later version.
11
* Almanah is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with Almanah. If not, see <http://www.gnu.org/licenses/>.
22
#include <glib/gi18n.h>
23
#include <glib/gstdio.h>
30
#ifdef ENABLE_ENCRYPTION
32
#endif /* ENABLE_ENCRYPTION */
35
#include "storage-manager.h"
36
#include "almanah-marshal.h"
38
#define ENCRYPTED_SUFFIX ".encrypted"
40
static void almanah_storage_manager_finalize (GObject *object);
41
static void almanah_storage_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
42
static void almanah_storage_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
43
static gboolean simple_query (AlmanahStorageManager *self, const gchar *query, GError **error, ...);
45
struct _AlmanahStorageManagerPrivate {
46
gchar *filename, *plain_filename;
47
gchar *encryption_key;
60
SIGNAL_ENTRY_MODIFIED,
62
SIGNAL_ENTRY_TAG_ADDED,
63
SIGNAL_ENTRY_TAG_REMOVED,
67
static guint storage_manager_signals[LAST_SIGNAL] = { 0, };
69
G_DEFINE_TYPE (AlmanahStorageManager, almanah_storage_manager, G_TYPE_OBJECT)
70
#define ALMANAH_STORAGE_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ALMANAH_TYPE_STORAGE_MANAGER, AlmanahStorageManagerPrivate))
73
almanah_storage_manager_error_quark (void)
75
return g_quark_from_static_string ("almanah-storage-manager-error-quark");
79
almanah_storage_manager_class_init (AlmanahStorageManagerClass *klass)
81
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
83
g_type_class_add_private (klass, sizeof (AlmanahStorageManagerPrivate));
85
gobject_class->set_property = almanah_storage_manager_set_property;
86
gobject_class->get_property = almanah_storage_manager_get_property;
87
gobject_class->finalize = almanah_storage_manager_finalize;
89
g_object_class_install_property (gobject_class, PROP_FILENAME,
90
g_param_spec_string ("filename",
91
"Database filename", "The path and filename for the unencrypted SQLite database.",
93
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
95
g_object_class_install_property (gobject_class, PROP_ENCRYPTION_KEY,
96
g_param_spec_string ("encryption-key",
97
"Encryption key", "The identifier for the encryption key in the user's keyring.",
99
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
101
storage_manager_signals[SIGNAL_DISCONNECTED] = g_signal_new ("disconnected",
102
G_TYPE_FROM_CLASS (klass),
105
almanah_marshal_VOID__STRING_STRING,
106
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
107
storage_manager_signals[SIGNAL_ENTRY_ADDED] = g_signal_new ("entry-added",
108
G_TYPE_FROM_CLASS (klass),
111
g_cclosure_marshal_VOID__OBJECT,
112
G_TYPE_NONE, 1, ALMANAH_TYPE_ENTRY);
113
storage_manager_signals[SIGNAL_ENTRY_MODIFIED] = g_signal_new ("entry-modified",
114
G_TYPE_FROM_CLASS (klass),
117
g_cclosure_marshal_VOID__OBJECT,
118
G_TYPE_NONE, 1, ALMANAH_TYPE_ENTRY);
119
storage_manager_signals[SIGNAL_ENTRY_REMOVED] = g_signal_new ("entry-removed",
120
G_TYPE_FROM_CLASS (klass),
123
g_cclosure_marshal_VOID__BOXED,
124
G_TYPE_NONE, 1, G_TYPE_DATE);
125
storage_manager_signals[SIGNAL_ENTRY_TAG_ADDED] = g_signal_new ("entry-tag-added",
126
G_TYPE_FROM_CLASS (klass),
129
almanah_marshal_VOID__OBJECT_STRING,
130
G_TYPE_NONE, 2, ALMANAH_TYPE_ENTRY, G_TYPE_STRING);
131
storage_manager_signals[SIGNAL_ENTRY_TAG_REMOVED] = g_signal_new ("entry-tag-removed",
132
G_TYPE_FROM_CLASS (klass),
135
almanah_marshal_VOID__OBJECT_STRING,
136
G_TYPE_NONE, 2, ALMANAH_TYPE_ENTRY, G_TYPE_STRING);
140
almanah_storage_manager_init (AlmanahStorageManager *self)
142
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_STORAGE_MANAGER, AlmanahStorageManagerPrivate);
143
self->priv->filename = NULL;
144
self->priv->plain_filename = NULL;
145
self->priv->encryption_key = NULL;
146
self->priv->decrypted = FALSE;
150
* almanah_storage_manager_new:
151
* @filename: database filename to open
152
* @encryption_key: identifier for the encryption key to use in the user's keyring, or %NULL
154
* Creates a new #AlmanahStorageManager, connected to the given database @filename.
156
* If @filename is for an encrypted database, it will automatically be changed to the canonical filename for the unencrypted database, even if that
157
* file doesn't exist, and even if Almanah was compiled without encryption support. Database filenames are always passed as the unencrypted filename.
159
* If @encryption_key is %NULL, encryption will be disabled.
161
* Return value: the new #AlmanahStorageManager
163
AlmanahStorageManager *
164
almanah_storage_manager_new (const gchar *filename, const gchar *encryption_key)
166
gchar *new_filename = NULL;
167
AlmanahStorageManager *sm;
169
if (g_str_has_suffix (filename, ENCRYPTED_SUFFIX) == TRUE)
170
filename = new_filename = g_strndup (filename, strlen (filename) - strlen (ENCRYPTED_SUFFIX));
172
sm = g_object_new (ALMANAH_TYPE_STORAGE_MANAGER,
173
"filename", filename,
174
"encryption-key", encryption_key,
176
g_free (new_filename);
182
almanah_storage_manager_finalize (GObject *object)
184
AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
186
g_free (priv->filename);
187
g_free (priv->plain_filename);
188
g_free (priv->encryption_key);
190
/* Chain up to the parent class */
191
G_OBJECT_CLASS (almanah_storage_manager_parent_class)->finalize (object);
195
almanah_storage_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
197
AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
199
switch (property_id) {
201
g_value_set_string (value, g_strdup (priv->filename));
203
case PROP_ENCRYPTION_KEY:
204
g_value_set_string (value, priv->encryption_key);
207
/* We don't have any other property... */
208
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
214
almanah_storage_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
216
AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
218
switch (property_id) {
220
priv->plain_filename = g_strdup (g_value_get_string (value));
221
priv->filename = g_strjoin (NULL, priv->plain_filename, ENCRYPTED_SUFFIX, NULL);
223
case PROP_ENCRYPTION_KEY:
224
g_free (priv->encryption_key);
225
priv->encryption_key = g_value_dup_string (value);
226
g_object_notify (object, "encryption-key");
229
/* We don't have any other property... */
230
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
236
create_tables (AlmanahStorageManager *self)
238
/* Dates are stored in ISO 8601 format…sort of */
240
const gchar *queries[] = {
241
"CREATE TABLE IF NOT EXISTS entries (year INTEGER, month INTEGER, day INTEGER, content TEXT, PRIMARY KEY (year, month, day))",
242
"ALTER TABLE entries ADD COLUMN is_important INTEGER", /* added in 0.7.0 */
243
"ALTER TABLE entries ADD COLUMN edited_year INTEGER", /* added in 0.8.0 */
244
"ALTER TABLE entries ADD COLUMN edited_month INTEGER", /* added in 0.8.0 */
245
"ALTER TABLE entries ADD COLUMN edited_day INTEGER", /* added in 0.8.0 */
246
"ALTER TABLE entries ADD COLUMN version INTEGER DEFAULT 1", /* added in 0.8.0 */
247
"CREATE TABLE IF NOT EXISTS entry_tag (year INTEGER, month INTEGER, day INTEGER, tag TEXT)", /* added in 0.10.0 */
248
"CREATE INDEX idx_tag ON entry_tag(tag)", /* added in 0.10.0, for information take a look at: http://www.sqlite.org/queryplanner.html */
253
while (queries[i] != NULL)
254
simple_query (self, queries[i++], NULL);
257
#ifdef ENABLE_ENCRYPTION
259
AlmanahStorageManager *storage_manager;
260
GIOChannel *cipher_io_channel;
261
GIOChannel *plain_io_channel;
262
gpgme_data_t gpgme_cipher;
263
gpgme_data_t gpgme_plain;
268
prepare_gpgme (AlmanahStorageManager *self, gboolean encrypting, CipherOperation *operation, GError **error)
270
gpgme_error_t error_gpgme;
272
/* Check for a minimum GPGME version (bgo#599598) */
273
if (gpgme_check_version (MIN_GPGME_VERSION) == NULL) {
274
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_BAD_VERSION,
275
_("GPGME is not at least version %s"),
280
/* Check OpenPGP's supported */
281
error_gpgme = gpgme_engine_check_version (GPGME_PROTOCOL_OpenPGP);
282
if (error_gpgme != GPG_ERR_NO_ERROR) {
283
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_UNSUPPORTED,
284
_("GPGME doesn't support OpenPGP: %s"),
285
gpgme_strerror (error_gpgme));
289
/* Set up for the operation */
290
error_gpgme = gpgme_new (&(operation->context));
291
if (error_gpgme != GPG_ERR_NO_ERROR) {
292
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_CREATING_CONTEXT,
293
_("Error creating cipher context: %s"),
294
gpgme_strerror (error_gpgme));
298
gpgme_set_protocol (operation->context, GPGME_PROTOCOL_OpenPGP);
299
gpgme_set_armor (operation->context, TRUE);
300
gpgme_set_textmode (operation->context, FALSE);
306
open_db_files (AlmanahStorageManager *self, gboolean encrypting, CipherOperation *operation, GError **error)
308
GError *io_error = NULL;
309
gpgme_error_t error_gpgme;
311
/* Open the encrypted file */
312
operation->cipher_io_channel = g_io_channel_new_file (self->priv->filename, encrypting ? "w" : "r", &io_error);
313
if (operation->cipher_io_channel == NULL) {
314
g_propagate_error (error, io_error);
318
/* Pass it to GPGME */
319
error_gpgme = gpgme_data_new_from_fd (&(operation->gpgme_cipher), g_io_channel_unix_get_fd (operation->cipher_io_channel));
320
if (error_gpgme != GPG_ERR_NO_ERROR) {
321
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_OPENING_FILE,
322
_("Error opening encrypted database file \"%s\": %s"),
323
self->priv->filename, gpgme_strerror (error_gpgme));
327
/* Open/Create the plain file */
328
operation->plain_io_channel = g_io_channel_new_file (self->priv->plain_filename, encrypting ? "r" : "w", &io_error);
329
if (operation->plain_io_channel == NULL) {
330
g_propagate_error (error, io_error);
334
/* Ensure the permissions are restricted to only the current user */
335
fchmod (g_io_channel_unix_get_fd (operation->plain_io_channel), S_IRWXU);
337
/* Pass it to GPGME */
338
error_gpgme = gpgme_data_new_from_fd (&(operation->gpgme_plain), g_io_channel_unix_get_fd (operation->plain_io_channel));
339
if (error_gpgme != GPG_ERR_NO_ERROR) {
340
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_OPENING_FILE,
341
_("Error opening plain database file \"%s\": %s"),
342
self->priv->plain_filename, gpgme_strerror (error_gpgme));
350
cipher_operation_free (CipherOperation *operation)
352
gpgme_data_release (operation->gpgme_cipher);
353
gpgme_data_release (operation->gpgme_plain);
355
if (operation->cipher_io_channel != NULL) {
356
g_io_channel_flush (operation->cipher_io_channel, NULL);
357
g_io_channel_unref (operation->cipher_io_channel);
360
if (operation->plain_io_channel != NULL) {
361
g_io_channel_shutdown (operation->plain_io_channel, TRUE, NULL);
362
g_io_channel_unref (operation->plain_io_channel);
365
/* We could free the operation before the context is even created (bgo#599598) */
366
if (operation->context != NULL) {
367
gpgme_signers_clear (operation->context);
368
gpgme_release (operation->context);
371
g_object_unref (operation->storage_manager);
376
database_idle_cb (CipherOperation *operation)
378
AlmanahStorageManager *self = operation->storage_manager;
379
gpgme_error_t error_gpgme;
381
if (gpgme_wait (operation->context, &error_gpgme, FALSE) != NULL || error_gpgme != GPG_ERR_NO_ERROR) {
383
gchar *warning_message = NULL;
385
/* Check to see if the encrypted file is 0B in size, which isn't good. Not much we can do about it except quit without deleting the
386
* plaintext database. */
387
g_stat (self->priv->filename, &db_stat);
388
if (g_file_test (self->priv->filename, G_FILE_TEST_IS_REGULAR) == FALSE || db_stat.st_size == 0) {
389
warning_message = g_strdup (_("The encrypted database is empty. The plain database file has been left undeleted as backup."));
390
} else if (g_unlink (self->priv->plain_filename) != 0) {
391
/* Delete the plain file */
392
warning_message = g_strdup_printf (_("Could not delete plain database file \"%s\"."), self->priv->plain_filename);
395
/* A slight assumption that we're disconnecting at this point (we're technically only encrypting), but a valid one. */
396
g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0,
397
(error_gpgme == GPG_ERR_NO_ERROR) ? NULL: gpgme_strerror (error_gpgme),
399
g_free (warning_message);
402
cipher_operation_free (operation);
411
decrypt_database (AlmanahStorageManager *self, GError **error)
413
GError *preparation_error = NULL;
414
CipherOperation *operation;
415
gpgme_error_t error_gpgme;
417
operation = g_new0 (CipherOperation, 1);
418
operation->storage_manager = g_object_ref (self);
421
if (prepare_gpgme (self, FALSE, operation, &preparation_error) != TRUE ||
422
open_db_files (self, FALSE, operation, &preparation_error) != TRUE) {
423
cipher_operation_free (operation);
424
g_propagate_error (error, preparation_error);
428
/* Decrypt and verify! */
429
error_gpgme = gpgme_op_decrypt_verify (operation->context, operation->gpgme_cipher, operation->gpgme_plain);
430
if (error_gpgme != GPG_ERR_NO_ERROR) {
431
cipher_operation_free (operation);
432
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_DECRYPTING,
433
_("Error decrypting database: %s"),
434
gpgme_strerror (error_gpgme));
438
/* Do this one synchronously */
439
cipher_operation_free (operation);
445
encrypt_database (AlmanahStorageManager *self, const gchar *encryption_key, GError **error)
447
GError *preparation_error = NULL;
448
CipherOperation *operation;
449
gpgme_error_t error_gpgme;
450
gpgme_key_t gpgme_keys[2] = { NULL, };
452
operation = g_new0 (CipherOperation, 1);
453
operation->storage_manager = g_object_ref (self);
456
if (prepare_gpgme (self, TRUE, operation, &preparation_error) != TRUE) {
457
cipher_operation_free (operation);
458
g_propagate_error (error, preparation_error);
462
/* Set up signing and the recipient */
463
error_gpgme = gpgme_get_key (operation->context, encryption_key, &gpgme_keys[0], FALSE);
464
if (error_gpgme != GPG_ERR_NO_ERROR || gpgme_keys[0] == NULL) {
465
cipher_operation_free (operation);
466
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_GETTING_KEY,
467
_("Error getting encryption key: %s"),
468
gpgme_strerror (error_gpgme));
472
gpgme_signers_add (operation->context, gpgme_keys[0]);
474
if (open_db_files (self, TRUE, operation, &preparation_error) != TRUE) {
475
cipher_operation_free (operation);
476
g_propagate_error (error, preparation_error);
480
/* Encrypt and sign! */
481
error_gpgme = gpgme_op_encrypt_sign_start (operation->context, gpgme_keys, 0, operation->gpgme_plain, operation->gpgme_cipher);
482
gpgme_key_unref (gpgme_keys[0]);
484
if (error_gpgme != GPG_ERR_NO_ERROR) {
485
cipher_operation_free (operation);
487
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_ENCRYPTING,
488
_("Error encrypting database: %s"),
489
gpgme_strerror (error_gpgme));
493
/* The operation will be completed in the idle function */
494
g_idle_add ((GSourceFunc) database_idle_cb, operation);
500
get_encryption_key (AlmanahStorageManager *self)
504
gchar *encryption_key;
506
encryption_key = g_strdup (self->priv->encryption_key);
507
if (encryption_key == NULL || encryption_key[0] == '\0') {
508
g_free (encryption_key);
512
/* Key is generally in the form openpgp:FOOBARKEY, and GPGME doesn't like the openpgp: prefix, so it must be removed. */
513
key_parts = g_strsplit (encryption_key, ":", 2);
514
g_free (encryption_key);
516
for (i = 0; key_parts[i] != NULL; i++) {
517
if (strcmp (key_parts[i], "openpgp") != 0)
518
encryption_key = key_parts[i];
520
g_free (key_parts[i]);
524
return encryption_key;
526
#endif /* ENABLE_ENCRYPTION */
529
back_up_file (const gchar *filename)
531
GFile *original_file, *backup_file;
532
gchar *backup_filename;
534
/* Make a backup of the encrypted database file */
535
original_file = g_file_new_for_path (filename);
536
backup_filename = g_strdup_printf ("%s~", filename);
537
backup_file = g_file_new_for_path (backup_filename);
538
g_free (backup_filename);
540
g_file_copy_async (original_file, backup_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, NULL, NULL);
542
g_object_unref (original_file);
543
g_object_unref (backup_file);
547
almanah_storage_manager_connect (AlmanahStorageManager *self, GError **error)
549
#ifdef ENABLE_ENCRYPTION
550
struct stat encrypted_db_stat, plaintext_db_stat;
552
g_stat (self->priv->filename, &encrypted_db_stat);
554
/* If we're decrypting, don't bother if the cipher file doesn't exist (i.e. the database hasn't yet been created), or is empty
556
if (g_file_test (self->priv->filename, G_FILE_TEST_IS_REGULAR) == TRUE && encrypted_db_stat.st_size > 0) {
557
GError *child_error = NULL;
559
/* Make a backup of the encrypted database file */
560
back_up_file (self->priv->filename);
562
g_stat (self->priv->plain_filename, &plaintext_db_stat);
564
/* Only decrypt the database if the plaintext database doesn't exist or is empty. If the plaintext database exists and is non-empty,
565
* don't decrypt — just use that database. */
566
if (g_file_test (self->priv->plain_filename, G_FILE_TEST_IS_REGULAR) != TRUE || plaintext_db_stat.st_size == 0) {
567
/* Decrypt the database, or display an error if that fails (but not if it fails due to a missing encrypted DB file — just
568
* fall through and try to open the plain DB file in that case). */
569
if (decrypt_database (self, &child_error) != TRUE) {
570
if (child_error->code != G_FILE_ERROR_NOENT) {
571
g_propagate_error (error, child_error);
575
g_error_free (child_error);
580
self->priv->decrypted = TRUE;
582
/* Make a backup of the plaintext database file */
583
back_up_file (self->priv->plain_filename);
584
self->priv->decrypted = FALSE;
585
#endif /* ENABLE_ENCRYPTION */
587
/* Open the plain database */
588
if (sqlite3_open (self->priv->plain_filename, &(self->priv->connection)) != SQLITE_OK) {
589
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_OPENING_FILE,
590
_("Could not open database \"%s\". SQLite provided the following error message: %s"),
591
self->priv->filename, sqlite3_errmsg (self->priv->connection));
595
/* Can't hurt to create the tables now */
596
create_tables (self);
602
almanah_storage_manager_disconnect (AlmanahStorageManager *self, GError **error)
604
#ifdef ENABLE_ENCRYPTION
605
gchar *encryption_key;
606
GError *child_error = NULL;
607
#endif /* ENABLE_ENCRYPTION */
609
/* Close the DB connection */
610
sqlite3_close (self->priv->connection);
612
#ifdef ENABLE_ENCRYPTION
613
/* If the database wasn't encrypted before we opened it, we won't encrypt it when closing. In fact, we'll go so far as to delete the old
614
* encrypted database file. */
615
if (self->priv->decrypted == FALSE)
616
goto delete_encrypted_db;
618
/* Get the encryption key */
619
encryption_key = get_encryption_key (self);
620
if (encryption_key == NULL)
621
goto delete_encrypted_db;
623
/* Encrypt the plain DB file */
624
if (encrypt_database (self, encryption_key, &child_error) != TRUE) {
625
g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, child_error->message);
627
if (g_error_matches (child_error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_GETTING_KEY) == TRUE)
628
g_propagate_error (error, child_error);
630
g_error_free (child_error);
632
g_free (encryption_key);
636
g_free (encryption_key);
637
#else /* ENABLE_ENCRYPTION */
638
g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, NULL);
639
#endif /* !ENABLE_ENCRYPTION */
643
#ifdef ENABLE_ENCRYPTION
645
/* Delete the old encrypted database and return */
646
g_unlink (self->priv->filename);
647
g_signal_emit (self, storage_manager_signals[SIGNAL_DISCONNECTED], 0, NULL, NULL);
649
#endif /* ENABLE_ENCRYPTION */
653
simple_query (AlmanahStorageManager *self, const gchar *query, GError **error, ...)
655
AlmanahStorageManagerPrivate *priv = self->priv;
659
va_start (params, error);
660
new_query = sqlite3_vmprintf (query, params);
663
g_debug ("Database query: %s", new_query);
665
if (sqlite3_exec (priv->connection, new_query, NULL, NULL, NULL) != SQLITE_OK) {
666
g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_RUNNING_QUERY,
667
_("Could not run query \"%s\". SQLite provided the following error message: %s"),
668
new_query, sqlite3_errmsg (priv->connection));
669
sqlite3_free (new_query);
673
sqlite3_free (new_query);
679
almanah_storage_manager_get_statistics (AlmanahStorageManager *self, guint *entry_count)
681
sqlite3_stmt *statement;
685
/* Get the number of entries and the number of letters */
686
if (sqlite3_prepare_v2 (self->priv->connection, "SELECT COUNT (year) FROM entries", -1, &statement, NULL) != SQLITE_OK)
689
if (sqlite3_step (statement) != SQLITE_ROW) {
690
sqlite3_finalize (statement);
694
*entry_count = sqlite3_column_int (statement, 0);
695
sqlite3_finalize (statement);
701
almanah_storage_manager_entry_exists (AlmanahStorageManager *self, GDate *date)
703
sqlite3_stmt *statement;
704
gboolean exists = FALSE;
706
if (sqlite3_prepare_v2 (self->priv->connection, "SELECT day FROM entries WHERE year = ? AND month = ? AND day = ? LIMIT 1", -1,
707
&statement, NULL) != SQLITE_OK) {
711
sqlite3_bind_int (statement, 1, g_date_get_year (date));
712
sqlite3_bind_int (statement, 2, g_date_get_month (date));
713
sqlite3_bind_int (statement, 3, g_date_get_day (date));
715
/* If there's a result, this'll return SQLITE_ROW; it'll return SQLITE_DONE otherwise */
716
if (sqlite3_step (statement) == SQLITE_ROW)
719
sqlite3_finalize (statement);
724
static AlmanahEntry *
725
build_entry_from_statement (sqlite3_stmt *statement)
727
GDate date, last_edited;
730
/* Assumes query for SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version, ... FROM entries ... */
733
g_date_set_dmy (&date,
734
sqlite3_column_int (statement, 2),
735
sqlite3_column_int (statement, 3),
736
sqlite3_column_int (statement, 4));
738
/* Get the content */
739
entry = almanah_entry_new (&date);
740
almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0), sqlite3_column_int (statement, 8));
741
almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 1) == 1) ? TRUE : FALSE);
743
/* Set the last-edited date if possible (for backwards-compatibility, we have to assume that not all entries have valid last-edited dates set,
744
* since last-edited support was only added in 0.8.0). */
745
if (g_date_valid_dmy (sqlite3_column_int (statement, 5),
746
sqlite3_column_int (statement, 6),
747
sqlite3_column_int (statement, 7)) == TRUE) {
748
g_date_set_dmy (&last_edited,
749
sqlite3_column_int (statement, 5),
750
sqlite3_column_int (statement, 6),
751
sqlite3_column_int (statement, 7));
752
almanah_entry_set_last_edited (entry, &last_edited);
759
* almanah_storage_manager_get_entry:
760
* @self: an #AlmanahStorageManager
761
* @date: the date of the entry
763
* Gets the entry for the specified day from the database. If an entry can't be found it will return %NULL.
765
* Return value: an #AlmanahEntry or %NULL
768
almanah_storage_manager_get_entry (AlmanahStorageManager *self, GDate *date)
771
sqlite3_stmt *statement;
773
/* Prepare the statement */
774
if (sqlite3_prepare_v2 (self->priv->connection,
775
"SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version FROM entries "
776
"WHERE year = ? AND month = ? AND day = ?", -1,
777
&statement, NULL) != SQLITE_OK) {
781
/* Bind parameters */
782
sqlite3_bind_int (statement, 1, g_date_get_year (date));
783
sqlite3_bind_int (statement, 2, g_date_get_month (date));
784
sqlite3_bind_int (statement, 3, g_date_get_day (date));
786
/* Execute the statement */
787
if (sqlite3_step (statement) != SQLITE_ROW) {
788
sqlite3_finalize (statement);
793
entry = build_entry_from_statement (statement);
794
sqlite3_finalize (statement);
800
* almanah_storage_manager_set_entry:
801
* @self: an #AlmanahStorageManager
802
* @entry: an #AlmanahEntry
804
* Saves the specified @entry in the database synchronously. If the @entry's content is empty, it will delete @entry's rows in the database.
806
* The entry's last-edited date should be manually updated before storing it in the database, if desired.
808
* Return value: %TRUE on success, %FALSE otherwise
811
almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *entry)
815
almanah_entry_get_date (entry, &date);
817
if (almanah_entry_is_empty (entry) == TRUE) {
818
/* Delete the entry */
819
gboolean success = simple_query (self, "DELETE FROM entries WHERE year = %u AND month = %u AND day = %u", NULL,
820
g_date_get_year (&date),
821
g_date_get_month (&date),
822
g_date_get_day (&date));
824
/* Signal of the operation */
825
g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_REMOVED], 0, &date);
831
sqlite3_stmt *statement;
833
gboolean existed_before;
836
existed_before = almanah_storage_manager_entry_exists (self, &date);
838
/* Prepare the statement */
839
if (sqlite3_prepare_v2 (self->priv->connection,
840
"REPLACE INTO entries "
841
"(year, month, day, content, is_important, edited_day, edited_month, edited_year, version) "
842
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", -1,
843
&statement, NULL) != SQLITE_OK) {
847
/* Bind parameters */
848
sqlite3_bind_int (statement, 1, g_date_get_year (&date));
849
sqlite3_bind_int (statement, 2, g_date_get_month (&date));
850
sqlite3_bind_int (statement, 3, g_date_get_day (&date));
852
data = almanah_entry_get_data (entry, &length, &version);
853
sqlite3_bind_blob (statement, 4, data, length, SQLITE_TRANSIENT);
854
sqlite3_bind_int (statement, 9, version);
856
sqlite3_bind_int (statement, 5, almanah_entry_is_important (entry));
858
almanah_entry_get_last_edited (entry, &last_edited);
859
sqlite3_bind_int (statement, 6, g_date_get_day (&last_edited));
860
sqlite3_bind_int (statement, 7, g_date_get_month (&last_edited));
861
sqlite3_bind_int (statement, 8, g_date_get_year (&last_edited));
863
/* Execute the statement */
864
if (sqlite3_step (statement) != SQLITE_DONE) {
865
sqlite3_finalize (statement);
869
sqlite3_finalize (statement);
871
/* Signal of the operation */
872
if (existed_before == TRUE)
873
g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_MODIFIED], 0, entry);
875
g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_ADDED], 0, entry);
882
* almanah_storage_manager_iter_init:
883
* @iter: an #AlmanahStorageManagerIter to initialise
885
* Initialises the given iterator so it can be used by #AlmanahStorageManager functions. Typically, initialisers are allocated on the stack, so need
886
* explicitly initialising before being passed to functions such as almanah_storage_manager_get_entries().
891
almanah_storage_manager_iter_init (AlmanahStorageManagerIter *iter)
893
g_return_if_fail (iter != NULL);
895
iter->statement = NULL;
896
iter->user_data = NULL;
897
iter->finished = FALSE;
901
GtkTextBuffer *text_buffer;
902
gchar *search_string;
906
* almanah_storage_manager_search_entries:
907
* @self: an #AlmanahStorageManager
908
* @search_string: string for which to search in entry content
909
* @iter: an #AlmanahStorageManagerIter to keep track of the query
911
* Searches for @search_string in the content in entries in the database, and returns the results iteratively. @iter should be initialised with
912
* almanah_storage_manager_iter_init() and passed to almanah_storage_manager_search_entries(). This will then return a matching #AlmanahEntry every
913
* time it's called with the same @iter until it reaches the end of the result set, when it will return %NULL. It will also finish and return %NULL on
914
* error or if there are no results.
916
* The results are returned in descending date order.
918
* Calling functions must get every result from the result set (i.e. not stop calling almanah_storage_manager_search_entries() until it returns
921
* Return value: an #AlmanahEntry, or %NULL; unref with g_object_unref()
924
almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string, AlmanahStorageManagerIter *iter)
926
sqlite3_stmt *statement;
927
GtkTextBuffer *text_buffer;
929
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), NULL);
930
g_return_val_if_fail (iter != NULL, NULL);
931
g_return_val_if_fail (iter->statement != NULL || search_string != NULL, NULL);
932
g_return_val_if_fail (iter->statement == NULL || iter->user_data != NULL, NULL);
934
if (iter->finished == TRUE)
937
if (iter->statement == NULL) {
940
/* Prepare the statement. */
941
if (sqlite3_prepare_v2 (self->priv->connection,
942
"SELECT e.content, e.is_important, e.day, e.month, e.year, e.edited_day, e.edited_month, e.edited_year, e.version, GROUP_CONCAT(et.tag) AS tags FROM entries AS e "
943
"LEFT JOIN entry_tag AS et ON (e.day=et.day AND e.month=et.month AND e.year=et.year) "
944
"GROUP BY e.year, e.month, e.day "
945
"ORDER BY e.year DESC, e.month DESC, e.day DESC", -1,
946
(sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
950
/* Set up persistent data for the operation */
951
data = g_slice_new (SearchData);
952
data->text_buffer = gtk_text_buffer_new (NULL);
953
data->search_string = g_strdup (search_string);
954
iter->user_data = data;
957
statement = iter->statement;
958
text_buffer = ((SearchData*) iter->user_data)->text_buffer;
959
search_string = ((SearchData*) iter->user_data)->search_string;
961
/* Execute the statement */
962
switch (sqlite3_step (statement)) {
964
GtkTextIter text_iter;
965
AlmanahEntry *entry = build_entry_from_statement (statement);
966
const gchar *tags = sqlite3_column_text (statement, 9);
968
/* Deserialise the entry into our buffer */
969
gtk_text_buffer_set_text (text_buffer, "", 0);
970
if (almanah_entry_get_content (entry, text_buffer, TRUE, NULL) == FALSE) {
971
/* Error: return the next entry instead */
972
g_object_unref (entry);
973
g_warning (_("Error deserializing entry into buffer while searching."));
974
return almanah_storage_manager_search_entries (self, NULL, iter);
977
/* Perform the search */
978
gtk_text_buffer_get_start_iter (text_buffer, &text_iter);
979
if (gtk_text_iter_forward_search (&text_iter, search_string,
980
GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY | GTK_TEXT_SEARCH_CASE_INSENSITIVE,
981
NULL, NULL, NULL) == TRUE) {
982
/* A match was found! */
984
} else if (tags != NULL && (strstr (tags, search_string) != NULL)) {
985
/* A match in an entry tag */
989
/* Free stuff up and return the next match instead */
990
g_object_unref (entry);
991
return almanah_storage_manager_search_entries (self, NULL, iter);
1001
/* Clean up the iter and return */
1002
sqlite3_finalize (statement);
1003
iter->statement = NULL;
1004
g_object_unref (((SearchData*) iter->user_data)->text_buffer);
1005
g_free (((SearchData*) iter->user_data)->search_string);
1006
g_slice_free (SearchData, iter->user_data);
1007
iter->user_data = NULL;
1008
iter->finished = TRUE;
1014
g_assert_not_reached ();
1018
gchar *search_string;
1019
AlmanahStorageManagerSearchCallback progress_callback;
1020
gpointer progress_user_data;
1021
GDestroyNotify progress_user_data_destroy;
1026
search_async_data_free (SearchAsyncData *data)
1028
g_free (data->search_string);
1030
g_slice_free (SearchAsyncData, data);
1034
AlmanahStorageManagerSearchCallback callback;
1035
AlmanahStorageManager *storage_manager;
1036
AlmanahEntry *entry;
1038
} ProgressCallbackData;
1041
progress_callback_data_free (ProgressCallbackData *data)
1043
g_object_unref (data->entry);
1044
g_object_unref (data->storage_manager);
1046
g_slice_free (ProgressCallbackData, data);
1050
search_entry_async_progress_cb (ProgressCallbackData *data)
1052
data->callback (data->storage_manager, data->entry, data->user_data);
1058
search_entries_async_thread (GSimpleAsyncResult *result, AlmanahStorageManager *storage_manager, GCancellable *cancellable)
1060
AlmanahStorageManagerIter iter;
1061
AlmanahEntry *entry;
1062
SearchAsyncData *search_data;
1063
ProgressCallbackData *progress_data;
1064
GError *error = NULL;
1066
search_data = g_simple_async_result_get_op_res_gpointer (result);
1068
almanah_storage_manager_iter_init (&iter);
1069
while ((entry = almanah_storage_manager_search_entries (storage_manager, search_data->search_string, &iter)) != NULL) {
1070
/* Don't do any unnecessary work */
1071
if (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, &error)) {
1072
g_simple_async_result_set_from_error (result, error);
1073
g_error_free (error);
1077
search_data->count++;
1079
/* Queue a progress callback for the result */
1080
progress_data = g_slice_new (ProgressCallbackData);
1081
progress_data->callback = search_data->progress_callback;
1082
progress_data->storage_manager = g_object_ref (storage_manager);
1083
progress_data->entry = g_object_ref (entry);
1084
progress_data->user_data = search_data->progress_user_data;
1086
/* We have to use G_PRIORITY_DEFAULT here to contend with the GAsyncReadyCallback for the whole search operation. All the progress
1087
* callbacks must have been made before the finished callback. */
1088
g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) search_entry_async_progress_cb,
1089
progress_data, (GDestroyNotify) progress_callback_data_free);
1094
* almanah_storage_manager_search_entries_async_finish:
1095
* @self: an #AlmanahStorageManager
1096
* @result: a #GSimpleAsyncResult
1097
* @error: a #GError or %NULL
1099
* Finish an asynchronous search started with almanah_storage_manager_search_entries_async().
1101
* Return value: the number of entries which matched the search string, or <code class="literal">-1</code> on error
1104
almanah_storage_manager_search_entries_async_finish (AlmanahStorageManager *self, GAsyncResult *result, GError **error)
1106
SearchAsyncData *search_data = NULL;
1109
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), -1);
1110
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1);
1111
g_return_val_if_fail (error == NULL || *error == NULL, -1);
1113
if (g_simple_async_result_is_valid (result, G_OBJECT (self), almanah_storage_manager_search_entries_async) == FALSE) {
1117
/* Check for errors */
1118
search_data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
1120
/* Extract the number of results */
1121
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error) == FALSE) {
1122
retval = search_data->count;
1125
/* Notify of destruction of the user data. We have to do this here so that we can guarantee that all of the progress callbacks have
1127
if (search_data != NULL && search_data->progress_user_data_destroy != NULL) {
1128
search_data->progress_user_data_destroy (search_data->progress_user_data);
1135
* almanah_storage_manager_search_entries_async:
1136
* @self: an #AlmanahStorageManager
1137
* @search_string: the string of search terms being queried against
1138
* @cancellable: (allow-none): a #GCancellable, or %NULL
1139
* @progress_callback: (scope notified) (allow-none) (closure progress_user_data): a function to call for each result as it's found, or %NULL
1140
* @progress_user_data: (closure): data to pass to @progress_callback
1141
* @progress_user_data_destroy: (allow-none): a function to destroy the @progress_user_data when it will not be used any more, or %NULL
1142
* @callback: a #GAsyncReadyCallback to call once the search is complete
1143
* @user_data: the data to pass to @callback
1145
* Launch an asynchronous search for @search_string in the content in entries in the database.
1147
* When the @search_string was found in an entry, @progess_callback will be called with the #AlmanahEntry which was found.
1149
* When the search finishes, @callback will be called, then you can call almanah_storage_manager_search_entries_async_finish() to get the number of
1150
* entries found in total by the operation.
1153
almanah_storage_manager_search_entries_async (AlmanahStorageManager *self, const gchar *search_string, GCancellable *cancellable,
1154
AlmanahStorageManagerSearchCallback progress_callback, gpointer progress_user_data,
1155
GDestroyNotify progress_user_data_destroy,
1156
GAsyncReadyCallback callback, gpointer user_data)
1158
GSimpleAsyncResult *result;
1159
SearchAsyncData *search_data;
1161
g_return_if_fail (ALMANAH_IS_STORAGE_MANAGER (self));
1162
g_return_if_fail (search_string != NULL);
1163
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1164
g_return_if_fail (callback != NULL);
1166
result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, almanah_storage_manager_search_entries_async);
1168
search_data = g_slice_new (SearchAsyncData);
1169
search_data->search_string = g_strdup (search_string);
1170
search_data->progress_callback = progress_callback;
1171
search_data->progress_user_data = progress_user_data;
1172
search_data->progress_user_data_destroy = progress_user_data_destroy;
1173
search_data->count = 0;
1175
g_simple_async_result_set_op_res_gpointer (result, search_data, (GDestroyNotify) search_async_data_free);
1176
g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) search_entries_async_thread, G_PRIORITY_DEFAULT, cancellable);
1178
g_object_unref (result);
1182
* almanah_storage_manager_get_entries:
1183
* @self: an #AlmanahStorageManager
1184
* @iter: an #AlmanahStorageManagerIter to keep track of the query
1186
* Iterates through every single #AlmanahEntry in the database using the given #AlmanahStorageManagerIter. @iter should be initialised with
1187
* almanah_storage_manager_iter_init() and passed to almanah_storage_manager_get_entries(). This will then return an #AlmanahEntry every time it's
1188
* called with the same @iter until it reaches the end of the result set, when it will return %NULL. It will also finish and return %NULL on error.
1190
* Calling functions must get every result from the result set (i.e. not stop calling almanah_storage_manager_get_entries() until it returns %NULL).
1192
* Return value: an #AlmanahEntry, or %NULL; unref with g_object_unref()
1195
almanah_storage_manager_get_entries (AlmanahStorageManager *self, AlmanahStorageManagerIter *iter)
1197
sqlite3_stmt *statement;
1199
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), NULL);
1200
g_return_val_if_fail (iter != NULL, NULL);
1202
if (iter->finished == TRUE)
1205
if (iter->statement == NULL) {
1206
/* Prepare the statement */
1207
if (sqlite3_prepare_v2 (self->priv->connection,
1208
"SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version "
1210
(sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
1215
statement = iter->statement;
1217
/* Execute the statement */
1218
switch (sqlite3_step (statement)) {
1220
return build_entry_from_statement (statement);
1227
case SQLITE_CORRUPT:
1229
/* Clean up the iter and return */
1230
sqlite3_finalize (statement);
1231
iter->statement = NULL;
1232
iter->finished = TRUE;
1238
g_assert_not_reached ();
1241
/* NOTE: Free results with g_free. Return value is 0-based. */
1243
almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days)
1245
sqlite3_stmt *statement;
1249
/* Build the result array */
1250
i = g_date_get_days_in_month (month, year);
1251
if (num_days != NULL)
1253
days = g_malloc0 (sizeof (gboolean) * i);
1255
/* Prepare and run the query */
1256
if (sqlite3_prepare_v2 (self->priv->connection, "SELECT day FROM entries WHERE year = ? AND month = ?", -1, &statement, NULL) != SQLITE_OK) {
1261
sqlite3_bind_int (statement, 1, year);
1262
sqlite3_bind_int (statement, 2, month);
1264
/* For each day which is returned, mark it in the array of days */
1265
while ((result = sqlite3_step (statement)) == SQLITE_ROW)
1266
days[sqlite3_column_int (statement, 0) - 1] = TRUE;
1268
sqlite3_finalize (statement);
1270
if (result != SQLITE_DONE) {
1279
/* NOTE: Free results with g_free. Return value is 0-based. */
1281
almanah_storage_manager_get_month_important_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days)
1283
sqlite3_stmt *statement;
1287
/* Build the result array */
1288
i = g_date_get_days_in_month (month, year);
1289
if (num_days != NULL)
1291
days = g_malloc0 (sizeof (gboolean) * i);
1293
/* Prepare and run the query */
1294
if (sqlite3_prepare_v2 (self->priv->connection, "SELECT day FROM entries WHERE year = ? AND month = ? AND is_important = 1", -1,
1295
&statement, NULL) != SQLITE_OK) {
1300
sqlite3_bind_int (statement, 1, year);
1301
sqlite3_bind_int (statement, 2, month);
1303
/* For each day which is returned, mark it in the array of days */
1304
while ((result = sqlite3_step (statement)) == SQLITE_ROW)
1305
days[sqlite3_column_int (statement, 0) - 1] = TRUE;
1307
sqlite3_finalize (statement);
1309
if (result != SQLITE_DONE) {
1319
almanah_storage_manager_get_filename (AlmanahStorageManager *self, gboolean plain)
1321
return (plain == TRUE) ? self->priv->plain_filename : self->priv->filename;
1325
* almanah_storage_manager_entry_add_tag:
1326
* @self: an #AlmanahStorageManager
1327
* @entry: an #AlmanahEntry
1330
* Append the string in @tag as a tag for the entry @entry. If the @tag is empty or the @entry don't be previuslly saved, returns %FALSE
1332
* Return value: %TRUE on success, %FALSE otherwise
1335
almanah_storage_manager_entry_add_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
1337
GDate entry_last_edited;
1339
sqlite3_stmt *statement;
1342
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
1343
g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
1344
g_return_val_if_fail (g_utf8_strlen (tag, 1) == 1, FALSE);
1346
almanah_entry_get_date (entry, &entry_date);
1347
if (g_date_valid (&entry_date) != TRUE) {
1348
g_debug ("Invalid entry date");
1352
/* Don't duplicate tags */
1353
if (almanah_storage_manager_entry_check_tag (self, entry, tag)) {
1354
g_debug ("Duplicated tag now allowed");
1358
if ((result_error = sqlite3_prepare_v2 (self->priv->connection,
1359
"INSERT INTO entry_tag (year, month, day, tag) VALUES (?, ?, ?, ?)",
1360
-1, &statement, NULL)) != SQLITE_OK) {
1361
g_debug ("Can't prepare statement. SQLite error code: %d", result_error);
1365
sqlite3_bind_int (statement, 1, g_date_get_year (&entry_date));
1366
sqlite3_bind_int (statement, 2, g_date_get_month (&entry_date));
1367
sqlite3_bind_int (statement, 3, g_date_get_day (&entry_date));
1368
/* @TODO: STATIC or TRANSIENT */
1369
sqlite3_bind_text (statement, 4, tag, -1, SQLITE_STATIC);
1371
if (sqlite3_step (statement) != SQLITE_DONE) {
1372
sqlite3_finalize (statement);
1373
g_debug ("Can't save tag");
1377
sqlite3_finalize (statement);
1379
g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_TAG_ADDED], 0, entry, g_strdup (tag));
1385
* almanah_storage_manager_entry_remove_tag:
1386
* @self: an #AlmanahStorageManager
1387
* @entry: an #AlmanahEntry
1388
* @tag: a string with the tag to be removed
1390
* Remove the tag with the given string in @tag as a tag for the entry @entry.
1392
* Return value: %TRUE on success, %FALSE otherwise
1395
almanah_storage_manager_entry_remove_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
1400
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
1401
g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
1402
g_return_val_if_fail (g_utf8_strlen (tag, 1) == 1, FALSE);
1404
almanah_entry_get_date (entry, &date);
1406
result = simple_query (self, "DELETE FROM entry_tag WHERE year = %u AND month = %u AND day = %u AND tag = '%s'", NULL,
1407
g_date_get_year (&date),
1408
g_date_get_month (&date),
1409
g_date_get_day (&date),
1413
g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_TAG_REMOVED], 0, entry, tag);
1419
* almanah_storage_manager_entry_get_tags:
1420
* @self: an #AlmanahStorageManager
1421
* @entry: an #AlmanahEntry
1423
* Gets the tags added to an entry by the user from the database.
1426
almanah_storage_manager_entry_get_tags (AlmanahStorageManager *self, AlmanahEntry *entry)
1430
sqlite3_stmt *statement;
1433
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
1434
g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
1436
almanah_entry_get_date (entry, &date);
1437
if (g_date_valid (&date) != TRUE) {
1438
g_debug ("Invalid entry date");
1442
if (sqlite3_prepare_v2 (self->priv->connection,
1443
"SELECT DISTINCT tag FROM entry_tag WHERE year = ? AND month = ? AND day = ?",
1444
-1, &statement, NULL) != SQLITE_OK) {
1445
g_debug ("Can't prepare statement");
1449
sqlite3_bind_int (statement, 1, g_date_get_year (&date));
1450
sqlite3_bind_int (statement, 2, g_date_get_month (&date));
1451
sqlite3_bind_int (statement, 3, g_date_get_day (&date));
1453
while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
1454
tags = g_list_append (tags, g_strdup (sqlite3_column_text (statement, 0)));
1457
sqlite3_finalize (statement);
1459
if (result != SQLITE_DONE) {
1460
g_debug ("Error quering for tags from database: %s", sqlite3_errmsg (self->priv->connection));
1469
* almanah_storage_manager_entry_check_tag:
1470
* @self: an #AlmanahStorageManager
1471
* @entry: an #AlmanahEntry to check into it
1472
* @tag: the tag to be checked
1474
* Check if a tag has been added to an entry
1476
* Return value: TRUE if the tag already added to the entry, FALSE otherwise
1479
almanah_storage_manager_entry_check_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
1481
gboolean result, q_result;
1482
sqlite3_stmt *statement;
1485
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
1486
g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
1487
g_return_val_if_fail (g_utf8_strlen (tag, 1) == 1, FALSE);
1491
almanah_entry_get_date (entry, &date);
1492
if (g_date_valid (&date) != TRUE) {
1493
g_debug ("Invalid entry date");
1497
if (sqlite3_prepare_v2 (self->priv->connection,
1498
"SELECT count(1) FROM entry_tag WHERE year = ? AND month = ? AND day = ? AND tag = ?",
1499
-1, &statement, NULL) != SQLITE_OK) {
1500
g_debug ("Can't prepare statement");
1504
sqlite3_bind_int (statement, 1, g_date_get_year (&date));
1505
sqlite3_bind_int (statement, 2, g_date_get_month (&date));
1506
sqlite3_bind_int (statement, 3, g_date_get_day (&date));
1507
sqlite3_bind_text (statement, 4, tag, -1, SQLITE_STATIC);
1509
if ((q_result = sqlite3_step (statement)) == SQLITE_ROW) {
1510
if (sqlite3_column_int (statement, 0) > 0)
1514
if (q_result != SQLITE_DONE) {
1515
g_debug ("Error quering for a tag from database: %s", sqlite3_errmsg (self->priv->connection));
1518
sqlite3_finalize (statement);
1525
* almanah_storage_manager_get_tags:
1526
* @self: an #AlmanahStorageManager
1528
* Gets all the tags added to entries by the user from the database.
1530
* Return value: #GList with all the tags.
1533
almanah_storage_manager_get_tags (AlmanahStorageManager *self)
1536
sqlite3_stmt *statement;
1539
g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
1541
if ((result = sqlite3_prepare_v2 (self->priv->connection, "SELECT DISTINCT tag FROM entry_tag", -1, &statement, NULL)) != SQLITE_OK) {
1542
g_debug ("Can't prepare statement, error code: %d", result);
1546
while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
1547
tags = g_list_append (tags, g_strdup (sqlite3_column_text (statement, 0)));
1550
sqlite3_finalize (statement);
1552
if (result != SQLITE_DONE) {
1553
g_debug ("Error quering for tags from database: %s", sqlite3_errmsg (self->priv->connection));