1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Sankar P <psankar@novell.com>
18 * Srinivasa Ragavan <sragavan@novell.com>
19 */
20
21 #include "evolution-data-server-config.h"
22
23 #include <errno.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include <glib/gi18n-lib.h>
30 #include <glib/gstdio.h>
31
32 #include <sqlite3.h>
33
34 #include "camel-debug.h"
35 #include "camel-folder-search.h"
36 #include "camel-object.h"
37 #include "camel-string-utils.h"
38
39 #include "camel-db.h"
40
41 #define MESSAGE_INFO_TABLE_VERSION 3
42
43 /* how long to wait before invoking sync on the file */
44 #define SYNC_TIMEOUT_SECONDS 5
45
46 static sqlite3_vfs *old_vfs = NULL;
47 static GThreadPool *sync_pool = NULL;
48
49 typedef struct {
50 sqlite3_file parent;
51 sqlite3_file *old_vfs_file; /* pointer to old_vfs' file */
52 GRecMutex sync_mutex;
53 guint timeout_id;
54 gint flags;
55
56 /* Do know how many syncs are pending, to not close
57 the file before the last sync is over */
58 guint pending_syncs;
59 GMutex pending_syncs_lock;
60 GCond pending_syncs_cond;
61 } CamelSqlite3File;
62
63 static gint
call_old_file_Sync(CamelSqlite3File * cFile,gint flags)64 call_old_file_Sync (CamelSqlite3File *cFile,
65 gint flags)
66 {
67 g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
68 g_return_val_if_fail (cFile != NULL, SQLITE_ERROR);
69
70 g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR);
71 return cFile->old_vfs_file->pMethods->xSync (cFile->old_vfs_file, flags);
72 }
73
74 typedef struct {
75 GCond cond;
76 GMutex mutex;
77 gboolean is_set;
78 } SyncDone;
79
80 struct SyncRequestData
81 {
82 CamelSqlite3File *cFile;
83 guint32 flags;
84 SyncDone *done; /* not NULL when waiting for a finish; will be freed by the caller */
85 };
86
87 static void
sync_request_thread_cb(gpointer task_data,gpointer null_data)88 sync_request_thread_cb (gpointer task_data,
89 gpointer null_data)
90 {
91 struct SyncRequestData *sync_data = task_data;
92 SyncDone *done;
93
94 g_return_if_fail (sync_data != NULL);
95 g_return_if_fail (sync_data->cFile != NULL);
96
97 call_old_file_Sync (sync_data->cFile, sync_data->flags);
98
99 g_mutex_lock (&sync_data->cFile->pending_syncs_lock);
100 g_warn_if_fail (sync_data->cFile->pending_syncs > 0);
101 sync_data->cFile->pending_syncs--;
102 if (!sync_data->cFile->pending_syncs)
103 g_cond_signal (&sync_data->cFile->pending_syncs_cond);
104 g_mutex_unlock (&sync_data->cFile->pending_syncs_lock);
105
106 done = sync_data->done;
107 g_slice_free (struct SyncRequestData, sync_data);
108
109 if (done != NULL) {
110 g_mutex_lock (&done->mutex);
111 done->is_set = TRUE;
112 g_cond_broadcast (&done->cond);
113 g_mutex_unlock (&done->mutex);
114 }
115 }
116
117 static void
sync_push_request(CamelSqlite3File * cFile,gboolean wait_for_finish)118 sync_push_request (CamelSqlite3File *cFile,
119 gboolean wait_for_finish)
120 {
121 struct SyncRequestData *data;
122 SyncDone *done = NULL;
123 GError *error = NULL;
124
125 g_return_if_fail (cFile != NULL);
126 g_return_if_fail (sync_pool != NULL);
127
128 g_rec_mutex_lock (&cFile->sync_mutex);
129
130 if (!cFile->flags) {
131 /* nothing to sync, might be when xClose is called
132 * without any pending xSync request */
133 g_rec_mutex_unlock (&cFile->sync_mutex);
134 return;
135 }
136
137 if (wait_for_finish) {
138 done = g_slice_new (SyncDone);
139 g_cond_init (&done->cond);
140 g_mutex_init (&done->mutex);
141 done->is_set = FALSE;
142 }
143
144 data = g_slice_new0 (struct SyncRequestData);
145 data->cFile = cFile;
146 data->flags = cFile->flags;
147 data->done = done;
148
149 cFile->flags = 0;
150
151 g_mutex_lock (&cFile->pending_syncs_lock);
152 cFile->pending_syncs++;
153 g_mutex_unlock (&cFile->pending_syncs_lock);
154
155 g_rec_mutex_unlock (&cFile->sync_mutex);
156
157 g_thread_pool_push (sync_pool, data, &error);
158
159 if (error) {
160 g_warning ("%s: Failed to push to thread pool: %s\n", G_STRFUNC, error->message);
161 g_error_free (error);
162
163 if (done != NULL) {
164 g_cond_clear (&done->cond);
165 g_mutex_clear (&done->mutex);
166 g_slice_free (SyncDone, done);
167 }
168
169 return;
170 }
171
172 if (done != NULL) {
173 g_mutex_lock (&done->mutex);
174 while (!done->is_set)
175 g_cond_wait (&done->cond, &done->mutex);
176 g_mutex_unlock (&done->mutex);
177
178 g_cond_clear (&done->cond);
179 g_mutex_clear (&done->mutex);
180 g_slice_free (SyncDone, done);
181 }
182 }
183
184 static gboolean
sync_push_request_timeout(CamelSqlite3File * cFile)185 sync_push_request_timeout (CamelSqlite3File *cFile)
186 {
187 g_rec_mutex_lock (&cFile->sync_mutex);
188
189 if (cFile->timeout_id != 0) {
190 sync_push_request (cFile, FALSE);
191 cFile->timeout_id = 0;
192 }
193
194 g_rec_mutex_unlock (&cFile->sync_mutex);
195
196 return FALSE;
197 }
198
199 #define def_subclassed(_nm, _params, _call) \
200 static gint \
201 camel_sqlite3_file_ ## _nm _params \
202 { \
203 CamelSqlite3File *cFile; \
204 \
205 g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); \
206 g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); \
207 \
208 cFile = (CamelSqlite3File *) pFile; \
209 g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); \
210 return cFile->old_vfs_file->pMethods->_nm _call; \
211 }
212
213 #define def_subclassed_void(_nm, _params, _call) \
214 static void \
215 camel_sqlite3_file_ ## _nm _params \
216 { \
217 CamelSqlite3File *cFile; \
218 \
219 g_return_if_fail (old_vfs != NULL); \
220 g_return_if_fail (pFile != NULL); \
221 \
222 cFile = (CamelSqlite3File *) pFile; \
223 g_return_if_fail (cFile->old_vfs_file->pMethods != NULL); \
224 cFile->old_vfs_file->pMethods->_nm _call; \
225 }
226
227 def_subclassed (xRead, (sqlite3_file *pFile, gpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst))
228 def_subclassed (xWrite, (sqlite3_file *pFile, gconstpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst))
229 def_subclassed (xTruncate, (sqlite3_file *pFile, sqlite3_int64 size), (cFile->old_vfs_file, size))
230 def_subclassed (xFileSize, (sqlite3_file *pFile, sqlite3_int64 *pSize), (cFile->old_vfs_file, pSize))
231 def_subclassed (xLock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType))
232 def_subclassed (xUnlock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType))
233 def_subclassed (xFileControl, (sqlite3_file *pFile, gint op, gpointer pArg), (cFile->old_vfs_file, op, pArg))
234 def_subclassed (xSectorSize, (sqlite3_file *pFile), (cFile->old_vfs_file))
235 def_subclassed (xDeviceCharacteristics, (sqlite3_file *pFile), (cFile->old_vfs_file))
236 def_subclassed (xShmMap, (sqlite3_file *pFile, gint iPg, gint pgsz, gint n, void volatile **arr), (cFile->old_vfs_file, iPg, pgsz, n, arr))
237 def_subclassed (xShmLock, (sqlite3_file *pFile, gint offset, gint n, gint flags), (cFile->old_vfs_file, offset, n, flags))
238 def_subclassed_void (xShmBarrier, (sqlite3_file *pFile), (cFile->old_vfs_file))
239 def_subclassed (xShmUnmap, (sqlite3_file *pFile, gint deleteFlag), (cFile->old_vfs_file, deleteFlag))
240 def_subclassed (xFetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, int iAmt, void **pp), (cFile->old_vfs_file, iOfst, iAmt, pp))
241 def_subclassed (xUnfetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, void *p), (cFile->old_vfs_file, iOfst, p))
242
243 #undef def_subclassed
244
245 static gint
camel_sqlite3_file_xCheckReservedLock(sqlite3_file * pFile,gint * pResOut)246 camel_sqlite3_file_xCheckReservedLock (sqlite3_file *pFile,
247 gint *pResOut)
248 {
249 CamelSqlite3File *cFile;
250
251 g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
252 g_return_val_if_fail (pFile != NULL, SQLITE_ERROR);
253
254 cFile = (CamelSqlite3File *) pFile;
255 g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR);
256
257 /* check version in runtime */
258 if (sqlite3_libversion_number () < 3006000)
259 return ((gint (*)(sqlite3_file *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file);
260 else
261 return ((gint (*)(sqlite3_file *, gint *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file, pResOut);
262 }
263
264 static gint
camel_sqlite3_file_xClose(sqlite3_file * pFile)265 camel_sqlite3_file_xClose (sqlite3_file *pFile)
266 {
267 CamelSqlite3File *cFile;
268 gint res;
269
270 g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
271 g_return_val_if_fail (pFile != NULL, SQLITE_ERROR);
272
273 cFile = (CamelSqlite3File *) pFile;
274
275 g_rec_mutex_lock (&cFile->sync_mutex);
276
277 /* Cancel any pending sync requests. */
278 if (cFile->timeout_id > 0) {
279 g_source_remove (cFile->timeout_id);
280 cFile->timeout_id = 0;
281 }
282
283 g_rec_mutex_unlock (&cFile->sync_mutex);
284
285 /* Make the last sync. */
286 sync_push_request (cFile, TRUE);
287
288 g_mutex_lock (&cFile->pending_syncs_lock);
289 while (cFile->pending_syncs > 0) {
290 g_cond_wait (&cFile->pending_syncs_cond, &cFile->pending_syncs_lock);
291 }
292 g_mutex_unlock (&cFile->pending_syncs_lock);
293
294 if (cFile->old_vfs_file->pMethods)
295 res = cFile->old_vfs_file->pMethods->xClose (cFile->old_vfs_file);
296 else
297 res = SQLITE_OK;
298
299 g_free (cFile->old_vfs_file);
300 cFile->old_vfs_file = NULL;
301
302 g_rec_mutex_clear (&cFile->sync_mutex);
303 g_mutex_clear (&cFile->pending_syncs_lock);
304 g_cond_clear (&cFile->pending_syncs_cond);
305
306 return res;
307 }
308
309 static gint
camel_sqlite3_file_xSync(sqlite3_file * pFile,gint flags)310 camel_sqlite3_file_xSync (sqlite3_file *pFile,
311 gint flags)
312 {
313 CamelSqlite3File *cFile;
314
315 g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR);
316 g_return_val_if_fail (pFile != NULL, SQLITE_ERROR);
317
318 cFile = (CamelSqlite3File *) pFile;
319
320 g_rec_mutex_lock (&cFile->sync_mutex);
321
322 /* If a sync request is already scheduled, accumulate flags. */
323 cFile->flags |= flags;
324
325 /* Cancel any pending sync requests. */
326 if (cFile->timeout_id > 0)
327 g_source_remove (cFile->timeout_id);
328
329 /* Wait SYNC_TIMEOUT_SECONDS before we actually sync. */
330 cFile->timeout_id = g_timeout_add_seconds (
331 SYNC_TIMEOUT_SECONDS, (GSourceFunc)
332 sync_push_request_timeout, cFile);
333 g_source_set_name_by_id (
334 cFile->timeout_id,
335 "[camel] sync_push_request_timeout");
336
337 g_rec_mutex_unlock (&cFile->sync_mutex);
338
339 return SQLITE_OK;
340 }
341
342 static gint
camel_sqlite3_vfs_xOpen(sqlite3_vfs * pVfs,const gchar * zPath,sqlite3_file * pFile,gint flags,gint * pOutFlags)343 camel_sqlite3_vfs_xOpen (sqlite3_vfs *pVfs,
344 const gchar *zPath,
345 sqlite3_file *pFile,
346 gint flags,
347 gint *pOutFlags)
348 {
349 static GRecMutex only_once_lock;
350 static sqlite3_io_methods io_methods = {0};
351 CamelSqlite3File *cFile;
352 gint res;
353
354 g_return_val_if_fail (old_vfs != NULL, -1);
355 g_return_val_if_fail (pFile != NULL, -1);
356
357 cFile = (CamelSqlite3File *) pFile;
358 cFile->old_vfs_file = g_malloc0 (old_vfs->szOsFile);
359
360 res = old_vfs->xOpen (old_vfs, zPath, cFile->old_vfs_file, flags, pOutFlags);
361 if (res != SQLITE_OK) {
362 g_free (cFile->old_vfs_file);
363 return res;
364 }
365
366 g_rec_mutex_init (&cFile->sync_mutex);
367 g_mutex_init (&cFile->pending_syncs_lock);
368 g_cond_init (&cFile->pending_syncs_cond);
369
370 cFile->pending_syncs = 0;
371
372 g_rec_mutex_lock (&only_once_lock);
373
374 if (!sync_pool)
375 sync_pool = g_thread_pool_new (sync_request_thread_cb, NULL, 2, FALSE, NULL);
376
377 /* cFile->old_vfs_file->pMethods is NULL when open failed for some reason,
378 * thus do not initialize our structure when do not know the version */
379 if (io_methods.xClose == NULL && cFile->old_vfs_file->pMethods) {
380 /* initialize our subclass function only once */
381 io_methods.iVersion = cFile->old_vfs_file->pMethods->iVersion;
382
383 /* check version in compile time */
384 #if SQLITE_VERSION_NUMBER < 3006000
385 io_methods.xCheckReservedLock = (gint (*)(sqlite3_file *)) camel_sqlite3_file_xCheckReservedLock;
386 #else
387 io_methods.xCheckReservedLock = camel_sqlite3_file_xCheckReservedLock;
388 #endif
389
390 #define use_subclassed(x) io_methods.x = camel_sqlite3_file_ ## x
391 use_subclassed (xClose);
392 use_subclassed (xRead);
393 use_subclassed (xWrite);
394 use_subclassed (xTruncate);
395 use_subclassed (xSync);
396 use_subclassed (xFileSize);
397 use_subclassed (xLock);
398 use_subclassed (xUnlock);
399 use_subclassed (xFileControl);
400 use_subclassed (xSectorSize);
401 use_subclassed (xDeviceCharacteristics);
402
403 if (io_methods.iVersion > 1) {
404 use_subclassed (xShmMap);
405 use_subclassed (xShmLock);
406 use_subclassed (xShmBarrier);
407 use_subclassed (xShmUnmap);
408 }
409
410 if (io_methods.iVersion > 2) {
411 use_subclassed (xFetch);
412 use_subclassed (xUnfetch);
413 }
414
415 if (io_methods.iVersion > 3) {
416 g_warning ("%s: Unchecked IOMethods version %d, downgrading to version 3", G_STRFUNC, io_methods.iVersion);
417 io_methods.iVersion = 3;
418 }
419 #undef use_subclassed
420 }
421
422 g_rec_mutex_unlock (&only_once_lock);
423
424 cFile->parent.pMethods = &io_methods;
425
426 return res;
427 }
428
429 static gpointer
init_sqlite_vfs(void)430 init_sqlite_vfs (void)
431 {
432 static sqlite3_vfs vfs = { 0 };
433
434 old_vfs = sqlite3_vfs_find (NULL);
435 g_return_val_if_fail (old_vfs != NULL, NULL);
436
437 memcpy (&vfs, old_vfs, sizeof (sqlite3_vfs));
438
439 vfs.szOsFile = sizeof (CamelSqlite3File);
440 vfs.zName = "camel_sqlite3_vfs";
441 vfs.xOpen = camel_sqlite3_vfs_xOpen;
442
443 sqlite3_vfs_register (&vfs, 1);
444
445 if (g_getenv ("CAMEL_SQLITE_SHARED_CACHE"))
446 sqlite3_enable_shared_cache (TRUE);
447
448 return NULL;
449 }
450
451 #define d(x) if (camel_debug("sqlite")) x
452 #define START(stmt) \
453 if (camel_debug ("dbtime")) { \
454 g_print ( \
455 "\n===========\n" \
456 "DB SQL operation [%s] started\n", stmt); \
457 if (!cdb->priv->timer) { \
458 cdb->priv->timer = g_timer_new (); \
459 } else { \
460 g_timer_reset (cdb->priv->timer); \
461 } \
462 }
463 #define END \
464 if (camel_debug ("dbtime")) { \
465 g_timer_stop (cdb->priv->timer); \
466 g_print ( \
467 "DB Operation ended. " \
468 "Time Taken : %f\n###########\n", \
469 g_timer_elapsed (cdb->priv->timer, NULL)); \
470 }
471 #define STARTTS(stmt) \
472 if (camel_debug ("dbtimets")) { \
473 g_print ( \
474 "\n===========\n" \
475 "DB SQL operation [%s] started\n", stmt); \
476 if (!cdb->priv->timer) { \
477 cdb->priv->timer = g_timer_new (); \
478 } else { \
479 g_timer_reset (cdb->priv->timer); \
480 } \
481 }
482 #define ENDTS \
483 if (camel_debug ("dbtimets")) { \
484 g_timer_stop (cdb->priv->timer); \
485 g_print ( \
486 "DB Operation ended. " \
487 "Time Taken : %f\n###########\n", \
488 g_timer_elapsed (cdb->priv->timer, NULL)); \
489 }
490
491 struct _CamelDBPrivate {
492 sqlite3 *db;
493 GTimer *timer;
494 GRWLock rwlock;
495 gchar *filename;
496 GMutex transaction_lock;
497 GThread *transaction_thread;
498 guint32 transaction_level;
499 gboolean is_foldersdb;
500 };
501
G_DEFINE_TYPE_WITH_PRIVATE(CamelDB,camel_db,G_TYPE_OBJECT)502 G_DEFINE_TYPE_WITH_PRIVATE (CamelDB, camel_db, G_TYPE_OBJECT)
503
504 static void
505 camel_db_finalize (GObject *object)
506 {
507 CamelDB *cdb = CAMEL_DB (object);
508
509 sqlite3_close (cdb->priv->db);
510 g_rw_lock_clear (&cdb->priv->rwlock);
511 g_mutex_clear (&cdb->priv->transaction_lock);
512 g_free (cdb->priv->filename);
513
514 d (g_print ("\nDatabase succesfully closed \n"));
515
516 /* Chain up to parent's finalize() method. */
517 G_OBJECT_CLASS (camel_db_parent_class)->finalize (object);
518 }
519
520 static void
camel_db_class_init(CamelDBClass * class)521 camel_db_class_init (CamelDBClass *class)
522 {
523 GObjectClass *object_class;
524
525 object_class = G_OBJECT_CLASS (class);
526 object_class->finalize = camel_db_finalize;
527 }
528
529 static void
camel_db_init(CamelDB * cdb)530 camel_db_init (CamelDB *cdb)
531 {
532 cdb->priv = camel_db_get_instance_private (cdb);
533
534 g_rw_lock_init (&cdb->priv->rwlock);
535 g_mutex_init (&cdb->priv->transaction_lock);
536 cdb->priv->transaction_thread = NULL;
537 cdb->priv->transaction_level = 0;
538 cdb->priv->timer = NULL;
539 }
540
541 /*
542 * cdb_sql_exec
543 * @db:
544 * @stmt:
545 * @error:
546 *
547 * Callers should hold the lock
548 */
549 static gint
cdb_sql_exec(sqlite3 * db,const gchar * stmt,gint (* callback)(gpointer,gint,gchar **,gchar **),gpointer data,gint * out_sqlite_error_code,GError ** error)550 cdb_sql_exec (sqlite3 *db,
551 const gchar *stmt,
552 gint (*callback)(gpointer ,gint,gchar **,gchar **),
553 gpointer data,
554 gint *out_sqlite_error_code,
555 GError **error)
556 {
557 gchar *errmsg = NULL;
558 gint ret = -1, retries = 0;
559
560 g_return_val_if_fail (stmt != NULL, -1);
561
562 d (g_print ("Camel SQL Exec:\n%s\n", stmt));
563
564 ret = sqlite3_exec (db, stmt, callback, data, &errmsg);
565 while (ret == SQLITE_BUSY || ret == SQLITE_LOCKED || ret == -1) {
566 /* try for ~15 seconds, then give up */
567 if (retries > 150)
568 break;
569 retries++;
570
571 g_clear_pointer (&errmsg, sqlite3_free);
572 g_thread_yield ();
573 g_usleep (100 * 1000); /* Sleep for 100 ms */
574
575 ret = sqlite3_exec (db, stmt, NULL, NULL, &errmsg);
576 }
577
578 if (out_sqlite_error_code)
579 *out_sqlite_error_code = ret;
580
581 if (ret != SQLITE_OK) {
582 d (g_print ("Error in SQL EXEC statement: %s [%s].\n", stmt, errmsg));
583 g_set_error (
584 error, CAMEL_ERROR,
585 CAMEL_ERROR_GENERIC, "%s", errmsg);
586 sqlite3_free (errmsg);
587 errmsg = NULL;
588 return -1;
589 }
590
591 g_clear_pointer (&errmsg, sqlite3_free);
592
593 return 0;
594 }
595
596 /* checks whether string 'where' contains whole word 'what',
597 * case insensitively (ascii, not utf8, same as 'LIKE' in SQLite3)
598 */
599 static void
cdb_match_func(sqlite3_context * ctx,gint nArgs,sqlite3_value ** values)600 cdb_match_func (sqlite3_context *ctx,
601 gint nArgs,
602 sqlite3_value **values)
603 {
604 gboolean matches = FALSE;
605 const gchar *what, *where;
606
607 g_return_if_fail (ctx != NULL);
608 g_return_if_fail (nArgs == 2);
609 g_return_if_fail (values != NULL);
610
611 what = (const gchar *) sqlite3_value_text (values[0]);
612 where = (const gchar *) sqlite3_value_text (values[1]);
613
614 if (what && where && !*what) {
615 matches = TRUE;
616 } else if (what && where) {
617 gboolean word = TRUE;
618 gint i, j;
619
620 for (i = 0, j = 0; where[i] && !matches; i++) {
621 gchar c = where[i];
622
623 if (c == ' ') {
624 word = TRUE;
625 j = 0;
626 } else if (word && tolower (c) == tolower (what[j])) {
627 j++;
628 if (what[j] == 0 && (where[i + 1] == 0 || isspace (where[i + 1])))
629 matches = TRUE;
630 } else {
631 word = FALSE;
632 }
633 }
634 }
635
636 sqlite3_result_int (ctx, matches ? 1 : 0);
637 }
638
639 static void
cdb_camel_compare_date_func(sqlite3_context * ctx,gint nArgs,sqlite3_value ** values)640 cdb_camel_compare_date_func (sqlite3_context *ctx,
641 gint nArgs,
642 sqlite3_value **values)
643 {
644 sqlite3_int64 v1, v2;
645
646 g_return_if_fail (ctx != NULL);
647 g_return_if_fail (nArgs == 2);
648 g_return_if_fail (values != NULL);
649
650 v1 = sqlite3_value_int64 (values[0]);
651 v2 = sqlite3_value_int64 (values[1]);
652
653 sqlite3_result_int (ctx, camel_folder_search_util_compare_date (v1, v2));
654 }
655
656 static void
cdb_writer_lock(CamelDB * cdb)657 cdb_writer_lock (CamelDB *cdb)
658 {
659 g_return_if_fail (cdb != NULL);
660
661 g_mutex_lock (&cdb->priv->transaction_lock);
662 if (cdb->priv->transaction_thread != g_thread_self ()) {
663 g_mutex_unlock (&cdb->priv->transaction_lock);
664
665 g_rw_lock_writer_lock (&cdb->priv->rwlock);
666
667 g_mutex_lock (&cdb->priv->transaction_lock);
668
669 g_warn_if_fail (cdb->priv->transaction_thread == NULL);
670 g_warn_if_fail (cdb->priv->transaction_level == 0);
671
672 cdb->priv->transaction_thread = g_thread_self ();
673 }
674
675 cdb->priv->transaction_level++;
676
677 g_mutex_unlock (&cdb->priv->transaction_lock);
678 }
679
680 static void
cdb_writer_unlock(CamelDB * cdb)681 cdb_writer_unlock (CamelDB *cdb)
682 {
683 g_return_if_fail (cdb != NULL);
684
685 g_mutex_lock (&cdb->priv->transaction_lock);
686
687 g_warn_if_fail (cdb->priv->transaction_thread == g_thread_self ());
688 g_warn_if_fail (cdb->priv->transaction_level > 0);
689
690 cdb->priv->transaction_level--;
691
692 if (!cdb->priv->transaction_level) {
693 cdb->priv->transaction_thread = NULL;
694 g_mutex_unlock (&cdb->priv->transaction_lock);
695
696 g_rw_lock_writer_unlock (&cdb->priv->rwlock);
697 } else {
698 g_mutex_unlock (&cdb->priv->transaction_lock);
699 }
700 }
701
702 static void
cdb_reader_lock(CamelDB * cdb)703 cdb_reader_lock (CamelDB *cdb)
704 {
705 g_return_if_fail (cdb != NULL);
706
707 g_mutex_lock (&cdb->priv->transaction_lock);
708 if (cdb->priv->transaction_thread == g_thread_self ()) {
709 /* already holding write lock */
710 g_mutex_unlock (&cdb->priv->transaction_lock);
711 } else {
712 g_mutex_unlock (&cdb->priv->transaction_lock);
713
714 g_rw_lock_reader_lock (&cdb->priv->rwlock);
715 }
716 }
717
718 static void
cdb_reader_unlock(CamelDB * cdb)719 cdb_reader_unlock (CamelDB *cdb)
720 {
721 g_return_if_fail (cdb != NULL);
722
723 g_mutex_lock (&cdb->priv->transaction_lock);
724 if (cdb->priv->transaction_thread == g_thread_self ()) {
725 /* already holding write lock */
726 g_mutex_unlock (&cdb->priv->transaction_lock);
727 } else {
728 g_mutex_unlock (&cdb->priv->transaction_lock);
729
730 g_rw_lock_reader_unlock (&cdb->priv->rwlock);
731 }
732 }
733
734 static gboolean
cdb_is_in_transaction(CamelDB * cdb)735 cdb_is_in_transaction (CamelDB *cdb)
736 {
737 gboolean res;
738
739 g_return_val_if_fail (cdb != NULL, FALSE);
740
741 g_mutex_lock (&cdb->priv->transaction_lock);
742 res = cdb->priv->transaction_level > 0 && cdb->priv->transaction_thread == g_thread_self ();
743 g_mutex_unlock (&cdb->priv->transaction_lock);
744
745 return res;
746 }
747
748 static gchar *
cdb_construct_transaction_stmt(CamelDB * cdb,const gchar * prefix)749 cdb_construct_transaction_stmt (CamelDB *cdb,
750 const gchar *prefix)
751 {
752 gchar *name;
753
754 g_return_val_if_fail (cdb != NULL, NULL);
755
756 g_mutex_lock (&cdb->priv->transaction_lock);
757 g_warn_if_fail (cdb->priv->transaction_thread == g_thread_self ());
758 name = g_strdup_printf ("%sTN%d", prefix ? prefix : "", cdb->priv->transaction_level);
759 g_mutex_unlock (&cdb->priv->transaction_lock);
760
761 return name;
762 }
763
764 static gint
camel_db_command_internal(CamelDB * cdb,const gchar * stmt,gint * out_sqlite_error_code,GError ** error)765 camel_db_command_internal (CamelDB *cdb,
766 const gchar *stmt,
767 gint *out_sqlite_error_code,
768 GError **error)
769 {
770 gint ret;
771
772 if (!cdb)
773 return TRUE;
774
775 cdb_writer_lock (cdb);
776
777 START (stmt);
778 ret = cdb_sql_exec (cdb->priv->db, stmt, NULL, NULL, out_sqlite_error_code, error);
779 END;
780
781 cdb_writer_unlock (cdb);
782
783 return ret;
784 }
785
786 /**
787 * camel_db_new:
788 * @filename: A filename with the database to open/create
789 * @error: return location for a #GError, or %NULL
790 *
791 * Returns: (transfer full): A new #CamelDB with @filename as its database file.
792 * Free it with g_object_unref() when no longer needed.
793 *
794 * Since: 3.24
795 **/
796 CamelDB *
camel_db_new(const gchar * filename,GError ** error)797 camel_db_new (const gchar *filename,
798 GError **error)
799 {
800 static GOnce vfs_once = G_ONCE_INIT;
801 CamelDB *cdb;
802 sqlite3 *db;
803 gint ret, cdb_sqlite_error_code = SQLITE_OK;
804 gboolean reopening = FALSE;
805 GError *local_error = NULL;
806
807 g_once (&vfs_once, (GThreadFunc) init_sqlite_vfs, NULL);
808
809 reopen:
810 ret = sqlite3_open (filename, &db);
811 if (ret) {
812 if (!db) {
813 g_set_error (
814 error, CAMEL_ERROR,
815 CAMEL_ERROR_GENERIC,
816 _("Insufficient memory"));
817 } else {
818 const gchar *errmsg;
819 errmsg = sqlite3_errmsg (db);
820 d (g_print ("Can't open database %s: %s\n", filename, errmsg));
821 g_set_error (
822 error, CAMEL_ERROR,
823 CAMEL_ERROR_GENERIC, "%s", errmsg);
824 sqlite3_close (db);
825 }
826 return NULL;
827 }
828
829 cdb = g_object_new (CAMEL_TYPE_DB, NULL);
830 cdb->priv->db = db;
831 cdb->priv->filename = g_strdup (filename);
832 d (g_print ("\nDatabase succesfully opened \n"));
833
834 sqlite3_create_function (db, "MATCH", 2, SQLITE_UTF8, NULL, cdb_match_func, NULL, NULL);
835 sqlite3_create_function (db, "CAMELCOMPAREDATE", 2, SQLITE_UTF8, NULL, cdb_camel_compare_date_func, NULL, NULL);
836
837 /* Which is big / costlier ? A Stack frame or a pointer */
838 if (g_getenv ("CAMEL_SQLITE_DEFAULT_CACHE_SIZE") != NULL) {
839 gchar *cache = NULL;
840
841 cache = g_strdup_printf ("PRAGMA cache_size=%s", g_getenv ("CAMEL_SQLITE_DEFAULT_CACHE_SIZE"));
842 camel_db_command_internal (cdb, cache, &cdb_sqlite_error_code, &local_error);
843 g_free (cache);
844 }
845
846 if (cdb_sqlite_error_code == SQLITE_OK)
847 camel_db_command_internal (cdb, "ATTACH DATABASE ':memory:' AS mem", &cdb_sqlite_error_code, &local_error);
848
849 if (cdb_sqlite_error_code == SQLITE_OK && g_getenv ("CAMEL_SQLITE_IN_MEMORY") != NULL) {
850 /* Optionally turn off Journaling, this gets over fsync issues, but could be risky */
851 camel_db_command_internal (cdb, "PRAGMA main.journal_mode = off", &cdb_sqlite_error_code, &local_error);
852 if (cdb_sqlite_error_code == SQLITE_OK)
853 camel_db_command_internal (cdb, "PRAGMA temp_store = memory", &cdb_sqlite_error_code, &local_error);
854 }
855
856 if (!reopening && (
857 cdb_sqlite_error_code == SQLITE_CANTOPEN ||
858 cdb_sqlite_error_code == SQLITE_CORRUPT ||
859 cdb_sqlite_error_code == SQLITE_NOTADB)) {
860 gchar *second_filename;
861
862 g_clear_object (&cdb);
863
864 reopening = TRUE;
865
866 second_filename = g_strconcat (filename, ".corrupt", NULL);
867 if (g_rename (filename, second_filename) == -1) {
868 if (!local_error) {
869 g_set_error (&local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
870 _("Could not rename “%s” to %s: %s"),
871 filename, second_filename, g_strerror (errno));
872 }
873
874 g_propagate_error (error, local_error);
875
876 g_free (second_filename);
877
878 return NULL;
879 }
880
881 g_free (second_filename);
882
883 g_warning ("%s: Failed to open '%s', renamed old file to .corrupt; code:%s (%d) error:%s", G_STRFUNC, filename,
884 cdb_sqlite_error_code == SQLITE_CANTOPEN ? "SQLITE_CANTOPEN" :
885 cdb_sqlite_error_code == SQLITE_CORRUPT ? "SQLITE_CORRUPT" :
886 cdb_sqlite_error_code == SQLITE_NOTADB ? "SQLITE_NOTADB" : "???",
887 cdb_sqlite_error_code, local_error ? local_error->message : "Unknown error");
888
889 g_clear_error (&local_error);
890
891 goto reopen;
892 }
893
894 if (local_error) {
895 g_propagate_error (error, local_error);
896 g_clear_object (&cdb);
897 return NULL;
898 }
899
900 sqlite3_busy_timeout (cdb->priv->db, CAMEL_DB_SLEEP_INTERVAL);
901
902 return cdb;
903 }
904
905 /**
906 * camel_db_get_filename:
907 * @cdb: a #CamelDB
908 *
909 * Returns: (transfer none): A filename associated with @cdb.
910 *
911 * Since: 3.24
912 **/
913 const gchar *
camel_db_get_filename(CamelDB * cdb)914 camel_db_get_filename (CamelDB *cdb)
915 {
916 g_return_val_if_fail (CAMEL_IS_DB (cdb), NULL);
917
918 return cdb->priv->filename;
919 }
920
921 /**
922 * camel_db_set_collate:
923 * @cdb: a #CamelDB
924 * @col: a column name; currently unused
925 * @collate: collation name
926 * @func: (scope call): a #CamelDBCollate collation function
927 *
928 * Defines a collation @collate, which can be used in SQL (SQLite)
929 * statement as a collation function. The @func is called when
930 * colation is used.
931 *
932 * Since: 2.24
933 **/
934 gint
camel_db_set_collate(CamelDB * cdb,const gchar * col,const gchar * collate,CamelDBCollate func)935 camel_db_set_collate (CamelDB *cdb,
936 const gchar *col,
937 const gchar *collate,
938 CamelDBCollate func)
939 {
940 gint ret = 0;
941
942 if (!cdb)
943 return 0;
944
945 cdb_writer_lock (cdb);
946 d (g_print ("Creating Collation %s on %s with %p\n", collate, col, (gpointer) func));
947 if (collate && func)
948 ret = sqlite3_create_collation (cdb->priv->db, collate, SQLITE_UTF8, NULL, func);
949 cdb_writer_unlock (cdb);
950
951 return ret;
952 }
953
954 /**
955 * camel_db_command:
956 * @cdb: a #CamelDB
957 * @stmt: an SQL (SQLite) statement to execute
958 * @error: return location for a #GError, or %NULL
959 *
960 * Executes an SQLite command.
961 *
962 * Returns: 0 on success, -1 on error
963 *
964 * Since: 2.24
965 **/
966 gint
camel_db_command(CamelDB * cdb,const gchar * stmt,GError ** error)967 camel_db_command (CamelDB *cdb,
968 const gchar *stmt,
969 GError **error)
970 {
971 return camel_db_command_internal (cdb, stmt, NULL, error);
972 }
973
974 /**
975 * camel_db_begin_transaction:
976 * @cdb: a #CamelDB
977 * @error: return location for a #GError, or %NULL
978 *
979 * Begins transaction. End it with camel_db_end_transaction() or camel_db_abort_transaction().
980 *
981 * Returns: 0 on success, -1 on error
982 *
983 * Since: 2.24
984 **/
985 gint
camel_db_begin_transaction(CamelDB * cdb,GError ** error)986 camel_db_begin_transaction (CamelDB *cdb,
987 GError **error)
988 {
989 gchar *stmt;
990 gint res;
991
992 if (!cdb)
993 return -1;
994
995 cdb_writer_lock (cdb);
996
997 stmt = cdb_construct_transaction_stmt (cdb, "SAVEPOINT ");
998
999 STARTTS (stmt);
1000 res = cdb_sql_exec (cdb->priv->db, stmt, NULL, NULL, NULL, error);
1001 g_free (stmt);
1002
1003 return res;
1004 }
1005
1006 /**
1007 * camel_db_end_transaction:
1008 * @cdb: a #CamelDB
1009 * @error: return location for a #GError, or %NULL
1010 *
1011 * Ends an ongoing transaction by committing the changes.
1012 *
1013 * Returns: 0 on success, -1 on error
1014 *
1015 * Since: 2.24
1016 **/
1017 gint
camel_db_end_transaction(CamelDB * cdb,GError ** error)1018 camel_db_end_transaction (CamelDB *cdb,
1019 GError **error)
1020 {
1021 gchar *stmt;
1022 gint ret;
1023
1024 if (!cdb)
1025 return -1;
1026
1027 stmt = cdb_construct_transaction_stmt (cdb, "RELEASE SAVEPOINT ");
1028 ret = cdb_sql_exec (cdb->priv->db, stmt, NULL, NULL, NULL, error);
1029 g_free (stmt);
1030
1031 ENDTS;
1032 cdb_writer_unlock (cdb);
1033 camel_db_release_cache_memory ();
1034
1035 return ret;
1036 }
1037
1038 /**
1039 * camel_db_abort_transaction:
1040 * @cdb: a #CamelDB
1041 * @error: return location for a #GError, or %NULL
1042 *
1043 * Ends an ongoing transaction by ignoring the changes.
1044 *
1045 * Returns: 0 on success, -1 on error
1046 *
1047 * Since: 2.24
1048 **/
1049 gint
camel_db_abort_transaction(CamelDB * cdb,GError ** error)1050 camel_db_abort_transaction (CamelDB *cdb,
1051 GError **error)
1052 {
1053 gchar *stmt;
1054 gint ret;
1055
1056 stmt = cdb_construct_transaction_stmt (cdb, "ROLLBACK TO SAVEPOINT ");
1057 ret = cdb_sql_exec (cdb->priv->db, stmt, NULL, NULL, NULL, error);
1058 g_free (stmt);
1059
1060 cdb_writer_unlock (cdb);
1061 camel_db_release_cache_memory ();
1062
1063 return ret;
1064 }
1065
1066 /**
1067 * camel_db_add_to_transaction:
1068 * @cdb: a #CamelDB
1069 * @query: an SQL (SQLite) statement
1070 * @error: return location for a #GError, or %NULL
1071 *
1072 * Adds a statement to an ongoing transaction.
1073 *
1074 * Returns: 0 on success, -1 on error
1075 *
1076 * Since: 2.24
1077 **/
1078 gint
camel_db_add_to_transaction(CamelDB * cdb,const gchar * query,GError ** error)1079 camel_db_add_to_transaction (CamelDB *cdb,
1080 const gchar *query,
1081 GError **error)
1082 {
1083 if (!cdb)
1084 return -1;
1085
1086 g_return_val_if_fail (cdb_is_in_transaction (cdb), -1);
1087 g_return_val_if_fail (query != NULL, -1);
1088
1089 return (cdb_sql_exec (cdb->priv->db, query, NULL, NULL, NULL, error));
1090 }
1091
1092 /**
1093 * camel_db_transaction_command:
1094 * @cdb: a #CamelDB
1095 * @qry_list: (element-type utf8) (transfer none): A #GList of querries
1096 * @error: return location for a #GError, or %NULL
1097 *
1098 * Runs the list of commands as a single transaction.
1099 *
1100 * Returns: 0 on success, -1 on error
1101 *
1102 * Since: 2.24
1103 **/
1104 gint
camel_db_transaction_command(CamelDB * cdb,const GList * qry_list,GError ** error)1105 camel_db_transaction_command (CamelDB *cdb,
1106 const GList *qry_list,
1107 GError **error)
1108 {
1109 gboolean in_transaction = FALSE;
1110 gint ret;
1111 const gchar *query;
1112
1113 if (!cdb)
1114 return -1;
1115
1116 ret = camel_db_begin_transaction (cdb, error);
1117 if (ret)
1118 goto end;
1119
1120 in_transaction = TRUE;
1121
1122 while (qry_list) {
1123 query = qry_list->data;
1124 ret = cdb_sql_exec (cdb->priv->db, query, NULL, NULL, NULL, error);
1125 if (ret)
1126 goto end;
1127 qry_list = g_list_next (qry_list);
1128 }
1129
1130 ret = camel_db_end_transaction (cdb, error);
1131 in_transaction = FALSE;
1132 end:
1133 if (in_transaction)
1134 ret = camel_db_abort_transaction (cdb, error);
1135
1136 return ret;
1137 }
1138
1139 static gint
count_cb(gpointer data,gint argc,gchar ** argv,gchar ** azColName)1140 count_cb (gpointer data,
1141 gint argc,
1142 gchar **argv,
1143 gchar **azColName)
1144 {
1145 gint i;
1146
1147 for (i = 0; i < argc; i++) {
1148 if (strstr (azColName[i], "COUNT")) {
1149 *(guint32 *)data = argv [i] ? strtoul (argv [i], NULL, 10) : 0;
1150 }
1151 }
1152
1153 return 0;
1154 }
1155
1156 /**
1157 * camel_db_count_message_info:
1158 * @cdb: a #CamelDB
1159 * @query: a COUNT() query
1160 * @count: (out): the result of the query
1161 * @error: return location for a #GError, or %NULL
1162 *
1163 * Executes a COUNT() query (like "SELECT COUNT(*) FROM table") and provides
1164 * the result of it as an unsigned 32-bit integer.
1165 *
1166 * Returns: 0 on success, -1 on error
1167 *
1168 * Since: 2.26
1169 **/
1170 gint
camel_db_count_message_info(CamelDB * cdb,const gchar * query,guint32 * count,GError ** error)1171 camel_db_count_message_info (CamelDB *cdb,
1172 const gchar *query,
1173 guint32 *count,
1174 GError **error)
1175 {
1176 gint ret = -1;
1177
1178 g_return_val_if_fail (query != NULL, -1);
1179
1180 cdb_reader_lock (cdb);
1181
1182 START (query);
1183 ret = cdb_sql_exec (cdb->priv->db, query, count_cb, count, NULL, error);
1184 END;
1185
1186 cdb_reader_unlock (cdb);
1187
1188 camel_db_release_cache_memory ();
1189
1190 return ret;
1191 }
1192
1193 /**
1194 * camel_db_count_junk_message_info:
1195 * @cdb: a #CamelDB
1196 * @table_name: name of the table
1197 * @count: (out): where to store the resulting count
1198 * @error: return location for a #GError, or %NULL
1199 *
1200 * Counts how many junk messages is stored in the given table.
1201 *
1202 * Returns: 0 on success, -1 on error
1203 *
1204 * Since: 2.24
1205 **/
1206 gint
camel_db_count_junk_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1207 camel_db_count_junk_message_info (CamelDB *cdb,
1208 const gchar *table_name,
1209 guint32 *count,
1210 GError **error)
1211 {
1212 gint ret;
1213 gchar *query;
1214
1215 if (!cdb)
1216 return -1;
1217
1218 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 1", table_name);
1219
1220 ret = camel_db_count_message_info (cdb, query, count, error);
1221 sqlite3_free (query);
1222
1223 return ret;
1224 }
1225
1226 /**
1227 * camel_db_count_unread_message_info:
1228 * @cdb: a #CamelDB
1229 * @table_name: name of the table
1230 * @count: (out): where to store the resulting count
1231 * @error: return location for a #GError, or %NULL
1232 *
1233 * Counts how many unread messages is stored in the given table.
1234 *
1235 * Returns: 0 on success, -1 on error
1236 *
1237 * Since: 2.24
1238 **/
1239 gint
camel_db_count_unread_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1240 camel_db_count_unread_message_info (CamelDB *cdb,
1241 const gchar *table_name,
1242 guint32 *count,
1243 GError **error)
1244 {
1245 gint ret;
1246 gchar *query;
1247
1248 if (!cdb)
1249 return -1;
1250
1251 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE read = 0", table_name);
1252
1253 ret = camel_db_count_message_info (cdb, query, count, error);
1254 sqlite3_free (query);
1255
1256 return ret;
1257 }
1258
1259 /**
1260 * camel_db_count_visible_unread_message_info:
1261 * @cdb: a #CamelDB
1262 * @table_name: name of the table
1263 * @count: (out): where to store the resulting count
1264 * @error: return location for a #GError, or %NULL
1265 *
1266 * Counts how many visible (not deleted and not junk) and unread messages is stored in the given table.
1267 *
1268 * Returns: 0 on success, -1 on error
1269 *
1270 * Since: 2.24
1271 **/
1272 gint
camel_db_count_visible_unread_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1273 camel_db_count_visible_unread_message_info (CamelDB *cdb,
1274 const gchar *table_name,
1275 guint32 *count,
1276 GError **error)
1277 {
1278 gint ret;
1279 gchar *query;
1280
1281 if (!cdb)
1282 return -1;
1283
1284 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE read = 0 AND junk = 0 AND deleted = 0", table_name);
1285
1286 ret = camel_db_count_message_info (cdb, query, count, error);
1287 sqlite3_free (query);
1288
1289 return ret;
1290 }
1291
1292 /**
1293 * camel_db_count_visible_message_info:
1294 * @cdb: a #CamelDB
1295 * @table_name: name of the table
1296 * @count: (out): where to store the resulting count
1297 * @error: return location for a #GError, or %NULL
1298 *
1299 * Counts how many visible (not deleted and not junk) messages is stored in the given table.
1300 *
1301 * Returns: 0 on success, -1 on error
1302 *
1303 * Since: 2.24
1304 **/
1305 gint
camel_db_count_visible_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1306 camel_db_count_visible_message_info (CamelDB *cdb,
1307 const gchar *table_name,
1308 guint32 *count,
1309 GError **error)
1310 {
1311 gint ret;
1312 gchar *query;
1313
1314 if (!cdb)
1315 return -1;
1316
1317 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 0 AND deleted = 0", table_name);
1318
1319 ret = camel_db_count_message_info (cdb, query, count, error);
1320 sqlite3_free (query);
1321
1322 return ret;
1323 }
1324
1325 /**
1326 * camel_db_count_junk_not-deleted_message_info:
1327 * @cdb: a #CamelDB
1328 * @table_name: name of the table
1329 * @count: (out): where to store the resulting count
1330 * @error: return location for a #GError, or %NULL
1331 *
1332 * Counts how many junk, but not deleted, messages is stored in the given table.
1333 *
1334 * Returns: 0 on success, -1 on error
1335 *
1336 * Since: 2.24
1337 **/
1338 gint
camel_db_count_junk_not_deleted_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1339 camel_db_count_junk_not_deleted_message_info (CamelDB *cdb,
1340 const gchar *table_name,
1341 guint32 *count,
1342 GError **error)
1343 {
1344 gint ret;
1345 gchar *query;
1346
1347 if (!cdb)
1348 return -1;
1349
1350 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 1 AND deleted = 0", table_name);
1351
1352 ret = camel_db_count_message_info (cdb, query, count, error);
1353 sqlite3_free (query);
1354
1355 return ret;
1356 }
1357
1358 /**
1359 * camel_db_count_deleted_message_info:
1360 * @cdb: a #CamelDB
1361 * @table_name: name of the table
1362 * @count: (out): where to store the resulting count
1363 * @error: return location for a #GError, or %NULL
1364 *
1365 * Counts how many deleted messages is stored in the given table.
1366 *
1367 * Returns: 0 on success, -1 on error
1368 *
1369 * Since: 2.24
1370 **/
1371 gint
camel_db_count_deleted_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1372 camel_db_count_deleted_message_info (CamelDB *cdb,
1373 const gchar *table_name,
1374 guint32 *count,
1375 GError **error)
1376 {
1377 gint ret;
1378 gchar *query;
1379
1380 if (!cdb)
1381 return -1;
1382
1383 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE deleted = 1", table_name);
1384
1385 ret = camel_db_count_message_info (cdb, query, count, error);
1386 sqlite3_free (query);
1387
1388 return ret;
1389 }
1390
1391 /**
1392 * camel_db_count_total_message_info:
1393 * @cdb: a #CamelDB
1394 * @table_name: name of the table
1395 * @count: (out): where to store the resulting count
1396 * @error: return location for a #GError, or %NULL
1397 *
1398 * Counts how many messages is stored in the given table.
1399 *
1400 * Returns: 0 on success, -1 on error
1401 *
1402 * Since: 2.24
1403 **/
1404 gint
camel_db_count_total_message_info(CamelDB * cdb,const gchar * table_name,guint32 * count,GError ** error)1405 camel_db_count_total_message_info (CamelDB *cdb,
1406 const gchar *table_name,
1407 guint32 *count,
1408 GError **error)
1409 {
1410
1411 gint ret;
1412 gchar *query;
1413
1414 if (!cdb)
1415 return -1;
1416
1417 query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q where read=0 or read=1", table_name);
1418
1419 ret = camel_db_count_message_info (cdb, query, count, error);
1420 sqlite3_free (query);
1421
1422 return ret;
1423 }
1424
1425 /**
1426 * camel_db_select:
1427 * @cdb: a #CamelDB
1428 * @stmt: a SELECT statment to execute
1429 * @callback: (scope call) (closure user_data): a callback to call for each row
1430 * @user_data: user data for the @callback
1431 * @error: return location for a #GError, or %NULL
1432 *
1433 * Executes a SELECT staement and calls the @callback for each selected row.
1434 *
1435 * Returns: 0 on success, -1 on error
1436 *
1437 * Since: 2.24
1438 **/
1439 gint
camel_db_select(CamelDB * cdb,const gchar * stmt,CamelDBSelectCB callback,gpointer user_data,GError ** error)1440 camel_db_select (CamelDB *cdb,
1441 const gchar *stmt,
1442 CamelDBSelectCB callback,
1443 gpointer user_data,
1444 GError **error)
1445 {
1446 gint ret = -1;
1447
1448 if (!cdb)
1449 return ret;
1450
1451 g_return_val_if_fail (stmt != NULL, ret);
1452
1453 d (g_print ("\n%s:\n%s \n", G_STRFUNC, stmt));
1454 cdb_reader_lock (cdb);
1455
1456 START (stmt);
1457 ret = cdb_sql_exec (cdb->priv->db, stmt, callback, user_data, NULL, error);
1458 END;
1459
1460 cdb_reader_unlock (cdb);
1461 camel_db_release_cache_memory ();
1462
1463 return ret;
1464 }
1465
1466 static gint
read_uids_callback(gpointer ref_array,gint ncol,gchar ** cols,gchar ** name)1467 read_uids_callback (gpointer ref_array,
1468 gint ncol,
1469 gchar **cols,
1470 gchar **name)
1471 {
1472 GPtrArray *array = ref_array;
1473
1474 g_return_val_if_fail (ncol == 1, 0);
1475
1476 if (cols[0])
1477 g_ptr_array_add (array, (gchar *) (camel_pstring_strdup (cols[0])));
1478
1479 return 0;
1480 }
1481
1482 static gint
read_uids_to_hash_callback(gpointer ref_hash,gint ncol,gchar ** cols,gchar ** name)1483 read_uids_to_hash_callback (gpointer ref_hash,
1484 gint ncol,
1485 gchar **cols,
1486 gchar **name)
1487 {
1488 GHashTable *hash = ref_hash;
1489
1490 g_return_val_if_fail (ncol == 2, 0);
1491
1492 if (cols[0])
1493 g_hash_table_insert (hash, (gchar *) camel_pstring_strdup (cols[0]), GUINT_TO_POINTER (cols[1] ? strtoul (cols[1], NULL, 10) : 0));
1494
1495 return 0;
1496 }
1497
1498 /**
1499 * camel_db_get_folder_uids:
1500 * @cdb: a #CamelDB
1501 * @folder_name: full name of the folder
1502 * @sort_by: (nullable): optional ORDER BY clause (without the "ORDER BY" prefix)
1503 * @collate: (nullable): optional collate function name to use
1504 * @hash: (element-type utf8 guint32): a hash table to fill
1505 * @error: return location for a #GError, or %NULL
1506 *
1507 * Fills hash with uid->GUINT_TO_POINTER (flag). Use camel_pstring_free()
1508 * to free the keys of the @hash.
1509 *
1510 * Returns: 0 on success, -1 on error
1511 *
1512 * Since: 2.24
1513 **/
1514 gint
camel_db_get_folder_uids(CamelDB * cdb,const gchar * folder_name,const gchar * sort_by,const gchar * collate,GHashTable * hash,GError ** error)1515 camel_db_get_folder_uids (CamelDB *cdb,
1516 const gchar *folder_name,
1517 const gchar *sort_by,
1518 const gchar *collate,
1519 GHashTable *hash,
1520 GError **error)
1521 {
1522 gchar *sel_query;
1523 gint ret;
1524
1525 sel_query = sqlite3_mprintf (
1526 "SELECT uid,flags FROM %Q%s%s%s%s",
1527 folder_name,
1528 sort_by ? " order by " : "",
1529 sort_by ? sort_by : "",
1530 (sort_by && collate) ? " collate " : "",
1531 (sort_by && collate) ? collate : "");
1532
1533 ret = camel_db_select (cdb, sel_query, read_uids_to_hash_callback, hash, error);
1534 sqlite3_free (sel_query);
1535
1536 return ret;
1537 }
1538
1539 /**
1540 * camel_db_get_folder_junk_uids:
1541 * @cdb: a #CamelDB
1542 * @folder_name: full name of the folder
1543 * @error: return location for a #GError, or %NULL
1544 *
1545 * Returns: (element-type utf8) (transfer full) (nullable): An array
1546 * of the UID-s of the junk messages in the given folder. Use
1547 * camel_pstring_free() to free the elements.
1548 *
1549 * Since: 2.24
1550 **/
1551 GPtrArray *
camel_db_get_folder_junk_uids(CamelDB * cdb,const gchar * folder_name,GError ** error)1552 camel_db_get_folder_junk_uids (CamelDB *cdb,
1553 const gchar *folder_name,
1554 GError **error)
1555 {
1556 gchar *sel_query;
1557 gint ret;
1558 GPtrArray *array = g_ptr_array_new ();
1559
1560 sel_query = sqlite3_mprintf ("SELECT uid FROM %Q where junk=1", folder_name);
1561
1562 ret = camel_db_select (cdb, sel_query, read_uids_callback, array, error);
1563
1564 sqlite3_free (sel_query);
1565
1566 if (!array->len || ret != 0) {
1567 g_ptr_array_free (array, TRUE);
1568 array = NULL;
1569 }
1570
1571 return array;
1572 }
1573
1574 /**
1575 * camel_db_get_folder_deleted_uids:
1576 * @cdb: a #CamelDB
1577 * @folder_name: full name of the folder
1578 * @error: return location for a #GError, or %NULL
1579 *
1580 * Returns: (element-type utf8) (transfer full) (nullable): An array
1581 * of the UID-s of the deleted messages in the given folder. Use
1582 * camel_pstring_free() to free the elements.
1583 *
1584 * Since: 2.24
1585 **/
1586 GPtrArray *
camel_db_get_folder_deleted_uids(CamelDB * cdb,const gchar * folder_name,GError ** error)1587 camel_db_get_folder_deleted_uids (CamelDB *cdb,
1588 const gchar *folder_name,
1589 GError **error)
1590 {
1591 gchar *sel_query;
1592 gint ret;
1593 GPtrArray *array = g_ptr_array_new ();
1594
1595 sel_query = sqlite3_mprintf ("SELECT uid FROM %Q where deleted=1", folder_name);
1596
1597 ret = camel_db_select (cdb, sel_query, read_uids_callback, array, error);
1598 sqlite3_free (sel_query);
1599
1600 if (!array->len || ret != 0) {
1601 g_ptr_array_free (array, TRUE);
1602 array = NULL;
1603 }
1604
1605 return array;
1606 }
1607
1608 /**
1609 * camel_db_create_folders_table:
1610 * @cdb: a #CamelDB
1611 * @error: return location for a #GError, or %NULL
1612 *
1613 * Creates a 'folders' table, if it doesn't exist yet.
1614 *
1615 * Returns: 0 on success, -1 on error
1616 *
1617 * Since: 2.24
1618 **/
1619 gint
camel_db_create_folders_table(CamelDB * cdb,GError ** error)1620 camel_db_create_folders_table (CamelDB *cdb,
1621 GError **error)
1622 {
1623 const gchar *query = "CREATE TABLE IF NOT EXISTS folders ( "
1624 "folder_name TEXT PRIMARY KEY, "
1625 "version REAL, "
1626 "flags INTEGER, "
1627 "nextuid INTEGER, "
1628 "time NUMERIC, "
1629 "saved_count INTEGER, "
1630 "unread_count INTEGER, "
1631 "deleted_count INTEGER, "
1632 "junk_count INTEGER, "
1633 "visible_count INTEGER, "
1634 "jnd_count INTEGER, "
1635 "bdata TEXT )";
1636
1637 g_return_val_if_fail (CAMEL_IS_DB (cdb), -1);
1638
1639 camel_db_release_cache_memory ();
1640
1641 cdb->priv->is_foldersdb = TRUE;
1642
1643 return camel_db_command (cdb, query, error);
1644 }
1645
1646 static gint
camel_db_create_message_info_table(CamelDB * cdb,const gchar * folder_name,GError ** error)1647 camel_db_create_message_info_table (CamelDB *cdb,
1648 const gchar *folder_name,
1649 GError **error)
1650 {
1651 gint ret;
1652 gchar *table_creation_query, *safe_index;
1653
1654 /* README: It is possible to compress all system flags into a single
1655 * column and use just as userflags but that makes querying for other
1656 * applications difficult and bloats the parsing code. Instead, it is
1657 * better to bloat the tables. Sqlite should have some optimizations
1658 * for sparse columns etc. */
1659 table_creation_query = sqlite3_mprintf (
1660 "CREATE TABLE IF NOT EXISTS %Q ( "
1661 "uid TEXT PRIMARY KEY , "
1662 "flags INTEGER , "
1663 "msg_type INTEGER , "
1664 "read INTEGER , "
1665 "deleted INTEGER , "
1666 "replied INTEGER , "
1667 "important INTEGER , "
1668 "junk INTEGER , "
1669 "attachment INTEGER , "
1670 "dirty INTEGER , "
1671 "size INTEGER , "
1672 "dsent NUMERIC , "
1673 "dreceived NUMERIC , "
1674 "subject TEXT , "
1675 "mail_from TEXT , "
1676 "mail_to TEXT , "
1677 "mail_cc TEXT , "
1678 "mlist TEXT , "
1679 "followup_flag TEXT , "
1680 "followup_completed_on TEXT , "
1681 "followup_due_by TEXT , "
1682 "part TEXT , "
1683 "labels TEXT , "
1684 "usertags TEXT , "
1685 "cinfo TEXT , "
1686 "bdata TEXT, "
1687 "userheaders TEXT, "
1688 "preview TEXT, "
1689 "created TEXT, "
1690 "modified TEXT)",
1691 folder_name);
1692 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1693 sqlite3_free (table_creation_query);
1694
1695 if (ret != 0)
1696 return ret;
1697
1698 /* FIXME: sqlize folder_name before you create the index */
1699 safe_index = g_strdup_printf ("SINDEX-%s", folder_name);
1700 table_creation_query = sqlite3_mprintf ("DROP INDEX IF EXISTS %Q", safe_index);
1701 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1702 g_free (safe_index);
1703 sqlite3_free (table_creation_query);
1704
1705 if (ret != 0)
1706 return ret;
1707
1708 /* Index on deleted*/
1709 safe_index = g_strdup_printf ("DELINDEX-%s", folder_name);
1710 table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (deleted)", safe_index, folder_name);
1711 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1712 g_free (safe_index);
1713 sqlite3_free (table_creation_query);
1714
1715 if (ret != 0)
1716 return ret;
1717
1718 /* Index on Junk*/
1719 safe_index = g_strdup_printf ("JUNKINDEX-%s", folder_name);
1720 table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (junk)", safe_index, folder_name);
1721 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1722 g_free (safe_index);
1723 sqlite3_free (table_creation_query);
1724
1725 if (ret != 0)
1726 return ret;
1727
1728 /* Index on unread*/
1729 safe_index = g_strdup_printf ("READINDEX-%s", folder_name);
1730 table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (read)", safe_index, folder_name);
1731 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1732 g_free (safe_index);
1733 sqlite3_free (table_creation_query);
1734
1735 return ret;
1736 }
1737
1738 static gint
camel_db_migrate_folder_prepare(CamelDB * cdb,const gchar * folder_name,gint version,GError ** error)1739 camel_db_migrate_folder_prepare (CamelDB *cdb,
1740 const gchar *folder_name,
1741 gint version,
1742 GError **error)
1743 {
1744 gint ret = 0;
1745 gchar *table_creation_query;
1746
1747 /* Migration stage one: storing the old data */
1748
1749 if (version < 0) {
1750 ret = camel_db_create_message_info_table (cdb, folder_name, error);
1751 g_clear_error (error);
1752 } else if (version < 3) {
1753
1754 /* Between version 0-1 the following things are changed
1755 * ADDED: created: time
1756 * ADDED: modified: time
1757 * RENAMED: msg_security to dirty
1758 *
1759 * Between version 2-3 the following things are changed
1760 * ADDED: userheaders: text
1761 * ADDED: preview: text
1762 */
1763
1764 table_creation_query = sqlite3_mprintf ("DROP TABLE IF EXISTS 'mem.%q'", folder_name);
1765 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1766 sqlite3_free (table_creation_query);
1767
1768 table_creation_query = sqlite3_mprintf (
1769 "CREATE TEMP TABLE IF NOT EXISTS 'mem.%q' ( "
1770 "uid TEXT PRIMARY KEY , "
1771 "flags INTEGER , "
1772 "msg_type INTEGER , "
1773 "read INTEGER , "
1774 "deleted INTEGER , "
1775 "replied INTEGER , "
1776 "important INTEGER , "
1777 "junk INTEGER , "
1778 "attachment INTEGER , "
1779 "dirty INTEGER , "
1780 "size INTEGER , "
1781 "dsent NUMERIC , "
1782 "dreceived NUMERIC , "
1783 "subject TEXT , "
1784 "mail_from TEXT , "
1785 "mail_to TEXT , "
1786 "mail_cc TEXT , "
1787 "mlist TEXT , "
1788 "followup_flag TEXT , "
1789 "followup_completed_on TEXT , "
1790 "followup_due_by TEXT , "
1791 "part TEXT , "
1792 "labels TEXT , "
1793 "usertags TEXT , "
1794 "cinfo TEXT , "
1795 "bdata TEXT, "
1796 "userheaders TEXT, "
1797 "preview TEXT, "
1798 "created TEXT, "
1799 "modified TEXT )",
1800 folder_name);
1801 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1802 sqlite3_free (table_creation_query);
1803 g_clear_error (error);
1804
1805 table_creation_query = sqlite3_mprintf (
1806 "INSERT INTO 'mem.%q' SELECT "
1807 "uid , flags , msg_type , read , deleted , "
1808 "replied , important , junk , attachment , dirty , "
1809 "size , dsent , dreceived , subject , mail_from , "
1810 "mail_to , mail_cc , mlist , followup_flag , "
1811 "followup_completed_on , followup_due_by , "
1812 "part , labels , usertags , cinfo , bdata , '', '', "
1813 "strftime(\"%%s\", 'now'), "
1814 "strftime(\"%%s\", 'now') FROM %Q",
1815 folder_name, folder_name);
1816 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1817 sqlite3_free (table_creation_query);
1818 g_clear_error (error);
1819
1820 table_creation_query = sqlite3_mprintf ("DROP TABLE IF EXISTS %Q", folder_name);
1821 ret = camel_db_add_to_transaction (cdb, table_creation_query, error);
1822 sqlite3_free (table_creation_query);
1823 g_clear_error (error);
1824
1825 ret = camel_db_create_message_info_table (cdb, folder_name, error);
1826 g_clear_error (error);
1827 }
1828
1829 /* Add later version migrations here */
1830
1831 return ret;
1832 }
1833
1834 static gint
camel_db_migrate_folder_recreate(CamelDB * cdb,const gchar * folder_name,gint version,GError ** error)1835 camel_db_migrate_folder_recreate (CamelDB *cdb,
1836 const gchar *folder_name,
1837 gint version,
1838 GError **error)
1839 {
1840 gint ret = 0;
1841 gchar *table_creation_query;
1842
1843 /* Migration stage two: writing back the old data */
1844
1845 if (version < 3) {
1846 GError *local_error = NULL;
1847
1848 table_creation_query = sqlite3_mprintf (
1849 "INSERT INTO %Q SELECT uid , flags , msg_type , "
1850 "read , deleted , replied , important , junk , "
1851 "attachment , dirty , size , dsent , dreceived , "
1852 "subject , mail_from , mail_to , mail_cc , mlist , "
1853 "followup_flag , followup_completed_on , "
1854 "followup_due_by , part , labels , usertags , "
1855 "cinfo , bdata, userheaders, preview, created, modified FROM 'mem.%q'",
1856 folder_name, folder_name);
1857 ret = camel_db_add_to_transaction (cdb, table_creation_query, &local_error);
1858 sqlite3_free (table_creation_query);
1859
1860 if (!local_error) {
1861 table_creation_query = sqlite3_mprintf ("DROP TABLE 'mem.%q'", folder_name);
1862 ret = camel_db_add_to_transaction (cdb, table_creation_query, &local_error);
1863 sqlite3_free (table_creation_query);
1864 }
1865
1866 if (local_error) {
1867 if (local_error->message && strstr (local_error->message, "no such table") != NULL) {
1868 /* ignore 'no such table' errors here */
1869 g_clear_error (&local_error);
1870 ret = 0;
1871 } else {
1872 g_propagate_error (error, local_error);
1873 }
1874 }
1875 }
1876
1877 /* Add later version migrations here */
1878
1879 return ret;
1880 }
1881
1882 /**
1883 * camel_db_reset_folder_version:
1884 * @cdb: a #CamelDB
1885 * @folder_name: full name of the folder
1886 * @reset_version: version number to set
1887 * @error: return location for a #GError, or %NULL
1888 *
1889 * Sets a version number for the given folder.
1890 *
1891 * Returns: 0 on success, -1 on error
1892 *
1893 * Since: 2.28
1894 **/
1895 gint
camel_db_reset_folder_version(CamelDB * cdb,const gchar * folder_name,gint reset_version,GError ** error)1896 camel_db_reset_folder_version (CamelDB *cdb,
1897 const gchar *folder_name,
1898 gint reset_version,
1899 GError **error)
1900 {
1901 gint ret = 0;
1902 gchar *version_creation_query;
1903 gchar *version_insert_query;
1904 gchar *drop_folder_query;
1905
1906 drop_folder_query = sqlite3_mprintf ("DROP TABLE IF EXISTS '%q_version'", folder_name);
1907 version_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_version' ( version TEXT )", folder_name);
1908
1909 version_insert_query = sqlite3_mprintf ("INSERT INTO '%q_version' VALUES ('%d')", folder_name, reset_version);
1910
1911 ret = camel_db_add_to_transaction (cdb, drop_folder_query, error);
1912 ret = camel_db_add_to_transaction (cdb, version_creation_query, error);
1913 ret = camel_db_add_to_transaction (cdb, version_insert_query, error);
1914
1915 sqlite3_free (drop_folder_query);
1916 sqlite3_free (version_creation_query);
1917 sqlite3_free (version_insert_query);
1918
1919 return ret;
1920 }
1921
1922 static gint
camel_db_write_folder_version(CamelDB * cdb,const gchar * folder_name,gint old_version,GError ** error)1923 camel_db_write_folder_version (CamelDB *cdb,
1924 const gchar *folder_name,
1925 gint old_version,
1926 GError **error)
1927 {
1928 gint ret = 0;
1929 gchar *version_creation_query;
1930 gchar *version_insert_query;
1931
1932 version_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_version' ( version TEXT )", folder_name);
1933
1934 if (old_version == -1)
1935 version_insert_query = sqlite3_mprintf ("INSERT INTO '%q_version' VALUES ('" G_STRINGIFY (MESSAGE_INFO_TABLE_VERSION) "')", folder_name);
1936 else
1937 version_insert_query = sqlite3_mprintf ("UPDATE '%q_version' SET version='" G_STRINGIFY (MESSAGE_INFO_TABLE_VERSION) "'", folder_name);
1938
1939 ret = camel_db_add_to_transaction (cdb, version_creation_query, error);
1940 ret = camel_db_add_to_transaction (cdb, version_insert_query, error);
1941
1942 sqlite3_free (version_creation_query);
1943 sqlite3_free (version_insert_query);
1944
1945 return ret;
1946 }
1947
1948 static gint
read_version_callback(gpointer ref,gint ncol,gchar ** cols,gchar ** name)1949 read_version_callback (gpointer ref,
1950 gint ncol,
1951 gchar **cols,
1952 gchar **name)
1953 {
1954 gint *version = (gint *) ref;
1955
1956 if (cols[0])
1957 *version = strtoul (cols [0], NULL, 10);
1958
1959 return 0;
1960 }
1961
1962 static gint
camel_db_get_folder_version(CamelDB * cdb,const gchar * folder_name,GError ** error)1963 camel_db_get_folder_version (CamelDB *cdb,
1964 const gchar *folder_name,
1965 GError **error)
1966 {
1967 gint version = -1;
1968 gchar *query;
1969
1970 query = sqlite3_mprintf ("SELECT version FROM '%q_version'", folder_name);
1971 camel_db_select (cdb, query, read_version_callback, &version, error);
1972 sqlite3_free (query);
1973
1974 return version;
1975 }
1976
1977 /**
1978 * camel_db_prepare_message_info_table:
1979 * @cdb: a #CamelDB
1980 * @folder_name: full name of the folder
1981 * @error: return location for a #GError, or %NULL
1982 *
1983 * Prepares message info table for the given folder.
1984 *
1985 * Returns: 0 on success, -1 on error
1986 *
1987 * Since: 2.24
1988 **/
1989 gint
camel_db_prepare_message_info_table(CamelDB * cdb,const gchar * folder_name,GError ** error)1990 camel_db_prepare_message_info_table (CamelDB *cdb,
1991 const gchar *folder_name,
1992 GError **error)
1993 {
1994 gint ret, current_version;
1995 gboolean in_transaction = TRUE;
1996 GError *err = NULL;
1997
1998 /* Make sure we have the table already */
1999 camel_db_begin_transaction (cdb, &err);
2000 ret = camel_db_create_message_info_table (cdb, folder_name, &err);
2001 if (err)
2002 goto exit;
2003
2004 camel_db_end_transaction (cdb, &err);
2005 in_transaction = FALSE;
2006
2007 /* Migration stage zero: version fetch */
2008 current_version = camel_db_get_folder_version (cdb, folder_name, &err);
2009 if (err && err->message && strstr (err->message, "no such table") != NULL) {
2010 g_clear_error (&err);
2011 current_version = -1;
2012 }
2013
2014 /* Avoid the migration when not needed */
2015 if (current_version == MESSAGE_INFO_TABLE_VERSION)
2016 goto exit;
2017
2018 camel_db_begin_transaction (cdb, &err);
2019 in_transaction = TRUE;
2020
2021 /* Migration stage one: storing the old data if necessary */
2022 ret = camel_db_migrate_folder_prepare (cdb, folder_name, current_version, &err);
2023 if (err)
2024 goto exit;
2025
2026 /* Migration stage two: rewriting the old data if necessary */
2027 ret = camel_db_migrate_folder_recreate (cdb, folder_name, current_version, &err);
2028 if (err)
2029 goto exit;
2030
2031 /* Final step: (over)write the current version label */
2032 ret = camel_db_write_folder_version (cdb, folder_name, current_version, &err);
2033 if (err)
2034 goto exit;
2035
2036 camel_db_end_transaction (cdb, &err);
2037 in_transaction = FALSE;
2038
2039 exit:
2040 if (err && in_transaction)
2041 camel_db_abort_transaction (cdb, NULL);
2042
2043 if (err)
2044 g_propagate_error (error, err);
2045
2046 return ret;
2047 }
2048
2049 /**
2050 * camel_db_write_message_info_record:
2051 * @cdb: a #CamelDB
2052 * @folder_name: full name of the folder
2053 * @record: a #CamelMIRecord
2054 * @error: return location for a #GError, or %NULL
2055 *
2056 * Write the @record to the message info table of the given folder.
2057 *
2058 * Returns: 0 on success, -1 on error
2059 *
2060 * Since: 2.24
2061 **/
2062 gint
camel_db_write_message_info_record(CamelDB * cdb,const gchar * folder_name,CamelMIRecord * record,GError ** error)2063 camel_db_write_message_info_record (CamelDB *cdb,
2064 const gchar *folder_name,
2065 CamelMIRecord *record,
2066 GError **error)
2067 {
2068 gint ret;
2069 gchar *ins_query;
2070
2071 if (!record) {
2072 g_warn_if_reached ();
2073 return -1;
2074 }
2075
2076 /* NB: UGLIEST Hack. We can't modify the schema now. We are using dirty (an unsed one to notify of FLAGGED/Dirty infos */
2077
2078 ins_query = sqlite3_mprintf (
2079 "INSERT OR REPLACE INTO %Q VALUES ("
2080 "%Q, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, "
2081 "%lld, %lld, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, "
2082 "%Q, %Q, %Q, %Q, %Q, %Q, %Q, "
2083 "strftime(\"%%s\", 'now'), "
2084 "strftime(\"%%s\", 'now') )",
2085 folder_name,
2086 record->uid,
2087 record->flags,
2088 record->msg_type,
2089 record->read,
2090 record->deleted,
2091 record->replied,
2092 record->important,
2093 record->junk,
2094 record->attachment,
2095 record->dirty,
2096 record->size,
2097 record->dsent,
2098 record->dreceived,
2099 record->subject,
2100 record->from,
2101 record->to,
2102 record->cc,
2103 record->mlist,
2104 record->followup_flag,
2105 record->followup_completed_on,
2106 record->followup_due_by,
2107 record->part,
2108 record->labels,
2109 record->usertags,
2110 record->cinfo,
2111 record->bdata,
2112 record->userheaders,
2113 record->preview);
2114
2115 ret = camel_db_add_to_transaction (cdb, ins_query, error);
2116
2117 sqlite3_free (ins_query);
2118
2119 return ret;
2120 }
2121
2122 /**
2123 * camel_db_write_folder_info_record:
2124 * @cdb: a #CamelDB
2125 * @record: a #CamelFIRecord
2126 * @error: return location for a #GError, or %NULL
2127 *
2128 * Write the @record to the 'folders' table.
2129 *
2130 * Returns: 0 on success, -1 on error
2131 *
2132 * Since: 2.24
2133 **/
2134 gint
camel_db_write_folder_info_record(CamelDB * cdb,CamelFIRecord * record,GError ** error)2135 camel_db_write_folder_info_record (CamelDB *cdb,
2136 CamelFIRecord *record,
2137 GError **error)
2138 {
2139 gint ret;
2140
2141 gchar *del_query;
2142 gchar *ins_query;
2143
2144 ins_query = sqlite3_mprintf (
2145 "INSERT INTO folders VALUES ("
2146 "%Q, %d, %d, %d, %lld, %d, %d, %d, %d, %d, %d, %Q ) ",
2147 record->folder_name,
2148 record->version,
2149 record->flags,
2150 record->nextuid,
2151 record->timestamp,
2152 record->saved_count,
2153 record->unread_count,
2154 record->deleted_count,
2155 record->junk_count,
2156 record->visible_count,
2157 record->jnd_count,
2158 record->bdata);
2159
2160 del_query = sqlite3_mprintf (
2161 "DELETE FROM folders WHERE folder_name = %Q",
2162 record->folder_name);
2163
2164 ret = camel_db_add_to_transaction (cdb, del_query, error);
2165 ret = camel_db_add_to_transaction (cdb, ins_query, error);
2166
2167 sqlite3_free (del_query);
2168 sqlite3_free (ins_query);
2169
2170 return ret;
2171 }
2172
2173 struct ReadFirData {
2174 GHashTable *columns_hash;
2175 CamelFIRecord *record;
2176 };
2177
2178 static gint
read_fir_callback(gpointer ref,gint ncol,gchar ** cols,gchar ** name)2179 read_fir_callback (gpointer ref,
2180 gint ncol,
2181 gchar **cols,
2182 gchar **name)
2183 {
2184 struct ReadFirData *rfd = ref;
2185 gint i;
2186
2187 d (g_print ("\nread_fir_callback called \n"));
2188
2189 for (i = 0; i < ncol; ++i) {
2190 if (!name[i] || !cols[i])
2191 continue;
2192
2193 switch (camel_db_get_column_ident (&rfd->columns_hash, i, ncol, name)) {
2194 case CAMEL_DB_COLUMN_FOLDER_NAME:
2195 rfd->record->folder_name = g_strdup (cols[i]);
2196 break;
2197 case CAMEL_DB_COLUMN_VERSION:
2198 rfd->record->version = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2199 break;
2200 case CAMEL_DB_COLUMN_FLAGS:
2201 rfd->record->flags = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2202 break;
2203 case CAMEL_DB_COLUMN_NEXTUID:
2204 rfd->record->nextuid = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2205 break;
2206 case CAMEL_DB_COLUMN_TIME:
2207 rfd->record->timestamp = cols[i] ? g_ascii_strtoll (cols[i], NULL, 10) : 0;
2208 break;
2209 case CAMEL_DB_COLUMN_SAVED_COUNT:
2210 rfd->record->saved_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2211 break;
2212 case CAMEL_DB_COLUMN_UNREAD_COUNT:
2213 rfd->record->unread_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2214 break;
2215 case CAMEL_DB_COLUMN_DELETED_COUNT:
2216 rfd->record->deleted_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2217 break;
2218 case CAMEL_DB_COLUMN_JUNK_COUNT:
2219 rfd->record->junk_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2220 break;
2221 case CAMEL_DB_COLUMN_VISIBLE_COUNT:
2222 rfd->record->visible_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2223 break;
2224 case CAMEL_DB_COLUMN_JND_COUNT:
2225 rfd->record->jnd_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0;
2226 break;
2227 case CAMEL_DB_COLUMN_BDATA:
2228 rfd->record->bdata = g_strdup (cols[i]);
2229 break;
2230 default:
2231 g_warn_if_reached ();
2232 break;
2233 }
2234 }
2235
2236 return 0;
2237 }
2238
2239 /**
2240 * camel_db_read_folder_info_record:
2241 * @cdb: a #CamelDB
2242 * @folder_name: full name of the folder
2243 * @record: (out caller-allocates): a #CamelFIRecord
2244 * @error: return location for a #GError, or %NULL
2245 *
2246 * reads folder information for the given folder and stores it into the @record.
2247 *
2248 * Returns: 0 on success, -1 on error
2249 *
2250 * Since: 2.24
2251 **/
2252 gint
camel_db_read_folder_info_record(CamelDB * cdb,const gchar * folder_name,CamelFIRecord * record,GError ** error)2253 camel_db_read_folder_info_record (CamelDB *cdb,
2254 const gchar *folder_name,
2255 CamelFIRecord *record,
2256 GError **error)
2257 {
2258 struct ReadFirData rfd;
2259 gchar *query;
2260 gint ret;
2261
2262 rfd.columns_hash = NULL;
2263 rfd.record = record;
2264
2265 query = sqlite3_mprintf ("SELECT * FROM folders WHERE folder_name = %Q", folder_name);
2266 ret = camel_db_select (cdb, query, read_fir_callback, &rfd, error);
2267 sqlite3_free (query);
2268
2269 if (rfd.columns_hash)
2270 g_hash_table_destroy (rfd.columns_hash);
2271
2272 return ret;
2273 }
2274
2275 /**
2276 * camel_db_read_message_info_record_with_uid:
2277 * @cdb: a #CamelDB
2278 * @folder_name: full name of the folder
2279 * @uid: a message info UID to read the record for
2280 * @user_data: user data of the @callback
2281 * @callback: (scope call) (closure user_data): callback to call for the found row
2282 * @error: return location for a #GError, or %NULL
2283 *
2284 * Selects single message info for the given @uid in folder @folder_name and calls
2285 * the @callback for it.
2286 *
2287 * Returns: 0 on success, -1 on error
2288 *
2289 * Since: 2.24
2290 **/
2291 gint
camel_db_read_message_info_record_with_uid(CamelDB * cdb,const gchar * folder_name,const gchar * uid,gpointer user_data,CamelDBSelectCB callback,GError ** error)2292 camel_db_read_message_info_record_with_uid (CamelDB *cdb,
2293 const gchar *folder_name,
2294 const gchar *uid,
2295 gpointer user_data,
2296 CamelDBSelectCB callback,
2297 GError **error)
2298 {
2299 gchar *query;
2300 gint ret;
2301
2302 query = sqlite3_mprintf (
2303 "SELECT uid, flags, size, dsent, dreceived, subject, "
2304 "mail_from, mail_to, mail_cc, mlist, part, labels, "
2305 "usertags, cinfo, bdata, userheaders, preview FROM %Q WHERE uid = %Q",
2306 folder_name, uid);
2307 ret = camel_db_select (cdb, query, callback, user_data, error);
2308 sqlite3_free (query);
2309
2310 return (ret);
2311 }
2312
2313 /**
2314 * camel_db_read_message_info_records:
2315 * @cdb: a #CamelDB
2316 * @folder_name: full name of the folder
2317 * @user_data: user data for the @callback
2318 * @callback: (scope async) (closure user_data): callback to call for each found row
2319 * @error: return location for a #GError, or %NULL
2320 *
2321 * Reads all mesasge info records for the given folder and calls @callback for them.
2322 *
2323 * Returns: 0 on success, -1 on error
2324 *
2325 * Since: 2.24
2326 **/
2327 gint
camel_db_read_message_info_records(CamelDB * cdb,const gchar * folder_name,gpointer user_data,CamelDBSelectCB callback,GError ** error)2328 camel_db_read_message_info_records (CamelDB *cdb,
2329 const gchar *folder_name,
2330 gpointer user_data,
2331 CamelDBSelectCB callback,
2332 GError **error)
2333 {
2334 gchar *query;
2335 gint ret;
2336
2337 query = sqlite3_mprintf (
2338 "SELECT uid, flags, size, dsent, dreceived, subject, "
2339 "mail_from, mail_to, mail_cc, mlist, part, labels, "
2340 "usertags, cinfo, bdata, userheaders, preview FROM %Q ", folder_name);
2341 ret = camel_db_select (cdb, query, callback, user_data, error);
2342 sqlite3_free (query);
2343
2344 return (ret);
2345 }
2346
2347 /**
2348 * camel_db_delete_uid:
2349 * @cdb: a #CamelDB
2350 * @folder_name: full name of the folder
2351 * @uid: a message info UID to delete
2352 * @error: return location for a #GError, or %NULL
2353 *
2354 * Deletes single mesage info in the given folder with
2355 * the given UID.
2356 *
2357 * Returns: 0 on success, -1 on error
2358 *
2359 * Since: 2.24
2360 **/
2361 gint
camel_db_delete_uid(CamelDB * cdb,const gchar * folder_name,const gchar * uid,GError ** error)2362 camel_db_delete_uid (CamelDB *cdb,
2363 const gchar *folder_name,
2364 const gchar *uid,
2365 GError **error)
2366 {
2367 gchar *tab;
2368 gint ret;
2369
2370 camel_db_begin_transaction (cdb, error);
2371
2372 tab = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = %Q", folder_name, uid);
2373 ret = camel_db_add_to_transaction (cdb, tab, error);
2374 sqlite3_free (tab);
2375
2376 ret = camel_db_end_transaction (cdb, error);
2377
2378 camel_db_release_cache_memory ();
2379 return ret;
2380 }
2381
2382 static gint
cdb_delete_ids(CamelDB * cdb,const gchar * folder_name,const GList * uids,const gchar * uid_prefix,const gchar * field,GError ** error)2383 cdb_delete_ids (CamelDB *cdb,
2384 const gchar *folder_name,
2385 const GList *uids,
2386 const gchar *uid_prefix,
2387 const gchar *field,
2388 GError **error)
2389 {
2390 gchar *tmp;
2391 gint ret;
2392 gboolean first = TRUE;
2393 GString *str = g_string_new ("DELETE FROM ");
2394 const GList *iterator;
2395
2396 camel_db_begin_transaction (cdb, error);
2397
2398 tmp = sqlite3_mprintf ("%Q WHERE %s IN (", folder_name, field);
2399 g_string_append_printf (str, "%s ", tmp);
2400 sqlite3_free (tmp);
2401
2402 iterator = uids;
2403
2404 while (iterator) {
2405 gchar *foo = g_strdup_printf ("%s%s", uid_prefix, (gchar *) iterator->data);
2406 tmp = sqlite3_mprintf ("%Q", foo);
2407 g_free (foo);
2408 iterator = iterator->next;
2409
2410 if (first == TRUE) {
2411 g_string_append_printf (str, " %s ", tmp);
2412 first = FALSE;
2413 } else {
2414 g_string_append_printf (str, ", %s ", tmp);
2415 }
2416
2417 sqlite3_free (tmp);
2418 }
2419
2420 g_string_append_c (str, ')');
2421
2422 ret = camel_db_add_to_transaction (cdb, str->str, error);
2423
2424 if (ret == -1)
2425 camel_db_abort_transaction (cdb, NULL);
2426 else
2427 ret = camel_db_end_transaction (cdb, error);
2428
2429 camel_db_release_cache_memory ();
2430
2431 g_string_free (str, TRUE);
2432
2433 return ret;
2434 }
2435
2436 /**
2437 * camel_db_delete_uids:
2438 * @cdb: a #CamelDB
2439 * @folder_name: full name of the folder
2440 * @uids: (element-type utf8) (transfer none): A #GList of uids
2441 * @error: return location for a #GError, or %NULL
2442 *
2443 * Deletes a list of message UIDs as one transaction.
2444 *
2445 * Returns: 0 on success, -1 on error
2446 *
2447 * Since: 2.24
2448 **/
2449 gint
camel_db_delete_uids(CamelDB * cdb,const gchar * folder_name,const GList * uids,GError ** error)2450 camel_db_delete_uids (CamelDB *cdb,
2451 const gchar *folder_name,
2452 const GList *uids,
2453 GError **error)
2454 {
2455 if (!uids || !uids->data)
2456 return 0;
2457
2458 return cdb_delete_ids (cdb, folder_name, uids, "", "uid", error);
2459 }
2460
2461 /**
2462 * camel_db_clear_folder_summary:
2463 * @cdb: a #CamelDB
2464 * @folder_name: full name of the folder
2465 * @error: return location for a #GError, or %NULL
2466 *
2467 * Deletes the given folder from the 'folders' table and empties
2468 * its message info table.
2469 *
2470 * Returns: 0 on success, -1 on error
2471 *
2472 * Since: 2.24
2473 **/
2474 gint
camel_db_clear_folder_summary(CamelDB * cdb,const gchar * folder_name,GError ** error)2475 camel_db_clear_folder_summary (CamelDB *cdb,
2476 const gchar *folder_name,
2477 GError **error)
2478 {
2479 gint ret;
2480 gchar *folders_del;
2481 gchar *msginfo_del;
2482
2483 folders_del = sqlite3_mprintf ("DELETE FROM folders WHERE folder_name = %Q", folder_name);
2484 msginfo_del = sqlite3_mprintf ("DELETE FROM %Q ", folder_name);
2485
2486 camel_db_begin_transaction (cdb, error);
2487
2488 camel_db_add_to_transaction (cdb, msginfo_del, error);
2489 camel_db_add_to_transaction (cdb, folders_del, error);
2490
2491 ret = camel_db_end_transaction (cdb, error);
2492
2493 sqlite3_free (folders_del);
2494 sqlite3_free (msginfo_del);
2495
2496 return ret;
2497 }
2498
2499 /**
2500 * camel_db_delete_folder:
2501 * @cdb: a #CamelDB
2502 * @folder_name: full name of the folder
2503 * @error: return location for a #GError, or %NULL
2504 *
2505 * Deletes the given folder from the 'folders' table and also drops
2506 * its message info table.
2507 *
2508 * Returns: 0 on success, -1 on error
2509 *
2510 * Since: 2.24
2511 **/
2512 gint
camel_db_delete_folder(CamelDB * cdb,const gchar * folder_name,GError ** error)2513 camel_db_delete_folder (CamelDB *cdb,
2514 const gchar *folder_name,
2515 GError **error)
2516 {
2517 gint ret;
2518 gchar *del;
2519
2520 camel_db_begin_transaction (cdb, error);
2521
2522 del = sqlite3_mprintf ("DELETE FROM folders WHERE folder_name = %Q", folder_name);
2523 ret = camel_db_add_to_transaction (cdb, del, error);
2524 sqlite3_free (del);
2525
2526 del = sqlite3_mprintf ("DROP TABLE %Q ", folder_name);
2527 ret = camel_db_add_to_transaction (cdb, del, error);
2528 sqlite3_free (del);
2529
2530 ret = camel_db_end_transaction (cdb, error);
2531
2532 camel_db_release_cache_memory ();
2533 return ret;
2534 }
2535
2536 /**
2537 * camel_db_rename_folder:
2538 * @cdb: a #CamelDB
2539 * @old_folder_name: full name of the existing folder
2540 * @new_folder_name: full name of the folder to rename it to
2541 * @error: return location for a #GError, or %NULL
2542 *
2543 * Renames tables for the @old_folder_name to be used with @new_folder_name.
2544 *
2545 * Returns: 0 on success, -1 on error
2546 *
2547 * Since: 2.24
2548 **/
2549 gint
camel_db_rename_folder(CamelDB * cdb,const gchar * old_folder_name,const gchar * new_folder_name,GError ** error)2550 camel_db_rename_folder (CamelDB *cdb,
2551 const gchar *old_folder_name,
2552 const gchar *new_folder_name,
2553 GError **error)
2554 {
2555 gint ret;
2556 gchar *cmd;
2557
2558 camel_db_begin_transaction (cdb, error);
2559
2560 cmd = sqlite3_mprintf ("ALTER TABLE %Q RENAME TO %Q", old_folder_name, new_folder_name);
2561 ret = camel_db_add_to_transaction (cdb, cmd, error);
2562 sqlite3_free (cmd);
2563
2564 cmd = sqlite3_mprintf ("ALTER TABLE '%q_version' RENAME TO '%q_version'", old_folder_name, new_folder_name);
2565 ret = camel_db_add_to_transaction (cdb, cmd, error);
2566 sqlite3_free (cmd);
2567
2568 cmd = sqlite3_mprintf ("UPDATE %Q SET modified=strftime(\"%%s\", 'now'), created=strftime(\"%%s\", 'now')", new_folder_name);
2569 ret = camel_db_add_to_transaction (cdb, cmd, error);
2570 sqlite3_free (cmd);
2571
2572 cmd = sqlite3_mprintf ("UPDATE folders SET folder_name = %Q WHERE folder_name = %Q", new_folder_name, old_folder_name);
2573 ret = camel_db_add_to_transaction (cdb, cmd, error);
2574 sqlite3_free (cmd);
2575
2576 ret = camel_db_end_transaction (cdb, error);
2577
2578 camel_db_release_cache_memory ();
2579 return ret;
2580 }
2581
2582 /**
2583 * camel_db_camel_mir_free:
2584 * @record: (nullable): a #CamelMIRecord
2585 *
2586 * Frees the @record and all of its associated data.
2587 *
2588 * Since: 2.24
2589 **/
2590 void
camel_db_camel_mir_free(CamelMIRecord * record)2591 camel_db_camel_mir_free (CamelMIRecord *record)
2592 {
2593 if (record) {
2594 camel_pstring_free (record->uid);
2595 camel_pstring_free (record->subject);
2596 camel_pstring_free (record->from);
2597 camel_pstring_free (record->to);
2598 camel_pstring_free (record->cc);
2599 camel_pstring_free (record->mlist);
2600 g_free (record->followup_flag);
2601 g_free (record->followup_completed_on);
2602 g_free (record->followup_due_by);
2603 g_free (record->part);
2604 g_free (record->labels);
2605 g_free (record->usertags);
2606 g_free (record->cinfo);
2607 g_free (record->bdata);
2608 g_free (record->userheaders);
2609 g_free (record->preview);
2610
2611 g_free (record);
2612 }
2613 }
2614
2615 /**
2616 * camel_db_sqlize_string:
2617 * @string: a string to "sqlize"
2618 *
2619 * Converts the @string to be usable in the SQLite statements.
2620 *
2621 * Returns: (transfer full): A newly allocated sqlized @string. The returned
2622 * value should be freed with camel_db_sqlize_string(), when no longer needed.
2623 *
2624 * Since: 2.24
2625 **/
2626 gchar *
camel_db_sqlize_string(const gchar * string)2627 camel_db_sqlize_string (const gchar *string)
2628 {
2629 return sqlite3_mprintf ("%Q", string);
2630 }
2631
2632 /**
2633 * camel_db_free_sqlized_string:
2634 * @string: (nullable): a string to free
2635 *
2636 * Frees a string previosuly returned by camel_db_sqlize_string().
2637 *
2638 * Since: 2.24
2639 **/
2640 void
camel_db_free_sqlized_string(gchar * string)2641 camel_db_free_sqlized_string (gchar *string)
2642 {
2643 if (string)
2644 sqlite3_free (string);
2645 }
2646
2647 /*
2648 "( uid TEXT PRIMARY KEY ,
2649 flags INTEGER ,
2650 msg_type INTEGER ,
2651 replied INTEGER ,
2652 dirty INTEGER ,
2653 size INTEGER ,
2654 dsent NUMERIC ,
2655 dreceived NUMERIC ,
2656 mlist TEXT ,
2657 followup_flag TEXT ,
2658 followup_completed_on TEXT ,
2659 followup_due_by TEXT ," */
2660
2661 /**
2662 * camel_db_get_column_name:
2663 * @raw_name: raw name to find the column name for
2664 *
2665 * Returns: (nullable): A corresponding column name in the message info table
2666 * for the @raw_name, or %NULL, when there is no corresponding column in the summary.
2667 *
2668 * Since: 2.24
2669 **/
2670 gchar *
camel_db_get_column_name(const gchar * raw_name)2671 camel_db_get_column_name (const gchar *raw_name)
2672 {
2673 if (!g_ascii_strcasecmp (raw_name, "Subject"))
2674 return g_strdup ("subject");
2675 else if (!g_ascii_strcasecmp (raw_name, "from"))
2676 return g_strdup ("mail_from");
2677 else if (!g_ascii_strcasecmp (raw_name, "Cc"))
2678 return g_strdup ("mail_cc");
2679 else if (!g_ascii_strcasecmp (raw_name, "To"))
2680 return g_strdup ("mail_to");
2681 else if (!g_ascii_strcasecmp (raw_name, "Flagged"))
2682 return g_strdup ("important");
2683 else if (!g_ascii_strcasecmp (raw_name, "deleted"))
2684 return g_strdup ("deleted");
2685 else if (!g_ascii_strcasecmp (raw_name, "junk"))
2686 return g_strdup ("junk");
2687 else if (!g_ascii_strcasecmp (raw_name, "Answered"))
2688 return g_strdup ("replied");
2689 else if (!g_ascii_strcasecmp (raw_name, "Seen"))
2690 return g_strdup ("read");
2691 else if (!g_ascii_strcasecmp (raw_name, "user-tag"))
2692 return g_strdup ("usertags");
2693 else if (!g_ascii_strcasecmp (raw_name, "user-flag"))
2694 return g_strdup ("labels");
2695 else if (!g_ascii_strcasecmp (raw_name, "Attachments"))
2696 return g_strdup ("attachment");
2697 else if (!g_ascii_strcasecmp (raw_name, "x-camel-mlist"))
2698 return g_strdup ("mlist");
2699
2700 /* indicate the header name is not part of the summary */
2701 return NULL;
2702 }
2703
2704 /**
2705 * camel_db_start_in_memory_transactions:
2706 * @cdb: a #CamelDB
2707 * @error: return location for a #GError, or %NULL
2708 *
2709 * Creates an in-memory table for a batch transactions. Use camel_db_flush_in_memory_transactions()
2710 * to commit the changes and free the in-memory table.
2711 *
2712 * Returns: 0 on success, -1 on error
2713 *
2714 * Since: 2.26
2715 **/
2716 gint
camel_db_start_in_memory_transactions(CamelDB * cdb,GError ** error)2717 camel_db_start_in_memory_transactions (CamelDB *cdb,
2718 GError **error)
2719 {
2720 gint ret;
2721 gchar *cmd = sqlite3_mprintf ("ATTACH DATABASE ':memory:' AS %s", CAMEL_DB_IN_MEMORY_DB);
2722
2723 ret = camel_db_command (cdb, cmd, error);
2724 sqlite3_free (cmd);
2725
2726 cmd = sqlite3_mprintf (
2727 "CREATE TEMPORARY TABLE %Q ( "
2728 "uid TEXT PRIMARY KEY , "
2729 "flags INTEGER , "
2730 "msg_type INTEGER , "
2731 "read INTEGER , "
2732 "deleted INTEGER , "
2733 "replied INTEGER , "
2734 "important INTEGER , "
2735 "junk INTEGER , "
2736 "attachment INTEGER , "
2737 "dirty INTEGER , "
2738 "size INTEGER , "
2739 "dsent NUMERIC , "
2740 "dreceived NUMERIC , "
2741 "subject TEXT , "
2742 "mail_from TEXT , "
2743 "mail_to TEXT , "
2744 "mail_cc TEXT , "
2745 "mlist TEXT , "
2746 "followup_flag TEXT , "
2747 "followup_completed_on TEXT , "
2748 "followup_due_by TEXT , "
2749 "part TEXT , "
2750 "labels TEXT , "
2751 "usertags TEXT , "
2752 "cinfo TEXT , "
2753 "bdata TEXT, "
2754 "userheaders TEXT, "
2755 "preview TEXT)",
2756 CAMEL_DB_IN_MEMORY_TABLE);
2757 ret = camel_db_command (cdb, cmd, error);
2758 if (ret != 0 )
2759 abort ();
2760 sqlite3_free (cmd);
2761
2762 return ret;
2763 }
2764
2765 /**
2766 * camel_db_flush_in_memory_transactions:
2767 * @cdb: a #CamelDB
2768 * @folder_name: full name of the folder
2769 * @error: return location for a #GError, or %NULL
2770 *
2771 * A pair function for camel_db_start_in_memory_transactions(),
2772 * to commit the changes to @folder_name and free the in-memory table.
2773 *
2774 * Returns: 0 on success, -1 on error
2775 *
2776 * Since: 2.26
2777 **/
2778 gint
camel_db_flush_in_memory_transactions(CamelDB * cdb,const gchar * folder_name,GError ** error)2779 camel_db_flush_in_memory_transactions (CamelDB *cdb,
2780 const gchar *folder_name,
2781 GError **error)
2782 {
2783 gint ret;
2784 gchar *cmd = sqlite3_mprintf ("INSERT INTO %Q SELECT * FROM %Q", folder_name, CAMEL_DB_IN_MEMORY_TABLE);
2785
2786 ret = camel_db_command (cdb, cmd, error);
2787 sqlite3_free (cmd);
2788
2789 cmd = sqlite3_mprintf ("DROP TABLE %Q", CAMEL_DB_IN_MEMORY_TABLE);
2790 ret = camel_db_command (cdb, cmd, error);
2791 sqlite3_free (cmd);
2792
2793 cmd = sqlite3_mprintf ("DETACH %Q", CAMEL_DB_IN_MEMORY_DB);
2794 ret = camel_db_command (cdb, cmd, error);
2795 sqlite3_free (cmd);
2796
2797 return ret;
2798 }
2799
2800 static struct _known_column_names {
2801 const gchar *name;
2802 CamelDBKnownColumnNames ident;
2803 } known_column_names[] = {
2804 { "attachment", CAMEL_DB_COLUMN_ATTACHMENT },
2805 { "bdata", CAMEL_DB_COLUMN_BDATA },
2806 { "cinfo", CAMEL_DB_COLUMN_CINFO },
2807 { "deleted", CAMEL_DB_COLUMN_DELETED },
2808 { "deleted_count", CAMEL_DB_COLUMN_DELETED_COUNT },
2809 { "dreceived", CAMEL_DB_COLUMN_DRECEIVED },
2810 { "dsent", CAMEL_DB_COLUMN_DSENT },
2811 { "flags", CAMEL_DB_COLUMN_FLAGS },
2812 { "folder_name", CAMEL_DB_COLUMN_FOLDER_NAME },
2813 { "followup_completed_on", CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON },
2814 { "followup_due_by", CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY },
2815 { "followup_flag", CAMEL_DB_COLUMN_FOLLOWUP_FLAG },
2816 { "important", CAMEL_DB_COLUMN_IMPORTANT },
2817 { "jnd_count", CAMEL_DB_COLUMN_JND_COUNT },
2818 { "junk", CAMEL_DB_COLUMN_JUNK },
2819 { "junk_count", CAMEL_DB_COLUMN_JUNK_COUNT },
2820 { "labels", CAMEL_DB_COLUMN_LABELS },
2821 { "mail_cc", CAMEL_DB_COLUMN_MAIL_CC },
2822 { "mail_from", CAMEL_DB_COLUMN_MAIL_FROM },
2823 { "mail_to", CAMEL_DB_COLUMN_MAIL_TO },
2824 { "mlist", CAMEL_DB_COLUMN_MLIST },
2825 { "nextuid", CAMEL_DB_COLUMN_NEXTUID },
2826 { "part", CAMEL_DB_COLUMN_PART },
2827 { "preview", CAMEL_DB_COLUMN_PREVIEW },
2828 { "read", CAMEL_DB_COLUMN_READ },
2829 { "replied", CAMEL_DB_COLUMN_REPLIED },
2830 { "saved_count", CAMEL_DB_COLUMN_SAVED_COUNT },
2831 { "size", CAMEL_DB_COLUMN_SIZE },
2832 { "subject", CAMEL_DB_COLUMN_SUBJECT },
2833 { "time", CAMEL_DB_COLUMN_TIME },
2834 { "uid", CAMEL_DB_COLUMN_UID },
2835 { "unread_count", CAMEL_DB_COLUMN_UNREAD_COUNT },
2836 { "userheaders", CAMEL_DB_COLUMN_USERHEADERS },
2837 { "usertags", CAMEL_DB_COLUMN_USERTAGS },
2838 { "version", CAMEL_DB_COLUMN_VERSION },
2839 { "visible_count", CAMEL_DB_COLUMN_VISIBLE_COUNT },
2840 { "vuid", CAMEL_DB_COLUMN_VUID }
2841 };
2842
2843 /**
2844 * camel_db_get_column_ident:
2845 * @hash: (inout): a #GHashTable
2846 * @index: an index to start with, between 0 and @ncols
2847 * @ncols: number of @col_names
2848 * @col_names: (array length=ncols): column names to traverse
2849 *
2850 * Traverses column name from index @index into an enum
2851 * #CamelDBKnownColumnNames value. The @col_names contains @ncols columns.
2852 * First time this is called is created the @hash from col_names indexes into
2853 * the enum, and this is reused for every other call. The function expects
2854 * that column names are returned always in the same order. When all rows
2855 * are read the @hash table can be freed with g_hash_table_destroy().
2856 *
2857 * Since: 3.4
2858 **/
2859 CamelDBKnownColumnNames
camel_db_get_column_ident(GHashTable ** hash,gint index,gint ncols,gchar ** col_names)2860 camel_db_get_column_ident (GHashTable **hash,
2861 gint index,
2862 gint ncols,
2863 gchar **col_names)
2864 {
2865 gpointer value = NULL;
2866
2867 g_return_val_if_fail (hash != NULL, CAMEL_DB_COLUMN_UNKNOWN);
2868 g_return_val_if_fail (col_names != NULL, CAMEL_DB_COLUMN_UNKNOWN);
2869 g_return_val_if_fail (ncols > 0, CAMEL_DB_COLUMN_UNKNOWN);
2870 g_return_val_if_fail (index >= 0, CAMEL_DB_COLUMN_UNKNOWN);
2871 g_return_val_if_fail (index < ncols, CAMEL_DB_COLUMN_UNKNOWN);
2872
2873 if (!*hash) {
2874 gint ii, jj, from, max = G_N_ELEMENTS (known_column_names);
2875
2876 *hash = g_hash_table_new (g_direct_hash, g_direct_equal);
2877
2878 for (ii = 0, jj = 0; ii < ncols; ii++) {
2879 const gchar *name = col_names[ii];
2880 gboolean first = TRUE;
2881
2882 if (!name)
2883 continue;
2884
2885 for (from = jj; first || jj != from; jj = (jj + 1) % max, first = FALSE) {
2886 if (g_str_equal (name, known_column_names[jj].name)) {
2887 g_hash_table_insert (*hash, GINT_TO_POINTER (ii), GINT_TO_POINTER (known_column_names[jj].ident));
2888 break;
2889 }
2890 }
2891
2892 if (from == jj && !first)
2893 g_warning ("%s: missing column name '%s' in a list of known columns", G_STRFUNC, name);
2894 }
2895 }
2896
2897 g_return_val_if_fail (g_hash_table_lookup_extended (*hash, GINT_TO_POINTER (index), NULL, &value), CAMEL_DB_COLUMN_UNKNOWN);
2898
2899 return GPOINTER_TO_INT (value);
2900 }
2901
2902 static gint
get_number_cb(gpointer data,gint argc,gchar ** argv,gchar ** azColName)2903 get_number_cb (gpointer data,
2904 gint argc,
2905 gchar **argv,
2906 gchar **azColName)
2907 {
2908 guint64 *pui64 = data;
2909
2910 if (argc == 1) {
2911 *pui64 = argv[0] ? g_ascii_strtoull (argv[0], NULL, 10) : 0;
2912 } else {
2913 *pui64 = 0;
2914 }
2915
2916 return 0;
2917 }
2918
2919 /**
2920 * camel_db_maybe_run_maintenance:
2921 * @cdb: a #CamelDB
2922 * @error: a #GError or %NULL
2923 *
2924 * Runs a @cdb maintenance, which includes vacuum, if necessary.
2925 *
2926 * Returns: Whether succeeded.
2927 *
2928 * Since: 3.16
2929 **/
2930 gboolean
camel_db_maybe_run_maintenance(CamelDB * cdb,GError ** error)2931 camel_db_maybe_run_maintenance (CamelDB *cdb,
2932 GError **error)
2933 {
2934 GError *local_error = NULL;
2935 guint64 page_count = 0, page_size = 0, freelist_count = 0;
2936 gboolean success = FALSE;
2937
2938 g_return_val_if_fail (CAMEL_IS_DB (cdb), FALSE);
2939
2940 if (cdb->priv->is_foldersdb) {
2941 /* Drop 'Deletes' table, leftover from the previous versions. */
2942 if (camel_db_command (cdb, "DROP TABLE IF EXISTS 'Deletes'", error) == -1)
2943 return FALSE;
2944 }
2945
2946 cdb_writer_lock (cdb);
2947
2948 if (cdb_sql_exec (cdb->priv->db, "PRAGMA page_count;", get_number_cb, &page_count, NULL, &local_error) == SQLITE_OK &&
2949 cdb_sql_exec (cdb->priv->db, "PRAGMA page_size;", get_number_cb, &page_size, NULL, &local_error) == SQLITE_OK &&
2950 cdb_sql_exec (cdb->priv->db, "PRAGMA freelist_count;", get_number_cb, &freelist_count, NULL, &local_error) == SQLITE_OK) {
2951 /* Vacuum, if there's more than 5% of the free pages, or when free pages use more than 10MB */
2952 success = !page_count || !freelist_count || (freelist_count * page_size < 1024 * 1024 * 10 && freelist_count * 1000 / page_count <= 50) ||
2953 cdb_sql_exec (cdb->priv->db, "vacuum;", NULL, NULL, NULL, &local_error) == SQLITE_OK;
2954 }
2955
2956 cdb_writer_unlock (cdb);
2957
2958 if (local_error) {
2959 g_propagate_error (error, local_error);
2960 success = FALSE;
2961 }
2962
2963 return success;
2964 }
2965
2966 /**
2967 * camel_db_release_cache_memory:
2968 *
2969 * Instructs sqlite to release its memory, if possible. This can be avoided
2970 * when CAMEL_SQLITE_FREE_CACHE environment variable is set.
2971 *
2972 * Since: 3.24
2973 **/
2974 void
camel_db_release_cache_memory(void)2975 camel_db_release_cache_memory (void)
2976 {
2977 static gint env_set = -1;
2978
2979 if (env_set == -1)
2980 env_set = g_getenv("CAMEL_SQLITE_FREE_CACHE") ? 1 : 0;
2981
2982 if (!env_set)
2983 sqlite3_release_memory (CAMEL_DB_FREE_CACHE_SIZE);
2984 }
2985