[geary/wip/713150-conversations: 6/9] ConversationTable, MessageConversationTable garbage collection
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/713150-conversations: 6/9] ConversationTable, MessageConversationTable garbage collection
- Date: Tue, 3 Mar 2015 01:28:56 +0000 (UTC)
commit e744e1461772d2061906717c38b28c81654a37f9
Author: Jim Nelson <jim yorba org>
Date: Mon Mar 2 16:35:16 2015 -0800
ConversationTable, MessageConversationTable garbage collection
src/client/application/geary-args.vala | 4 +
src/client/application/geary-controller.vala | 10 ++-
src/engine/api/geary-account.vala | 27 +++++-
src/engine/imap-db/imap-db-account.vala | 6 +-
src/engine/imap-db/imap-db-database.vala | 14 ++-
src/engine/imap-db/imap-db-gc.vala | 105 +++++++++++++++++++-
.../imap-engine/imap-engine-generic-account.vala | 15 ++-
7 files changed, 166 insertions(+), 15 deletions(-)
---
diff --git a/src/client/application/geary-args.vala b/src/client/application/geary-args.vala
index 8ba4d2a..d3bf529 100644
--- a/src/client/application/geary-args.vala
+++ b/src/client/application/geary-args.vala
@@ -24,6 +24,8 @@ private const OptionEntry[] options = {
{ "log-folder-normalization", 0, 0, OptionArg.NONE, ref log_folder_normalization, N_("Log folder
normalization"), null },
{ "inspector", 'i', 0, OptionArg.NONE, ref inspector, N_("Allow inspection of WebView"), null },
{ "revoke-certs", 0, 0, OptionArg.NONE, ref revoke_certs, N_("Revoke all server certificates with TLS
warnings"), null },
+ { "force-cleanup", 0, OptionFlags.HIDDEN, OptionArg.NONE, ref force_cleanup, N_("Force a mail database
cleanup (not recommended)"), null },
+ { "force-rebuild", 0, OptionFlags.HIDDEN, OptionArg.NONE, ref force_rebuild, N_("Force a mail database
rebuild (not recommended)"), null },
{ "version", 'V', 0, OptionArg.NONE, ref version, N_("Display program version"), null },
{ null }
};
@@ -40,6 +42,8 @@ public bool log_sql = false;
public bool log_folder_normalization = false;
public bool inspector = false;
public bool revoke_certs = false;
+public bool force_cleanup = false;
+public bool force_rebuild = false;
public bool version = false;
public bool parse(string[] args) {
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index c60574c..4d354da 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -100,6 +100,7 @@ public class GearyController : Geary.BaseObject {
= new Gee.HashMap<Geary.Account, Geary.App.EmailStore>();
private Gee.HashMap<Geary.Account, Geary.Folder> inboxes
= new Gee.HashMap<Geary.Account, Geary.Folder>();
+ private Geary.Account.OpenFlag account_flags = Geary.Account.OpenFlag.NONE;
private Geary.Folder? current_folder = null;
private Cancellable cancellable_folder = new Cancellable();
private Cancellable cancellable_search = new Cancellable();
@@ -232,6 +233,13 @@ public class GearyController : Geary.BaseObject {
libnotify = new Libnotify(new_messages_monitor);
libnotify.invoked.connect(on_libnotify_invoked);
+ // Geary.Account flags
+ if (Args.force_cleanup)
+ account_flags |= Geary.Account.OpenFlag.FORCE_DB_CLEANUP;
+
+ if (Args.force_rebuild)
+ account_flags |= Geary.Account.OpenFlag.FORCE_DB_REBUILD;
+
// This is fired after the accounts are ready.
Geary.Engine.instance.opened.connect(on_engine_opened);
@@ -1058,7 +1066,7 @@ public class GearyController : Geary.BaseObject {
do {
try {
account.set_data(PROP_ATTEMPT_OPEN_ACCOUNT, true);
- yield account.open_async(cancellable);
+ yield account.open_async(account_flags, cancellable);
retry = false;
} catch (Error open_err) {
debug("Unable to open account %s: %s", account.to_string(), open_err.message);
diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala
index b5f950d..9c1287a 100644
--- a/src/engine/api/geary-account.vala
+++ b/src/engine/api/geary-account.vala
@@ -21,6 +21,31 @@
*/
public abstract class Geary.Account : BaseObject {
+ /**
+ * Flags for specific options when opening an { link Account}.
+ *
+ * @see open_async
+ */
+ [Flags]
+ public enum OpenFlag {
+ NONE = 0,
+ /**
+ * Tell the local database / disk store to rebuild.
+ *
+ * This should be used sparingly or only if directed by the user. The local database
+ * should perform this automatically when it detects it's necessary.
+ */
+ FORCE_DB_REBUILD,
+ /**
+ * Tell the local database / disk store to cleanup.
+ *
+ *
+ * This should be used sparingly or only if directed by the user. The local database
+ * should perform this automatically when it detects it's necessary.
+ */
+ FORCE_DB_CLEANUP
+ }
+
public enum Problem {
RECV_EMAIL_LOGIN_FAILED,
SEND_EMAIL_LOGIN_FAILED,
@@ -227,7 +252,7 @@ public abstract class Geary.Account : BaseObject {
* @throws EngineError.VERSION if the local store was created or updated for a different
* version of Geary.
*/
- public abstract async void open_async(Cancellable? cancellable = null) throws Error;
+ public abstract async void open_async(OpenFlag open_flags, Cancellable? cancellable = null) throws Error;
/**
* Closes the { link Account}, which makes most its operations unavailable.
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index e638c06..56133b1 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -71,8 +71,8 @@ private class Geary.ImapDB.Account : BaseObject {
attachments_dir = ImapDB.Attachment.get_attachments_dir(user_data_dir);
}
- public async void open_async(File user_data_dir, File schema_dir, Cancellable? cancellable)
- throws Error {
+ public async void open_async(Database.ImplFlag impl_flags, File user_data_dir, File schema_dir,
+ Cancellable? cancellable) throws Error {
if (db != null)
throw new EngineError.ALREADY_OPEN("IMAP database already open");
@@ -82,7 +82,7 @@ private class Geary.ImapDB.Account : BaseObject {
try {
yield db.open_async(
Db.DatabaseFlags.CREATE_DIRECTORY | Db.DatabaseFlags.CREATE_FILE |
Db.DatabaseFlags.CHECK_CORRUPTION,
- cancellable);
+ impl_flags, cancellable);
} catch (Error err) {
warning("Unable to open database: %s", err.message);
diff --git a/src/engine/imap-db/imap-db-database.vala b/src/engine/imap-db/imap-db-database.vala
index 90c2fc8..549d0be 100644
--- a/src/engine/imap-db/imap-db-database.vala
+++ b/src/engine/imap-db/imap-db-database.vala
@@ -10,6 +10,13 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
private const string DB_FILENAME = "geary.db";
private const int OPEN_PUMP_EVENT_LOOP_MSEC = 100;
+ [Flags]
+ public enum ImplFlag {
+ NONE = 0,
+ FORCE_REAP,
+ FORCE_VACUUM
+ }
+
private ProgressMonitor upgrade_monitor;
private ProgressMonitor vacuum_monitor;
private string account_owner_email;
@@ -35,7 +42,8 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
* This should only be done from the main thread, as it is designed to pump the event loop
* while the database is being opened and updated.
*/
- public async void open_async(Db.DatabaseFlags flags, Cancellable? cancellable) throws Error {
+ public async void open_async(Db.DatabaseFlags flags, ImplFlag impl_flags, Cancellable? cancellable)
+ throws Error {
open_background(flags, on_prepare_database_connection, pump_event_loop,
OPEN_PUMP_EVENT_LOOP_MSEC, cancellable);
@@ -52,7 +60,7 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
// VACUUM needs to execute in the foreground with the user given a busy prompt (and cannot
// be run at the same time as REAP)
- if ((recommended & GC.RecommendedOperation.VACUUM) != 0) {
+ if ((recommended & GC.RecommendedOperation.VACUUM) != 0 || (impl_flags & ImplFlag.FORCE_VACUUM) !=
0) {
if (!vacuum_monitor.is_in_progress)
vacuum_monitor.notify_start();
@@ -69,7 +77,7 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
}
// REAP can run in the background while the application is executing
- if ((recommended & GC.RecommendedOperation.REAP) != 0) {
+ if ((recommended & GC.RecommendedOperation.REAP) != 0 || (impl_flags & ImplFlag.FORCE_REAP) != 0) {
// run in the background and allow application to continue running
gc.reap_async.begin(gc_cancellable, on_reap_async_completed);
}
diff --git a/src/engine/imap-db/imap-db-gc.vala b/src/engine/imap-db/imap-db-gc.vala
index bba994f..1050a22 100644
--- a/src/engine/imap-db/imap-db-gc.vala
+++ b/src/engine/imap-db/imap-db-gc.vala
@@ -316,7 +316,7 @@ private class Geary.ImapDB.GC {
// at a time, deleting it from the message table and subsidiary tables. Although slow, we
// do want this to be a background task that doesn't interrupt the user. This approach
// also means gc can be interrupted at any time (i.e. the user exits the application)
- // without leaving the database in an incoherent state. gc can be resumed even if'
+ // without leaving the database in an incoherent state. gc can be resumed even if
// interrupted.
//
@@ -340,7 +340,40 @@ private class Geary.ImapDB.GC {
debug("[%s] Reaped %d messages", to_string(), count);
}
- message("[%s] Reaped completed: %d messages", to_string(), count);
+ message("[%s] Reap completed: %d messages reaped", to_string(), count);
+
+ //
+ // Delete orphaned conversations (which shouldn't happen, but it's a simple check to
+ // perform)
+ //
+
+ count = 0;
+ yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => {
+ Db.Statement stmt = cx.prepare("""
+ SELECT id
+ FROM ConversationTable
+ WHERE NOT EXISTS (
+ SELECT conversation_id
+ FROM MessageConversationTable
+ WHERE MessageConversationTable.conversation_id = ConversationTable.id
+ )
+ """);
+
+ for (Db.Result result = stmt.exec(cancellable); !result.finished; result.next(cancellable)) {
+ stmt = cx.prepare("""
+ DELETE FROM ConversationTable
+ WHERE id = ?
+ """);
+ stmt.bind_rowid(0, result.rowid_at(0));
+
+ stmt.exec(cancellable);
+ count++;
+ }
+
+ return Db.TransactionOutcome.COMMIT;
+ }, cancellable);
+
+ message("[%s] Reap orphan conversations completed: %d conversations reaped", to_string(), count);
//
// Now reap on-disk attachment files marked for deletion. Since they're added to the
@@ -363,7 +396,8 @@ private class Geary.ImapDB.GC {
debug("[%s] Reaped %d attachment files", to_string(), count);
}
- message("[%s] Completed: Reaped %d attachment files", to_string(), count);
+ message("[%s] Reap orphan attachment files completed: Reaped %d attachment files", to_string(),
+ count);
//
// To be sure everything's clean, delete any empty directories in the attachment dir tree,
@@ -462,6 +496,71 @@ private class Geary.ImapDB.GC {
stmt.exec(cancellable);
//
+ // See which conversation the message is linked to (if any)
+ //
+
+ stmt = cx.prepare("""
+ SELECT conversation_id
+ FROM MessageConversationTable
+ WHERE message_id = ?
+ """);
+ stmt.bind_rowid(0, message_id);
+
+ result = stmt.exec(cancellable);
+ int64 conversation_id = !result.finished ? result.rowid_at(0) : Db.INVALID_ROWID;
+
+ //
+ // Unlink from message-conversation table
+ //
+ // Don't delete, as MessageConversationTable tracks *all* Message-IDs seen in emails'
+ // REFERENCES (Message-ID, In-Reply-To, References). These rows are only deleted when
+ // the conversation itself is deleted, meaning all related emails are gone.
+ //
+
+ stmt = cx.prepare("""
+ UPDATE MessageConversationTable
+ SET message_id = ?
+ WHERE message_id = ?
+ """);
+ stmt.bind_null(0);
+ stmt.bind_rowid(1, message_id);
+
+ stmt.exec(cancellable);
+
+ //
+ // Look for orphaned conversations, where all Message-IDs linked to it are no longer
+ // referenced in the MessageTable
+ //
+
+ if (conversation_id != Db.INVALID_ROWID) {
+ stmt = cx.prepare("""
+ SELECT COUNT(*)
+ FROM MessageConversationTable
+ WHERE conversation_id = ? AND message_id IS NOT NULL
+ """);
+ stmt.bind_rowid(0, conversation_id);
+
+ result = stmt.exec(cancellable);
+ if (result.finished || result.int_at(0) == 0) {
+ stmt = cx.prepare("""
+ DELETE FROM MessageConversationTable
+ WHERE conversation_id = ?
+ """);
+ stmt.bind_rowid(0, conversation_id);
+
+ stmt.exec(cancellable);
+
+ stmt = cx.prepare("""
+ DELETE FROM ConversationTable
+ WHERE id = ?
+ """);
+ stmt.bind_rowid(0, conversation_id);
+
+ stmt.exec(cancellable);
+ }
+ }
+
+ //
// Delete from message table
//
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 8372b0c..3980400 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -99,7 +99,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
throw new EngineError.OPEN_REQUIRED("Account %s not opened", to_string());
}
- public override async void open_async(Cancellable? cancellable = null) throws Error {
+ public override async void open_async(Account.OpenFlag open_flags, Cancellable? cancellable = null)
throws Error {
if (open)
throw new EngineError.ALREADY_OPEN("Account %s already opened", to_string());
@@ -107,7 +107,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
Error? throw_err = null;
try {
- yield internal_open_async(cancellable);
+ yield internal_open_async(open_flags, cancellable);
} catch (Error err) {
throw_err = err;
}
@@ -118,9 +118,16 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
throw throw_err;
}
- private async void internal_open_async(Cancellable? cancellable) throws Error {
+ private async void internal_open_async(Account.OpenFlag open_flags, Cancellable? cancellable) throws
Error {
+ // Translate Account flags to ImapDB.Database flags
+ ImapDB.Database.ImplFlag db_flags = ImapDB.Database.ImplFlag.NONE;
+ if ((open_flags & Account.OpenFlag.FORCE_DB_REBUILD) != 0)
+ db_flags |= ImapDB.Database.ImplFlag.FORCE_VACUUM;
+ if ((open_flags & Account.OpenFlag.FORCE_DB_CLEANUP) != 0)
+ db_flags |= ImapDB.Database.ImplFlag.FORCE_REAP;
+
try {
- yield local.open_async(information.settings_dir, Engine.instance.resource_dir.get_child("sql"),
+ yield local.open_async(db_flags, information.settings_dir,
Engine.instance.resource_dir.get_child("sql"),
cancellable);
} catch (Error err) {
// convert database-open errors
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]