Further engine rework: Revised interface to Geary.Folder

Now that the Geary.Folder interface is not tied to the internal
SQLite/IMAP folder objects, it is free to offer more data and
better information to the client via its signals.  This commit
makes the "email-removed" signal more useful and removes two
list_*() calls that were only useful to the EngineFolder itself
when manipulating the internal Folder objects.
This commit is contained in:
Jim Nelson 2012-03-22 15:02:28 -07:00
parent 6f5f05099e
commit c6cac07c76
8 changed files with 165 additions and 135 deletions

View file

@ -276,8 +276,8 @@ public class Geary.Conversations : Object {
~Conversations() {
if (monitor_new) {
folder.messages_appended.disconnect(on_folder_messages_appended);
folder.message_removed.disconnect(on_folder_message_removed);
folder.email_appended.disconnect(on_folder_email_appended);
folder.email_removed.disconnect(on_folder_email_removed);
}
// Manually detach all the weak refs in the Conversation objects
@ -335,8 +335,8 @@ public class Geary.Conversations : Object {
monitor_new = true;
cancellable_monitor = cancellable;
folder.messages_appended.connect(on_folder_messages_appended);
folder.message_removed.connect(on_folder_message_removed);
folder.email_appended.connect(on_folder_email_appended);
folder.email_removed.connect(on_folder_email_removed);
return true;
}
@ -709,7 +709,7 @@ public class Geary.Conversations : Object {
}
}
private void on_folder_messages_appended() {
private void on_folder_email_appended() {
// Find highest identifier by ordering
// TODO: optimize.
Geary.EmailIdentifier? highest = null;
@ -733,8 +733,9 @@ public class Geary.Conversations : Object {
lazy_load_by_id(highest, int.MAX, Folder.ListFlags.EXCLUDING_ID, cancellable_monitor);
}
private void on_folder_message_removed(Geary.EmailIdentifier removed_id) {
remove_email(removed_id);
private void on_folder_email_removed(Gee.Collection<Geary.EmailIdentifier> removed_ids) {
foreach (Geary.EmailIdentifier id in removed_ids)
remove_email(id);
}
}

View file

@ -65,14 +65,27 @@ public interface Geary.Folder : Object {
public signal void closed(CloseReason reason);
/**
* "messages-appended" is fired when new messages have been appended to the list of messages in
* "email-appended" is fired when new messages have been appended to the list of messages in
* the folder (and therefore old message position numbers remain valid, but the total count of
* the messages in the folder has changed).
*/
public signal void messages_appended(int total);
public signal void email_appended(Gee.Collection<Geary.EmailIdentifier> ids);
/**
* "message-removed" is fired when a message has been removed (deleted or moved) from the
* "email-locally-appended" is fired when previously unknown messages have been appended to the
* list of messages in the folder. This is similar to "email-appended", but that signal
* lists all messages appended to the folder. "email-locally-appended" only reports emails that
* have not been seen prior. Hence, an email that is removed from the folder and returned
* later will not be listed here (unless it was removed from the local store in the meantime).
*
* Note that these messages were appended as well, hence their positional addressing may have
* changed since last seen in this folder. However, it's important to realize that this list
* does *not* represent all newly appended messages.
*/
public signal void email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids);
/**
* "email-removed" is fired when a message has been removed (deleted or moved) from the
* folder (and therefore old message position numbers may no longer be valid, i.e. those after
* the removed message).
*
@ -80,14 +93,14 @@ public interface Geary.Folder : Object {
* known locally (and therefore the caller could not have record of). If this happens, this
* signal will *not* fire, although "email-count-changed" will.
*/
public signal void message_removed(Geary.EmailIdentifier id);
public signal void email_removed(Gee.Collection<Geary.EmailIdentifier> ids);
/**
* "email-count-changed" is fired when the total count of email in a folder has changed in any way.
*
* Note that this signal will be fired alongside "messages-appended" or "message-removed".
* That is, do not use both signals to process email count changes; one will suffice.
* This signal will fire after those (although see the note at "message-removed").
* This signal will fire after those (although see the note at "messages-removed").
*/
public signal void email_count_changed(int new_count, CountChangeReason reason);
@ -97,8 +110,7 @@ public interface Geary.Folder : Object {
* This signal will be fired both when changes occur on the client side via the
* mark_email_async() method as well as changes occur remotely.
*/
public signal void email_flags_changed(Gee.Map<Geary.EmailIdentifier,
Geary.EmailFlags> flag_map);
public signal void email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map);
/**
* This helper method should be called by implementors of Folder rather than firing the signal
@ -119,14 +131,21 @@ public interface Geary.Folder : Object {
* directly. This allows subclasses and superclasses the opportunity to inspect the email
* and update state before and/or after the signal has been fired.
*/
protected abstract void notify_messages_appended(int total);
protected abstract void notify_email_appended(Gee.Collection<Geary.EmailIdentifier> ids);
/**
* This helper method should be called by implementors of Folder rather than firing the signal
* directly. This allows subclasses and superclasses the opportunity to inspect the email
* and update state before and/or after the signal has been fired.
*/
protected abstract void notify_message_removed(Geary.EmailIdentifier id);
protected abstract void notify_email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids);
/**
* This helper method should be called by implementors of Folder rather than firing the signal
* directly. This allows subclasses and superclasses the opportunity to inspect the email
* and update state before and/or after the signal has been fired.
*/
protected abstract void notify_email_removed(Gee.Collection<Geary.EmailIdentifier> ids);
/**
* This helper method should be called by implementors of Folder rather than firing the signal
@ -271,35 +290,6 @@ public interface Geary.Folder : Object {
public abstract void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
ListFlags flags, EmailCallback cb, Cancellable? cancellable = null);
/**
* Like list_email_async(), but the caller passes a sparse list of email by it's ordered
* position in the folder. If any of the positions in the sparse list are out of range,
* only the emails within range are reported. The list is not guaranteed to be in any
* particular order.
*
* See the notes in list_email_async() regarding issues about local versus remote stores.
*
* The Folder must be opened prior to attempting this operation.
*
* All positions are one-based.
*/
public abstract async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
throws Error;
/**
* Similar in contract to list_email_sparse_async(), but like lazy_list_email(), the
* messages are passed back to the caller in chunks as they're retrieved. When null is passed
* as the first parameter, all the messages have been fetched. If an Error occurs during
* processing, it's passed as the second parameter. There's no guarantee of the returned
* messages' order.
*
* The Folder must be opened prior to attempting this operation.
*/
public abstract void lazy_list_email_sparse(int[] by_position,
Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb,
Cancellable? cancellable = null);
/**
* Similar in contract to list_email_async(), but uses Geary.EmailIdentifier rather than
* positional addressing. This allows for a batch of messages to be listed from a starting

View file

@ -5,20 +5,29 @@
*/
public abstract class Geary.AbstractFolder : Object, Geary.Folder {
protected virtual void notify_opened(Geary.Folder.OpenState state, int count) {
/*
* notify_* methods for AbstractFolder are marked internal because the SendReplayOperations
* need access to them to report changes as they occur.
*/
internal virtual void notify_opened(Geary.Folder.OpenState state, int count) {
opened(state, count);
}
protected virtual void notify_closed(Geary.Folder.CloseReason reason) {
internal virtual void notify_closed(Geary.Folder.CloseReason reason) {
closed(reason);
}
internal virtual void notify_messages_appended(int total) {
messages_appended(total);
internal virtual void notify_email_appended(Gee.Collection<Geary.EmailIdentifier> ids) {
email_appended(ids);
}
internal virtual void notify_message_removed(Geary.EmailIdentifier id) {
message_removed(id);
internal virtual void notify_email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids) {
email_locally_appended(ids);
}
internal virtual void notify_email_removed(Gee.Collection<Geary.EmailIdentifier> ids) {
email_removed(ids);
}
internal virtual void notify_email_count_changed(int new_count, Folder.CountChangeReason reason) {
@ -50,14 +59,6 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
public abstract void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null);
public abstract async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
throws Error;
public abstract void lazy_list_email_sparse(int[] by_position,
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb,
Cancellable? cancellable = null);
public abstract async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
throws Error;

View file

@ -19,9 +19,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
private SendReplayQueue? send_replay_queue = null;
private NonblockingMutex normalize_email_positions_mutex = new NonblockingMutex();
public virtual signal void local_added(Gee.Collection<Geary.EmailIdentifier> added) {
}
public EngineFolder(Imap.Account remote, Sqlite.Account local, Sqlite.Folder local_folder) {
this.remote = remote;
this.local = local;
@ -33,10 +30,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
warning("Folder %s destroyed without closing", to_string());
}
protected virtual void notify_local_added(Gee.Collection<Geary.EmailIdentifier> added) {
local_added(added);
}
public override Geary.FolderPath get_path() {
return local_folder.get_path();
}
@ -245,19 +238,25 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
Gee.HashSet<Geary.EmailIdentifier> created = new Gee.HashSet<Geary.EmailIdentifier>(
Hashable.hash_func, Equalable.equal_func);
Gee.HashSet<Geary.EmailIdentifier> appended = new Gee.HashSet<Geary.EmailIdentifier>(
Hashable.hash_func, Equalable.equal_func);
foreach (Geary.Email email in list) {
debug("Creating Email ID %s", email.id.to_string());
// need to report both if it was created (not known before) and appended (which
// could mean created or simply a known email associated with this folder)
if (yield local_folder.create_email_async(email, null))
created.add(email.id);
appended.add(email.id);
}
// save new remote count
remote_count = new_remote_count;
notify_messages_appended(new_remote_count);
notify_email_appended(appended);
if (created.size > 0)
notify_local_added(created);
notify_email_locally_appended(created);
} catch (Error err) {
debug("Unable to normalize local store of newly appended messages to %s: %s",
to_string(), err.message);
@ -295,7 +294,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
yield local_folder.remove_marked_email_async(owned_id, out marked, null);
if (!marked)
notify_message_removed(owned_id);
notify_email_removed(new Geary.Singleton<Geary.EmailIdentifier>(owned_id));
} catch (Error err2) {
debug("Unable to remove message #%d from %s: %s", remote_position, to_string(),
err2.message);
@ -375,48 +374,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
yield op.wait_for_ready();
}
public override async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
throws Error {
if (by_position.length == 0)
return null;
Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
yield do_list_email_sparse_async(by_position, required_fields, accumulator, null,
cancellable, flags.is_all_set(Folder.ListFlags.LOCAL_ONLY));
return accumulator;
}
// TODO: Capture Error and report via EmailCallback.
public override void lazy_list_email_sparse(int[] by_position, Geary.Email.Field required_fields,
Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
// schedule listing in the background, using the callback to drive availability of email
do_list_email_sparse_async.begin(by_position, required_fields, null, cb, cancellable,
flags.is_all_set(Folder.ListFlags.LOCAL_ONLY));
}
private async void do_list_email_sparse_async(int[] by_position, Geary.Email.Field required_fields,
Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable, bool local_only)
throws Error {
if (!opened)
throw new EngineError.OPEN_REQUIRED("%s is not open", to_string());
if (by_position.length == 0) {
// signal finished
if (cb != null)
cb(null, null);
return;
}
// Schedule list operation and wait for completion.
ListEmailSparse op = new ListEmailSparse(this, by_position, required_fields, accumulator,
cb, cancellable, local_only);
send_replay_queue.schedule(op);
yield op.wait_for_ready();
}
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
Cancellable? cancellable = null) throws Error {
@ -529,7 +486,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
}
if (created_ids.size > 0)
notify_local_added(created_ids);
notify_email_locally_appended(created_ids);
if (cb != null)
cb(remote_list, null);
@ -579,13 +536,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
Geary.Email email = yield remote_folder.fetch_email_async(id, fields, cancellable);
// save to local store
if (yield local_folder.create_email_async(email, cancellable)) {
// TODO: A Singleton collection would be useful here.
Gee.ArrayList<Geary.EmailIdentifier> ids = new Gee.ArrayList<Geary.EmailIdentifier>();
ids.add(email.id);
notify_local_added(ids);
}
if (yield local_folder.create_email_async(email, cancellable))
notify_email_locally_appended(new Geary.Singleton<Geary.EmailIdentifier>(email.id));
return email;
}
@ -681,7 +633,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
}
if (created_ids.size > 0)
notify_local_added(created_ids);
notify_email_locally_appended(created_ids);
} catch (Error e) {
local_count = 0; // prevent compiler warning
error = e;

View file

@ -165,6 +165,7 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
int remote_ctr = 0;
int local_ctr = 0;
Gee.ArrayList<Geary.EmailIdentifier> appended_ids = new Gee.ArrayList<Geary.EmailIdentifier>();
Gee.ArrayList<Geary.EmailIdentifier> removed_ids = new Gee.ArrayList<Geary.EmailIdentifier>();
Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> flags_changed = new Gee.HashMap<Geary.EmailIdentifier,
Geary.EmailFlags>();
@ -195,6 +196,7 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
} else if (remote_uid.value < local_uid.value) {
// one we'd not seen before is present, add and move to next remote
batch.add(new CreateLocalEmailOperation(local_folder, remote_email));
appended_ids.add(remote_email.id);
remote_ctr++;
} else {
@ -212,10 +214,9 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
// CreateEmailOperations were updates of emails existing previously or additions of emails
// that were on the server earlier but not stored locally (i.e. this value represents emails
// added to the top of the stack)
int appended = 0;
for (; remote_ctr < remote_length; remote_ctr++) {
batch.add(new CreateLocalEmailOperation(local_folder, old_remote[remote_ctr]));
appended++;
appended_ids.add(old_remote[remote_ctr].id);
}
// remove anything left over ... use local count rather than remote as we're still in a stage
@ -240,14 +241,13 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
// signalled)
if (removed_ids.size > 0) {
debug("Notifying of %d removed emails since %s last seen", removed_ids.size, to_string());
foreach (Geary.EmailIdentifier removed_id in removed_ids)
notify_message_removed(removed_id);
notify_email_removed(removed_ids);
}
// notify additions
if (appended > 0) {
debug("Notifying of %d appended emails since %s last seen", appended, to_string());
notify_messages_appended(appended);
if (appended_ids.size > 0) {
debug("Notifying of %d appended emails since %s last seen", appended_ids.size, to_string());
notify_email_appended(appended_ids);
}
// notify flag changes
@ -282,10 +282,10 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
base.notify_closed(reason);
}
protected override void notify_local_added(Gee.Collection<Geary.EmailIdentifier> added) {
schedule_prefetch(added);
protected override void notify_email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids) {
schedule_prefetch(ids);
base.notify_local_added(added);
base.notify_email_locally_appended(ids);
}
/**

View file

@ -11,7 +11,7 @@ private class Geary.MarkEmail : Geary.SendReplayOperation {
private Geary.EmailFlags? flags_to_remove;
private Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags>? original_flags = null;
private Cancellable? cancellable;
public MarkEmail(EngineFolder engine, Gee.List<Geary.EmailIdentifier> to_mark,
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
Cancellable? cancellable = null) {
@ -70,7 +70,7 @@ private class Geary.RemoveEmail : Geary.SendReplayOperation {
public override async bool replay_local() throws Error {
foreach (Geary.EmailIdentifier id in to_remove) {
yield engine.local_folder.mark_removed_async(id, true, cancellable);
engine.notify_message_removed(id);
engine.notify_email_removed(new Geary.Singleton<Geary.EmailIdentifier>(id));
}
original_count = engine.remote_count;
@ -93,8 +93,8 @@ private class Geary.RemoveEmail : Geary.SendReplayOperation {
foreach (Geary.EmailIdentifier id in to_remove)
yield engine.local_folder.mark_removed_async(id, false, cancellable);
engine.notify_messages_appended(to_remove.size);
engine.notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.REMOVED);
engine.notify_email_appended(to_remove);
engine.notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.ADDED);
}
}

View file

@ -0,0 +1,85 @@
/* Copyright 2012 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.
*/
/**
* Singleton is a simple way of creating a one-item read-only collection.
*/
private class Geary.Singleton<G> : Gee.AbstractCollection<G> {
private class IteratorImpl<G> : Object, Gee.Iterator<G> {
private G item;
private bool done = false;
public IteratorImpl(G item) {
this.item = item;
}
public bool first() {
done = false;
return true;
}
public new G? get() {
return item;
}
public bool has_next() {
return !done;
}
public bool next() {
if (done)
return false;
done = true;
return true;
}
public void remove() {
message("Geary.Singleton is read-only");
}
}
public G item { get; private set; }
public override int size { get { return 1; } }
private EqualFunc equal_func;
public Singleton(G item, EqualFunc? equal_func = null) {
this.item = item;
if (equal_func != null)
this.equal_func = equal_func;
else if (typeof(G).is_a(typeof(Geary.Equalable)))
this.equal_func = Geary.Equalable.equal_func;
else
this.equal_func = Gee.Functions.get_equal_func_for(typeof(G));
}
public override bool add(G element) {
return false;
}
public override void clear() {
message("Geary.Singleton is read-only");
}
public override bool contains(G element) {
return equal_func(item, element);
}
public override Gee.Iterator<G> iterator() {
return new IteratorImpl<G>(item);
}
public override bool remove(G element) {
message("Geary.Singleton is read-only");
return false;
}
}

View file

@ -155,6 +155,7 @@ def build(bld):
'../engine/util/util-numeric.vala',
'../engine/util/util-reference-semantics.vala',
'../engine/util/util-scheduler.vala',
'../engine/util/util-singleton.vala',
'../engine/util/util-stream.vala',
'../engine/util/util-string.vala',
'../engine/util/util-trillian.vala'