Don't load all engine contacts on startup
Prepare for doing composer autocomplete via queries by not loading all contacts at startup in the engine. Update Geary.ContactStore API to that end, and convert Geary.Account.get_contact_store accessor method to a property.
This commit is contained in:
parent
304377c787
commit
0b734f7ba2
17 changed files with 218 additions and 271 deletions
|
|
@ -166,6 +166,7 @@ src/engine/app/email-store/app-copy-operation.vala
|
|||
src/engine/app/email-store/app-fetch-operation.vala
|
||||
src/engine/app/email-store/app-list-operation.vala
|
||||
src/engine/app/email-store/app-mark-operation.vala
|
||||
src/engine/common/common-contact-store-impl.vala
|
||||
src/engine/common/common-message-data.vala
|
||||
src/engine/db/db.vala
|
||||
src/engine/db/db-connection.vala
|
||||
|
|
@ -218,7 +219,6 @@ src/engine/imap/command/imap-status-command.vala
|
|||
src/engine/imap/command/imap-store-command.vala
|
||||
src/engine/imap-db/imap-db-account.vala
|
||||
src/engine/imap-db/imap-db-attachment.vala
|
||||
src/engine/imap-db/imap-db-contact.vala
|
||||
src/engine/imap-db/imap-db-database.vala
|
||||
src/engine/imap-db/imap-db-email-identifier.vala
|
||||
src/engine/imap-db/imap-db-folder.vala
|
||||
|
|
@ -239,7 +239,6 @@ src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala
|
|||
src/engine/imap-engine/imap-engine-account-operation.vala
|
||||
src/engine/imap-engine/imap-engine-account-processor.vala
|
||||
src/engine/imap-engine/imap-engine-account-synchronizer.vala
|
||||
src/engine/imap-engine/imap-engine-contact-store.vala
|
||||
src/engine/imap-engine/imap-engine-email-prefetcher.vala
|
||||
src/engine/imap-engine/imap-engine-generic-account.vala
|
||||
src/engine/imap-engine/imap-engine-generic-folder.vala
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ public class Application.ContactStore : Geary.BaseObject {
|
|||
}
|
||||
if (contact == null) {
|
||||
Geary.Contact? engine =
|
||||
this.account.get_contact_store().get_by_rfc822(mailbox);
|
||||
yield this.account.contact_store.get_by_rfc822(
|
||||
mailbox, cancellable
|
||||
);
|
||||
contact = new Contact(this, individual, engine, mailbox);
|
||||
if (individual != null) {
|
||||
this.contact_id_cache.set_entry(individual.id, contact);
|
||||
|
|
|
|||
|
|
@ -182,14 +182,17 @@ public class Application.Contact : Geary.BaseObject {
|
|||
throws GLib.Error {
|
||||
ContactStore? store = this.store;
|
||||
if (store != null && this.contact != null) {
|
||||
Geary.ContactFlags flags = new Geary.ContactFlags();
|
||||
flags.add(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES);
|
||||
Geary.ContactFlags flags = (
|
||||
this.contact.contact_flags ?? new Geary.ContactFlags()
|
||||
);
|
||||
if (enabled) {
|
||||
flags.add(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES);
|
||||
} else {
|
||||
flags.remove(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES);
|
||||
}
|
||||
|
||||
yield store.account.get_contact_store().mark_contacts_async(
|
||||
Geary.Collection.single(this.contact),
|
||||
enabled ? flags : null,
|
||||
!enabled ? flags : null //,
|
||||
// XXX cancellable
|
||||
yield store.account.contact_store.update_contacts(
|
||||
Geary.Collection.single(this.contact), cancellable
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -616,20 +616,16 @@ public class Application.Controller : Geary.BaseObject {
|
|||
);
|
||||
account.report_problem.connect(on_report_problem);
|
||||
connect_account_async.begin(account, cancellable_open_account);
|
||||
|
||||
ContactListStore list_store = this.contact_list_store_cache.create(account.get_contact_store());
|
||||
account.contacts_loaded.connect(list_store.set_sort_function);
|
||||
}
|
||||
|
||||
private async void close_account(Geary.AccountInformation config) {
|
||||
AccountContext? context = this.accounts.get(config);
|
||||
if (context != null) {
|
||||
Geary.Account account = context.account;
|
||||
Geary.ContactStore contact_store = account.get_contact_store();
|
||||
Geary.ContactStore contact_store = account.contact_store;
|
||||
ContactListStore list_store =
|
||||
this.contact_list_store_cache.get(contact_store);
|
||||
|
||||
account.contacts_loaded.disconnect(list_store.set_sort_function);
|
||||
this.contact_list_store_cache.unset(contact_store);
|
||||
|
||||
if (this.current_account == account) {
|
||||
|
|
|
|||
|
|
@ -240,8 +240,6 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
|
||||
public Configuration config { get; set; }
|
||||
|
||||
private ContactListStore? contact_list_store = null;
|
||||
|
||||
private string body_html = "";
|
||||
|
||||
[GtkChild]
|
||||
|
|
@ -380,8 +378,6 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
// Is the composer closing (e.g. saving a draft or sending)?
|
||||
private bool is_closing = false;
|
||||
|
||||
private ContactListStoreCache contact_list_store_cache;
|
||||
|
||||
private ComposerContainer container {
|
||||
get { return (ComposerContainer) parent; }
|
||||
}
|
||||
|
|
@ -406,7 +402,6 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
Configuration config) {
|
||||
base_ref();
|
||||
this.account = account;
|
||||
this.contact_list_store_cache = contact_list_store_cache;
|
||||
this.config = config;
|
||||
this.compose_type = compose_type;
|
||||
if (this.compose_type == ComposeType.NEW_MESSAGE)
|
||||
|
|
@ -653,22 +648,7 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
* Loads and sets contact auto-complete data for the current account.
|
||||
*/
|
||||
private void load_entry_completions() {
|
||||
Geary.ContactStore contacts = this.account.get_contact_store();
|
||||
if (this.contact_list_store == null ||
|
||||
this.contact_list_store.contact_store != contacts) {
|
||||
ContactListStore? store = this.contact_list_store_cache.get(contacts);
|
||||
|
||||
if (store == null) {
|
||||
error("Error loading contact_list_store from cache");
|
||||
} else {
|
||||
this.contact_list_store = store;
|
||||
|
||||
this.to_entry.completion = new ContactEntryCompletion(store);
|
||||
this.cc_entry.completion = new ContactEntryCompletion(store);
|
||||
this.bcc_entry.completion = new ContactEntryCompletion(store);
|
||||
this.reply_to_entry.completion = new ContactEntryCompletion(store);
|
||||
}
|
||||
}
|
||||
Geary.ContactStore contacts = this.account.contact_store;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -70,29 +70,29 @@ public class ContactListStore : Gtk.ListStore, Geary.BaseInterface {
|
|||
base_ref();
|
||||
set_column_types(Column.get_types());
|
||||
this.contact_store = contact_store;
|
||||
contact_store.contacts_added.connect(on_contacts_added);
|
||||
contact_store.contacts_updated.connect(on_contacts_updated);
|
||||
//contact_store.contacts_added.connect(on_contacts_added);
|
||||
//contact_store.contacts_updated.connect(on_contacts_updated);
|
||||
}
|
||||
|
||||
~ContactListStore() {
|
||||
base_unref();
|
||||
this.contact_store.contacts_added.disconnect(on_contacts_added);
|
||||
this.contact_store.contacts_updated.disconnect(on_contacts_updated);
|
||||
//this.contact_store.contacts_added.disconnect(on_contacts_added);
|
||||
//this.contact_store.contacts_updated.disconnect(on_contacts_updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads contacts from the model's contact store.
|
||||
*/
|
||||
public async void load() {
|
||||
uint count = 0;
|
||||
foreach (Geary.Contact contact in this.contact_store.contacts) {
|
||||
add_contact(contact);
|
||||
count++;
|
||||
if (count % LOAD_BATCH_SIZE == 0) {
|
||||
Idle.add(load.callback);
|
||||
yield;
|
||||
}
|
||||
}
|
||||
// uint count = 0;
|
||||
// foreach (Geary.Contact contact in this.contact_store.contacts) {
|
||||
// add_contact(contact);
|
||||
// count++;
|
||||
// if (count % LOAD_BATCH_SIZE == 0) {
|
||||
// Idle.add(load.callback);
|
||||
// yield;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public void set_sort_function() {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ public abstract class Geary.Account : BaseObject {
|
|||
*/
|
||||
public ClientService outgoing { get; private set; }
|
||||
|
||||
/**
|
||||
* The contact information store for this account.
|
||||
*/
|
||||
public Geary.ContactStore contact_store { get; protected set; }
|
||||
|
||||
public Geary.ProgressMonitor search_upgrade_monitor { get; protected set; }
|
||||
public Geary.ProgressMonitor db_upgrade_monitor { get; protected set; }
|
||||
public Geary.ProgressMonitor db_vacuum_monitor { get; protected set; }
|
||||
|
|
@ -151,8 +156,6 @@ public abstract class Geary.Account : BaseObject {
|
|||
*/
|
||||
public signal void report_problem(Geary.ProblemReport problem);
|
||||
|
||||
public signal void contacts_loaded();
|
||||
|
||||
/**
|
||||
* Fired when folders become available or unavailable in the account.
|
||||
*
|
||||
|
|
@ -384,11 +387,6 @@ public abstract class Geary.Account : BaseObject {
|
|||
*/
|
||||
public abstract Gee.Collection<Geary.Folder> list_folders() throws Error;
|
||||
|
||||
/**
|
||||
* Gets a perpetually update-to-date collection of autocompletion contacts.
|
||||
*/
|
||||
public abstract Geary.ContactStore get_contact_store();
|
||||
|
||||
/**
|
||||
* Returns the folder representing the given special folder type. If no such folder exists,
|
||||
* null is returned.
|
||||
|
|
|
|||
|
|
@ -1,50 +1,28 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public abstract class Geary.ContactStore : BaseObject {
|
||||
public Gee.Collection<Contact> contacts {
|
||||
owned get { return contact_map.values; }
|
||||
}
|
||||
/**
|
||||
* Interface for objects that provide contact information storage.
|
||||
*
|
||||
* Implementations of this class will typically be backed by a
|
||||
* database. As such, to avoid IO overhead, batch calls together using
|
||||
* collections of contacts wherever possible.
|
||||
*/
|
||||
public interface Geary.ContactStore : GLib.Object {
|
||||
|
||||
private Gee.Map<string, Contact> contact_map;
|
||||
/** Returns the contact matching the given email address, if any */
|
||||
public abstract async Contact? get_by_rfc822(Geary.RFC822.MailboxAddress address,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error;
|
||||
|
||||
public signal void contacts_added(Gee.Collection<Contact> contacts);
|
||||
/** Updates (or adds) a set of contacts in the underlying store */
|
||||
public abstract async void update_contacts(Gee.Collection<Contact> updated,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error;
|
||||
|
||||
public signal void contacts_updated(Gee.Collection<Contact> contacts);
|
||||
|
||||
protected ContactStore() {
|
||||
contact_map = new Gee.HashMap<string, Contact>();
|
||||
}
|
||||
|
||||
public void update_contacts(Gee.Collection<Contact> new_contacts) {
|
||||
Gee.LinkedList<Contact> added = new Gee.LinkedList<Contact>();
|
||||
Gee.LinkedList<Contact> updated = new Gee.LinkedList<Contact>();
|
||||
|
||||
foreach (Contact contact in new_contacts) {
|
||||
Contact? old_contact = contact_map[contact.normalized_email];
|
||||
if (old_contact == null) {
|
||||
contact_map[contact.normalized_email] = contact;
|
||||
added.add(contact);
|
||||
} else if (old_contact.highest_importance < contact.highest_importance) {
|
||||
old_contact.highest_importance = contact.highest_importance;
|
||||
updated.add(contact);
|
||||
}
|
||||
}
|
||||
|
||||
if (!added.is_empty)
|
||||
contacts_added(added);
|
||||
|
||||
if (!updated.is_empty)
|
||||
contacts_updated(updated);
|
||||
}
|
||||
|
||||
public abstract async void mark_contacts_async(Gee.Collection<Contact> contacts, ContactFlags? to_add,
|
||||
ContactFlags? to_remove) throws Error;
|
||||
|
||||
public Contact? get_by_rfc822(Geary.RFC822.MailboxAddress address) {
|
||||
return contact_map[address.address.normalize().casefold()];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
141
src/engine/common/common-contact-store-impl.vala
Normal file
141
src/engine/common/common-contact-store-impl.vala
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An database-backed implementation of Geary.Contacts
|
||||
*/
|
||||
internal class Geary.ContactStoreImpl : BaseObject, Geary.ContactStore {
|
||||
|
||||
|
||||
// Insert or update a contact in the ContactTable. If contact
|
||||
// already exists, flags are merged and the importance is updated
|
||||
// to the highest importance seen.
|
||||
//
|
||||
// Internal and static since it is used by ImapDB.Database during
|
||||
// upgrades
|
||||
internal static void do_update_contact(Db.Connection cx,
|
||||
Contact contact,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
Contact? existing = do_fetch_contact(
|
||||
cx, contact.email, cancellable
|
||||
);
|
||||
|
||||
if (contact == null) {
|
||||
// Not found, so just insert it
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"INSERT INTO ContactTable(normalized_email, email, real_name, flags, highest_importance) "
|
||||
+ "VALUES(?, ?, ?, ?, ?)");
|
||||
stmt.bind_string(0, contact.normalized_email);
|
||||
stmt.bind_string(1, contact.email);
|
||||
stmt.bind_string(2, contact.real_name);
|
||||
stmt.bind_string(3, (contact.contact_flags != null) ? contact.contact_flags.serialize() : null);
|
||||
stmt.bind_int(4, contact.highest_importance);
|
||||
|
||||
stmt.exec(cancellable);
|
||||
} else {
|
||||
// Update existing contact
|
||||
|
||||
// Merge two flags sets together
|
||||
ContactFlags? merged_flags = contact.contact_flags;
|
||||
if (existing.contact_flags != null) {
|
||||
if (merged_flags != null) {
|
||||
merged_flags.add_all(existing.contact_flags);
|
||||
} else {
|
||||
merged_flags = existing.contact_flags;
|
||||
}
|
||||
}
|
||||
|
||||
// update remaining fields, careful not to overwrite
|
||||
// non-null real_name with null (but using latest
|
||||
// real_name if supplied) ... email is not updated (it's
|
||||
// how existing was keyed), normalized_email is inserted at
|
||||
// the same time as email, leaving only real_name, flags,
|
||||
// and highest_importance
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"UPDATE ContactTable SET real_name=?, flags=?, highest_importance=? WHERE email=?");
|
||||
stmt.bind_string(
|
||||
0, !String.is_empty(contact.real_name) ? contact.real_name : existing.real_name
|
||||
);
|
||||
stmt.bind_string(
|
||||
1, (merged_flags != null) ? merged_flags.serialize() : null
|
||||
);
|
||||
stmt.bind_int(
|
||||
2, int.max(contact.highest_importance, existing.highest_importance)
|
||||
);
|
||||
stmt.bind_string(
|
||||
3, contact.email
|
||||
);
|
||||
|
||||
stmt.exec(cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
// Static since it is indirectly used by ImapDB.Database during
|
||||
// upgrades
|
||||
private static Contact? do_fetch_contact(Db.Connection cx,
|
||||
string email,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"SELECT real_name, highest_importance, normalized_email, flags FROM ContactTable "
|
||||
+ "WHERE email=?");
|
||||
stmt.bind_string(0, email);
|
||||
|
||||
Db.Result result = stmt.exec(cancellable);
|
||||
if (result.finished)
|
||||
return null;
|
||||
|
||||
return new Contact(
|
||||
email,
|
||||
result.string_at(0),
|
||||
result.int_at(1),
|
||||
result.string_at(2),
|
||||
ContactFlags.deserialize(result.string_at(3))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private Geary.Db.Database backing;
|
||||
|
||||
|
||||
internal ContactStoreImpl(Geary.Db.Database backing) {
|
||||
base_ref();
|
||||
this.backing = backing;
|
||||
}
|
||||
|
||||
/** Returns the contact matching the given email address, if any */
|
||||
public async Contact? get_by_rfc822(Geary.RFC822.MailboxAddress address,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
Contact? contact = null;
|
||||
yield this.backing.exec_transaction_async(
|
||||
Db.TransactionType.RO,
|
||||
(cx, cancellable) => {
|
||||
contact = do_fetch_contact(cx, address.mailbox, cancellable);
|
||||
return Db.TransactionOutcome.COMMIT;
|
||||
},
|
||||
cancellable);
|
||||
return contact;
|
||||
}
|
||||
|
||||
public async void update_contacts(Gee.Collection<Contact> updated,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
yield this.backing.exec_transaction_async(
|
||||
Db.TransactionType.RW,
|
||||
(cx, cancellable) => {
|
||||
foreach (Contact contact in updated) {
|
||||
do_update_contact(cx, contact, cancellable);
|
||||
}
|
||||
return Db.TransactionOutcome.COMMIT;
|
||||
},
|
||||
cancellable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -74,7 +74,6 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
private static Gee.HashMap<string, string> search_op_is_values =
|
||||
new Gee.HashMap<string, string>();
|
||||
|
||||
public signal void contacts_loaded();
|
||||
|
||||
/**
|
||||
* The root path for all remote IMAP folders.
|
||||
|
|
@ -90,7 +89,7 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
}
|
||||
|
||||
// Only available when the Account is opened
|
||||
public ImapEngine.ContactStore contact_store { get; private set; }
|
||||
public ContactStore contact_store { get; private set; }
|
||||
public IntervalProgressMonitor search_index_monitor { get; private set;
|
||||
default = new IntervalProgressMonitor(ProgressType.SEARCH_INDEX, 0, 0); }
|
||||
public SimpleProgressMonitor upgrade_monitor { get; private set; default = new SimpleProgressMonitor(
|
||||
|
|
@ -257,7 +256,6 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
|
||||
public Account(AccountInformation config) {
|
||||
this.account_information = config;
|
||||
this.contact_store = new ImapEngine.ContactStore(this);
|
||||
this.name = config.id + ":db";
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +352,7 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
return false;
|
||||
});
|
||||
|
||||
initialize_contacts(cancellable);
|
||||
this.contact_store = new ContactStoreImpl(db);
|
||||
}
|
||||
|
||||
public async void close_async(Cancellable? cancellable) throws Error {
|
||||
|
|
@ -458,40 +456,6 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
}, cancellable);
|
||||
}
|
||||
|
||||
private void initialize_contacts(Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Gee.Collection<Contact> contacts = new Gee.LinkedList<Contact>();
|
||||
Db.TransactionOutcome outcome = db.exec_transaction(Db.TransactionType.RO,
|
||||
(context) => {
|
||||
Db.Statement statement = context.prepare(
|
||||
"SELECT email, real_name, highest_importance, normalized_email, flags " +
|
||||
"FROM ContactTable");
|
||||
|
||||
Db.Result result = statement.exec(cancellable);
|
||||
while (!result.finished) {
|
||||
try {
|
||||
Contact contact = new Contact(result.nonnull_string_at(0), result.string_at(1),
|
||||
result.int_at(2), result.string_at(3), ContactFlags.deserialize(result.string_at(4)));
|
||||
contacts.add(contact);
|
||||
} catch (Geary.DatabaseError err) {
|
||||
// We don't want to abandon loading all contacts just because there was a
|
||||
// problem with one.
|
||||
debug("Problem loading contact: %s", err.message);
|
||||
}
|
||||
|
||||
result.next();
|
||||
}
|
||||
|
||||
return Db.TransactionOutcome.DONE;
|
||||
}, cancellable);
|
||||
|
||||
if (outcome == Db.TransactionOutcome.DONE) {
|
||||
contact_store.update_contacts(contacts);
|
||||
contacts_loaded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all children of a given folder.
|
||||
*
|
||||
|
|
@ -1392,21 +1356,6 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
return email;
|
||||
}
|
||||
|
||||
public async void update_contact_flags_async(Geary.Contact contact, Cancellable? cancellable)
|
||||
throws Error{
|
||||
check_open();
|
||||
|
||||
yield db.exec_transaction_async(Db.TransactionType.RW, (cx, cancellable) => {
|
||||
Db.Statement update_stmt =
|
||||
cx.prepare("UPDATE ContactTable SET flags=? WHERE email=?");
|
||||
update_stmt.bind_string(0, contact.contact_flags.serialize());
|
||||
update_stmt.bind_string(1, contact.email);
|
||||
update_stmt.exec(cancellable);
|
||||
|
||||
return Db.TransactionOutcome.COMMIT;
|
||||
}, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of each passed-in email identifier to the set of folders
|
||||
* that contain it. If an email id doesn't appear in the resulting map,
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
namespace Geary.ImapDB {
|
||||
|
||||
private Contact? do_fetch_contact(Db.Connection cx, string email, Cancellable? cancellable)
|
||||
throws Error {
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"SELECT real_name, highest_importance, normalized_email, flags FROM ContactTable "
|
||||
+ "WHERE email=?");
|
||||
stmt.bind_string(0, email);
|
||||
|
||||
Db.Result result = stmt.exec(cancellable);
|
||||
if (result.finished)
|
||||
return null;
|
||||
|
||||
return new Contact(email, result.string_at(0), result.int_at(1), result.string_at(2),
|
||||
ContactFlags.deserialize(result.string_at(3)));
|
||||
}
|
||||
|
||||
// Insert or update a contact in the ContactTable. If contact already exists, flags are merged
|
||||
// and the importance is updated to the highest importance seen.
|
||||
private void do_update_contact(Db.Connection connection, Contact contact,
|
||||
Cancellable? cancellable) throws Error {
|
||||
Contact? existing_contact = do_fetch_contact(connection, contact.email, cancellable);
|
||||
|
||||
// If not found, insert and done
|
||||
if (existing_contact == null) {
|
||||
Db.Statement stmt = connection.prepare(
|
||||
"INSERT INTO ContactTable(normalized_email, email, real_name, flags, highest_importance) "
|
||||
+ "VALUES(?, ?, ?, ?, ?)");
|
||||
stmt.bind_string(0, contact.normalized_email);
|
||||
stmt.bind_string(1, contact.email);
|
||||
stmt.bind_string(2, contact.real_name);
|
||||
stmt.bind_string(3, (contact.contact_flags != null) ? contact.contact_flags.serialize() : null);
|
||||
stmt.bind_int(4, contact.highest_importance);
|
||||
|
||||
stmt.exec(cancellable);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// merge two flags sets together
|
||||
ContactFlags? merged_flags = contact.contact_flags;
|
||||
if (existing_contact.contact_flags != null) {
|
||||
if (merged_flags != null)
|
||||
merged_flags.add_all(existing_contact.contact_flags);
|
||||
else
|
||||
merged_flags = existing_contact.contact_flags;
|
||||
}
|
||||
|
||||
// update remaining fields, careful not to overwrite non-null real_name with null (but
|
||||
// using latest real_name if supplied) ... email is not updated (it's how existing_contact was
|
||||
// keyed), normalized_email is inserted at the same time as email, leaving only real_name,
|
||||
// flags, and highest_importance
|
||||
Db.Statement stmt = connection.prepare(
|
||||
"UPDATE ContactTable SET real_name=?, flags=?, highest_importance=? WHERE email=?");
|
||||
stmt.bind_string(0, !String.is_empty(contact.real_name) ? contact.real_name : existing_contact.real_name);
|
||||
stmt.bind_string(1, (merged_flags != null) ? merged_flags.serialize() : null);
|
||||
stmt.bind_int(2, int.max(contact.highest_importance, existing_contact.highest_importance));
|
||||
stmt.bind_string(3, contact.email);
|
||||
|
||||
stmt.exec(cancellable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
|
|||
MessageAddresses message_addresses =
|
||||
new MessageAddresses.from_result(account_owner_email, result);
|
||||
foreach (Contact contact in message_addresses.contacts) {
|
||||
do_update_contact(cx, contact, null);
|
||||
ContactStoreImpl.do_update_contact(cx, contact, null);
|
||||
}
|
||||
result.next();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,8 +321,11 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
return Db.TransactionOutcome.COMMIT;
|
||||
}, cancellable);
|
||||
|
||||
if (updated_contacts.size > 0)
|
||||
contact_store.update_contacts(updated_contacts);
|
||||
if (updated_contacts.size > 0) {
|
||||
yield this.contact_store.update_contacts(
|
||||
updated_contacts, cancellable
|
||||
);
|
||||
}
|
||||
|
||||
if (update_totals) {
|
||||
// Update the email_unread properties.
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
internal class Geary.ImapEngine.ContactStore : Geary.ContactStore {
|
||||
private weak ImapDB.Account account;
|
||||
|
||||
internal ContactStore(ImapDB.Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public override async void mark_contacts_async(Gee.Collection<Contact> contacts, ContactFlags? to_add,
|
||||
ContactFlags? to_remove) throws Error{
|
||||
foreach (Contact contact in contacts) {
|
||||
if (contact.contact_flags == null)
|
||||
contact.contact_flags = new Geary.ContactFlags();
|
||||
|
||||
if (to_add != null)
|
||||
contact.contact_flags.add_all(to_add);
|
||||
|
||||
if (to_remove != null)
|
||||
contact.contact_flags.remove_all(to_remove);
|
||||
|
||||
yield account.update_contact_flags_async(contact, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
base(config, imap, smtp);
|
||||
|
||||
this.local = local;
|
||||
this.local.contacts_loaded.connect(() => { contacts_loaded(); });
|
||||
this.contact_store = local.contact_store;
|
||||
|
||||
imap.min_pool_size = IMAP_MIN_POOL_SIZE;
|
||||
imap.notify["current-status"].connect(
|
||||
|
|
@ -485,10 +485,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
return all_folders;
|
||||
}
|
||||
|
||||
public override Geary.ContactStore get_contact_store() {
|
||||
return local.contact_store;
|
||||
}
|
||||
|
||||
public override async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special,
|
||||
Cancellable? cancellable) throws Error {
|
||||
if (!(special in get_supported_special_folders())) {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ geary_engine_vala_sources = files(
|
|||
'app/email-store/app-list-operation.vala',
|
||||
'app/email-store/app-mark-operation.vala',
|
||||
|
||||
'common/common-contact-store-impl.vala',
|
||||
'common/common-message-data.vala',
|
||||
|
||||
'db/db.vala',
|
||||
|
|
@ -172,7 +173,6 @@ geary_engine_vala_sources = files(
|
|||
|
||||
'imap-db/imap-db-account.vala',
|
||||
'imap-db/imap-db-attachment.vala',
|
||||
'imap-db/imap-db-contact.vala',
|
||||
'imap-db/imap-db-database.vala',
|
||||
'imap-db/imap-db-email-identifier.vala',
|
||||
'imap-db/imap-db-folder.vala',
|
||||
|
|
@ -189,7 +189,6 @@ geary_engine_vala_sources = files(
|
|||
'imap-engine/imap-engine-account-operation.vala',
|
||||
'imap-engine/imap-engine-account-processor.vala',
|
||||
'imap-engine/imap-engine-account-synchronizer.vala',
|
||||
'imap-engine/imap-engine-contact-store.vala',
|
||||
'imap-engine/imap-engine-email-prefetcher.vala',
|
||||
'imap-engine/imap-engine-generic-account.vala',
|
||||
'imap-engine/imap-engine-generic-folder.vala',
|
||||
|
|
|
|||
|
|
@ -16,18 +16,24 @@ public class Geary.MockAccount : Account, MockObject {
|
|||
|
||||
}
|
||||
|
||||
public class MockContactStore : ContactStore {
|
||||
public class MockContactStore : GLib.Object, ContactStore {
|
||||
|
||||
internal MockContactStore() {
|
||||
|
||||
}
|
||||
|
||||
public override async void
|
||||
mark_contacts_async(Gee.Collection<Contact> contacts,
|
||||
ContactFlags? to_add,
|
||||
ContactFlags? to_remove) throws Error {
|
||||
throw new EngineError.UNSUPPORTED("Mock method");
|
||||
}
|
||||
public async Contact? get_by_rfc822(Geary.RFC822.MailboxAddress address,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
throw new EngineError.UNSUPPORTED("Mock method");
|
||||
}
|
||||
|
||||
public async void update_contacts(Gee.Collection<Contact> contacts,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
throw new EngineError.UNSUPPORTED("Mock method");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -171,10 +177,6 @@ public class Geary.MockAccount : Account, MockObject {
|
|||
);
|
||||
}
|
||||
|
||||
public override Geary.ContactStore get_contact_store() {
|
||||
return new MockContactStore();
|
||||
}
|
||||
|
||||
public override Folder? get_special_folder(SpecialFolderType special)
|
||||
throws Error {
|
||||
return object_call<Folder?>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue