Monitor currently selected folder: #3793

This commit represents the first half of this ticket, as it only monitors additions (new
emails) in the folder.  A second commit will follow for monitoring removals (deleted emails)
in the folder.
This commit is contained in:
Jim Nelson 2011-07-26 15:29:08 -07:00
parent e812ef6166
commit d1208e8efe
12 changed files with 303 additions and 134 deletions

View file

@ -205,17 +205,16 @@ public class MainWindow : Gtk.Window {
message_list_store.clear();
if (current_folder != null) {
current_folder.email_added_removed.disconnect(on_email_added_removed);
current_folder.list_appended.disconnect(on_folder_list_appended);
yield current_folder.close_async();
}
current_folder = folder;
current_folder.email_added_removed.connect(on_email_added_removed);
current_folder.list_appended.connect(on_folder_list_appended);
yield current_folder.open_async(true);
current_folder.lazy_list_email_async(-1, 50, MessageListStore.REQUIRED_FIELDS,
on_list_email_ready);
current_folder.lazy_list_email(-1, 50, MessageListStore.REQUIRED_FIELDS, on_list_email_ready);
}
private void on_list_email_ready(Gee.List<Geary.Email>? email, Error? err) {
@ -288,11 +287,17 @@ public class MainWindow : Gtk.Window {
}
}
private void on_email_added_removed(Gee.List<Geary.Email>? added, Gee.List<Geary.Folder>? removed) {
if (added != null) {
foreach (Geary.Email email in added)
message_list_store.append_envelope(email);
private void on_folder_list_appended() {
int high = message_list_store.get_highest_folder_position();
if (high < 0) {
debug("Unable to find highest message position in %s", current_folder.to_string());
return;
}
// Want to get the one *after* the highest position in the message list
current_folder.lazy_list_email(high + 1, -1, MessageListStore.REQUIRED_FIELDS,
on_list_email_ready);
}
private async void search_folders_for_children(Gee.Collection<Geary.Folder> folders) {

View file

@ -66,7 +66,7 @@ public class MessageListStore : Gtk.TreeStore {
Gtk.TreeIter iter;
append(out iter, null);
string? pre = null;;
string? pre = null;
string? post = null;
if (envelope.properties != null) {
pre = envelope.properties.is_unread() ? "<b>" : null;
@ -96,6 +96,27 @@ public class MessageListStore : Gtk.TreeStore {
return email;
}
// Returns -1 if the list is empty.
public int get_highest_folder_position() {
Gtk.TreeIter iter;
if (!get_iter_first(out iter))
return -1;
int high = int.MIN;
// TODO: It would be more efficient to maintain highest and lowest values in a table or
// as items are added and removed; this will do for now.
do {
Geary.Email email;
get(iter, Column.MESSAGE_OBJECT, out email);
if (email.location.position > high)
high = email.location.position;
} while (iter_next(ref iter));
return high;
}
private int sort_by_date(Gtk.TreeModel model, Gtk.TreeIter aiter, Gtk.TreeIter biter) {
Geary.Email aenvelope;
get(aiter, Column.MESSAGE_OBJECT, out aenvelope);

View file

@ -87,6 +87,7 @@ class ImapConsole : Gtk.Window {
"xlist",
"examine",
"fetch",
"uid-fetch",
"help",
"exit",
"quit",
@ -159,6 +160,7 @@ class ImapConsole : Gtk.Window {
break;
case "fetch":
case "uid-fetch":
fetch(cmd, args);
break;
@ -386,12 +388,16 @@ class ImapConsole : Gtk.Window {
status("Fetching %s".printf(args[0]));
Geary.Imap.MessageSet msg_set = (cmd.down() == "fetch")
? new Geary.Imap.MessageSet.custom(args[0])
: new Geary.Imap.MessageSet.uid_custom(args[0]);
Geary.Imap.FetchDataType[] data_items = new Geary.Imap.FetchDataType[0];
for (int ctr = 1; ctr < args.length; ctr++)
data_items += Geary.Imap.FetchDataType.decode(args[ctr]);
cx.send_async.begin(new Geary.Imap.FetchCommand(cx.generate_tag(),
new Geary.Imap.MessageSet.custom(args[0]), data_items), null, on_fetch);
cx.send_async.begin(new Geary.Imap.FetchCommand(cx.generate_tag(), msg_set, data_items),
null, on_fetch);
}
private void on_fetch(Object? source, AsyncResult result) {

View file

@ -13,13 +13,8 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
closed(reason);
}
protected virtual void notify_email_added_removed(Gee.List<Geary.Email>? added,
Gee.List<Geary.Email>? removed) {
email_added_removed(added, removed);
}
protected virtual void notify_updated() {
updated();
protected virtual void notify_list_appended(int total) {
list_appended(total);
}
public abstract Geary.FolderPath get_path();
@ -30,7 +25,7 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
public abstract async void close_async(Cancellable? cancellable = null) throws Error;
public abstract async int get_email_count(Cancellable? cancellable = null) throws Error;
public abstract async int get_email_count_async(Cancellable? cancellable = null) throws Error;
public abstract async void create_email_async(Geary.Email email, Cancellable? cancellable = null)
throws Error;
@ -38,7 +33,7 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
public abstract async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
public virtual void lazy_list_email_async(int low, int count, Geary.Email.Field required_fields,
public virtual void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
EmailCallback cb, Cancellable? cancellable = null) {
do_lazy_list_email_async.begin(low, count, required_fields, cb, cancellable);
}
@ -61,7 +56,7 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
public abstract async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
public virtual void lazy_list_email_sparse_async(int[] by_position,
public virtual void lazy_list_email_sparse(int[] by_position,
Geary.Email.Field required_fields, EmailCallback cb, Cancellable? cancellable = null) {
do_lazy_list_email_sparse_async.begin(by_position, required_fields, cb, cancellable);
}

View file

@ -9,8 +9,9 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
private RemoteAccount remote;
private LocalAccount local;
private RemoteFolder? remote_folder = null;
private LocalFolder local_folder;
private RemoteFolder? remote_folder = null;
private int remote_count = -1;
private bool opened = false;
private Geary.NonblockingSemaphore remote_semaphore = new Geary.NonblockingSemaphore(true);
@ -18,15 +19,11 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
this.remote = remote;
this.local = local;
this.local_folder = local_folder;
local_folder.updated.connect(on_local_updated);
}
~EngineFolder() {
if (opened)
warning("Folder %s destroyed without closing", to_string());
local_folder.updated.disconnect(on_local_updated);
}
public override Geary.FolderPath get_path() {
@ -89,9 +86,14 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
// update flags, properties, etc.
yield local.update_folder_async(folder, cancellable);
// signals
folder.list_appended.connect(on_remote_list_appended);
// state
remote_count = yield folder.get_email_count_async(cancellable);
// all set; bless the remote folder as opened
remote_folder = folder;
remote_folder.updated.connect(on_remote_updated);
} else {
debug("Unable to prepare remote folder %s: prepare_opened_file() failed", to_string());
}
@ -129,10 +131,12 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
yield remote_semaphore.wait_async();
remote_semaphore = new Geary.NonblockingSemaphore(true);
remote_folder.updated.disconnect(on_remote_updated);
RemoteFolder? folder = remote_folder;
remote_folder = null;
// signals
folder.list_appended.disconnect(on_remote_list_appended);
folder.close_async.begin(cancellable);
notify_closed(CloseReason.FOLDER_CLOSED);
@ -141,15 +145,63 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
opened = false;
}
public override async int get_email_count(Cancellable? cancellable = null) throws Error {
private void on_remote_list_appended(int total) {
// need to prefetch PROPERTIES (or, in the future NONE or LOCATION) fields to create a
// normalized placeholder in the local database of the message, so all positions are
// properly relative to the end of the message list; once this is done, notify user of new
// messages
do_normalize_appended_messages.begin(total);
}
private async void do_normalize_appended_messages(int new_remote_count) {
// this only works when the list is grown
assert(new_remote_count > remote_count);
try {
// if no mail in local store, nothing needs to be done here; the store is "normalized"
int local_count = yield local_folder.get_email_count_async();
if (local_count == 0) {
notify_list_appended(new_remote_count);
return;
}
if (!yield wait_for_remote_to_open()) {
notify_list_appended(new_remote_count);
return;
}
// normalize starting at the message *after* the highest position of the local store,
// which has now changed
Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(remote_count + 1, -1,
Geary.Email.Field.PROPERTIES, null);
assert(list != null && list.size > 0);
foreach (Geary.Email email in list)
yield local_folder.create_email_async(email, null);
// save new remote count
remote_count = new_remote_count;
notify_list_appended(new_remote_count);
} catch (Error err) {
debug("Unable to normalize local store of newly appended messages to %s: %s",
to_string(), err.message);
}
}
public override async int get_email_count_async(Cancellable? cancellable = null) throws Error {
// TODO: Use monitoring to avoid round-trip to the server
if (!opened)
throw new EngineError.OPEN_REQUIRED("%s is not open", to_string());
// if connected, use stashed remote count (which is always kept current once remote folder
// is opened)
if (yield wait_for_remote_to_open())
return yield remote_folder.get_email_count(cancellable);
return remote_count;
return yield local_folder.get_email_count(cancellable);
return yield local_folder.get_email_count_async(cancellable);
}
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
@ -165,7 +217,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
return accumulator;
}
public override void lazy_list_email_async(int low, int count, Geary.Email.Field required_fields,
public override void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
EmailCallback cb, Cancellable? cancellable = null) {
// schedule do_list_email_async(), using the callback to drive availability of email
do_list_email_async.begin(low, count, required_fields, null, cb, cancellable);
@ -236,10 +288,11 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
}
// fixup local email positions to match server's positions
if (local_list_size > 0) {
if (local_list_size > 0 && local_count < remote_count) {
int adjustment = remote_count - local_count;
foreach (Geary.Email email in local_list) {
int new_position = email.location.position + (low - local_low);
email.update_location(new Geary.EmailLocation(new_position, email.location.ordering));
email.update_location(new Geary.EmailLocation(email.location.position + adjustment,
email.location.ordering));
}
}
@ -306,7 +359,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
return accumulator;
}
public override void lazy_list_email_sparse_async(int[] by_position, Geary.Email.Field required_fields,
public override void lazy_list_email_sparse(int[] by_position, Geary.Email.Field required_fields,
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);
@ -560,12 +613,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
yield local_folder.remove_email_async(email, cancellable);
}
private void on_local_updated() {
}
private void on_remote_updated() {
}
// In order to maintain positions for all messages without storing all of them locally,
// the database stores entries for the lowest requested email to the highest (newest), which
// means there can be no gaps between the last in the database and the last on the server.
@ -576,8 +623,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
if (!yield wait_for_remote_to_open())
throw new EngineError.SERVER_UNAVAILABLE("No connection to %s", remote.to_string());
local_count = yield local_folder.get_email_count(cancellable);
remote_count = yield remote_folder.get_email_count(cancellable);
local_count = yield local_folder.get_email_count_async(cancellable);
remote_count = yield remote_folder.get_email_count_async(cancellable);
// fixup span specifier
normalize_span_specifiers(ref low, ref count, remote_count);
@ -598,7 +645,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
// Use PROPERTIES as they're the most useful information for certain actions (such as
// finding duplicates when we start using INTERNALDATE and RFC822.SIZE) and cheap to fetch
// TODO: Consider only fetching their UID; would need Geary.Email.Field.LOCATION (or\
//
// TODO: Consider only fetching their UID; would need Geary.Email.Field.LOCATION (or
// perhaps NONE is considered a call for just the UID).
Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(high, prefetch_count,
Geary.Email.Field.PROPERTIES, cancellable);

View file

@ -19,6 +19,11 @@ public interface Geary.Folder : Object {
FOLDER_CLOSED
}
public enum Direction {
BEFORE,
AFTER
}
/**
* This is fired when the Folder is successfully opened by a caller. It will only fire once
* until the Folder is closed, with the OpenState indicating what has been opened.
@ -36,57 +41,32 @@ public interface Geary.Folder : Object {
public signal void closed(CloseReason reason);
/**
* "email-added-removed" is fired when new email has been detected due to background monitoring
* operations or if an unrelated operation causes or reveals the existence or removal of
* messages.
*
* There are no guarantees of what Geary.Email.Field fields will be available when these are
* reported. If more information is required, use the fetch or list operations.
* "list-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 email_added_removed(Gee.List<Geary.Email>? added,
Gee.List<Geary.Email>? removed);
/**
* TBD.
*/
public signal void updated();
public signal void list_appended(int total);
/**
* 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 virtual void notify_opened(OpenState state) {
opened(state);
}
protected abstract void notify_opened(OpenState state);
/**
* 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 virtual void notify_closed(CloseReason reason) {
closed(reason);
}
protected abstract void notify_closed(CloseReason reason);
/**
* 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 virtual void notify_email_added_removed(Gee.List<Geary.Email>? added,
Gee.List<Geary.Email>? removed) {
email_added_removed(added, removed);
}
/**
* 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.
*/
public virtual void notify_updated() {
updated();
}
protected abstract void notify_list_appended(int total);
public abstract Geary.FolderPath get_path();
@ -125,7 +105,7 @@ public interface Geary.Folder : Object {
*
* The Folder must be opened prior to attempting this operation.
*/
public abstract async int get_email_count(Cancellable? cancellable = null) throws Error;
public abstract async int get_email_count_async(Cancellable? cancellable = null) throws Error;
/**
* If the Folder object detects that the supplied Email does not have sufficient fields for
@ -188,7 +168,7 @@ public interface Geary.Folder : Object {
*
* The Folder must be opened prior to attempting this operation.
*/
public abstract void lazy_list_email_async(int low, int count, Geary.Email.Field required_fields,
public abstract void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
EmailCallback cb, Cancellable? cancellable = null);
/**
@ -207,7 +187,7 @@ public interface Geary.Folder : Object {
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
/**
* Similar in contract to list_email_sparse_async(), but like lazy_list_email_async(), the
* 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
@ -215,7 +195,7 @@ public interface Geary.Folder : Object {
*
* The Folder must be opened prior to attempting this operation.
*/
public abstract void lazy_list_email_sparse_async(int[] by_position,
public abstract void lazy_list_email_sparse(int[] by_position,
Geary.Email.Field required_fields, EmailCallback cb, Cancellable? cancellable = null);
/**

View file

@ -60,21 +60,41 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
// if same, no problem-o
if (local_properties.uid_next.value != remote_properties.uid_next.value) {
debug("UID next changed: %lld -> %lld", local_properties.uid_next.value,
debug("UID next changed for %s: %lld -> %lld", to_string(), local_properties.uid_next.value,
remote_properties.uid_next.value);
// fetch everything from the last seen UID (+1) to the current next UID
// fetch everything from the last seen UID (+1) to the current next UID that's not
// already in the local store (since the uidnext field isn't reported by NOOP or IDLE,
// it's possible these were fetched the last time the folder was selected)
//
// TODO: Could break this fetch up in chunks if it helps
Gee.List<Geary.Email>? newest = yield imap_remote_folder.list_email_uid_async(
local_properties.uid_next, null, Geary.Email.Field.PROPERTIES, cancellable);
int64 uid_start_value = local_properties.uid_next.value;
for (;;) {
Geary.EmailIdentifier start_id = new Imap.EmailIdentifier(new Imap.UID(uid_start_value));
Geary.Email.Field available_fields;
if (!yield imap_local_folder.is_email_present(start_id, out available_fields, cancellable))
break;
debug("already have UID %lld in %s local store", uid_start_value, to_string());
if (++uid_start_value >= remote_properties.uid_next.value)
break;
}
if (newest != null && newest.size > 0) {
debug("saving %d newest emails", newest.size);
foreach (Geary.Email email in newest) {
try {
yield local_folder.create_email_async(email, cancellable);
} catch (Error newest_err) {
debug("Unable to save new email in %s: %s", to_string(), newest_err.message);
if (uid_start_value < remote_properties.uid_next.value) {
Imap.UID uid_start = new Imap.UID(uid_start_value);
Gee.List<Geary.Email>? newest = yield imap_remote_folder.list_email_uid_async(
uid_start, null, Geary.Email.Field.PROPERTIES, cancellable);
if (newest != null && newest.size > 0) {
debug("saving %d newest emails in %s", newest.size, to_string());
foreach (Geary.Email email in newest) {
try {
yield local_folder.create_email_async(email, cancellable);
} catch (Error newest_err) {
debug("Unable to save new email in %s: %s", to_string(), newest_err.message);
}
}
}
}

View file

@ -45,12 +45,16 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Geary
mailbox = yield session_mgr.select_examine_mailbox(path.get_fullpath(info.delim), !readonly,
cancellable);
// TODO: hook up signals
// update with new information
this.readonly = Trillian.from_boolean(readonly);
properties = new Imap.FolderProperties(mailbox.count, mailbox.recent, mailbox.unseen,
// connect to signals
mailbox.exists_altered.connect(on_exists_altered);
mailbox.flags_altered.connect(on_flags_altered);
mailbox.expunged.connect(on_expunged);
properties = new Imap.FolderProperties(mailbox.exists, mailbox.recent, mailbox.unseen,
mailbox.uid_validity, mailbox.uid_next, properties.attrs);
notify_opened(Geary.Folder.OpenState.REMOTE);
@ -60,18 +64,36 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Geary
if (mailbox == null)
return;
mailbox.exists_altered.disconnect(on_exists_altered);
mailbox.flags_altered.disconnect(on_flags_altered);
mailbox.expunged.disconnect(on_expunged);
mailbox = null;
readonly = Trillian.UNKNOWN;
notify_closed(CloseReason.FOLDER_CLOSED);
}
public override async int get_email_count(Cancellable? cancellable = null) throws Error {
private void on_exists_altered(int exists) {
assert(mailbox != null);
notify_list_appended(exists);
}
private void on_flags_altered(FetchResults flags) {
assert(mailbox != null);
// TODO: Notify of changes
}
private void on_expunged(MessageNumber expunged) {
assert(mailbox != null);
// TODO: Notify of changes
}
public override async int get_email_count_async(Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
// TODO: Need to monitor folder for updates to the message count
return mailbox.count;
return mailbox.exists;
}
public override async void create_email_async(Geary.Email email, Cancellable? cancellable = null) throws Error {
@ -86,8 +108,7 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Geary
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
// TODO: Need to use a monitored count
normalize_span_specifiers(ref low, ref count, mailbox.count);
normalize_span_specifiers(ref low, ref count, mailbox.exists);
return yield mailbox.list_set_async(new MessageSet.range(low, count), fields, cancellable);
}

View file

@ -36,7 +36,7 @@ public class Geary.Imap.NoopResults : Geary.Imap.CommandResults {
foreach (ServerData data in response.server_data) {
try {
int ordinal = data.get_as_string(1).as_int().clamp(-1, int.MAX);
int ordinal = data.get_as_string(1).as_int(-1, int.MAX);
ServerDataType type = ServerDataType.from_parameter(data.get_as_string(2));
switch (type) {

View file

@ -6,6 +6,7 @@
public class Geary.Imap.ClientSessionManager {
public const int MIN_POOL_SIZE = 2;
public const int SELECTED_KEEPALIVE_SEC = 5;
private Credentials cred;
private uint default_port;
@ -14,6 +15,7 @@ public class Geary.Imap.ClientSessionManager {
private Gee.HashSet<SelectedContext> examined_contexts = new Gee.HashSet<SelectedContext>();
private Gee.HashSet<SelectedContext> selected_contexts = new Gee.HashSet<SelectedContext>();
private int keepalive_sec = ClientSession.DEFAULT_KEEPALIVE_SEC;
private int selected_keepalive_sec = SELECTED_KEEPALIVE_SEC;
public ClientSessionManager(Credentials cred, uint default_port) {
this.cred = cred;
@ -36,14 +38,23 @@ public class Geary.Imap.ClientSessionManager {
/**
* Set to zero or negative value if keepalives should be disabled. (This is not recommended.)
*
* This only affects newly created sessions or sessions leaving the selected/examined state
* and returning to an authorized state.
*/
public void set_keepalive(int keepalive_sec) {
// set for future connections
this.keepalive_sec = keepalive_sec;
// set for all current connections
foreach (ClientSession session in sessions)
session.enable_keepalives(keepalive_sec);
}
/**
* Set to zero or negative value if keepalives should be disabled when a mailbox is selected
* or examined. (This is not recommended.)
*
* This only affects newly selected/examined sessions.
*/
public void set_selected_keepalive(int selected_keepalive_sec) {
this.selected_keepalive_sec = selected_keepalive_sec;
}
public async Gee.Collection<Geary.Imap.MailboxInformation> list_roots(
@ -166,8 +177,10 @@ public class Geary.Imap.ClientSessionManager {
bool removed = contexts.remove(context);
assert(removed);
if (context.session != null)
if (context.session != null) {
context.session.close_mailbox_async.begin();
context.session.enable_keepalives(keepalive_sec);
}
}
// This should only be called when sessions_mutex is locked.
@ -219,6 +232,8 @@ public class Geary.Imap.ClientSessionManager {
ClientSession authd = yield get_authorized_session(cancellable);
authd.enable_keepalives(selected_keepalive_sec);
results = yield authd.select_examine_async(folder, is_select, cancellable);
return authd;

View file

@ -5,16 +5,24 @@
*/
public class Geary.Imap.Mailbox : Geary.SmartReference {
public string name { get; private set; }
public int count { get; private set; }
public int recent { get; private set; }
public int unseen { get; private set; }
public bool is_readonly { get; private set; }
public UIDValidity? uid_validity { get; private set; }
public UID? uid_next { get; private set; }
public string name { get { return context.name; } }
public int exists { get { return context.exists; } }
public int recent { get { return context.recent; } }
public int unseen { get { return context.unseen; } }
public bool is_readonly { get { return context.is_readonly; } }
public UIDValidity? uid_validity { get { return context.uid_validity; } }
public UID? uid_next { get { return context.uid_next; } }
private SelectedContext context;
public signal void exists_altered(int exists);
public signal void recent_altered(int recent);
public signal void flags_altered(FetchResults flags);
public signal void expunged(MessageNumber msg_num);
public signal void closed();
public signal void disconnected(bool local);
@ -23,17 +31,22 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
base (context);
this.context = context;
context.exists_changed.connect(on_exists_changed);
context.closed.connect(on_closed);
context.disconnected.connect(on_disconnected);
name = context.name;
count = context.exists;
recent = context.recent;
unseen = context.unseen;
is_readonly = context.is_readonly;
uid_validity = context.uid_validity;
uid_next = context.uid_next;
context.unsolicited_exists.connect(on_unsolicited_exists);
context.unsolicited_expunged.connect(on_unsolicited_expunged);
context.unsolicited_flags.connect(on_unsolicited_flags);
context.unsolicited_recent.connect(on_unsolicited_recent);
}
~Mailbox() {
context.closed.disconnect(on_closed);
context.disconnected.disconnect(on_disconnected);
context.session.unsolicited_exists.disconnect(on_unsolicited_exists);
context.session.unsolicited_expunged.disconnect(on_unsolicited_expunged);
context.session.unsolicited_flags.disconnect(on_unsolicited_flags);
context.session.unsolicited_recent.disconnect(on_unsolicited_recent);
}
public async Gee.List<Geary.Email>? list_set_async(MessageSet msg_set, Geary.Email.Field fields,
@ -107,10 +120,6 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
return email;
}
private void on_exists_changed(int exists) {
count = exists;
}
private void on_closed() {
closed();
}
@ -119,6 +128,22 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
disconnected(local);
}
private void on_unsolicited_exists(int exists) {
exists_altered(exists);
}
private void on_unsolicited_recent(int recent) {
recent_altered(recent);
}
private void on_unsolicited_expunged(MessageNumber msg_num) {
expunged(msg_num);
}
private void on_unsolicited_flags(FetchResults flags) {
flags_altered(flags);
}
// store FetchDataTypes in a set because the same data type may be requested multiple times
// by different fields (i.e. ENVELOPE)
private static void fields_to_fetch_data_types(Geary.Email.Field fields,
@ -225,11 +250,22 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
}
}
internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
// A SelectedContext is a ReferenceSemantics object wrapping a ClientSession that is in a SELECTED
// or EXAMINED state (i.e. it has "cd'd" into a folder). Multiple Mailbox objects may be created
// that refer to this SelectedContext. When they're all destroyed, the session is returned to
// the AUTHORIZED state by the ClientSessionManager.
//
// This means there is some duplication between the SelectedContext and the Mailbox. In particular
// signals must be reflected to ensure order-of-operation is preserved (i.e. when the ClientSession
// "unsolicited-exists" signal is fired, a signal subscriber may then query SelectedContext for
// its exists count before it has received the notification).
//
// All this fancy stepping should not be exposed to a user of the IMAP portion of Geary, who should
// only see Geary.Imap.Mailbox, nor should it be exposed to the user of Geary.Engine, where all this
// should only be exposed via Geary.Folder.
private class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
public ClientSession? session { get; private set; }
protected int manual_ref_count { get; protected set; }
public string name { get; protected set; }
public int exists { get; protected set; }
public int recent { get; protected set; }
@ -238,9 +274,15 @@ internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
public UIDValidity? uid_validity { get; protected set; }
public UID? uid_next { get; protected set; }
public signal void exists_changed(int exists);
protected int manual_ref_count { get; protected set; }
public signal void recent_changed(int recent);
public signal void unsolicited_exists(int exists);
public signal void unsolicited_recent(int recent);
public signal void unsolicited_expunged(MessageNumber expunged);
public signal void unsolicited_flags(FetchResults flags);
public signal void closed();
@ -260,6 +302,8 @@ internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
session.current_mailbox_changed.connect(on_session_mailbox_changed);
session.unsolicited_exists.connect(on_unsolicited_exists);
session.unsolicited_recent.connect(on_unsolicited_recent);
session.unsolicited_expunged.connect(on_unsolicited_expunged);
session.unsolicited_flags.connect(on_unsolicited_flags);
session.logged_out.connect(on_session_logged_out);
session.disconnected.connect(on_session_disconnected);
}
@ -269,6 +313,8 @@ internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
session.current_mailbox_changed.disconnect(on_session_mailbox_changed);
session.unsolicited_exists.disconnect(on_unsolicited_exists);
session.unsolicited_recent.disconnect(on_unsolicited_recent);
session.unsolicited_recent.disconnect(on_unsolicited_recent);
session.unsolicited_expunged.disconnect(on_unsolicited_expunged);
session.logged_out.disconnect(on_session_logged_out);
session.disconnected.disconnect(on_session_disconnected);
}
@ -280,12 +326,20 @@ internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
private void on_unsolicited_exists(int exists) {
this.exists = exists;
exists_changed(exists);
unsolicited_exists(exists);
}
private void on_unsolicited_recent(int recent) {
this.recent = recent;
recent_changed(recent);
unsolicited_recent(recent);
}
private void on_unsolicited_expunged(MessageNumber expunged) {
unsolicited_expunged(expunged);
}
private void on_unsolicited_flags(FetchResults results) {
unsolicited_flags(results);
}
private void on_session_mailbox_changed(string? old_mailbox, string? new_mailbox, bool readonly) {

View file

@ -68,7 +68,7 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
notify_closed(CloseReason.FOLDER_CLOSED);
}
public override async int get_email_count(Cancellable? cancellable = null) throws Error {
public override async int get_email_count_async(Cancellable? cancellable = null) throws Error {
check_open();
// TODO: This can be cached and updated when changes occur
@ -109,13 +109,15 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
imap_message_properties_table, message_id, properties);
yield imap_message_properties_table.create_async(properties_row, cancellable);
}
notify_list_appended(yield get_email_count_async(cancellable));
}
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field required_fields, Cancellable? cancellable) throws Error {
check_open();
normalize_span_specifiers(ref low, ref count, yield get_email_count(cancellable));
normalize_span_specifiers(ref low, ref count, yield get_email_count_async(cancellable));
if (count == 0)
return null;
@ -259,6 +261,8 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
throw new EngineError.NOT_FOUND("UID required to delete local email");
yield location_table.remove_by_ordering_async(folder_row.id, uid.value, cancellable);
// TODO: Notify of changes
}
public async bool is_email_present(Geary.EmailIdentifier id, out Geary.Email.Field available_fields,