diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 86490129..02cc18f9 100644
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -8,3 +8,4 @@ install(FILES version-005.sql DESTINATION ${SQL_DEST})
install(FILES version-006.sql DESTINATION ${SQL_DEST})
install(FILES version-007.sql DESTINATION ${SQL_DEST})
install(FILES version-008.sql DESTINATION ${SQL_DEST})
+install(FILES version-009.sql DESTINATION ${SQL_DEST})
diff --git a/sql/version-009.sql b/sql/version-009.sql
new file mode 100644
index 00000000..ab236902
--- /dev/null
+++ b/sql/version-009.sql
@@ -0,0 +1,6 @@
+--
+-- Add flags column to the ContactTable
+--
+
+ALTER TABLE ContactTable ADD COLUMN flags TEXT;
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5c8dfd4b..810fbfd4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,13 +18,13 @@ engine/api/geary-attachment.vala
engine/api/geary-base-object.vala
engine/api/geary-composed-email.vala
engine/api/geary-contact.vala
+engine/api/geary-contact-flags.vala
engine/api/geary-contact-importance.vala
engine/api/geary-contact-store.vala
engine/api/geary-conversation.vala
engine/api/geary-conversation-monitor.vala
engine/api/geary-credentials.vala
engine/api/geary-credentials-mediator.vala
-engine/api/geary-email-flag.vala
engine/api/geary-email-flags.vala
engine/api/geary-email-identifier.vala
engine/api/geary-email-properties.vala
@@ -42,6 +42,8 @@ engine/api/geary-folder-supports-mark.vala
engine/api/geary-folder-supports-move.vala
engine/api/geary-folder-supports-remove.vala
engine/api/geary-logging.vala
+engine/api/geary-named-flag.vala
+engine/api/geary-named-flags.vala
engine/api/geary-service-provider.vala
engine/api/geary-special-folder-type.vala
@@ -123,6 +125,7 @@ engine/imap-db/outbox/smtp-outbox-folder-root.vala
engine/imap-engine/imap-engine.vala
engine/imap-engine/imap-engine-account-synchronizer.vala
engine/imap-engine/imap-engine-batch-operations.vala
+engine/imap-engine/imap-engine-contact-store.vala
engine/imap-engine/imap-engine-email-flag-watcher.vala
engine/imap-engine/imap-engine-email-prefetcher.vala
engine/imap-engine/imap-engine-generic-account.vala
diff --git a/src/client/views/conversation-viewer.vala b/src/client/views/conversation-viewer.vala
index 7fc58dff..9e0f2487 100644
--- a/src/client/views/conversation-viewer.vala
+++ b/src/client/views/conversation-viewer.vala
@@ -190,17 +190,23 @@ public class ConversationViewer : Gtk.Box {
}
if (remote_images) {
- if (email.load_remote_images().is_certain()) {
- show_images_email(div_message);
+ Geary.Contact contact = current_folder.account.get_contact_store().get_by_rfc822(
+ email.get_primary_originator());
+ bool always_load = contact != null && contact.always_load_remote_images();
+
+ if (always_load || email.load_remote_images().is_certain()) {
+ show_images_email(div_message, false);
} else {
WebKit.DOM.HTMLElement remote_images_bar =
Util.DOM.select(div_message, ".remote_images");
try {
((WebKit.DOM.Element) remote_images_bar).get_class_list().add("show");
remote_images_bar.set_inner_html("""%s %s
- """.printf(
+
+ """.printf(
remote_images_bar.get_inner_html(),
- _("This message contains remote images."), _("Show Images")));
+ _("This message contains remote images."), _("Show images"),
+ _("Always show from sender")));
} catch (Error error) {
warning("Error showing remote images bar: %s", error.message);
}
@@ -239,6 +245,7 @@ public class ConversationViewer : Gtk.Box {
bind_event(web_view, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, this);
bind_event(web_view, ".attachment_container .attachment", "contextmenu", (Callback) on_attachment_menu, this);
bind_event(web_view, ".remote_images .show_images", "click", (Callback) on_show_images, this);
+ bind_event(web_view, ".remote_images .show_from", "click", (Callback) on_show_images_from, this);
bind_event(web_view, ".remote_images .close_show_images", "click", (Callback) on_close_show_images, this);
// Update the search results
@@ -689,11 +696,50 @@ public class ConversationViewer : Gtk.Box {
ConversationViewer conversation_viewer) {
WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
if (email_element != null)
- conversation_viewer.show_images_email(email_element);
+ conversation_viewer.show_images_email(email_element, true);
}
- private void show_images_email(WebKit.DOM.Element email_element) {
- // TODO: Remember that these images have been shown.
+ private static void on_show_images_from(WebKit.DOM.Element element, WebKit.DOM.Event event,
+ ConversationViewer conversation_viewer) {
+ Geary.Email? email = conversation_viewer.get_email_from_element(element);
+ if (email == null)
+ return;
+
+ Geary.ContactStore contact_store =
+ conversation_viewer.current_folder.account.get_contact_store();
+ Geary.Contact? contact = contact_store.get_by_rfc822(email.get_primary_originator());
+ if (contact == null) {
+ debug("Couldn't find contact for %s", email.from.to_string());
+ return;
+ }
+
+ Geary.ContactFlags flags = new Geary.ContactFlags();
+ flags.add(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES);
+ Gee.ArrayList contact_list = new Gee.ArrayList();
+ contact_list.add(contact);
+ contact_store.mark_contacts_async.begin(contact_list, flags, null);
+
+ WebKit.DOM.Document document = conversation_viewer.web_view.get_dom_document();
+ try {
+ WebKit.DOM.NodeList nodes = document.query_selector_all(".email");
+ for (ulong i = 0; i < nodes.length; i ++) {
+ WebKit.DOM.Element? email_element = nodes.item(i) as WebKit.DOM.Element;
+ if (email_element != null) {
+ WebKit.DOM.Element? address = email_element.query_selector(".address_name");
+ if (address != null) {
+ WebKit.DOM.Element? mailto_link = address.parent_node as WebKit.DOM.Element;
+ if (mailto_link != null && contact.normalized_email ==
+ mailto_link.get_attribute("href").substring(7).normalize().casefold())
+ conversation_viewer.show_images_email(email_element, false);
+ }
+ }
+ }
+ } catch (Error error) {
+ debug("Error showing images: %s", error.message);
+ }
+ }
+
+ private void show_images_email(WebKit.DOM.Element email_element, bool remember) {
try {
WebKit.DOM.NodeList body_nodes = email_element.query_selector_all(".body");
for (ulong j = 0; j < body_nodes.length; j++) {
@@ -720,12 +766,14 @@ public class ConversationViewer : Gtk.Box {
warning("Error showing images: %s", error.message);
}
- // only add flag to load remote images if not already present
- Geary.Email? message = get_email_from_element(email_element);
- if (message != null && !message.load_remote_images().is_certain()) {
- Geary.EmailFlags flags = new Geary.EmailFlags();
- flags.add(Geary.EmailFlags.LOAD_REMOTE_IMAGES);
- mark_message(message, flags, null);
+ if (remember) {
+ // only add flag to load remote images if not already present
+ Geary.Email? message = get_email_from_element(email_element);
+ if (message != null && !message.load_remote_images().is_certain()) {
+ Geary.EmailFlags flags = new Geary.EmailFlags();
+ flags.add(Geary.EmailFlags.LOAD_REMOTE_IMAGES);
+ mark_message(message, flags, null);
+ }
}
}
diff --git a/src/engine/api/geary-contact-flags.vala b/src/engine/api/geary-contact-flags.vala
new file mode 100644
index 00000000..237e04b8
--- /dev/null
+++ b/src/engine/api/geary-contact-flags.vala
@@ -0,0 +1,50 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * A collection of NamedFlags that can be used to enable/disable various user-defined
+ * options for a contact. System- or Geary-defined flags are available as static
+ * members.
+ */
+
+public class Geary.ContactFlags : Geary.NamedFlags {
+ private static NamedFlag? _always_load_remote_images = null;
+ public static NamedFlag ALWAYS_LOAD_REMOTE_IMAGES { get {
+ if (_always_load_remote_images == null)
+ _always_load_remote_images = new NamedFlag("ALWAYSLOADREMOTEIMAGES");
+
+ return _always_load_remote_images;
+ } }
+
+ public ContactFlags() {
+ }
+
+ public static ContactFlags deserialize(string? flags) {
+ if (String.is_empty(flags))
+ return new ContactFlags();
+
+ ContactFlags result = new ContactFlags();
+
+ string[] tokens = flags.split(" ");
+ foreach (string flag in tokens)
+ result.add(new NamedFlag(flag));
+
+ return result;
+ }
+
+ public inline bool always_load_remote_images() {
+ return contains(ALWAYS_LOAD_REMOTE_IMAGES);
+ }
+
+ public string serialize() {
+ string ret = "";
+ foreach (NamedFlag flag in list)
+ ret += flag.serialize() + " ";
+
+ return ret.strip();
+ }
+}
+
diff --git a/src/engine/api/geary-contact-store.vala b/src/engine/api/geary-contact-store.vala
index b1c73c82..2a308762 100644
--- a/src/engine/api/geary-contact-store.vala
+++ b/src/engine/api/geary-contact-store.vala
@@ -4,7 +4,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class Geary.ContactStore : BaseObject {
+public abstract class Geary.ContactStore : BaseObject {
public Gee.Collection contacts {
owned get { return contact_map.values; }
}
@@ -24,6 +24,13 @@ public class Geary.ContactStore : BaseObject {
update_contact(contact);
}
+ public abstract async void mark_contacts_async(Gee.Collection 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()];
+ }
+
private void update_contact(Contact contact) {
Contact? old_contact = contact_map[contact.normalized_email];
if (old_contact == null) {
diff --git a/src/engine/api/geary-contact.vala b/src/engine/api/geary-contact.vala
index 180a4460..9ac575b9 100644
--- a/src/engine/api/geary-contact.vala
+++ b/src/engine/api/geary-contact.vala
@@ -9,12 +9,15 @@ public class Geary.Contact : BaseObject {
public string email { get; private set; }
public string? real_name { get; private set; }
public int highest_importance { get; set; }
+ public ContactFlags? contact_flags { get; set; default = null; }
- public Contact(string email, string? real_name, int highest_importance, string? normalized_email = null) {
+ public Contact(string email, string? real_name, int highest_importance,
+ string? normalized_email = null, ContactFlags? contact_flags = null) {
this.normalized_email = normalized_email ?? email.normalize().casefold();
this.email = email;
this.real_name = real_name;
this.highest_importance = highest_importance;
+ this.contact_flags = contact_flags;
}
public Contact.from_rfc822_address(RFC822.MailboxAddress address, int highest_importance) {
@@ -24,4 +27,8 @@ public class Geary.Contact : BaseObject {
public RFC822.MailboxAddress get_rfc822_address() {
return new RFC822.MailboxAddress(real_name, email);
}
+
+ public inline bool always_load_remote_images() {
+ return contact_flags != null && contact_flags.always_load_remote_images();
+ }
}
diff --git a/src/engine/api/geary-conversation.vala b/src/engine/api/geary-conversation.vala
index d78091f6..c31a30e2 100644
--- a/src/engine/api/geary-conversation.vala
+++ b/src/engine/api/geary-conversation.vala
@@ -126,7 +126,7 @@ public abstract class Geary.Conversation : BaseObject {
*/
public abstract Geary.EmailIdentifier? get_lowest_email_id();
- private bool check_flag(Geary.EmailFlag flag, bool contains) {
+ private bool check_flag(Geary.NamedFlag flag, bool contains) {
foreach (Geary.Email email in get_emails(Ordering.NONE)) {
if (email.email_flags != null && email.email_flags.contains(flag) == contains)
return true;
@@ -135,11 +135,11 @@ public abstract class Geary.Conversation : BaseObject {
return false;
}
- private bool has_flag(Geary.EmailFlag flag) {
+ private bool has_flag(Geary.NamedFlag flag) {
return check_flag(flag, true);
}
- private bool is_missing_flag(Geary.EmailFlag flag) {
+ private bool is_missing_flag(Geary.NamedFlag flag) {
return check_flag(flag, false);
}
}
diff --git a/src/engine/api/geary-email-flags.vala b/src/engine/api/geary-email-flags.vala
index dd4c0dc3..1a505036 100644
--- a/src/engine/api/geary-email-flags.vala
+++ b/src/engine/api/geary-email-flags.vala
@@ -4,97 +4,44 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class Geary.EmailFlags : BaseObject, Gee.Hashable {
- private static EmailFlag? _unread = null;
- public static EmailFlag UNREAD { get {
+/**
+ * A collection of NamedFlags that can be used to enable/disable various user-defined
+ * options for an email message. System- or Geary-defined flags are available as static
+ * members.
+ *
+ * Note that how flags are represented by a particular email storage system may differ from
+ * how they're presented here. In particular, the manner of serializing and deserializing
+ * the flags may be handled by an internal subclass.
+ */
+
+public class Geary.EmailFlags : Geary.NamedFlags {
+ private static NamedFlag? _unread = null;
+ public static NamedFlag UNREAD { get {
if (_unread == null)
- _unread = new EmailFlag("UNREAD");
+ _unread = new NamedFlag("UNREAD");
return _unread;
} }
- private static EmailFlag? _flagged = null;
- public static EmailFlag FLAGGED { get {
+ private static NamedFlag? _flagged = null;
+ public static NamedFlag FLAGGED { get {
if (_flagged == null)
- _flagged = new EmailFlag("FLAGGED");
+ _flagged = new NamedFlag("FLAGGED");
return _flagged;
} }
- private static EmailFlag? _load_remote_images = null;
- public static EmailFlag LOAD_REMOTE_IMAGES { get {
+ private static NamedFlag? _load_remote_images = null;
+ public static NamedFlag LOAD_REMOTE_IMAGES { get {
if (_load_remote_images == null)
- _load_remote_images = new EmailFlag("LOADREMOTEIMAGES");
+ _load_remote_images = new NamedFlag("LOADREMOTEIMAGES");
return _load_remote_images;
} }
- private Gee.Set list = new Gee.HashSet();
-
- public virtual signal void added(Gee.Collection flags) {
- }
-
- public virtual signal void removed(Gee.Collection flags) {
- }
-
public EmailFlags() {
}
- protected virtual void notify_added(Gee.Collection flags) {
- added(flags);
- }
-
- protected virtual void notify_removed(Gee.Collection flags) {
- removed(flags);
- }
-
- public bool contains(EmailFlag flag) {
- return list.contains(flag);
- }
-
- public Gee.Set get_all() {
- return list.read_only_view;
- }
-
- public virtual void add(EmailFlag flag) {
- if (!list.contains(flag)) {
- list.add(flag);
- notify_added(new Collection.SingleItem(flag));
- }
- }
-
- public virtual void add_all(EmailFlags flags) {
- Gee.ArrayList added = new Gee.ArrayList();
- foreach (EmailFlag flag in flags.get_all()) {
- if (!list.contains(flag))
- added.add(flag);
- }
-
- list.add_all(added);
- notify_added(added);
- }
-
- public virtual bool remove(EmailFlag flag) {
- bool removed = list.remove(flag);
- if (removed)
- notify_removed(new Collection.SingleItem(flag));
-
- return removed;
- }
-
- public virtual bool remove_all(EmailFlags flags) {
- Gee.ArrayList removed = new Gee.ArrayList();
- foreach (EmailFlag flag in flags.get_all()) {
- if (list.contains(flag))
- removed.add(flag);
- }
-
- list.remove_all(removed);
- notify_removed(removed);
-
- return removed.size > 0;
- }
-
// Convenience method to check if the unread flag is set.
public inline bool is_unread() {
return contains(UNREAD);
@@ -107,33 +54,5 @@ public class Geary.EmailFlags : BaseObject, Gee.Hashable {
public inline bool load_remote_images() {
return contains(LOAD_REMOTE_IMAGES);
}
-
- public bool equal_to(Geary.EmailFlags other) {
- if (this == other)
- return true;
-
- if (list.size != other.list.size)
- return false;
-
- foreach (EmailFlag flag in list) {
- if (!other.contains(flag))
- return false;
- }
-
- return true;
- }
-
- public uint hash() {
- return Geary.String.stri_hash(to_string());
- }
-
- public string to_string() {
- string ret = "[";
- foreach (EmailFlag flag in list) {
- ret += flag.to_string() + " ";
- }
-
- return ret + "]";
- }
}
diff --git a/src/engine/api/geary-email-flag.vala b/src/engine/api/geary-named-flag.vala
similarity index 54%
rename from src/engine/api/geary-email-flag.vala
rename to src/engine/api/geary-named-flag.vala
index f25277b3..13534c81 100644
--- a/src/engine/api/geary-email-flag.vala
+++ b/src/engine/api/geary-named-flag.vala
@@ -4,14 +4,20 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class Geary.EmailFlag : BaseObject, Gee.Hashable {
+/**
+ * Geary offers a couple of places where the user may mark an object (email, contact)
+ * with a named flag. The presence of the flag indicates if the state is enabled/on
+ * or disabled/off.
+ */
+
+public class Geary.NamedFlag : BaseObject, Gee.Hashable {
private string name;
- public EmailFlag(string name) {
+ public NamedFlag(string name) {
this.name = name;
}
- public bool equal_to(Geary.EmailFlag other) {
+ public bool equal_to(Geary.NamedFlag other) {
if (this == other)
return true;
@@ -22,6 +28,10 @@ public class Geary.EmailFlag : BaseObject, Gee.Hashable {
return name.down().hash();
}
+ public string serialize() {
+ return name;
+ }
+
public string to_string() {
return name;
}
diff --git a/src/engine/api/geary-named-flags.vala b/src/engine/api/geary-named-flags.vala
new file mode 100644
index 00000000..0592ecec
--- /dev/null
+++ b/src/engine/api/geary-named-flags.vala
@@ -0,0 +1,107 @@
+/* Copyright 2011-2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * A signalled collection of NamedFlags. Currently Geary uses these flags for enabling/disabling
+ * options with email or contacts.
+ */
+
+public class Geary.NamedFlags : BaseObject, Gee.Hashable {
+ protected Gee.Set list = new Gee.HashSet();
+
+ public virtual signal void added(Gee.Collection flags) {
+ }
+
+ public virtual signal void removed(Gee.Collection flags) {
+ }
+
+ public NamedFlags() {
+ }
+
+ protected virtual void notify_added(Gee.Collection flags) {
+ added(flags);
+ }
+
+ protected virtual void notify_removed(Gee.Collection flags) {
+ removed(flags);
+ }
+
+ public bool contains(NamedFlag flag) {
+ return list.contains(flag);
+ }
+
+ public Gee.Set get_all() {
+ return list.read_only_view;
+ }
+
+ public virtual void add(NamedFlag flag) {
+ if (!list.contains(flag)) {
+ list.add(flag);
+ notify_added(new Collection.SingleItem(flag));
+ }
+ }
+
+ public virtual void add_all(NamedFlags flags) {
+ Gee.ArrayList added = new Gee.ArrayList();
+ foreach (NamedFlag flag in flags.get_all()) {
+ if (!list.contains(flag))
+ added.add(flag);
+ }
+
+ list.add_all(added);
+ notify_added(added);
+ }
+
+ public virtual bool remove(NamedFlag flag) {
+ bool removed = list.remove(flag);
+ if (removed)
+ notify_removed(new Collection.SingleItem(flag));
+
+ return removed;
+ }
+
+ public virtual bool remove_all(NamedFlags flags) {
+ Gee.ArrayList removed = new Gee.ArrayList();
+ foreach (NamedFlag flag in flags.get_all()) {
+ if (list.contains(flag))
+ removed.add(flag);
+ }
+
+ list.remove_all(removed);
+ notify_removed(removed);
+
+ return removed.size > 0;
+ }
+
+ public bool equal_to(Geary.NamedFlags other) {
+ if (this == other)
+ return true;
+
+ if (list.size != other.list.size)
+ return false;
+
+ foreach (NamedFlag flag in list) {
+ if (!other.contains(flag))
+ return false;
+ }
+
+ return true;
+ }
+
+ public uint hash() {
+ return Geary.String.stri_hash(to_string());
+ }
+
+ public string to_string() {
+ string ret = "[";
+ foreach (NamedFlag flag in list) {
+ ret += flag.to_string() + " ";
+ }
+
+ return ret + "]";
+ }
+}
+
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index f8653e58..09a4de0a 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -23,11 +23,11 @@ private class Geary.ImapDB.Account : BaseObject {
private ImapDB.Database? db = null;
private Gee.HashMap folder_refs =
new Gee.HashMap();
- public ContactStore contact_store { get; private set; }
+ public ImapEngine.ContactStore contact_store { get; private set; }
public Account(Geary.AccountInformation account_information) {
this.account_information = account_information;
- contact_store = new ContactStore();
+ contact_store = new ImapEngine.ContactStore(this);
name = "IMAP database account for %s".printf(account_information.imap_credentials.user);
}
@@ -262,14 +262,14 @@ private class Geary.ImapDB.Account : BaseObject {
Db.TransactionOutcome outcome = db.exec_transaction(Db.TransactionType.RO,
(context) => {
Db.Statement statement = context.prepare(
- "SELECT email, real_name, highest_importance, normalized_email " +
+ "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.string_at(0), result.string_at(1),
- result.int_at(2), result.string_at(3));
+ 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
@@ -552,6 +552,21 @@ 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);
+ }
+
private void clear_duplicate_folders() {
int count = 0;
diff --git a/src/engine/imap-db/imap-db-contact.vala b/src/engine/imap-db/imap-db-contact.vala
index 9568575c..f109328f 100644
--- a/src/engine/imap-db/imap-db-contact.vala
+++ b/src/engine/imap-db/imap-db-contact.vala
@@ -6,19 +6,64 @@
namespace Geary.ImapDB {
-private static void do_update_contact_importance(Db.Connection connection, Contact contact,
- Cancellable? cancellable = null) throws Error {
- // TODO: Don't overwrite a non-null real_name with a null real_name.
- Db.Statement statement = connection.prepare(
- "INSERT OR REPLACE INTO ContactTable(normalized_email, email, real_name, highest_importance) "
- + "VALUES(?, ?, ?, MAX(COALESCE((SELECT highest_importance FROM ContactTable "
- + "WHERE email=?1), -1), ?))");
- statement.bind_string(0, contact.normalized_email);
- statement.bind_string(1, contact.email);
- statement.bind_string(2, contact.real_name);
- statement.bind_int(3, contact.highest_importance);
+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);
- statement.exec(cancellable);
+ 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);
}
}
diff --git a/src/engine/imap-db/imap-db-database.vala b/src/engine/imap-db/imap-db-database.vala
index 72878584..8f2828c1 100644
--- a/src/engine/imap-db/imap-db-database.vala
+++ b/src/engine/imap-db/imap-db-database.vala
@@ -43,7 +43,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_importance(get_master_connection(), contact);
+ do_update_contact(get_master_connection(), contact, null);
result.next();
}
} catch (Error err) {
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index 733dde14..e993404b 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -559,12 +559,12 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
Geary.Imap.EmailFlags flags = ((Geary.Imap.EmailFlags) map.get(id));
if (flags_to_add != null) {
- foreach (Geary.EmailFlag flag in flags_to_add.get_all())
+ foreach (Geary.NamedFlag flag in flags_to_add.get_all())
flags.add(flag);
}
if (flags_to_remove != null) {
- foreach (Geary.EmailFlag flag in flags_to_remove.get_all())
+ foreach (Geary.NamedFlag flag in flags_to_remove.get_all())
flags.remove(flag);
}
}
@@ -912,7 +912,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
MessageAddresses message_addresses =
new MessageAddresses.from_email(account_owner_email, email);
foreach (Contact contact in message_addresses.contacts)
- do_update_contact_importance(cx, contact, cancellable);
+ do_update_contact(cx, contact, cancellable);
updated_contacts = message_addresses.contacts;
return true;
@@ -1238,7 +1238,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
MessageAddresses message_addresses =
new MessageAddresses.from_row(account_owner_email, row);
foreach (Geary.Contact contact in message_addresses.contacts)
- do_update_contact_importance(cx, contact, cancellable);
+ do_update_contact(cx, contact, cancellable);
updated_contacts = message_addresses.contacts;
}
diff --git a/src/engine/imap-engine/imap-engine-contact-store.vala b/src/engine/imap-engine/imap-engine-contact-store.vala
new file mode 100644
index 00000000..0972d3c5
--- /dev/null
+++ b/src/engine/imap-engine/imap-engine-contact-store.vala
@@ -0,0 +1,26 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * 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 contacts, ContactFlags? to_add,
+ ContactFlags? to_remove) throws Error{
+ foreach (Contact contact in contacts) {
+ 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);
+ }
+ }
+}
diff --git a/src/engine/imap/api/imap-email-flags.vala b/src/engine/imap/api/imap-email-flags.vala
index 90808b45..4e996bde 100644
--- a/src/engine/imap/api/imap-email-flags.vala
+++ b/src/engine/imap/api/imap-email-flags.vala
@@ -20,8 +20,8 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
add(LOAD_REMOTE_IMAGES);
}
- protected override void notify_added(Gee.Collection added) {
- foreach (EmailFlag flag in added) {
+ protected override void notify_added(Gee.Collection added) {
+ foreach (NamedFlag flag in added) {
if (flag.equal_to(UNREAD))
message_flags.remove(MessageFlag.SEEN);
@@ -35,8 +35,8 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
base.notify_added(added);
}
- protected override void notify_removed(Gee.Collection removed) {
- foreach (EmailFlag flag in removed) {
+ protected override void notify_removed(Gee.Collection removed) {
+ foreach (NamedFlag flag in removed) {
if (flag.equal_to(UNREAD))
message_flags.add(MessageFlag.SEEN);