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