~ubuntu-branches/ubuntu/wily/almanah/wily

« back to all changes in this revision

Viewing changes to .pc/spellchecking_error/src/storage-manager.c

  • Committer: Package Import Robot
  • Author(s): Angel Abad
  • Date: 2013-05-14 10:27:40 UTC
  • mfrom: (1.4.3)
  • Revision ID: package-import@ubuntu.com-20130514102740-51ms12655c04devm
Tags: 0.10.8-1
* Imported Upstream version 0.10.8
* debian/control:
  - Build-Depends on versioned libgtk-3-dev (>= 3.5.6)
  - Remove libedataserverui-3.0-dev from Build-Depends
  - Build-Depends on libgtkspell3-3-dev
* debian/patches/desktop_keywords:
  - Add Keywords entry in .desktop file
* debian/patches/spellchecking_error:
  - Fix spellchecking errors

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
 
2
/*
 
3
 * Almanah
 
4
 * Copyright (C) Philip Withnall 2008–2010 <philip@tecnocode.co.uk>
 
5
 *
 
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.
 
10
 *
 
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.
 
15
 *
 
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/>.
 
18
 */
 
19
 
 
20
#include <config.h>
 
21
#include <glib.h>
 
22
#include <glib/gi18n.h>
 
23
#include <glib/gstdio.h>
 
24
#include <gio/gio.h>
 
25
#include <gtk/gtk.h>
 
26
#include <sqlite3.h>
 
27
#include <stdlib.h>
 
28
#include <sys/stat.h>
 
29
#include <string.h>
 
30
#ifdef ENABLE_ENCRYPTION
 
31
#include <gpgme.h>
 
32
#endif /* ENABLE_ENCRYPTION */
 
33
 
 
34
#include "entry.h"
 
35
#include "storage-manager.h"
 
36
#include "almanah-marshal.h"
 
37
 
 
38
#define ENCRYPTED_SUFFIX ".encrypted"
 
39
 
 
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, ...);
 
44
 
 
45
struct _AlmanahStorageManagerPrivate {
 
46
        gchar *filename, *plain_filename;
 
47
        gchar *encryption_key;
 
48
        sqlite3 *connection;
 
49
        gboolean decrypted;
 
50
};
 
51
 
 
52
enum {
 
53
        PROP_FILENAME = 1,
 
54
        PROP_ENCRYPTION_KEY,
 
55
};
 
56
 
 
57
enum {
 
58
        SIGNAL_DISCONNECTED,
 
59
        SIGNAL_ENTRY_ADDED,
 
60
        SIGNAL_ENTRY_MODIFIED,
 
61
        SIGNAL_ENTRY_REMOVED,
 
62
        SIGNAL_ENTRY_TAG_ADDED,
 
63
        SIGNAL_ENTRY_TAG_REMOVED,
 
64
        LAST_SIGNAL
 
65
};
 
66
 
 
67
static guint storage_manager_signals[LAST_SIGNAL] = { 0, };
 
68
 
 
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))
 
71
 
 
72
GQuark
 
73
almanah_storage_manager_error_quark (void)
 
74
{
 
75
        return g_quark_from_static_string ("almanah-storage-manager-error-quark");
 
76
}
 
77
 
 
78
static void
 
79
almanah_storage_manager_class_init (AlmanahStorageManagerClass *klass)
 
80
{
 
81
        GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
82
 
 
83
        g_type_class_add_private (klass, sizeof (AlmanahStorageManagerPrivate));
 
84
 
 
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;
 
88
 
 
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.",
 
92
                                                              NULL,
 
93
                                                              G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
94
 
 
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.",
 
98
                                                              NULL,
 
99
                                                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
100
 
 
101
        storage_manager_signals[SIGNAL_DISCONNECTED] = g_signal_new ("disconnected",
 
102
                                                                     G_TYPE_FROM_CLASS (klass),
 
103
                                                                     G_SIGNAL_RUN_LAST,
 
104
                                                                     0, NULL, NULL,
 
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),
 
109
                                                                    G_SIGNAL_RUN_LAST,
 
110
                                                                    0, NULL, NULL,
 
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),
 
115
                                                                       G_SIGNAL_RUN_LAST,
 
116
                                                                       0, NULL, NULL,
 
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),
 
121
                                                                      G_SIGNAL_RUN_LAST,
 
122
                                                                      0, NULL, NULL,
 
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),
 
127
                                                                        G_SIGNAL_RUN_LAST,
 
128
                                                                        0, NULL, NULL,
 
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),
 
133
                                                                          G_SIGNAL_RUN_LAST,
 
134
                                                                          0, NULL, NULL,
 
135
                                                                          almanah_marshal_VOID__OBJECT_STRING,
 
136
                                                                          G_TYPE_NONE, 2, ALMANAH_TYPE_ENTRY, G_TYPE_STRING);
 
137
}
 
138
 
 
139
static void
 
140
almanah_storage_manager_init (AlmanahStorageManager *self)
 
141
{
 
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;
 
147
}
 
148
 
 
149
/**
 
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
 
153
 *
 
154
 * Creates a new #AlmanahStorageManager, connected to the given database @filename.
 
155
 *
 
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.
 
158
 *
 
159
 * If @encryption_key is %NULL, encryption will be disabled.
 
160
 *
 
161
 * Return value: the new #AlmanahStorageManager
 
162
 **/
 
163
AlmanahStorageManager *
 
164
almanah_storage_manager_new (const gchar *filename, const gchar *encryption_key)
 
165
{
 
166
        gchar *new_filename = NULL;
 
167
        AlmanahStorageManager *sm;
 
168
 
 
169
        if (g_str_has_suffix (filename, ENCRYPTED_SUFFIX) == TRUE)
 
170
                filename = new_filename = g_strndup (filename, strlen (filename) - strlen (ENCRYPTED_SUFFIX));
 
171
 
 
172
        sm = g_object_new (ALMANAH_TYPE_STORAGE_MANAGER,
 
173
                           "filename", filename,
 
174
                           "encryption-key", encryption_key,
 
175
                           NULL);
 
176
        g_free (new_filename);
 
177
 
 
178
        return sm;
 
179
}
 
180
 
 
181
static void
 
182
almanah_storage_manager_finalize (GObject *object)
 
183
{
 
184
        AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
 
185
 
 
186
        g_free (priv->filename);
 
187
        g_free (priv->plain_filename);
 
188
        g_free (priv->encryption_key);
 
189
 
 
190
        /* Chain up to the parent class */
 
191
        G_OBJECT_CLASS (almanah_storage_manager_parent_class)->finalize (object);
 
192
}
 
193
 
 
194
static void
 
195
almanah_storage_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
 
196
{
 
197
        AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
 
198
 
 
199
        switch (property_id) {
 
200
                case PROP_FILENAME:
 
201
                        g_value_set_string (value, g_strdup (priv->filename));
 
202
                        break;
 
203
                case PROP_ENCRYPTION_KEY:
 
204
                        g_value_set_string (value, priv->encryption_key);
 
205
                        break;
 
206
                default:
 
207
                        /* We don't have any other property... */
 
208
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 
209
                        break;
 
210
        }
 
211
}
 
212
 
 
213
static void
 
214
almanah_storage_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
 
215
{
 
216
        AlmanahStorageManagerPrivate *priv = ALMANAH_STORAGE_MANAGER (object)->priv;
 
217
 
 
218
        switch (property_id) {
 
219
                case PROP_FILENAME:
 
220
                        priv->plain_filename = g_strdup (g_value_get_string (value));
 
221
                        priv->filename = g_strjoin (NULL, priv->plain_filename, ENCRYPTED_SUFFIX, NULL);
 
222
                        break;
 
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");
 
227
                        break;
 
228
                default:
 
229
                        /* We don't have any other property... */
 
230
                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 
231
                        break;
 
232
        }
 
233
}
 
234
 
 
235
static void
 
236
create_tables (AlmanahStorageManager *self)
 
237
{
 
238
        /* Dates are stored in ISO 8601 format…sort of */
 
239
        guint i;
 
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 */
 
249
                NULL
 
250
        };
 
251
 
 
252
        i = 0;
 
253
        while (queries[i] != NULL)
 
254
                simple_query (self, queries[i++], NULL);
 
255
}
 
256
 
 
257
#ifdef ENABLE_ENCRYPTION
 
258
typedef struct {
 
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;
 
264
        gpgme_ctx_t context;
 
265
} CipherOperation;
 
266
 
 
267
static gboolean
 
268
prepare_gpgme (AlmanahStorageManager *self, gboolean encrypting, CipherOperation *operation, GError **error)
 
269
{
 
270
        gpgme_error_t error_gpgme;
 
271
 
 
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"),
 
276
                             MIN_GPGME_VERSION);
 
277
                return FALSE;
 
278
        }
 
279
 
 
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));
 
286
                return FALSE;
 
287
        }
 
288
 
 
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));
 
295
                return FALSE;
 
296
        }
 
297
 
 
298
        gpgme_set_protocol (operation->context, GPGME_PROTOCOL_OpenPGP);
 
299
        gpgme_set_armor (operation->context, TRUE);
 
300
        gpgme_set_textmode (operation->context, FALSE);
 
301
 
 
302
        return TRUE;
 
303
}
 
304
 
 
305
static gboolean
 
306
open_db_files (AlmanahStorageManager *self, gboolean encrypting, CipherOperation *operation, GError **error)
 
307
{
 
308
        GError *io_error = NULL;
 
309
        gpgme_error_t error_gpgme;
 
310
 
 
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);
 
315
                return FALSE;
 
316
        }
 
317
 
 
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));
 
324
                return FALSE;
 
325
        }
 
326
 
 
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);
 
331
                return FALSE;
 
332
        }
 
333
 
 
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);
 
336
 
 
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));
 
343
                return FALSE;
 
344
        }
 
345
 
 
346
        return TRUE;
 
347
}
 
348
 
 
349
static void
 
350
cipher_operation_free (CipherOperation *operation)
 
351
{
 
352
        gpgme_data_release (operation->gpgme_cipher);
 
353
        gpgme_data_release (operation->gpgme_plain);
 
354
 
 
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);
 
358
        }
 
359
 
 
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);
 
363
        }
 
364
 
 
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);
 
369
        }
 
370
 
 
371
        g_object_unref (operation->storage_manager);
 
372
        g_free (operation);
 
373
}
 
374
 
 
375
static gboolean
 
376
database_idle_cb (CipherOperation *operation)
 
377
{
 
378
        AlmanahStorageManager *self = operation->storage_manager;
 
379
        gpgme_error_t error_gpgme;
 
380
 
 
381
        if (gpgme_wait (operation->context, &error_gpgme, FALSE) != NULL || error_gpgme != GPG_ERR_NO_ERROR) {
 
382
                struct stat db_stat;
 
383
                gchar *warning_message = NULL;
 
384
 
 
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);
 
393
                }
 
394
 
 
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),
 
398
                               warning_message);
 
399
                g_free (warning_message);
 
400
 
 
401
                /* Finished! */
 
402
                cipher_operation_free (operation);
 
403
 
 
404
                return FALSE;
 
405
        }
 
406
 
 
407
        return TRUE;
 
408
}
 
409
 
 
410
static gboolean
 
411
decrypt_database (AlmanahStorageManager *self, GError **error)
 
412
{
 
413
        GError *preparation_error = NULL;
 
414
        CipherOperation *operation;
 
415
        gpgme_error_t error_gpgme;
 
416
 
 
417
        operation = g_new0 (CipherOperation, 1);
 
418
        operation->storage_manager = g_object_ref (self);
 
419
 
 
420
        /* Set up */
 
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);
 
425
                return FALSE;
 
426
        }
 
427
 
 
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));
 
435
                return FALSE;
 
436
        }
 
437
 
 
438
        /* Do this one synchronously */
 
439
        cipher_operation_free (operation);
 
440
 
 
441
        return TRUE;
 
442
}
 
443
 
 
444
static gboolean
 
445
encrypt_database (AlmanahStorageManager *self, const gchar *encryption_key, GError **error)
 
446
{
 
447
        GError *preparation_error = NULL;
 
448
        CipherOperation *operation;
 
449
        gpgme_error_t error_gpgme;
 
450
        gpgme_key_t gpgme_keys[2] = { NULL, };
 
451
 
 
452
        operation = g_new0 (CipherOperation, 1);
 
453
        operation->storage_manager = g_object_ref (self);
 
454
 
 
455
        /* Set up */
 
456
        if (prepare_gpgme (self, TRUE, operation, &preparation_error) != TRUE) {
 
457
                cipher_operation_free (operation);
 
458
                g_propagate_error (error, preparation_error);
 
459
                return FALSE;
 
460
        }
 
461
 
 
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));
 
469
                return FALSE;
 
470
        }
 
471
 
 
472
        gpgme_signers_add (operation->context, gpgme_keys[0]);
 
473
 
 
474
        if (open_db_files (self, TRUE, operation, &preparation_error) != TRUE) {
 
475
                cipher_operation_free (operation);
 
476
                g_propagate_error (error, preparation_error);
 
477
                return FALSE;
 
478
        }
 
479
 
 
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]);
 
483
 
 
484
        if (error_gpgme != GPG_ERR_NO_ERROR) {
 
485
                cipher_operation_free (operation);
 
486
 
 
487
                g_set_error (error, ALMANAH_STORAGE_MANAGER_ERROR, ALMANAH_STORAGE_MANAGER_ERROR_ENCRYPTING,
 
488
                             _("Error encrypting database: %s"),
 
489
                             gpgme_strerror (error_gpgme));
 
490
                return FALSE;
 
491
        }
 
492
 
 
493
        /* The operation will be completed in the idle function */
 
494
        g_idle_add ((GSourceFunc) database_idle_cb, operation);
 
495
 
 
496
        return TRUE;
 
497
}
 
498
 
 
499
static gchar *
 
500
get_encryption_key (AlmanahStorageManager *self)
 
501
{
 
502
        gchar **key_parts;
 
503
        guint i;
 
504
        gchar *encryption_key;
 
505
 
 
506
        encryption_key = g_strdup (self->priv->encryption_key);
 
507
        if (encryption_key == NULL || encryption_key[0] == '\0') {
 
508
                g_free (encryption_key);
 
509
                return NULL;
 
510
        }
 
511
 
 
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);
 
515
 
 
516
        for (i = 0; key_parts[i] != NULL; i++) {
 
517
                if (strcmp (key_parts[i], "openpgp") != 0)
 
518
                        encryption_key = key_parts[i];
 
519
                else
 
520
                        g_free (key_parts[i]);
 
521
        }
 
522
        g_free (key_parts);
 
523
 
 
524
        return encryption_key;
 
525
}
 
526
#endif /* ENABLE_ENCRYPTION */
 
527
 
 
528
static void
 
529
back_up_file (const gchar *filename)
 
530
{
 
531
        GFile *original_file, *backup_file;
 
532
        gchar *backup_filename;
 
533
 
 
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);
 
539
 
 
540
        g_file_copy_async (original_file, backup_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, NULL, NULL);
 
541
 
 
542
        g_object_unref (original_file);
 
543
        g_object_unref (backup_file);
 
544
}
 
545
 
 
546
gboolean
 
547
almanah_storage_manager_connect (AlmanahStorageManager *self, GError **error)
 
548
{
 
549
#ifdef ENABLE_ENCRYPTION
 
550
        struct stat encrypted_db_stat, plaintext_db_stat;
 
551
 
 
552
        g_stat (self->priv->filename, &encrypted_db_stat);
 
553
 
 
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
 
555
         * (i.e. corrupt). */
 
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;
 
558
 
 
559
                /* Make a backup of the encrypted database file */
 
560
                back_up_file (self->priv->filename);
 
561
 
 
562
                g_stat (self->priv->plain_filename, &plaintext_db_stat);
 
563
 
 
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);
 
572
                                        return FALSE;
 
573
                                }
 
574
 
 
575
                                g_error_free (child_error);
 
576
                        }
 
577
                }
 
578
        }
 
579
 
 
580
        self->priv->decrypted = TRUE;
 
581
#else
 
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 */
 
586
 
 
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));
 
592
                return FALSE;
 
593
        }
 
594
 
 
595
        /* Can't hurt to create the tables now */
 
596
        create_tables (self);
 
597
 
 
598
        return TRUE;
 
599
}
 
600
 
 
601
gboolean
 
602
almanah_storage_manager_disconnect (AlmanahStorageManager *self, GError **error)
 
603
{
 
604
#ifdef ENABLE_ENCRYPTION
 
605
        gchar *encryption_key;
 
606
        GError *child_error = NULL;
 
607
#endif /* ENABLE_ENCRYPTION */
 
608
 
 
609
        /* Close the DB connection */
 
610
        sqlite3_close (self->priv->connection);
 
611
 
 
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;
 
617
 
 
618
        /* Get the encryption key */
 
619
        encryption_key = get_encryption_key (self);
 
620
        if (encryption_key == NULL)
 
621
                goto delete_encrypted_db;
 
622
 
 
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);
 
626
 
 
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);
 
629
                else
 
630
                        g_error_free (child_error);
 
631
 
 
632
                g_free (encryption_key);
 
633
                return FALSE;
 
634
        }
 
635
 
 
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 */
 
640
 
 
641
        return TRUE;
 
642
 
 
643
#ifdef ENABLE_ENCRYPTION
 
644
delete_encrypted_db:
 
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);
 
648
        return TRUE;
 
649
#endif /* ENABLE_ENCRYPTION */
 
650
}
 
651
 
 
652
static gboolean
 
653
simple_query (AlmanahStorageManager *self, const gchar *query, GError **error, ...)
 
654
{
 
655
        AlmanahStorageManagerPrivate *priv = self->priv;
 
656
        gchar *new_query;
 
657
        va_list params;
 
658
 
 
659
        va_start (params, error);
 
660
        new_query = sqlite3_vmprintf (query, params);
 
661
        va_end (params);
 
662
 
 
663
        g_debug ("Database query: %s", new_query);
 
664
 
 
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);
 
670
                return FALSE;
 
671
        }
 
672
 
 
673
        sqlite3_free (new_query);
 
674
 
 
675
        return TRUE;
 
676
}
 
677
 
 
678
gboolean
 
679
almanah_storage_manager_get_statistics (AlmanahStorageManager *self, guint *entry_count)
 
680
{
 
681
        sqlite3_stmt *statement;
 
682
 
 
683
        *entry_count = 0;
 
684
 
 
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)
 
687
                return FALSE;
 
688
 
 
689
        if (sqlite3_step (statement) != SQLITE_ROW) {
 
690
                sqlite3_finalize (statement);
 
691
                return FALSE;
 
692
        }
 
693
 
 
694
        *entry_count = sqlite3_column_int (statement, 0);
 
695
        sqlite3_finalize (statement);
 
696
 
 
697
        return TRUE;
 
698
}
 
699
 
 
700
gboolean
 
701
almanah_storage_manager_entry_exists (AlmanahStorageManager *self, GDate *date)
 
702
{
 
703
        sqlite3_stmt *statement;
 
704
        gboolean exists = FALSE;
 
705
 
 
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) {
 
708
                return FALSE;
 
709
        }
 
710
 
 
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));
 
714
 
 
715
        /* If there's a result, this'll return SQLITE_ROW; it'll return SQLITE_DONE otherwise */
 
716
        if (sqlite3_step (statement) == SQLITE_ROW)
 
717
                exists = TRUE;
 
718
 
 
719
        sqlite3_finalize (statement);
 
720
 
 
721
        return exists;
 
722
}
 
723
 
 
724
static AlmanahEntry *
 
725
build_entry_from_statement (sqlite3_stmt *statement)
 
726
{
 
727
        GDate date, last_edited;
 
728
        AlmanahEntry *entry;
 
729
 
 
730
        /* Assumes query for SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version, ... FROM entries ... */
 
731
 
 
732
        /* Get the date */
 
733
        g_date_set_dmy (&date,
 
734
                        sqlite3_column_int (statement, 2),
 
735
                        sqlite3_column_int (statement, 3),
 
736
                        sqlite3_column_int (statement, 4));
 
737
 
 
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);
 
742
 
 
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);
 
753
        }
 
754
 
 
755
        return entry;
 
756
}
 
757
 
 
758
/**
 
759
 * almanah_storage_manager_get_entry:
 
760
 * @self: an #AlmanahStorageManager
 
761
 * @date: the date of the entry
 
762
 *
 
763
 * Gets the entry for the specified day from the database. If an entry can't be found it will return %NULL.
 
764
 *
 
765
 * Return value: an #AlmanahEntry or %NULL
 
766
 **/
 
767
AlmanahEntry *
 
768
almanah_storage_manager_get_entry (AlmanahStorageManager *self, GDate *date)
 
769
{
 
770
        AlmanahEntry *entry;
 
771
        sqlite3_stmt *statement;
 
772
 
 
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) {
 
778
                return NULL;
 
779
        }
 
780
 
 
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));
 
785
 
 
786
        /* Execute the statement */
 
787
        if (sqlite3_step (statement) != SQLITE_ROW) {
 
788
                sqlite3_finalize (statement);
 
789
                return NULL;
 
790
        }
 
791
 
 
792
        /* Get the data */
 
793
        entry = build_entry_from_statement (statement);
 
794
        sqlite3_finalize (statement);
 
795
 
 
796
        return entry;
 
797
}
 
798
 
 
799
/**
 
800
 * almanah_storage_manager_set_entry:
 
801
 * @self: an #AlmanahStorageManager
 
802
 * @entry: an #AlmanahEntry
 
803
 *
 
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.
 
805
 *
 
806
 * The entry's last-edited date should be manually updated before storing it in the database, if desired.
 
807
 *
 
808
 * Return value: %TRUE on success, %FALSE otherwise
 
809
 **/
 
810
gboolean
 
811
almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *entry)
 
812
{
 
813
        GDate date;
 
814
 
 
815
        almanah_entry_get_date (entry, &date);
 
816
 
 
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));
 
823
 
 
824
                /* Signal of the operation */
 
825
                g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_REMOVED], 0, &date);
 
826
 
 
827
                return success;
 
828
        } else {
 
829
                const guint8 *data;
 
830
                gsize length;
 
831
                sqlite3_stmt *statement;
 
832
                GDate last_edited;
 
833
                gboolean existed_before;
 
834
                guint version;
 
835
 
 
836
                existed_before = almanah_storage_manager_entry_exists (self, &date);
 
837
 
 
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) {
 
844
                        return FALSE;
 
845
                }
 
846
 
 
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));
 
851
 
 
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);
 
855
 
 
856
                sqlite3_bind_int (statement, 5, almanah_entry_is_important (entry));
 
857
 
 
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));
 
862
 
 
863
                /* Execute the statement */
 
864
                if (sqlite3_step (statement) != SQLITE_DONE) {
 
865
                        sqlite3_finalize (statement);
 
866
                        return FALSE;
 
867
                }
 
868
 
 
869
                sqlite3_finalize (statement);
 
870
 
 
871
                /* Signal of the operation */
 
872
                if (existed_before == TRUE)
 
873
                        g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_MODIFIED], 0, entry);
 
874
                else
 
875
                        g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_ADDED], 0, entry);
 
876
 
 
877
                return TRUE;
 
878
        }
 
879
}
 
880
 
 
881
/**
 
882
 * almanah_storage_manager_iter_init:
 
883
 * @iter: an #AlmanahStorageManagerIter to initialise
 
884
 *
 
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().
 
887
 *
 
888
 * Since: 0.8.0
 
889
 **/
 
890
void
 
891
almanah_storage_manager_iter_init (AlmanahStorageManagerIter *iter)
 
892
{
 
893
        g_return_if_fail (iter != NULL);
 
894
 
 
895
        iter->statement = NULL;
 
896
        iter->user_data = NULL;
 
897
        iter->finished = FALSE;
 
898
}
 
899
 
 
900
typedef struct {
 
901
        GtkTextBuffer *text_buffer;
 
902
        gchar *search_string;
 
903
} SearchData;
 
904
 
 
905
/**
 
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
 
910
 *
 
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.
 
915
 *
 
916
 * The results are returned in descending date order.
 
917
 *
 
918
 * Calling functions must get every result from the result set (i.e. not stop calling almanah_storage_manager_search_entries() until it returns
 
919
 * %NULL).
 
920
 *
 
921
 * Return value: an #AlmanahEntry, or %NULL; unref with g_object_unref()
 
922
 **/
 
923
AlmanahEntry *
 
924
almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string, AlmanahStorageManagerIter *iter)
 
925
{
 
926
        sqlite3_stmt *statement;
 
927
        GtkTextBuffer *text_buffer;
 
928
 
 
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);
 
933
 
 
934
        if (iter->finished == TRUE)
 
935
                return NULL;
 
936
 
 
937
        if (iter->statement == NULL) {
 
938
                SearchData *data;
 
939
 
 
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) {
 
947
                        return NULL;
 
948
                }
 
949
 
 
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;
 
955
        }
 
956
 
 
957
        statement = iter->statement;
 
958
        text_buffer = ((SearchData*) iter->user_data)->text_buffer;
 
959
        search_string = ((SearchData*) iter->user_data)->search_string;
 
960
 
 
961
        /* Execute the statement */
 
962
        switch (sqlite3_step (statement)) {
 
963
                case SQLITE_ROW: {
 
964
                        GtkTextIter text_iter;
 
965
                        AlmanahEntry *entry = build_entry_from_statement (statement);
 
966
                        const gchar *tags = sqlite3_column_text (statement, 9);
 
967
 
 
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);
 
975
                        }
 
976
 
 
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! */
 
983
                                return entry;
 
984
                        } else if (tags != NULL && (strstr (tags, search_string) != NULL)) {
 
985
                                /* A match in an entry tag */
 
986
                                return entry;
 
987
                        }
 
988
 
 
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);
 
992
                }
 
993
                case SQLITE_DONE:
 
994
                case SQLITE_ERROR:
 
995
                case SQLITE_BUSY:
 
996
                case SQLITE_LOCKED:
 
997
                case SQLITE_NOMEM:
 
998
                case SQLITE_IOERR:
 
999
                case SQLITE_CORRUPT:
 
1000
                default: {
 
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;
 
1009
 
 
1010
                        return NULL;
 
1011
                }
 
1012
        }
 
1013
 
 
1014
        g_assert_not_reached ();
 
1015
}
 
1016
 
 
1017
typedef struct {
 
1018
        gchar *search_string;
 
1019
        AlmanahStorageManagerSearchCallback progress_callback;
 
1020
        gpointer progress_user_data;
 
1021
        GDestroyNotify progress_user_data_destroy;
 
1022
        guint count;
 
1023
} SearchAsyncData;
 
1024
 
 
1025
static void
 
1026
search_async_data_free (SearchAsyncData *data)
 
1027
{
 
1028
        g_free (data->search_string);
 
1029
 
 
1030
        g_slice_free (SearchAsyncData, data);
 
1031
}
 
1032
 
 
1033
typedef struct {
 
1034
        AlmanahStorageManagerSearchCallback callback;
 
1035
        AlmanahStorageManager *storage_manager;
 
1036
        AlmanahEntry *entry;
 
1037
        gpointer user_data;
 
1038
} ProgressCallbackData;
 
1039
 
 
1040
static void
 
1041
progress_callback_data_free (ProgressCallbackData *data)
 
1042
{
 
1043
        g_object_unref (data->entry);
 
1044
        g_object_unref (data->storage_manager);
 
1045
 
 
1046
        g_slice_free (ProgressCallbackData, data);
 
1047
}
 
1048
 
 
1049
static gboolean
 
1050
search_entry_async_progress_cb (ProgressCallbackData *data)
 
1051
{
 
1052
        data->callback (data->storage_manager, data->entry, data->user_data);
 
1053
 
 
1054
        return FALSE;
 
1055
}
 
1056
 
 
1057
static void
 
1058
search_entries_async_thread (GSimpleAsyncResult *result, AlmanahStorageManager *storage_manager, GCancellable *cancellable)
 
1059
{
 
1060
        AlmanahStorageManagerIter iter;
 
1061
        AlmanahEntry *entry;
 
1062
        SearchAsyncData *search_data;
 
1063
        ProgressCallbackData *progress_data;
 
1064
        GError *error = NULL;
 
1065
 
 
1066
        search_data = g_simple_async_result_get_op_res_gpointer (result);
 
1067
 
 
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);
 
1074
                        return;
 
1075
                }
 
1076
 
 
1077
                search_data->count++;
 
1078
 
 
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;
 
1085
 
 
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);
 
1090
        }
 
1091
}
 
1092
 
 
1093
/**
 
1094
 * almanah_storage_manager_search_entries_async_finish:
 
1095
 * @self: an #AlmanahStorageManager
 
1096
 * @result: a #GSimpleAsyncResult
 
1097
 * @error: a #GError or %NULL
 
1098
 *
 
1099
 * Finish an asynchronous search started with almanah_storage_manager_search_entries_async().
 
1100
 *
 
1101
 * Return value: the number of entries which matched the search string, or <code class="literal">-1</code> on error
 
1102
 */
 
1103
gint
 
1104
almanah_storage_manager_search_entries_async_finish (AlmanahStorageManager *self, GAsyncResult *result, GError **error)
 
1105
{
 
1106
        SearchAsyncData *search_data = NULL;
 
1107
        gint retval = -1;
 
1108
 
 
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);
 
1112
 
 
1113
        if (g_simple_async_result_is_valid (result, G_OBJECT (self), almanah_storage_manager_search_entries_async) == FALSE) {
 
1114
                return -1;
 
1115
        }
 
1116
 
 
1117
        /* Check for errors */
 
1118
        search_data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
 
1119
 
 
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;
 
1123
        }
 
1124
 
 
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
 
1126
         * been executed. */
 
1127
        if (search_data != NULL && search_data->progress_user_data_destroy != NULL) {
 
1128
                search_data->progress_user_data_destroy (search_data->progress_user_data);
 
1129
        }
 
1130
 
 
1131
        return retval;
 
1132
}
 
1133
 
 
1134
/**
 
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
 
1144
 *
 
1145
 * Launch an asynchronous search for @search_string in the content in entries in the database.
 
1146
 *
 
1147
 * When the @search_string was found in an entry, @progess_callback will be called with the #AlmanahEntry which was found.
 
1148
 *
 
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.
 
1151
 */
 
1152
void
 
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)
 
1157
{
 
1158
        GSimpleAsyncResult *result;
 
1159
        SearchAsyncData *search_data;
 
1160
 
 
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);
 
1165
 
 
1166
        result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, almanah_storage_manager_search_entries_async);
 
1167
 
 
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;
 
1174
 
 
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);
 
1177
 
 
1178
        g_object_unref (result);
 
1179
}
 
1180
 
 
1181
/**
 
1182
 * almanah_storage_manager_get_entries:
 
1183
 * @self: an #AlmanahStorageManager
 
1184
 * @iter: an #AlmanahStorageManagerIter to keep track of the query
 
1185
 *
 
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.
 
1189
 *
 
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).
 
1191
 *
 
1192
 * Return value: an #AlmanahEntry, or %NULL; unref with g_object_unref()
 
1193
 **/
 
1194
AlmanahEntry *
 
1195
almanah_storage_manager_get_entries (AlmanahStorageManager *self, AlmanahStorageManagerIter *iter)
 
1196
{
 
1197
        sqlite3_stmt *statement;
 
1198
 
 
1199
        g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), NULL);
 
1200
        g_return_val_if_fail (iter != NULL, NULL);
 
1201
 
 
1202
        if (iter->finished == TRUE)
 
1203
                return NULL;
 
1204
 
 
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 "
 
1209
                                        "FROM entries", -1,
 
1210
                                        (sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
 
1211
                        return NULL;
 
1212
                }
 
1213
        }
 
1214
 
 
1215
        statement = iter->statement;
 
1216
 
 
1217
        /* Execute the statement */
 
1218
        switch (sqlite3_step (statement)) {
 
1219
                case SQLITE_ROW:
 
1220
                        return build_entry_from_statement (statement);
 
1221
                case SQLITE_DONE:
 
1222
                case SQLITE_ERROR:
 
1223
                case SQLITE_BUSY:
 
1224
                case SQLITE_LOCKED:
 
1225
                case SQLITE_NOMEM:
 
1226
                case SQLITE_IOERR:
 
1227
                case SQLITE_CORRUPT:
 
1228
                default: {
 
1229
                        /* Clean up the iter and return */
 
1230
                        sqlite3_finalize (statement);
 
1231
                        iter->statement = NULL;
 
1232
                        iter->finished = TRUE;
 
1233
 
 
1234
                        return NULL;
 
1235
                }
 
1236
        }
 
1237
 
 
1238
        g_assert_not_reached ();
 
1239
}
 
1240
 
 
1241
/* NOTE: Free results with g_free. Return value is 0-based. */
 
1242
gboolean *
 
1243
almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days)
 
1244
{
 
1245
        sqlite3_stmt *statement;
 
1246
        gint i, result;
 
1247
        gboolean *days;
 
1248
 
 
1249
        /* Build the result array */
 
1250
        i = g_date_get_days_in_month (month, year);
 
1251
        if (num_days != NULL)
 
1252
                *num_days = i;
 
1253
        days = g_malloc0 (sizeof (gboolean) * i);
 
1254
 
 
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) {
 
1257
                g_free (days);
 
1258
                return NULL;
 
1259
        }
 
1260
 
 
1261
        sqlite3_bind_int (statement, 1, year);
 
1262
        sqlite3_bind_int (statement, 2, month);
 
1263
 
 
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;
 
1267
 
 
1268
        sqlite3_finalize (statement);
 
1269
 
 
1270
        if (result != SQLITE_DONE) {
 
1271
                /* Error */
 
1272
                g_free (days);
 
1273
                return NULL;
 
1274
        }
 
1275
 
 
1276
        return days;
 
1277
}
 
1278
 
 
1279
/* NOTE: Free results with g_free. Return value is 0-based. */
 
1280
gboolean *
 
1281
almanah_storage_manager_get_month_important_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days)
 
1282
{
 
1283
        sqlite3_stmt *statement;
 
1284
        gint i, result;
 
1285
        gboolean *days;
 
1286
 
 
1287
        /* Build the result array */
 
1288
        i = g_date_get_days_in_month (month, year);
 
1289
        if (num_days != NULL)
 
1290
                *num_days = i;
 
1291
        days = g_malloc0 (sizeof (gboolean) * i);
 
1292
 
 
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) {
 
1296
                g_free (days);
 
1297
                return NULL;
 
1298
        }
 
1299
 
 
1300
        sqlite3_bind_int (statement, 1, year);
 
1301
        sqlite3_bind_int (statement, 2, month);
 
1302
 
 
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;
 
1306
 
 
1307
        sqlite3_finalize (statement);
 
1308
 
 
1309
        if (result != SQLITE_DONE) {
 
1310
                /* Error */
 
1311
                g_free (days);
 
1312
                return NULL;
 
1313
        }
 
1314
 
 
1315
        return days;
 
1316
}
 
1317
 
 
1318
const gchar *
 
1319
almanah_storage_manager_get_filename (AlmanahStorageManager *self, gboolean plain)
 
1320
{
 
1321
        return (plain == TRUE) ? self->priv->plain_filename : self->priv->filename;
 
1322
}
 
1323
 
 
1324
/**
 
1325
 * almanah_storage_manager_entry_add_tag:
 
1326
 * @self: an #AlmanahStorageManager
 
1327
 * @entry: an #AlmanahEntry
 
1328
 * @tag: a string
 
1329
 *
 
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
 
1331
 *
 
1332
 * Return value: %TRUE on success, %FALSE otherwise
 
1333
 */
 
1334
gboolean
 
1335
almanah_storage_manager_entry_add_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
 
1336
{
 
1337
        GDate entry_last_edited;
 
1338
        GDate entry_date;
 
1339
        sqlite3_stmt *statement;
 
1340
        gint result_error;
 
1341
 
 
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);
 
1345
 
 
1346
        almanah_entry_get_date (entry, &entry_date);
 
1347
        if (g_date_valid (&entry_date) != TRUE) {
 
1348
                g_debug ("Invalid entry date");
 
1349
                return FALSE;
 
1350
        }
 
1351
 
 
1352
        /* Don't duplicate tags */
 
1353
        if (almanah_storage_manager_entry_check_tag (self, entry, tag)) {
 
1354
                g_debug ("Duplicated tag now allowed");
 
1355
                return FALSE;
 
1356
        }
 
1357
 
 
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);
 
1362
                return FALSE;
 
1363
        }
 
1364
 
 
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);
 
1370
 
 
1371
        if (sqlite3_step (statement) != SQLITE_DONE) {
 
1372
                sqlite3_finalize (statement);
 
1373
                g_debug ("Can't save tag");
 
1374
                return FALSE;
 
1375
        }
 
1376
 
 
1377
        sqlite3_finalize (statement);
 
1378
 
 
1379
        g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_TAG_ADDED], 0, entry, g_strdup (tag));
 
1380
 
 
1381
        return TRUE;
 
1382
}
 
1383
 
 
1384
/**
 
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
 
1389
 *
 
1390
 * Remove the tag with the given string in @tag as a tag for the entry @entry.
 
1391
 *
 
1392
 * Return value: %TRUE on success, %FALSE otherwise
 
1393
 */
 
1394
gboolean
 
1395
almanah_storage_manager_entry_remove_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
 
1396
{
 
1397
        GDate date;
 
1398
        gboolean result;
 
1399
 
 
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);
 
1403
 
 
1404
        almanah_entry_get_date (entry, &date);
 
1405
 
 
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),
 
1410
                               tag);
 
1411
 
 
1412
        if (result)
 
1413
                g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_TAG_REMOVED], 0, entry, tag);
 
1414
 
 
1415
        return result;
 
1416
}
 
1417
 
 
1418
/**
 
1419
 * almanah_storage_manager_entry_get_tags:
 
1420
 * @self: an #AlmanahStorageManager
 
1421
 * @entry: an #AlmanahEntry
 
1422
 *
 
1423
 * Gets the tags added to an entry by the user from the database.
 
1424
 */
 
1425
GList *
 
1426
almanah_storage_manager_entry_get_tags (AlmanahStorageManager *self, AlmanahEntry *entry)
 
1427
{
 
1428
        GList *tags = NULL;
 
1429
        GDate date;
 
1430
        sqlite3_stmt *statement;
 
1431
        gint result;
 
1432
 
 
1433
        g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
 
1434
        g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
 
1435
 
 
1436
        almanah_entry_get_date (entry, &date);
 
1437
        if (g_date_valid (&date) != TRUE) {
 
1438
                g_debug ("Invalid entry date");
 
1439
                return NULL;
 
1440
        }
 
1441
 
 
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");
 
1446
                return NULL;
 
1447
        }
 
1448
 
 
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));
 
1452
 
 
1453
        while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
 
1454
                tags = g_list_append (tags, g_strdup (sqlite3_column_text (statement, 0)));
 
1455
        }
 
1456
 
 
1457
        sqlite3_finalize (statement);
 
1458
 
 
1459
        if (result != SQLITE_DONE) {
 
1460
                g_debug ("Error quering for tags from database: %s", sqlite3_errmsg (self->priv->connection));
 
1461
                g_free (tags);
 
1462
                tags = NULL;
 
1463
        }
 
1464
 
 
1465
        return tags;
 
1466
}
 
1467
 
 
1468
/**
 
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
 
1473
 *
 
1474
 * Check if a tag has been added to an entry
 
1475
 *
 
1476
 * Return value: TRUE if the tag already added to the entry, FALSE otherwise
 
1477
 */
 
1478
gboolean
 
1479
almanah_storage_manager_entry_check_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
 
1480
{
 
1481
        gboolean result, q_result;
 
1482
        sqlite3_stmt *statement;
 
1483
        GDate date;
 
1484
 
 
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);
 
1488
 
 
1489
        result = FALSE;
 
1490
 
 
1491
        almanah_entry_get_date (entry, &date);
 
1492
        if (g_date_valid (&date) != TRUE) {
 
1493
                g_debug ("Invalid entry date");
 
1494
                return FALSE;
 
1495
        }
 
1496
 
 
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");
 
1501
                return FALSE;
 
1502
        }
 
1503
 
 
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);
 
1508
 
 
1509
        if ((q_result  = sqlite3_step (statement)) == SQLITE_ROW) {
 
1510
                if (sqlite3_column_int (statement, 0) > 0)
 
1511
                        result = TRUE;
 
1512
        }
 
1513
 
 
1514
        if (q_result != SQLITE_DONE) {
 
1515
                g_debug ("Error quering for a tag from database: %s", sqlite3_errmsg (self->priv->connection));
 
1516
        }
 
1517
 
 
1518
        sqlite3_finalize (statement);
 
1519
 
 
1520
        return result;
 
1521
}
 
1522
 
 
1523
 
 
1524
/**
 
1525
 * almanah_storage_manager_get_tags:
 
1526
 * @self: an #AlmanahStorageManager
 
1527
 *
 
1528
 * Gets all the tags added to entries by the user from the database.
 
1529
 *
 
1530
 * Return value: #GList with all the tags.
 
1531
 */
 
1532
GList *
 
1533
almanah_storage_manager_get_tags (AlmanahStorageManager *self)
 
1534
{
 
1535
        GList *tags = NULL;
 
1536
        sqlite3_stmt *statement;
 
1537
        gint result;
 
1538
 
 
1539
        g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
 
1540
 
 
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);
 
1543
                return NULL;
 
1544
        }
 
1545
 
 
1546
        while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
 
1547
                tags = g_list_append (tags, g_strdup (sqlite3_column_text (statement, 0)));
 
1548
        }
 
1549
 
 
1550
        sqlite3_finalize (statement);
 
1551
 
 
1552
        if (result != SQLITE_DONE) {
 
1553
                g_debug ("Error quering for tags from database: %s", sqlite3_errmsg (self->priv->connection));
 
1554
                g_free (tags);
 
1555
                tags = NULL;
 
1556
        }
 
1557
 
 
1558
        return tags;
 
1559
}