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:
parent
e812ef6166
commit
d1208e8efe
12 changed files with 303 additions and 134 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue