/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Sankar P * Srinivasa Ragavan */ #include "camel-db.h" #include "camel-string-utils.h" #include #include #include #include #include #include #include #include #include "camel-debug.h" #include "camel-object.h" /* how long to wait before invoking sync on the file */ #define SYNC_TIMEOUT_SECONDS 5 static sqlite3_vfs *old_vfs = NULL; static GThreadPool *sync_pool = NULL; typedef struct { sqlite3_file parent; sqlite3_file *old_vfs_file; /* pointer to old_vfs' file */ GRecMutex sync_mutex; guint timeout_id; gint flags; /* Do know how many syncs are pending, to not close the file before the last sync is over */ guint pending_syncs; GMutex pending_syncs_lock; GCond pending_syncs_cond; } CamelSqlite3File; static gint call_old_file_Sync (CamelSqlite3File *cFile, gint flags) { g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (cFile != NULL, SQLITE_ERROR); g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); return cFile->old_vfs_file->pMethods->xSync (cFile->old_vfs_file, flags); } typedef struct { GCond cond; GMutex mutex; gboolean is_set; } SyncDone; struct SyncRequestData { CamelSqlite3File *cFile; guint32 flags; SyncDone *done; /* not NULL when waiting for a finish; will be freed by the caller */ }; static void sync_request_thread_cb (gpointer task_data, gpointer null_data) { struct SyncRequestData *sync_data = task_data; SyncDone *done; g_return_if_fail (sync_data != NULL); g_return_if_fail (sync_data->cFile != NULL); call_old_file_Sync (sync_data->cFile, sync_data->flags); g_mutex_lock (&sync_data->cFile->pending_syncs_lock); g_warn_if_fail (sync_data->cFile->pending_syncs > 0); sync_data->cFile->pending_syncs--; if (!sync_data->cFile->pending_syncs) g_cond_signal (&sync_data->cFile->pending_syncs_cond); g_mutex_unlock (&sync_data->cFile->pending_syncs_lock); done = sync_data->done; g_free (sync_data); if (done != NULL) { g_mutex_lock (&done->mutex); done->is_set = TRUE; g_cond_broadcast (&done->cond); g_mutex_unlock (&done->mutex); } } static void sync_push_request (CamelSqlite3File *cFile, gboolean wait_for_finish) { struct SyncRequestData *data; SyncDone *done = NULL; GError *error = NULL; g_return_if_fail (cFile != NULL); g_return_if_fail (sync_pool != NULL); g_rec_mutex_lock (&cFile->sync_mutex); if (!cFile->flags) { /* nothing to sync, might be when xClose is called * without any pending xSync request */ g_rec_mutex_unlock (&cFile->sync_mutex); return; } if (wait_for_finish) { done = g_slice_new (SyncDone); g_cond_init (&done->cond); g_mutex_init (&done->mutex); done->is_set = FALSE; } data = g_new0 (struct SyncRequestData, 1); data->cFile = cFile; data->flags = cFile->flags; data->done = done; cFile->flags = 0; g_mutex_lock (&cFile->pending_syncs_lock); cFile->pending_syncs++; g_mutex_unlock (&cFile->pending_syncs_lock); g_rec_mutex_unlock (&cFile->sync_mutex); g_thread_pool_push (sync_pool, data, &error); if (error) { g_warning ("%s: Failed to push to thread pool: %s\n", G_STRFUNC, error->message); g_error_free (error); if (done != NULL) { g_cond_clear (&done->cond); g_mutex_clear (&done->mutex); g_slice_free (SyncDone, done); } return; } if (done != NULL) { g_mutex_lock (&done->mutex); while (!done->is_set) g_cond_wait (&done->cond, &done->mutex); g_mutex_unlock (&done->mutex); g_cond_clear (&done->cond); g_mutex_clear (&done->mutex); g_slice_free (SyncDone, done); } } static gboolean sync_push_request_timeout (CamelSqlite3File *cFile) { g_rec_mutex_lock (&cFile->sync_mutex); if (cFile->timeout_id != 0) { sync_push_request (cFile, FALSE); cFile->timeout_id = 0; } g_rec_mutex_unlock (&cFile->sync_mutex); return FALSE; } #define def_subclassed(_nm, _params, _call) \ static gint \ camel_sqlite3_file_ ## _nm _params \ { \ CamelSqlite3File *cFile; \ \ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); \ g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); \ \ cFile = (CamelSqlite3File *) pFile; \ g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); \ return cFile->old_vfs_file->pMethods->_nm _call; \ } #define def_subclassed_void(_nm, _params, _call) \ static void \ camel_sqlite3_file_ ## _nm _params \ { \ CamelSqlite3File *cFile; \ \ g_return_if_fail (old_vfs != NULL); \ g_return_if_fail (pFile != NULL); \ \ cFile = (CamelSqlite3File *) pFile; \ g_return_if_fail (cFile->old_vfs_file->pMethods != NULL); \ cFile->old_vfs_file->pMethods->_nm _call; \ } def_subclassed (xRead, (sqlite3_file *pFile, gpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst)) def_subclassed (xWrite, (sqlite3_file *pFile, gconstpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst)) def_subclassed (xTruncate, (sqlite3_file *pFile, sqlite3_int64 size), (cFile->old_vfs_file, size)) def_subclassed (xFileSize, (sqlite3_file *pFile, sqlite3_int64 *pSize), (cFile->old_vfs_file, pSize)) def_subclassed (xLock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType)) def_subclassed (xUnlock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType)) def_subclassed (xFileControl, (sqlite3_file *pFile, gint op, gpointer pArg), (cFile->old_vfs_file, op, pArg)) def_subclassed (xSectorSize, (sqlite3_file *pFile), (cFile->old_vfs_file)) def_subclassed (xDeviceCharacteristics, (sqlite3_file *pFile), (cFile->old_vfs_file)) def_subclassed (xShmMap, (sqlite3_file *pFile, gint iPg, gint pgsz, gint n, void volatile **arr), (cFile->old_vfs_file, iPg, pgsz, n, arr)) def_subclassed (xShmLock, (sqlite3_file *pFile, gint offset, gint n, gint flags), (cFile->old_vfs_file, offset, n, flags)) def_subclassed_void (xShmBarrier, (sqlite3_file *pFile), (cFile->old_vfs_file)) def_subclassed (xShmUnmap, (sqlite3_file *pFile, gint deleteFlag), (cFile->old_vfs_file, deleteFlag)) def_subclassed (xFetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, int iAmt, void **pp), (cFile->old_vfs_file, iOfst, iAmt, pp)) def_subclassed (xUnfetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, void *p), (cFile->old_vfs_file, iOfst, p)) #undef def_subclassed static gint camel_sqlite3_file_xCheckReservedLock (sqlite3_file *pFile, gint *pResOut) { CamelSqlite3File *cFile; g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); cFile = (CamelSqlite3File *) pFile; g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); /* check version in runtime */ if (sqlite3_libversion_number () < 3006000) return ((gint (*)(sqlite3_file *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file); else return ((gint (*)(sqlite3_file *, gint *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file, pResOut); } static gint camel_sqlite3_file_xClose (sqlite3_file *pFile) { CamelSqlite3File *cFile; gint res; g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); cFile = (CamelSqlite3File *) pFile; g_rec_mutex_lock (&cFile->sync_mutex); /* Cancel any pending sync requests. */ if (cFile->timeout_id > 0) { g_source_remove (cFile->timeout_id); cFile->timeout_id = 0; } g_rec_mutex_unlock (&cFile->sync_mutex); /* Make the last sync. */ sync_push_request (cFile, TRUE); g_mutex_lock (&cFile->pending_syncs_lock); while (cFile->pending_syncs > 0) { g_cond_wait (&cFile->pending_syncs_cond, &cFile->pending_syncs_lock); } g_mutex_unlock (&cFile->pending_syncs_lock); if (cFile->old_vfs_file->pMethods) res = cFile->old_vfs_file->pMethods->xClose (cFile->old_vfs_file); else res = SQLITE_OK; g_free (cFile->old_vfs_file); cFile->old_vfs_file = NULL; g_rec_mutex_clear (&cFile->sync_mutex); g_mutex_clear (&cFile->pending_syncs_lock); g_cond_clear (&cFile->pending_syncs_cond); return res; } static gint camel_sqlite3_file_xSync (sqlite3_file *pFile, gint flags) { CamelSqlite3File *cFile; g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); cFile = (CamelSqlite3File *) pFile; g_rec_mutex_lock (&cFile->sync_mutex); /* If a sync request is already scheduled, accumulate flags. */ cFile->flags |= flags; /* Cancel any pending sync requests. */ if (cFile->timeout_id > 0) g_source_remove (cFile->timeout_id); /* Wait SYNC_TIMEOUT_SECONDS before we actually sync. */ cFile->timeout_id = g_timeout_add_seconds ( SYNC_TIMEOUT_SECONDS, (GSourceFunc) sync_push_request_timeout, cFile); g_source_set_name_by_id ( cFile->timeout_id, "[camel] sync_push_request_timeout"); g_rec_mutex_unlock (&cFile->sync_mutex); return SQLITE_OK; } static gint camel_sqlite3_vfs_xOpen (sqlite3_vfs *pVfs, const gchar *zPath, sqlite3_file *pFile, gint flags, gint *pOutFlags) { static GRecMutex only_once_lock; static sqlite3_io_methods io_methods = {0}; CamelSqlite3File *cFile; gint res; g_return_val_if_fail (old_vfs != NULL, -1); g_return_val_if_fail (pFile != NULL, -1); cFile = (CamelSqlite3File *) pFile; cFile->old_vfs_file = g_malloc0 (old_vfs->szOsFile); res = old_vfs->xOpen (old_vfs, zPath, cFile->old_vfs_file, flags, pOutFlags); if (res != SQLITE_OK) { g_free (cFile->old_vfs_file); return res; } g_rec_mutex_init (&cFile->sync_mutex); g_mutex_init (&cFile->pending_syncs_lock); g_cond_init (&cFile->pending_syncs_cond); cFile->pending_syncs = 0; g_rec_mutex_lock (&only_once_lock); if (!sync_pool) sync_pool = g_thread_pool_new (sync_request_thread_cb, NULL, 2, FALSE, NULL); /* cFile->old_vfs_file->pMethods is NULL when open failed for some reason, * thus do not initialize our structure when do not know the version */ if (io_methods.xClose == NULL && cFile->old_vfs_file->pMethods) { /* initialize our subclass function only once */ io_methods.iVersion = cFile->old_vfs_file->pMethods->iVersion; /* check version in compile time */ #if SQLITE_VERSION_NUMBER < 3006000 io_methods.xCheckReservedLock = (gint (*)(sqlite3_file *)) camel_sqlite3_file_xCheckReservedLock; #else io_methods.xCheckReservedLock = camel_sqlite3_file_xCheckReservedLock; #endif #define use_subclassed(x) io_methods.x = camel_sqlite3_file_ ## x use_subclassed (xClose); use_subclassed (xRead); use_subclassed (xWrite); use_subclassed (xTruncate); use_subclassed (xSync); use_subclassed (xFileSize); use_subclassed (xLock); use_subclassed (xUnlock); use_subclassed (xFileControl); use_subclassed (xSectorSize); use_subclassed (xDeviceCharacteristics); if (io_methods.iVersion > 1) { use_subclassed (xShmMap); use_subclassed (xShmLock); use_subclassed (xShmBarrier); use_subclassed (xShmUnmap); } if (io_methods.iVersion > 2) { use_subclassed (xFetch); use_subclassed (xUnfetch); } if (io_methods.iVersion > 3) { g_warning ("%s: Unchecked IOMethods version %d, downgrading to version 3", G_STRFUNC, io_methods.iVersion); io_methods.iVersion = 3; } #undef use_subclassed } g_rec_mutex_unlock (&only_once_lock); cFile->parent.pMethods = &io_methods; return res; } static gpointer init_sqlite_vfs (void) { static sqlite3_vfs vfs = { 0 }; old_vfs = sqlite3_vfs_find (NULL); g_return_val_if_fail (old_vfs != NULL, NULL); memcpy (&vfs, old_vfs, sizeof (sqlite3_vfs)); vfs.szOsFile = sizeof (CamelSqlite3File); vfs.zName = "camel_sqlite3_vfs"; vfs.xOpen = camel_sqlite3_vfs_xOpen; sqlite3_vfs_register (&vfs, 1); return NULL; } #define d(x) if (camel_debug("sqlite")) x #define START(stmt) \ if (camel_debug ("dbtime")) { \ g_print ( \ "\n===========\n" \ "DB SQL operation [%s] started\n", stmt); \ if (!cdb->priv->timer) { \ cdb->priv->timer = g_timer_new (); \ } else { \ g_timer_reset (cdb->priv->timer); \ } \ } #define END \ if (camel_debug ("dbtime")) { \ g_timer_stop (cdb->priv->timer); \ g_print ( \ "DB Operation ended. " \ "Time Taken : %f\n###########\n", \ g_timer_elapsed (cdb->priv->timer, NULL)); \ } #define STARTTS(stmt) \ if (camel_debug ("dbtimets")) { \ g_print ( \ "\n===========\n" \ "DB SQL operation [%s] started\n", stmt); \ if (!cdb->priv->timer) { \ cdb->priv->timer = g_timer_new (); \ } else { \ g_timer_reset (cdb->priv->timer); \ } \ } #define ENDTS \ if (camel_debug ("dbtimets")) { \ g_timer_stop (cdb->priv->timer); \ g_print ( \ "DB Operation ended. " \ "Time Taken : %f\n###########\n", \ g_timer_elapsed (cdb->priv->timer, NULL)); \ } struct _CamelDBPrivate { GTimer *timer; GRWLock rwlock; gchar *file_name; GMutex transaction_lock; GThread *transaction_thread; guint32 transaction_level; }; /** * cdb_sql_exec * @db: * @stmt: * @error: * * Callers should hold the lock **/ 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) { gchar *errmsg = NULL; gint ret = -1, retries = 0; d (g_print ("Camel SQL Exec:\n%s\n", stmt)); ret = sqlite3_exec (db, stmt, callback, data, &errmsg); while (ret == SQLITE_BUSY || ret == SQLITE_LOCKED || ret == -1) { /* try for ~15 seconds, then give up */ if (retries > 150) break; retries++; if (errmsg) { sqlite3_free (errmsg); errmsg = NULL; } g_thread_yield (); g_usleep (100 * 1000); /* Sleep for 100 ms */ ret = sqlite3_exec (db, stmt, NULL, NULL, &errmsg); } if (out_sqlite_error_code) *out_sqlite_error_code = ret; if (ret != SQLITE_OK) { d (g_print ("Error in SQL EXEC statement: %s [%s].\n", stmt, errmsg)); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s", errmsg); sqlite3_free (errmsg); errmsg = NULL; return -1; } if (errmsg) { sqlite3_free (errmsg); errmsg = NULL; } return 0; } /* checks whether string 'where' contains whole word 'what', * case insensitively (ascii, not utf8, same as 'LIKE' in SQLite3) */ static void cdb_match_func (sqlite3_context *ctx, gint nArgs, sqlite3_value **values) { gboolean matches = FALSE; const gchar *what, *where; g_return_if_fail (ctx != NULL); g_return_if_fail (nArgs == 2); g_return_if_fail (values != NULL); what = (const gchar *) sqlite3_value_text (values[0]); where = (const gchar *) sqlite3_value_text (values[1]); if (what && where && !*what) { matches = TRUE; } else if (what && where) { gboolean word = TRUE; gint i, j; for (i = 0, j = 0; where[i] && !matches; i++) { gchar c = where[i]; if (c == ' ') { word = TRUE; j = 0; } else if (word && tolower (c) == tolower (what[j])) { j++; if (what[j] == 0 && (where[i + 1] == 0 || isspace (where[i + 1]))) matches = TRUE; } else { word = FALSE; } } } sqlite3_result_int (ctx, matches ? 1 : 0); } static void cdb_writer_lock (CamelDB *cdb) { g_return_if_fail (cdb != NULL); g_mutex_lock (&cdb->priv->transaction_lock); if (cdb->priv->transaction_thread != g_thread_self ()) { g_mutex_unlock (&cdb->priv->transaction_lock); g_rw_lock_writer_lock (&cdb->priv->rwlock); g_mutex_lock (&cdb->priv->transaction_lock); g_warn_if_fail (cdb->priv->transaction_thread == NULL); g_warn_if_fail (cdb->priv->transaction_level == 0); cdb->priv->transaction_thread = g_thread_self (); } cdb->priv->transaction_level++; g_mutex_unlock (&cdb->priv->transaction_lock); } static void cdb_writer_unlock (CamelDB *cdb) { g_return_if_fail (cdb != NULL); g_mutex_lock (&cdb->priv->transaction_lock); g_warn_if_fail (cdb->priv->transaction_thread == g_thread_self ()); g_warn_if_fail (cdb->priv->transaction_level > 0); cdb->priv->transaction_level--; if (!cdb->priv->transaction_level) { cdb->priv->transaction_thread = NULL; g_mutex_unlock (&cdb->priv->transaction_lock); g_rw_lock_writer_unlock (&cdb->priv->rwlock); } else { g_mutex_unlock (&cdb->priv->transaction_lock); } } static void cdb_reader_lock (CamelDB *cdb) { g_return_if_fail (cdb != NULL); g_mutex_lock (&cdb->priv->transaction_lock); if (cdb->priv->transaction_thread == g_thread_self ()) { /* already holding write lock */ g_mutex_unlock (&cdb->priv->transaction_lock); } else { g_mutex_unlock (&cdb->priv->transaction_lock); g_rw_lock_reader_lock (&cdb->priv->rwlock); } } static void cdb_reader_unlock (CamelDB *cdb) { g_return_if_fail (cdb != NULL); g_mutex_lock (&cdb->priv->transaction_lock); if (cdb->priv->transaction_thread == g_thread_self ()) { /* already holding write lock */ g_mutex_unlock (&cdb->priv->transaction_lock); } else { g_mutex_unlock (&cdb->priv->transaction_lock); g_rw_lock_reader_unlock (&cdb->priv->rwlock); } } static gboolean cdb_is_in_transaction (CamelDB *cdb) { gboolean res; g_return_val_if_fail (cdb != NULL, FALSE); g_mutex_lock (&cdb->priv->transaction_lock); res = cdb->priv->transaction_level > 0 && cdb->priv->transaction_thread == g_thread_self (); g_mutex_unlock (&cdb->priv->transaction_lock); return res; } static gchar * cdb_construct_transaction_stmt (CamelDB *cdb, const gchar *prefix) { gchar *name; g_return_val_if_fail (cdb != NULL, NULL); g_mutex_lock (&cdb->priv->transaction_lock); g_warn_if_fail (cdb->priv->transaction_thread == g_thread_self ()); name = g_strdup_printf ("%sTN%d", prefix ? prefix : "", cdb->priv->transaction_level); g_mutex_unlock (&cdb->priv->transaction_lock); return name; } static gint camel_db_command_internal (CamelDB *cdb, const gchar *stmt, gint *out_sqlite_error_code, GError **error) { gint ret; if (!cdb) return TRUE; cdb_writer_lock (cdb); START (stmt); ret = cdb_sql_exec (cdb->db, stmt, NULL, NULL, out_sqlite_error_code, error); END; cdb_writer_unlock (cdb); return ret; } /** * camel_db_open: * * Since: 2.24 **/ CamelDB * camel_db_open (const gchar *path, GError **error) { static GOnce vfs_once = G_ONCE_INIT; CamelDB *cdb; sqlite3 *db; gint ret, cdb_sqlite_error_code = SQLITE_OK; gboolean reopening = FALSE; GError *local_error = NULL; g_once (&vfs_once, (GThreadFunc) init_sqlite_vfs, NULL); CAMEL_DB_USE_SHARED_CACHE; reopen: ret = sqlite3_open (path, &db); if (ret) { if (!db) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Insufficient memory")); } else { const gchar *errmsg; errmsg = sqlite3_errmsg (db); d (g_print ("Can't open database %s: %s\n", path, errmsg)); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s", errmsg); sqlite3_close (db); } return NULL; } cdb = g_new (CamelDB, 1); cdb->db = db; cdb->priv = g_new0 (CamelDBPrivate, 1); cdb->priv->file_name = g_strdup (path); g_rw_lock_init (&cdb->priv->rwlock); g_mutex_init (&cdb->priv->transaction_lock); cdb->priv->transaction_thread = NULL; cdb->priv->transaction_level = 0; cdb->priv->timer = NULL; d (g_print ("\nDatabase succesfully opened \n")); sqlite3_create_function (db, "MATCH", 2, SQLITE_UTF8, NULL, cdb_match_func, NULL, NULL); /* Which is big / costlier ? A Stack frame or a pointer */ if (g_getenv ("CAMEL_SQLITE_DEFAULT_CACHE_SIZE") != NULL) { gchar *cache = NULL; cache = g_strdup_printf ("PRAGMA cache_size=%s", g_getenv ("CAMEL_SQLITE_DEFAULT_CACHE_SIZE")); camel_db_command_internal (cdb, cache, &cdb_sqlite_error_code, &local_error); g_free (cache); } if (cdb_sqlite_error_code == SQLITE_OK) camel_db_command_internal (cdb, "ATTACH DATABASE ':memory:' AS mem", &cdb_sqlite_error_code, &local_error); if (cdb_sqlite_error_code == SQLITE_OK && g_getenv ("CAMEL_SQLITE_IN_MEMORY") != NULL) { /* Optionally turn off Journaling, this gets over fsync issues, but could be risky */ camel_db_command_internal (cdb, "PRAGMA main.journal_mode = off", &cdb_sqlite_error_code, &local_error); if (cdb_sqlite_error_code == SQLITE_OK) camel_db_command_internal (cdb, "PRAGMA temp_store = memory", &cdb_sqlite_error_code, &local_error); } if (!reopening && ( cdb_sqlite_error_code == SQLITE_CANTOPEN || cdb_sqlite_error_code == SQLITE_CORRUPT || cdb_sqlite_error_code == SQLITE_NOTADB)) { gchar *second_filename; camel_db_close (cdb); reopening = TRUE; second_filename = g_strconcat (path, ".corrupt", NULL); if (g_rename (path, second_filename) == -1) { if (!local_error) { g_set_error (&local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Could not rename '%s' to %s: %s"), path, second_filename, g_strerror (errno)); } g_propagate_error (error, local_error); g_free (second_filename); return NULL; } g_free (second_filename); g_warning ("%s: Failed to open '%s', renamed old file to .corrupt; code:%s (%d) error:%s", G_STRFUNC, path, cdb_sqlite_error_code == SQLITE_CANTOPEN ? "SQLITE_CANTOPEN" : cdb_sqlite_error_code == SQLITE_CORRUPT ? "SQLITE_CORRUPT" : cdb_sqlite_error_code == SQLITE_NOTADB ? "SQLITE_NOTADB" : "???", cdb_sqlite_error_code, local_error ? local_error->message : "Unknown error"); g_clear_error (&local_error); goto reopen; } if (local_error) { g_propagate_error (error, local_error); camel_db_close (cdb); return NULL; } sqlite3_busy_timeout (cdb->db, CAMEL_DB_SLEEP_INTERVAL); return cdb; } /** * camel_db_clone: * * Since: 2.26 **/ CamelDB * camel_db_clone (CamelDB *cdb, GError **error) { return camel_db_open (cdb->priv->file_name, error); } /** * camel_db_close: * * Since: 2.24 **/ void camel_db_close (CamelDB *cdb) { if (cdb) { sqlite3_close (cdb->db); g_rw_lock_clear (&cdb->priv->rwlock); g_mutex_clear (&cdb->priv->transaction_lock); g_free (cdb->priv->file_name); g_free (cdb->priv); g_free (cdb); d (g_print ("\nDatabase succesfully closed \n")); } } /** * camel_db_set_collate: * * Since: 2.24 **/ gint camel_db_set_collate (CamelDB *cdb, const gchar *col, const gchar *collate, CamelDBCollate func) { gint ret = 0; if (!cdb) return 0; cdb_writer_lock (cdb); d (g_print ("Creating Collation %s on %s with %p\n", collate, col, (gpointer) func)); if (collate && func) ret = sqlite3_create_collation (cdb->db, collate, SQLITE_UTF8, NULL, func); cdb_writer_unlock (cdb); return ret; } /** * camel_db_command: * * Since: 2.24 **/ gint camel_db_command (CamelDB *cdb, const gchar *stmt, GError **error) { return camel_db_command_internal (cdb, stmt, NULL, error); } /** * camel_db_begin_transaction: * * Since: 2.24 **/ gint camel_db_begin_transaction (CamelDB *cdb, GError **error) { gchar *stmt; gint res; if (!cdb) return -1; cdb_writer_lock (cdb); stmt = cdb_construct_transaction_stmt (cdb, "SAVEPOINT "); STARTTS (stmt); res = cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error); g_free (stmt); return res; } /** * camel_db_end_transaction: * * Since: 2.24 **/ gint camel_db_end_transaction (CamelDB *cdb, GError **error) { gchar *stmt; gint ret; if (!cdb) return -1; stmt = cdb_construct_transaction_stmt (cdb, "RELEASE SAVEPOINT "); ret = cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error); g_free (stmt); ENDTS; cdb_writer_unlock (cdb); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } /** * camel_db_abort_transaction: * * Since: 2.24 **/ gint camel_db_abort_transaction (CamelDB *cdb, GError **error) { gchar *stmt; gint ret; stmt = cdb_construct_transaction_stmt (cdb, "ROLLBACK TO SAVEPOINT "); ret = cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error); g_free (stmt); cdb_writer_unlock (cdb); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } /** * camel_db_add_to_transaction: * * Since: 2.24 **/ gint camel_db_add_to_transaction (CamelDB *cdb, const gchar *stmt, GError **error) { if (!cdb) return -1; g_return_val_if_fail (cdb_is_in_transaction (cdb), -1); return (cdb_sql_exec (cdb->db, stmt, NULL, NULL, NULL, error)); } /** * camel_db_transaction_command: * * Since: 2.24 **/ gint camel_db_transaction_command (CamelDB *cdb, GList *qry_list, GError **error) { gboolean in_transaction = FALSE; gint ret; const gchar *query; if (!cdb) return -1; ret = camel_db_begin_transaction (cdb, error); if (ret) goto end; in_transaction = TRUE; while (qry_list) { query = qry_list->data; ret = cdb_sql_exec (cdb->db, query, NULL, NULL, NULL, error); if (ret) goto end; qry_list = g_list_next (qry_list); } ret = camel_db_end_transaction (cdb, error); in_transaction = FALSE; end: if (in_transaction) ret = camel_db_abort_transaction (cdb, error); return ret; } static gint count_cb (gpointer data, gint argc, gchar **argv, gchar **azColName) { gint i; for (i = 0; i < argc; i++) { if (strstr (azColName[i], "COUNT")) { *(guint32 *)data = argv [i] ? strtoul (argv [i], NULL, 10) : 0; } } return 0; } /** * camel_db_count_message_info: * * Since: 2.26 **/ gint camel_db_count_message_info (CamelDB *cdb, const gchar *query, guint32 *count, GError **error) { gint ret = -1; cdb_reader_lock (cdb); START (query); ret = cdb_sql_exec (cdb->db, query, count_cb, count, NULL, error); END; cdb_reader_unlock (cdb); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } /** * camel_db_count_junk_message_info: * * Since: 2.24 **/ gint camel_db_count_junk_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 1", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_count_unread_message_info: * * Since: 2.24 **/ gint camel_db_count_unread_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE read = 0", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_count_visible_unread_message_info: * * Since: 2.24 **/ gint camel_db_count_visible_unread_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE read = 0 AND junk = 0 AND deleted = 0", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_count_visible_message_info: * * Since: 2.24 **/ gint camel_db_count_visible_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 0 AND deleted = 0", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_count_junk_not-deleted_message_info: * * Since: 2.24 **/ gint camel_db_count_junk_not_deleted_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE junk = 1 AND deleted = 0", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_count_deleted_message_info: * * Since: 2.24 **/ gint camel_db_count_deleted_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q WHERE deleted = 1", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_count_total_message_info: * * Since: 2.24 **/ gint camel_db_count_total_message_info (CamelDB *cdb, const gchar *table_name, guint32 *count, GError **error) { gint ret; gchar *query; if (!cdb) return -1; query = sqlite3_mprintf ("SELECT COUNT (*) FROM %Q where read=0 or read=1", table_name); ret = camel_db_count_message_info (cdb, query, count, error); sqlite3_free (query); return ret; } /** * camel_db_select: * * Since: 2.24 **/ gint camel_db_select (CamelDB *cdb, const gchar *stmt, CamelDBSelectCB callback, gpointer data, GError **error) { gint ret = -1; if (!cdb) return ret; d (g_print ("\n%s:\n%s \n", G_STRFUNC, stmt)); cdb_reader_lock (cdb); START (stmt); ret = cdb_sql_exec (cdb->db, stmt, callback, data, NULL, error); END; cdb_reader_unlock (cdb); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } static gint read_uids_callback (gpointer ref_array, gint ncol, gchar **cols, gchar **name) { GPtrArray *array = ref_array; g_return_val_if_fail (ncol == 1, 0); if (cols[0]) g_ptr_array_add (array, (gchar *) (camel_pstring_strdup (cols[0]))); return 0; } static gint read_uids_to_hash_callback (gpointer ref_hash, gint ncol, gchar **cols, gchar **name) { GHashTable *hash = ref_hash; g_return_val_if_fail (ncol == 2, 0); if (cols[0]) g_hash_table_insert (hash, (gchar *) camel_pstring_strdup (cols[0]), GUINT_TO_POINTER (cols[1] ? strtoul (cols[1], NULL, 10) : 0)); return 0; } /** * camel_db_get_folder_uids: * * Fills hash with uid->GUINT_TO_POINTER (flag) * * Since: 2.24 **/ gint camel_db_get_folder_uids (CamelDB *db, const gchar *folder_name, const gchar *sort_by, const gchar *collate, GHashTable *hash, GError **error) { gchar *sel_query; gint ret; sel_query = sqlite3_mprintf ( "SELECT uid,flags FROM %Q%s%s%s%s", folder_name, sort_by ? " order by " : "", sort_by ? sort_by : "", (sort_by && collate) ? " collate " : "", (sort_by && collate) ? collate : ""); ret = camel_db_select (db, sel_query, read_uids_to_hash_callback, hash, error); sqlite3_free (sel_query); return ret; } /** * camel_db_get_folder_junk_uids: * * Since: 2.24 **/ GPtrArray * camel_db_get_folder_junk_uids (CamelDB *db, gchar *folder_name, GError **error) { gchar *sel_query; gint ret; GPtrArray *array = g_ptr_array_new (); sel_query = sqlite3_mprintf ("SELECT uid FROM %Q where junk=1", folder_name); ret = camel_db_select (db, sel_query, read_uids_callback, array, error); sqlite3_free (sel_query); if (!array->len || ret != 0) { g_ptr_array_free (array, TRUE); array = NULL; } return array; } /** * camel_db_get_folder_deleted_uids: * * Since: 2.24 **/ GPtrArray * camel_db_get_folder_deleted_uids (CamelDB *db, const gchar *folder_name, GError **error) { gchar *sel_query; gint ret; GPtrArray *array = g_ptr_array_new (); sel_query = sqlite3_mprintf ("SELECT uid FROM %Q where deleted=1", folder_name); ret = camel_db_select (db, sel_query, read_uids_callback, array, error); sqlite3_free (sel_query); if (!array->len || ret != 0) { g_ptr_array_free (array, TRUE); array = NULL; } return array; } struct ReadPreviewData { GHashTable *columns_hash; GHashTable *hash; }; static gint read_preview_callback (gpointer ref, gint ncol, gchar **cols, gchar **name) { struct ReadPreviewData *rpd = ref; const gchar *uid = NULL; gchar *msg = NULL; gint i; for (i = 0; i < ncol; ++i) { if (!name[i] || !cols[i]) continue; switch (camel_db_get_column_ident (&rpd->columns_hash, i, ncol, name)) { case CAMEL_DB_COLUMN_UID: uid = camel_pstring_strdup (cols[i]); break; case CAMEL_DB_COLUMN_PREVIEW: msg = g_strdup (cols[i]); break; default: g_warn_if_reached (); break; } } g_hash_table_insert (rpd->hash, (gchar *) uid, msg); return 0; } /** * camel_db_get_folder_preview: * * Since: 2.28 **/ GHashTable * camel_db_get_folder_preview (CamelDB *db, const gchar *folder_name, GError **error) { gchar *sel_query; gint ret; struct ReadPreviewData rpd; GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal); sel_query = sqlite3_mprintf ("SELECT uid, preview FROM '%q_preview'", folder_name); rpd.columns_hash = NULL; rpd.hash = hash; ret = camel_db_select (db, sel_query, read_preview_callback, &rpd, error); sqlite3_free (sel_query); if (rpd.columns_hash) g_hash_table_destroy (rpd.columns_hash); if (!g_hash_table_size (hash) || ret != 0) { g_hash_table_destroy (hash); hash = NULL; } return hash; } /** * camel_db_write_preview_record: * * Since: 2.28 **/ gint camel_db_write_preview_record (CamelDB *db, const gchar *folder_name, const gchar *uid, const gchar *msg, GError **error) { gchar *query; gint ret; query = sqlite3_mprintf ("INSERT OR REPLACE INTO '%q_preview' VALUES(%Q,%Q)", folder_name, uid, msg); ret = camel_db_add_to_transaction (db, query, error); sqlite3_free (query); return ret; } /** * camel_db_create_folders_table: * * Since: 2.24 **/ gint camel_db_create_folders_table (CamelDB *cdb, GError **error) { const gchar *query = "CREATE TABLE IF NOT EXISTS folders ( " "folder_name TEXT PRIMARY KEY, " "version REAL, " "flags INTEGER, " "nextuid INTEGER, " "time NUMERIC, " "saved_count INTEGER, " "unread_count INTEGER, " "deleted_count INTEGER, " "junk_count INTEGER, " "visible_count INTEGER, " "jnd_count INTEGER, " "bdata TEXT )"; CAMEL_DB_RELEASE_SQLITE_MEMORY; return ((camel_db_command (cdb, query, error))); } static gint camel_db_create_message_info_table (CamelDB *cdb, const gchar *folder_name, GError **error) { gint ret; gchar *table_creation_query, *safe_index; /* README: It is possible to compress all system flags into a single * column and use just as userflags but that makes querying for other * applications difficult and bloats the parsing code. Instead, it is * better to bloat the tables. Sqlite should have some optimizations * for sparse columns etc. */ table_creation_query = sqlite3_mprintf ( "CREATE TABLE IF NOT EXISTS %Q ( " "uid TEXT PRIMARY KEY , " "flags INTEGER , " "msg_type INTEGER , " "read INTEGER , " "deleted INTEGER , " "replied INTEGER , " "important INTEGER , " "junk INTEGER , " "attachment INTEGER , " "dirty INTEGER , " "size INTEGER , " "dsent NUMERIC , " "dreceived NUMERIC , " "subject TEXT , " "mail_from TEXT , " "mail_to TEXT , " "mail_cc TEXT , " "mlist TEXT , " "followup_flag TEXT , " "followup_completed_on TEXT , " "followup_due_by TEXT , " "part TEXT , " "labels TEXT , " "usertags TEXT , " "cinfo TEXT , " "bdata TEXT, " "created TEXT, " "modified TEXT)", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); table_creation_query = sqlite3_mprintf ( "CREATE TABLE IF NOT EXISTS '%q_bodystructure' ( " "uid TEXT PRIMARY KEY , " "bodystructure TEXT )", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); /* Create message preview table. */ table_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_preview' ( uid TEXT PRIMARY KEY , preview TEXT)", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); /* FIXME: sqlize folder_name before you create the index */ safe_index = g_strdup_printf ("SINDEX-%s", folder_name); table_creation_query = sqlite3_mprintf ("DROP INDEX IF EXISTS %Q", safe_index); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); g_free (safe_index); sqlite3_free (table_creation_query); /* INDEX on preview */ safe_index = g_strdup_printf ("SINDEX-%s-preview", folder_name); table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON '%q_preview' (uid, preview)", safe_index, folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); g_free (safe_index); sqlite3_free (table_creation_query); /* Index on deleted*/ safe_index = g_strdup_printf ("DELINDEX-%s", folder_name); table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (deleted)", safe_index, folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); g_free (safe_index); sqlite3_free (table_creation_query); /* Index on Junk*/ safe_index = g_strdup_printf ("JUNKINDEX-%s", folder_name); table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (junk)", safe_index, folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); g_free (safe_index); sqlite3_free (table_creation_query); /* Index on unread*/ safe_index = g_strdup_printf ("READINDEX-%s", folder_name); table_creation_query = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (read)", safe_index, folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); g_free (safe_index); sqlite3_free (table_creation_query); return ret; } static gint camel_db_migrate_folder_prepare (CamelDB *cdb, const gchar *folder_name, gint version, GError **error) { gint ret = 0; gchar *table_creation_query; /* Migration stage one: storing the old data */ if (version < 0) { ret = camel_db_create_message_info_table (cdb, folder_name, error); g_clear_error (error); } else if (version < 1) { /* Between version 0-1 the following things are changed * ADDED: created: time * ADDED: modified: time * RENAMED: msg_security to dirty * */ table_creation_query = sqlite3_mprintf ("DROP TABLE IF EXISTS 'mem.%q'", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); table_creation_query = sqlite3_mprintf ( "CREATE TEMP TABLE IF NOT EXISTS 'mem.%q' ( " "uid TEXT PRIMARY KEY , " "flags INTEGER , " "msg_type INTEGER , " "read INTEGER , " "deleted INTEGER , " "replied INTEGER , " "important INTEGER , " "junk INTEGER , " "attachment INTEGER , " "dirty INTEGER , " "size INTEGER , " "dsent NUMERIC , " "dreceived NUMERIC , " "subject TEXT , " "mail_from TEXT , " "mail_to TEXT , " "mail_cc TEXT , " "mlist TEXT , " "followup_flag TEXT , " "followup_completed_on TEXT , " "followup_due_by TEXT , " "part TEXT , " "labels TEXT , " "usertags TEXT , " "cinfo TEXT , " "bdata TEXT, " "created TEXT, " "modified TEXT )", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); g_clear_error (error); table_creation_query = sqlite3_mprintf ( "INSERT INTO 'mem.%q' SELECT " "uid , flags , msg_type , read , deleted , " "replied , important , junk , attachment , dirty , " "size , dsent , dreceived , subject , mail_from , " "mail_to , mail_cc , mlist , followup_flag , " "followup_completed_on , followup_due_by , " "part , labels , usertags , cinfo , bdata , " "strftime(\"%%s\", 'now'), " "strftime(\"%%s\", 'now') FROM %Q", folder_name, folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); g_clear_error (error); table_creation_query = sqlite3_mprintf ("DROP TABLE IF EXISTS %Q", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); g_clear_error (error); ret = camel_db_create_message_info_table (cdb, folder_name, error); g_clear_error (error); } /* Add later version migrations here */ return ret; } static gint camel_db_migrate_folder_recreate (CamelDB *cdb, const gchar *folder_name, gint version, GError **error) { gint ret = 0; gchar *table_creation_query; /* Migration stage two: writing back the old data */ if (version < 2) { GError *local_error = NULL; table_creation_query = sqlite3_mprintf ( "INSERT INTO %Q SELECT uid , flags , msg_type , " "read , deleted , replied , important , junk , " "attachment , dirty , size , dsent , dreceived , " "subject , mail_from , mail_to , mail_cc , mlist , " "followup_flag , followup_completed_on , " "followup_due_by , part , labels , usertags , " "cinfo , bdata, created, modified FROM 'mem.%q'", folder_name, folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, &local_error); sqlite3_free (table_creation_query); if (!local_error) { table_creation_query = sqlite3_mprintf ("DROP TABLE 'mem.%q'", folder_name); ret = camel_db_add_to_transaction (cdb, table_creation_query, &local_error); sqlite3_free (table_creation_query); } if (local_error) { if (local_error->message && strstr (local_error->message, "no such table") != NULL) { /* ignore 'no such table' errors here */ g_clear_error (&local_error); ret = 0; } else { g_propagate_error (error, local_error); } } } /* Add later version migrations here */ return ret; } /** * camel_db_reset_folder_version: * * Since: 2.28 **/ gint camel_db_reset_folder_version (CamelDB *cdb, const gchar *folder_name, gint reset_version, GError **error) { gint ret = 0; gchar *version_creation_query; gchar *version_insert_query; gchar *drop_folder_query; drop_folder_query = sqlite3_mprintf ("DROP TABLE IF EXISTS '%q_version'", folder_name); version_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_version' ( version TEXT )", folder_name); version_insert_query = sqlite3_mprintf ("INSERT INTO '%q_version' VALUES ('%d')", folder_name, reset_version); ret = camel_db_add_to_transaction (cdb, drop_folder_query, error); ret = camel_db_add_to_transaction (cdb, version_creation_query, error); ret = camel_db_add_to_transaction (cdb, version_insert_query, error); sqlite3_free (drop_folder_query); sqlite3_free (version_creation_query); sqlite3_free (version_insert_query); return ret; } static gint camel_db_write_folder_version (CamelDB *cdb, const gchar *folder_name, gint old_version, GError **error) { gint ret = 0; gchar *version_creation_query; gchar *version_insert_query; version_creation_query = sqlite3_mprintf ("CREATE TABLE IF NOT EXISTS '%q_version' ( version TEXT )", folder_name); if (old_version == -1) version_insert_query = sqlite3_mprintf ("INSERT INTO '%q_version' VALUES ('2')", folder_name); else version_insert_query = sqlite3_mprintf ("UPDATE '%q_version' SET version='2'", folder_name); ret = camel_db_add_to_transaction (cdb, version_creation_query, error); ret = camel_db_add_to_transaction (cdb, version_insert_query, error); sqlite3_free (version_creation_query); sqlite3_free (version_insert_query); return ret; } static gint read_version_callback (gpointer ref, gint ncol, gchar **cols, gchar **name) { gint *version = (gint *) ref; if (cols[0]) *version = strtoul (cols [0], NULL, 10); return 0; } static gint camel_db_get_folder_version (CamelDB *cdb, const gchar *folder_name, GError **error) { gint version = -1; gchar *query; query = sqlite3_mprintf ("SELECT version FROM '%q_version'", folder_name); camel_db_select (cdb, query, read_version_callback, &version, error); sqlite3_free (query); return version; } /** * camel_db_prepare_message_info_table: * * Since: 2.24 **/ gint camel_db_prepare_message_info_table (CamelDB *cdb, const gchar *folder_name, GError **error) { gint ret, current_version; gboolean in_transaction = TRUE; GError *err = NULL; /* Make sure we have the table already */ camel_db_begin_transaction (cdb, &err); ret = camel_db_create_message_info_table (cdb, folder_name, &err); if (err) goto exit; camel_db_end_transaction (cdb, &err); in_transaction = FALSE; /* Migration stage zero: version fetch */ current_version = camel_db_get_folder_version (cdb, folder_name, &err); if (err && err->message && strstr (err->message, "no such table") != NULL) { g_clear_error (&err); current_version = -1; } camel_db_begin_transaction (cdb, &err); in_transaction = TRUE; /* Migration stage one: storing the old data if necessary */ ret = camel_db_migrate_folder_prepare (cdb, folder_name, current_version, &err); if (err) goto exit; /* Migration stage two: rewriting the old data if necessary */ ret = camel_db_migrate_folder_recreate (cdb, folder_name, current_version, &err); if (err) goto exit; /* Final step: (over)write the current version label */ ret = camel_db_write_folder_version (cdb, folder_name, current_version, &err); if (err) goto exit; camel_db_end_transaction (cdb, &err); in_transaction = FALSE; exit: if (err && in_transaction) camel_db_abort_transaction (cdb, NULL); if (err) g_propagate_error (error, err); return ret; } static gint write_mir (CamelDB *cdb, const gchar *folder_name, CamelMIRecord *record, GError **error, gboolean delete_old_record) { gint ret; /*char *del_query;*/ gchar *ins_query; if (!record) { g_warn_if_reached (); return -1; } /* FIXME: We should migrate from this DELETE followed by INSERT model to an INSERT OR REPLACE model as pointed out by pvanhoof */ /* NB: UGLIEST Hack. We can't modify the schema now. We are using dirty (an unsed one to notify of FLAGGED/Dirty infos */ ins_query = sqlite3_mprintf ( "INSERT OR REPLACE INTO %Q VALUES (" "%Q, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, " "%lld, %lld, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %Q, " "%Q, %Q, %Q, %Q, %Q, " "strftime(\"%%s\", 'now'), " "strftime(\"%%s\", 'now') )", folder_name, record->uid, record->flags, record->msg_type, record->read, record->deleted, record->replied, record->important, record->junk, record->attachment, record->dirty, record->size, (gint64) record->dsent, (gint64) record->dreceived, record->subject, record->from, record->to, record->cc, record->mlist, record->followup_flag, record->followup_completed_on, record->followup_due_by, record->part, record->labels, record->usertags, record->cinfo, record->bdata); ret = camel_db_add_to_transaction (cdb, ins_query, error); sqlite3_free (ins_query); if (ret == 0) { ins_query = sqlite3_mprintf ( "INSERT OR REPLACE INTO " "'%q_bodystructure' VALUES (%Q, %Q )", folder_name, record->uid, record->bodystructure); ret = camel_db_add_to_transaction (cdb, ins_query, error); sqlite3_free (ins_query); } return ret; } /** * camel_db_write_fresh_message_info_record: * * Since: 2.26 **/ gint camel_db_write_fresh_message_info_record (CamelDB *cdb, const gchar *folder_name, CamelMIRecord *record, GError **error) { return write_mir (cdb, folder_name, record, error, FALSE); } /** * camel_db_write_message_info_record: * * Since: 2.24 **/ gint camel_db_write_message_info_record (CamelDB *cdb, const gchar *folder_name, CamelMIRecord *record, GError **error) { return write_mir (cdb, folder_name, record, error, TRUE); } /** * camel_db_write_folder_info_record: * * Since: 2.24 **/ gint camel_db_write_folder_info_record (CamelDB *cdb, CamelFIRecord *record, GError **error) { gint ret; gchar *del_query; gchar *ins_query; ins_query = sqlite3_mprintf ( "INSERT INTO folders VALUES (" "%Q, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %Q ) ", record->folder_name, record->version, record->flags, record->nextuid, record->time, record->saved_count, record->unread_count, record->deleted_count, record->junk_count, record->visible_count, record->jnd_count, record->bdata); del_query = sqlite3_mprintf ( "DELETE FROM folders WHERE folder_name = %Q", record->folder_name); ret = camel_db_add_to_transaction (cdb, del_query, error); ret = camel_db_add_to_transaction (cdb, ins_query, error); sqlite3_free (del_query); sqlite3_free (ins_query); return ret; } struct ReadFirData { GHashTable *columns_hash; CamelFIRecord *record; }; static gint read_fir_callback (gpointer ref, gint ncol, gchar **cols, gchar **name) { struct ReadFirData *rfd = ref; gint i; d (g_print ("\nread_fir_callback called \n")); for (i = 0; i < ncol; ++i) { if (!name[i] || !cols[i]) continue; switch (camel_db_get_column_ident (&rfd->columns_hash, i, ncol, name)) { case CAMEL_DB_COLUMN_FOLDER_NAME: rfd->record->folder_name = g_strdup (cols[i]); break; case CAMEL_DB_COLUMN_VERSION: rfd->record->version = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_FLAGS: rfd->record->flags = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_NEXTUID: rfd->record->nextuid = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_TIME: rfd->record->time = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_SAVED_COUNT: rfd->record->saved_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_UNREAD_COUNT: rfd->record->unread_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_DELETED_COUNT: rfd->record->deleted_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_JUNK_COUNT: rfd->record->junk_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_VISIBLE_COUNT: rfd->record->visible_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_JND_COUNT: rfd->record->jnd_count = cols[i] ? strtoul (cols[i], NULL, 10) : 0; break; case CAMEL_DB_COLUMN_BDATA: rfd->record->bdata = g_strdup (cols[i]); break; default: g_warn_if_reached (); break; } } return 0; } /** * camel_db_read_folder_info_record: * * Since: 2.24 **/ gint camel_db_read_folder_info_record (CamelDB *cdb, const gchar *folder_name, CamelFIRecord *record, GError **error) { struct ReadFirData rfd; gchar *query; gint ret; rfd.columns_hash = NULL; rfd.record = record; query = sqlite3_mprintf ("SELECT * FROM folders WHERE folder_name = %Q", folder_name); ret = camel_db_select (cdb, query, read_fir_callback, &rfd, error); sqlite3_free (query); if (rfd.columns_hash) g_hash_table_destroy (rfd.columns_hash); return ret; } /** * camel_db_read_message_info_record_with_uid: * * Since: 2.24 **/ gint camel_db_read_message_info_record_with_uid (CamelDB *cdb, const gchar *folder_name, const gchar *uid, gpointer p, CamelDBSelectCB read_mir_callback, GError **error) { gchar *query; gint ret; query = sqlite3_mprintf ( "SELECT uid, flags, size, dsent, dreceived, subject, " "mail_from, mail_to, mail_cc, mlist, part, labels, " "usertags, cinfo, bdata FROM %Q WHERE uid = %Q", folder_name, uid); ret = camel_db_select (cdb, query, read_mir_callback, p, error); sqlite3_free (query); return (ret); } /** * camel_db_read_message_info_records: * * Since: 2.24 **/ gint camel_db_read_message_info_records (CamelDB *cdb, const gchar *folder_name, gpointer p, CamelDBSelectCB read_mir_callback, GError **error) { gchar *query; gint ret; query = sqlite3_mprintf ( "SELECT uid, flags, size, dsent, dreceived, subject, " "mail_from, mail_to, mail_cc, mlist, part, labels, " "usertags, cinfo, bdata FROM %Q ", folder_name); ret = camel_db_select (cdb, query, read_mir_callback, p, error); sqlite3_free (query); return (ret); } /** * camel_db_create_deleted_table: * * Since: 2.24 **/ static gint camel_db_create_deleted_table (CamelDB *cdb, GError **error) { gint ret; gchar *table_creation_query; table_creation_query = sqlite3_mprintf ( "CREATE TABLE IF NOT EXISTS Deletes (" "id INTEGER primary key AUTOINCREMENT not null, " "uid TEXT, " "time TEXT, " "mailbox TEXT)"); ret = camel_db_add_to_transaction (cdb, table_creation_query, error); sqlite3_free (table_creation_query); return ret; } static gint camel_db_trim_deleted_table (CamelDB *cdb, GError **error) { gint ret = 0; /* TODO: We need a mechanism to get rid of very old deletes, or something * that keeps the list trimmed at a certain max (deleting upfront when * appending at the back) */ return ret; } /** * camel_db_delete_uid: * * Since: 2.24 **/ gint camel_db_delete_uid (CamelDB *cdb, const gchar *folder, const gchar *uid, GError **error) { gchar *tab; gint ret; camel_db_begin_transaction (cdb, error); ret = camel_db_create_deleted_table (cdb, error); tab = sqlite3_mprintf ( "INSERT OR REPLACE INTO Deletes (uid, mailbox, time) " "SELECT uid, %Q, strftime(\"%%s\", 'now') FROM %Q " "WHERE uid = %Q", folder, folder, uid); ret = camel_db_add_to_transaction (cdb, tab, error); sqlite3_free (tab); ret = camel_db_trim_deleted_table (cdb, error); tab = sqlite3_mprintf ("DELETE FROM '%q_bodystructure' WHERE uid = %Q", folder, uid); ret = camel_db_add_to_transaction (cdb, tab, error); sqlite3_free (tab); tab = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = %Q", folder, uid); ret = camel_db_add_to_transaction (cdb, tab, error); sqlite3_free (tab); ret = camel_db_end_transaction (cdb, error); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } static gint cdb_delete_ids (CamelDB *cdb, const gchar *folder_name, GList *uids, const gchar *uid_prefix, const gchar *field, GError **error) { gchar *tmp; gint ret; gchar *tab; gboolean first = TRUE; GString *str = g_string_new ("DELETE FROM "); GList *iterator; GString *ins_str = NULL; if (strcmp (field, "vuid") != 0) ins_str = g_string_new ("INSERT OR REPLACE INTO Deletes (uid, mailbox, time) SELECT uid, "); camel_db_begin_transaction (cdb, error); if (ins_str) ret = camel_db_create_deleted_table (cdb, error); if (ins_str) { tab = sqlite3_mprintf ("%Q, strftime(\"%%s\", 'now') FROM %Q WHERE %s IN (", folder_name, folder_name, field); g_string_append_printf (ins_str, "%s ", tab); sqlite3_free (tab); } tmp = sqlite3_mprintf ("%Q WHERE %s IN (", folder_name, field); g_string_append_printf (str, "%s ", tmp); sqlite3_free (tmp); iterator = uids; while (iterator) { gchar *foo = g_strdup_printf ("%s%s", uid_prefix, (gchar *) iterator->data); tmp = sqlite3_mprintf ("%Q", foo); g_free (foo); iterator = iterator->next; if (first == TRUE) { g_string_append_printf (str, " %s ", tmp); if (ins_str) g_string_append_printf (ins_str, " %s ", tmp); first = FALSE; } else { g_string_append_printf (str, ", %s ", tmp); if (ins_str) g_string_append_printf (ins_str, ", %s ", tmp); } sqlite3_free (tmp); } g_string_append (str, ")"); if (ins_str) { g_string_append (ins_str, ")"); ret = camel_db_add_to_transaction (cdb, ins_str->str, error); ret = camel_db_trim_deleted_table (cdb, error); } ret = camel_db_add_to_transaction (cdb, str->str, error); ret = camel_db_end_transaction (cdb, error); CAMEL_DB_RELEASE_SQLITE_MEMORY; if (ins_str) g_string_free (ins_str, TRUE); g_string_free (str, TRUE); return ret; } /** * camel_db_delete_uids: * * Since: 2.24 **/ gint camel_db_delete_uids (CamelDB *cdb, const gchar *folder_name, GList *uids, GError **error) { if (!uids || !uids->data) return 0; return cdb_delete_ids (cdb, folder_name, uids, "", "uid", error); } /** * camel_db_clear_folder_summary: * * Since: 2.24 **/ gint camel_db_clear_folder_summary (CamelDB *cdb, const gchar *folder, GError **error) { gint ret; gchar *folders_del; gchar *msginfo_del; gchar *bstruct_del; gchar *tab; folders_del = sqlite3_mprintf ("DELETE FROM folders WHERE folder_name = %Q", folder); msginfo_del = sqlite3_mprintf ("DELETE FROM %Q ", folder); bstruct_del = sqlite3_mprintf ("DELETE FROM '%q_bodystructure' ", folder); camel_db_begin_transaction (cdb, error); ret = camel_db_create_deleted_table (cdb, error); tab = sqlite3_mprintf ( "INSERT OR REPLACE INTO Deletes (uid, mailbox, time) " "SELECT uid, %Q, strftime(\"%%s\", 'now') FROM %Q", folder, folder); ret = camel_db_add_to_transaction (cdb, tab, error); sqlite3_free (tab); ret = camel_db_trim_deleted_table (cdb, error); camel_db_add_to_transaction (cdb, msginfo_del, error); camel_db_add_to_transaction (cdb, folders_del, error); camel_db_add_to_transaction (cdb, bstruct_del, error); ret = camel_db_end_transaction (cdb, error); sqlite3_free (folders_del); sqlite3_free (msginfo_del); sqlite3_free (bstruct_del); return ret; } /** * camel_db_delete_folder: * * Since: 2.24 **/ gint camel_db_delete_folder (CamelDB *cdb, const gchar *folder, GError **error) { gint ret; gchar *del; gchar *tab; camel_db_begin_transaction (cdb, error); ret = camel_db_create_deleted_table (cdb, error); tab = sqlite3_mprintf ( "INSERT OR REPLACE INTO Deletes (uid, mailbox, time) " "SELECT uid, %Q, strftime(\"%%s\", 'now') FROM %Q", folder, folder); ret = camel_db_add_to_transaction (cdb, tab, error); sqlite3_free (tab); ret = camel_db_trim_deleted_table (cdb, error); del = sqlite3_mprintf ("DELETE FROM folders WHERE folder_name = %Q", folder); ret = camel_db_add_to_transaction (cdb, del, error); sqlite3_free (del); del = sqlite3_mprintf ("DROP TABLE %Q ", folder); ret = camel_db_add_to_transaction (cdb, del, error); sqlite3_free (del); del = sqlite3_mprintf ("DROP TABLE '%q_bodystructure' ", folder); ret = camel_db_add_to_transaction (cdb, del, error); sqlite3_free (del); ret = camel_db_end_transaction (cdb, error); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } /** * camel_db_rename_folder: * * Since: 2.24 **/ gint camel_db_rename_folder (CamelDB *cdb, const gchar *old_folder, const gchar *new_folder, GError **error) { gint ret; gchar *cmd, *tab; camel_db_begin_transaction (cdb, error); ret = camel_db_create_deleted_table (cdb, error); tab = sqlite3_mprintf ( "INSERT OR REPLACE INTO Deletes (uid, mailbox, time) " "SELECT uid, %Q, strftime(\"%%s\", 'now') FROM %Q", old_folder, old_folder); ret = camel_db_add_to_transaction (cdb, tab, error); sqlite3_free (tab); ret = camel_db_trim_deleted_table (cdb, error); cmd = sqlite3_mprintf ("ALTER TABLE %Q RENAME TO %Q", old_folder, new_folder); ret = camel_db_add_to_transaction (cdb, cmd, error); sqlite3_free (cmd); cmd = sqlite3_mprintf ("ALTER TABLE '%q_version' RENAME TO '%q_version'", old_folder, new_folder); ret = camel_db_add_to_transaction (cdb, cmd, error); sqlite3_free (cmd); cmd = sqlite3_mprintf ("UPDATE %Q SET modified=strftime(\"%%s\", 'now'), created=strftime(\"%%s\", 'now')", new_folder); ret = camel_db_add_to_transaction (cdb, cmd, error); sqlite3_free (cmd); cmd = sqlite3_mprintf ("UPDATE folders SET folder_name = %Q WHERE folder_name = %Q", new_folder, old_folder); ret = camel_db_add_to_transaction (cdb, cmd, error); sqlite3_free (cmd); ret = camel_db_end_transaction (cdb, error); CAMEL_DB_RELEASE_SQLITE_MEMORY; return ret; } /** * camel_db_camel_mir_free: * * Since: 2.24 **/ void camel_db_camel_mir_free (CamelMIRecord *record) { if (record) { camel_pstring_free (record->uid); camel_pstring_free (record->subject); camel_pstring_free (record->from); camel_pstring_free (record->to); camel_pstring_free (record->cc); camel_pstring_free (record->mlist); camel_pstring_free (record->followup_flag); camel_pstring_free (record->followup_completed_on); camel_pstring_free (record->followup_due_by); g_free (record->part); g_free (record->labels); g_free (record->usertags); g_free (record->cinfo); g_free (record->bdata); g_free (record->bodystructure); g_free (record); } } /** * camel_db_sqlize_string: * * Since: 2.24 **/ gchar * camel_db_sqlize_string (const gchar *string) { return sqlite3_mprintf ("%Q", string); } /** * camel_db_free_sqlized_string: * * Since: 2.24 **/ void camel_db_free_sqlized_string (gchar *string) { sqlite3_free (string); string = NULL; } /* "( uid TEXT PRIMARY KEY , flags INTEGER , msg_type INTEGER , replied INTEGER , dirty INTEGER , size INTEGER , dsent NUMERIC , dreceived NUMERIC , mlist TEXT , followup_flag TEXT , followup_completed_on TEXT , followup_due_by TEXT ," */ /** * camel_db_get_column_name: * * Since: 2.24 **/ gchar * camel_db_get_column_name (const gchar *raw_name) { if (!g_ascii_strcasecmp (raw_name, "Subject")) return g_strdup ("subject"); else if (!g_ascii_strcasecmp (raw_name, "from")) return g_strdup ("mail_from"); else if (!g_ascii_strcasecmp (raw_name, "Cc")) return g_strdup ("mail_cc"); else if (!g_ascii_strcasecmp (raw_name, "To")) return g_strdup ("mail_to"); else if (!g_ascii_strcasecmp (raw_name, "Flagged")) return g_strdup ("important"); else if (!g_ascii_strcasecmp (raw_name, "deleted")) return g_strdup ("deleted"); else if (!g_ascii_strcasecmp (raw_name, "junk")) return g_strdup ("junk"); else if (!g_ascii_strcasecmp (raw_name, "Answered")) return g_strdup ("replied"); else if (!g_ascii_strcasecmp (raw_name, "Seen")) return g_strdup ("read"); else if (!g_ascii_strcasecmp (raw_name, "user-tag")) return g_strdup ("usertags"); else if (!g_ascii_strcasecmp (raw_name, "user-flag")) return g_strdup ("labels"); else if (!g_ascii_strcasecmp (raw_name, "Attachments")) return g_strdup ("attachment"); else if (!g_ascii_strcasecmp (raw_name, "x-camel-mlist")) return g_strdup ("mlist"); /* indicate the header name is not part of the summary */ return NULL; } /** * camel_db_start_in_memory_transactions: * * Since: 2.26 **/ gint camel_db_start_in_memory_transactions (CamelDB *cdb, GError **error) { gint ret; gchar *cmd = sqlite3_mprintf ("ATTACH DATABASE ':memory:' AS %s", CAMEL_DB_IN_MEMORY_DB); ret = camel_db_command (cdb, cmd, error); sqlite3_free (cmd); cmd = sqlite3_mprintf ( "CREATE TEMPORARY TABLE %Q ( " "uid TEXT PRIMARY KEY , " "flags INTEGER , " "msg_type INTEGER , " "read INTEGER , " "deleted INTEGER , " "replied INTEGER , " "important INTEGER , " "junk INTEGER , " "attachment INTEGER , " "dirty INTEGER , " "size INTEGER , " "dsent NUMERIC , " "dreceived NUMERIC , " "subject TEXT , " "mail_from TEXT , " "mail_to TEXT , " "mail_cc TEXT , " "mlist TEXT , " "followup_flag TEXT , " "followup_completed_on TEXT , " "followup_due_by TEXT , " "part TEXT , " "labels TEXT , " "usertags TEXT , " "cinfo TEXT , " "bdata TEXT )", CAMEL_DB_IN_MEMORY_TABLE); ret = camel_db_command (cdb, cmd, error); if (ret != 0 ) abort (); sqlite3_free (cmd); return ret; } /** * camel_db_flush_in_memory_transactions: * * Since: 2.26 **/ gint camel_db_flush_in_memory_transactions (CamelDB *cdb, const gchar *folder_name, GError **error) { gint ret; gchar *cmd = sqlite3_mprintf ("INSERT INTO %Q SELECT * FROM %Q", folder_name, CAMEL_DB_IN_MEMORY_TABLE); ret = camel_db_command (cdb, cmd, error); sqlite3_free (cmd); cmd = sqlite3_mprintf ("DROP TABLE %Q", CAMEL_DB_IN_MEMORY_TABLE); ret = camel_db_command (cdb, cmd, error); sqlite3_free (cmd); cmd = sqlite3_mprintf ("DETACH %Q", CAMEL_DB_IN_MEMORY_DB); ret = camel_db_command (cdb, cmd, error); sqlite3_free (cmd); return ret; } static struct _known_column_names { const gchar *name; CamelDBKnownColumnNames ident; } known_column_names[] = { { "attachment", CAMEL_DB_COLUMN_ATTACHMENT }, { "bdata", CAMEL_DB_COLUMN_BDATA }, { "bodystructure", CAMEL_DB_COLUMN_BODYSTRUCTURE }, { "cinfo", CAMEL_DB_COLUMN_CINFO }, { "deleted", CAMEL_DB_COLUMN_DELETED }, { "deleted_count", CAMEL_DB_COLUMN_DELETED_COUNT }, { "dreceived", CAMEL_DB_COLUMN_DRECEIVED }, { "dsent", CAMEL_DB_COLUMN_DSENT }, { "flags", CAMEL_DB_COLUMN_FLAGS }, { "folder_name", CAMEL_DB_COLUMN_FOLDER_NAME }, { "followup_completed_on", CAMEL_DB_COLUMN_FOLLOWUP_COMPLETED_ON }, { "followup_due_by", CAMEL_DB_COLUMN_FOLLOWUP_DUE_BY }, { "followup_flag", CAMEL_DB_COLUMN_FOLLOWUP_FLAG }, { "important", CAMEL_DB_COLUMN_IMPORTANT }, { "jnd_count", CAMEL_DB_COLUMN_JND_COUNT }, { "junk", CAMEL_DB_COLUMN_JUNK }, { "junk_count", CAMEL_DB_COLUMN_JUNK_COUNT }, { "labels", CAMEL_DB_COLUMN_LABELS }, { "mail_cc", CAMEL_DB_COLUMN_MAIL_CC }, { "mail_from", CAMEL_DB_COLUMN_MAIL_FROM }, { "mail_to", CAMEL_DB_COLUMN_MAIL_TO }, { "mlist", CAMEL_DB_COLUMN_MLIST }, { "nextuid", CAMEL_DB_COLUMN_NEXTUID }, { "part", CAMEL_DB_COLUMN_PART }, { "preview", CAMEL_DB_COLUMN_PREVIEW }, { "read", CAMEL_DB_COLUMN_READ }, { "replied", CAMEL_DB_COLUMN_REPLIED }, { "saved_count", CAMEL_DB_COLUMN_SAVED_COUNT }, { "size", CAMEL_DB_COLUMN_SIZE }, { "subject", CAMEL_DB_COLUMN_SUBJECT }, { "time", CAMEL_DB_COLUMN_TIME }, { "uid", CAMEL_DB_COLUMN_UID }, { "unread_count", CAMEL_DB_COLUMN_UNREAD_COUNT }, { "usertags", CAMEL_DB_COLUMN_USERTAGS }, { "version", CAMEL_DB_COLUMN_VERSION }, { "visible_count", CAMEL_DB_COLUMN_VISIBLE_COUNT }, { "vuid", CAMEL_DB_COLUMN_VUID } }; /** * camel_db_get_column_ident: * * Traverses column name from index @index into an enum * #CamelDBKnownColumnNames value. The @col_names contains @ncols columns. * First time this is called is created the @hash from col_names indexes into * the enum, and this is reused for every other call. The function expects * that column names are returned always in the same order. When all rows * are read the @hash table can be freed with g_hash_table_destroy(). * * Since: 3.4 **/ CamelDBKnownColumnNames camel_db_get_column_ident (GHashTable **hash, gint index, gint ncols, gchar **col_names) { gpointer value = NULL; g_return_val_if_fail (hash != NULL, CAMEL_DB_COLUMN_UNKNOWN); g_return_val_if_fail (col_names != NULL, CAMEL_DB_COLUMN_UNKNOWN); g_return_val_if_fail (ncols > 0, CAMEL_DB_COLUMN_UNKNOWN); g_return_val_if_fail (index >= 0, CAMEL_DB_COLUMN_UNKNOWN); g_return_val_if_fail (index < ncols, CAMEL_DB_COLUMN_UNKNOWN); if (!*hash) { gint ii, jj, from, max = G_N_ELEMENTS (known_column_names); *hash = g_hash_table_new (g_direct_hash, g_direct_equal); for (ii = 0, jj = 0; ii < ncols; ii++) { const gchar *name = col_names[ii]; gboolean first = TRUE; if (!name) continue; for (from = jj; first || jj != from; jj = (jj + 1) % max, first = FALSE) { if (g_str_equal (name, known_column_names[jj].name)) { g_hash_table_insert (*hash, GINT_TO_POINTER (ii), GINT_TO_POINTER (known_column_names[jj].ident)); break; } } if (from == jj && !first) g_warning ("%s: missing column name '%s' in a list of known columns", G_STRFUNC, name); } } g_return_val_if_fail (g_hash_table_lookup_extended (*hash, GINT_TO_POINTER (index), NULL, &value), CAMEL_DB_COLUMN_UNKNOWN); return GPOINTER_TO_INT (value); } static gint get_number_cb (gpointer data, gint argc, gchar **argv, gchar **azColName) { guint64 *pui64 = data; if (argc == 1) { *pui64 = argv[0] ? g_ascii_strtoull (argv[0], NULL, 10) : 0; } else { *pui64 = 0; } return 0; } /** * camel_db_maybe_run_maintenance: * @cdb: a #CamelDB instance * @error: (allow-none): a #GError or %NULL * * Runs a @cdb maintenance, which includes vacuum, if necessary. * * Returns: Whether succeeded. * * Since: 3.16 **/ gboolean camel_db_maybe_run_maintenance (CamelDB *cdb, GError **error) { GError *local_error = NULL; guint64 page_count = 0, freelist_count = 0; gboolean success = FALSE; g_return_val_if_fail (cdb != NULL, FALSE); cdb_writer_lock (cdb); if (cdb_sql_exec (cdb->db, "PRAGMA page_count;", get_number_cb, &page_count, NULL, &local_error) == SQLITE_OK && cdb_sql_exec (cdb->db, "PRAGMA freelist_count;", get_number_cb, &freelist_count, NULL, &local_error) == SQLITE_OK) { /* Vacuum, if there's more than 5% of the free pages */ success = !page_count || !freelist_count || freelist_count * 1000 / page_count <= 50 || cdb_sql_exec (cdb->db, "vacuum;", NULL, NULL, NULL, &local_error) == SQLITE_OK; } cdb_writer_unlock (cdb); if (local_error) { g_propagate_error (error, local_error); success = FALSE; } return success; }