Allow external images from whitelisted senders to be displayed: Closes #5642
This also cleans up some of the update logic for ContactTable, which could drop contacts' human-readable names when updating their importance.
This commit is contained in:
parent
6ce275f61d
commit
974e9459a3
17 changed files with 392 additions and 148 deletions
|
|
@ -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})
|
||||
|
|
|
|||
6
sql/version-009.sql
Normal file
6
sql/version-009.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
--
|
||||
-- Add flags column to the ContactTable
|
||||
--
|
||||
|
||||
ALTER TABLE ContactTable ADD COLUMN flags TEXT;
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
<input type="button" value="%s" class="show_images" />""".printf(
|
||||
<input type="button" value="%s" class="show_images" />
|
||||
<input type="button" value="%s" class="show_from" />""".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<Geary.Contact> contact_list = new Gee.ArrayList<Geary.Contact>();
|
||||
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,6 +766,7 @@ public class ConversationViewer : Gtk.Box {
|
|||
warning("Error showing images: %s", error.message);
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
|
@ -728,6 +775,7 @@ public class ConversationViewer : Gtk.Box {
|
|||
mark_message(message, flags, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void on_close_show_images(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||
ConversationViewer conversation_viewer) {
|
||||
|
|
|
|||
50
src/engine/api/geary-contact-flags.vala
Normal file
50
src/engine/api/geary-contact-flags.vala
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<Contact> 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<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()];
|
||||
}
|
||||
|
||||
private void update_contact(Contact contact) {
|
||||
Contact? old_contact = contact_map[contact.normalized_email];
|
||||
if (old_contact == null) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,97 +4,44 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.EmailFlags : BaseObject, Gee.Hashable<Geary.EmailFlags> {
|
||||
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<EmailFlag> list = new Gee.HashSet<EmailFlag>();
|
||||
|
||||
public virtual signal void added(Gee.Collection<EmailFlag> flags) {
|
||||
}
|
||||
|
||||
public virtual signal void removed(Gee.Collection<EmailFlag> flags) {
|
||||
}
|
||||
|
||||
public EmailFlags() {
|
||||
}
|
||||
|
||||
protected virtual void notify_added(Gee.Collection<EmailFlag> flags) {
|
||||
added(flags);
|
||||
}
|
||||
|
||||
protected virtual void notify_removed(Gee.Collection<EmailFlag> flags) {
|
||||
removed(flags);
|
||||
}
|
||||
|
||||
public bool contains(EmailFlag flag) {
|
||||
return list.contains(flag);
|
||||
}
|
||||
|
||||
public Gee.Set<EmailFlag> 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<EmailFlag>(flag));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void add_all(EmailFlags flags) {
|
||||
Gee.ArrayList<EmailFlag> added = new Gee.ArrayList<EmailFlag>();
|
||||
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<EmailFlag>(flag));
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public virtual bool remove_all(EmailFlags flags) {
|
||||
Gee.ArrayList<EmailFlag> removed = new Gee.ArrayList<EmailFlag>();
|
||||
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<Geary.EmailFlags> {
|
|||
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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,20 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.EmailFlag : BaseObject, Gee.Hashable<Geary.EmailFlag> {
|
||||
/**
|
||||
* 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<Geary.NamedFlag> {
|
||||
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<Geary.EmailFlag> {
|
|||
return name.down().hash();
|
||||
}
|
||||
|
||||
public string serialize() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return name;
|
||||
}
|
||||
107
src/engine/api/geary-named-flags.vala
Normal file
107
src/engine/api/geary-named-flags.vala
Normal file
|
|
@ -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<Geary.NamedFlags> {
|
||||
protected Gee.Set<NamedFlag> list = new Gee.HashSet<NamedFlag>();
|
||||
|
||||
public virtual signal void added(Gee.Collection<NamedFlag> flags) {
|
||||
}
|
||||
|
||||
public virtual signal void removed(Gee.Collection<NamedFlag> flags) {
|
||||
}
|
||||
|
||||
public NamedFlags() {
|
||||
}
|
||||
|
||||
protected virtual void notify_added(Gee.Collection<NamedFlag> flags) {
|
||||
added(flags);
|
||||
}
|
||||
|
||||
protected virtual void notify_removed(Gee.Collection<NamedFlag> flags) {
|
||||
removed(flags);
|
||||
}
|
||||
|
||||
public bool contains(NamedFlag flag) {
|
||||
return list.contains(flag);
|
||||
}
|
||||
|
||||
public Gee.Set<NamedFlag> 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<NamedFlag>(flag));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void add_all(NamedFlags flags) {
|
||||
Gee.ArrayList<NamedFlag> added = new Gee.ArrayList<NamedFlag>();
|
||||
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<NamedFlag>(flag));
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public virtual bool remove_all(NamedFlags flags) {
|
||||
Gee.ArrayList<NamedFlag> removed = new Gee.ArrayList<NamedFlag>();
|
||||
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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
private ImapDB.Database? db = null;
|
||||
private Gee.HashMap<Geary.FolderPath, FolderReference> folder_refs =
|
||||
new Gee.HashMap<Geary.FolderPath, FolderReference>();
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
26
src/engine/imap-engine/imap-engine-contact-store.vala
Normal file
26
src/engine/imap-engine/imap-engine-contact-store.vala
Normal file
|
|
@ -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<Contact> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,8 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
|
|||
add(LOAD_REMOTE_IMAGES);
|
||||
}
|
||||
|
||||
protected override void notify_added(Gee.Collection<EmailFlag> added) {
|
||||
foreach (EmailFlag flag in added) {
|
||||
protected override void notify_added(Gee.Collection<NamedFlag> 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<EmailFlag> removed) {
|
||||
foreach (EmailFlag flag in removed) {
|
||||
protected override void notify_removed(Gee.Collection<NamedFlag> removed) {
|
||||
foreach (NamedFlag flag in removed) {
|
||||
if (flag.equal_to(UNREAD))
|
||||
message_flags.add(MessageFlag.SEEN);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue