More responsive loading of folders: #3702

This commit introduces lazy loading of folder contents, which allows the Engine to report back email in chunks rather than all at once (which might require a round-trip to the server).  This allows for the local database results to be returned to the caller right away while background I/O is occuring.
This commit is contained in:
Jim Nelson 2011-06-28 16:33:27 -07:00
parent 708b3d754a
commit a774034aff
9 changed files with 531 additions and 105 deletions

View file

@ -198,12 +198,18 @@ public class MainWindow : Gtk.Window {
yield current_folder.open_async(true);
Gee.List<Geary.Email>? email = yield current_folder.list_email_async(1, 100,
Geary.Email.Field.ENVELOPE);
current_folder.lazy_list_email_async(1, 1000, Geary.Email.Field.ENVELOPE,
on_list_email_ready);
}
private void on_list_email_ready(Gee.List<Geary.Email>? email, Error? err) {
if (email != null && email.size > 0) {
foreach (Geary.Email envelope in email)
message_list_store.append_envelope(envelope);
}
if (err != null)
debug("Error while listing email: %s", err.message);
}
private void on_select_folder_completed(Object? source, AsyncResult result) {

View file

@ -0,0 +1,91 @@
/* Copyright 2011 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.
*/
public abstract class Geary.AbstractFolder : Object, Geary.Folder {
protected virtual void notify_opened() {
opened();
}
protected virtual void notify_closed(Geary.Folder.CloseReason reason) {
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();
}
public abstract string get_name();
public abstract Geary.FolderProperties? get_properties();
public abstract async void open_async(bool readonly, Cancellable? cancellable = null) throws Error;
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 void create_email_async(Geary.Email email, Cancellable? cancellable = null)
throws Error;
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,
EmailCallback cb, Cancellable? cancellable = null) {
do_lazy_list_email_async.begin(low, count, required_fields, cb, cancellable);
}
private async void do_lazy_list_email_async(int low, int count, Geary.Email.Field required_fields,
EmailCallback cb, Cancellable? cancellable = null) {
try {
Gee.List<Geary.Email>? list = yield list_email_async(low, count, required_fields,
cancellable);
if (list != null && list.size > 0)
cb(list, null);
cb(null, null);
} catch (Error err) {
cb(null, err);
}
}
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,
Geary.Email.Field required_fields, EmailCallback cb, Cancellable? cancellable = null) {
do_lazy_list_email_sparse_async.begin(by_position, required_fields, cb, cancellable);
}
private async void do_lazy_list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, EmailCallback cb, Cancellable? cancellable = null) {
try {
Gee.List<Geary.Email>? list = yield list_email_sparse_async(by_position,
required_fields, cancellable);
if (list != null && list.size > 0)
cb(list, null);
cb(null, null);
} catch(Error err) {
cb(null, err);
}
}
public abstract async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
Cancellable? cancellable = null) throws Error;
public virtual string to_string() {
return get_name();
}
}

View file

@ -4,11 +4,15 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
private class Geary.EngineFolder : Object, Geary.Folder {
private class Geary.EngineFolder : Geary.AbstractFolder {
private const int REMOTE_FETCH_CHUNK_COUNT = 10;
private RemoteAccount remote;
private LocalAccount local;
private RemoteFolder remote_folder;
private RemoteFolder? remote_folder = null;
private LocalFolder local_folder;
private bool opened = false;
private Geary.Common.NonblockingSemaphore remote_semaphore = new Geary.Common.NonblockingSemaphore();
public EngineFolder(RemoteAccount remote, LocalAccount local, LocalFolder local_folder) {
this.remote = remote;
@ -19,67 +23,166 @@ private class Geary.EngineFolder : Object, Geary.Folder {
}
~EngineFolder() {
if (opened)
warning("Folder %s destroyed without closing", get_name());
local_folder.updated.disconnect(on_local_updated);
}
public string get_name() {
public override string get_name() {
return local_folder.get_name();
}
public Geary.FolderProperties? get_properties() {
public override Geary.FolderProperties? get_properties() {
return null;
}
public async void create_email_async(Geary.Email email, Cancellable? cancellable) throws Error {
public override async void create_email_async(Geary.Email email, Cancellable? cancellable) throws Error {
throw new EngineError.READONLY("Engine currently read-only");
}
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
public override async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
if (opened)
throw new EngineError.ALREADY_OPEN("Folder %s already open", get_name());
yield local_folder.open_async(readonly, cancellable);
if (remote_folder == null) {
remote_folder = (RemoteFolder) yield remote.fetch_folder_async(null, local_folder.get_name(),
// Rather than wait for the remote folder to open (which blocks completion of this method),
// attempt to open in the background and treat this folder as "opened". If the remote
// doesn't open, this folder remains open but only able to work with the local cache.
//
// Note that any use of remote_folder in this class should first call
// wait_for_remote_to_open(), which uses a NonblockingSemaphore to indicate that the remote
// is open (or has failed to open). This allows for early calls to list and fetch emails
// can work out of the local cache until the remote is ready.
RemoteFolder folder = (RemoteFolder) yield remote.fetch_folder_async(null, local_folder.get_name(),
cancellable);
remote_folder.updated.connect(on_remote_updated);
}
open_remote_async.begin(folder, readonly, cancellable, on_open_remote_completed);
yield remote_folder.open_async(readonly, cancellable);
opened = true;
notify_opened();
}
public async void close_async(Cancellable? cancellable = null) throws Error {
yield local_folder.close_async(cancellable);
private async void open_remote_async(RemoteFolder folder, bool readonly, Cancellable? cancellable)
throws Error {
yield folder.open_async(readonly, cancellable);
if (remote_folder != null) {
remote_folder.updated.disconnect(on_remote_updated);
yield remote_folder.close_async(cancellable);
remote_folder = null;
remote_folder = folder;
remote_folder.updated.connect(on_remote_updated);
remote_semaphore.notify();
}
private void on_open_remote_completed(Object? source, AsyncResult result) {
try {
open_remote_async.end(result);
} catch (Error err) {
debug("Unable to open remote folder %s: %s", to_string(), err.message);
notify_closed(CloseReason.FOLDER_CLOSED);
remote_folder = null;
try {
remote_semaphore.notify();
} catch (Error err) {
debug("Unable to notify remote folder ready: %s", err.message);
}
}
}
public async int get_email_count(Cancellable? cancellable = null) throws Error {
private async void wait_for_remote_to_open() throws Error {
yield remote_semaphore.wait_async();
}
public override async void close_async(Cancellable? cancellable = null) throws Error {
yield local_folder.close_async(cancellable);
// if the remote folder is open, close it in the background so the caller isn't waiting for
// this method to complete (much like open_async())
if (remote_folder != null) {
yield remote_semaphore.wait_async();
remote_semaphore = new Geary.Common.NonblockingSemaphore();
remote_folder.updated.disconnect(on_remote_updated);
RemoteFolder? folder = remote_folder;
remote_folder = null;
folder.close_async.begin(cancellable);
notify_closed(CloseReason.FOLDER_CLOSED);
}
opened = false;
}
public override async int get_email_count(Cancellable? cancellable = null) throws Error {
// TODO
return 0;
}
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
assert(low >= 1);
assert(count >= 0);
if (count == 0)
return null;
Gee.List<Geary.Email>? local_list = yield local_folder.list_email_async(low, count,
required_fields, cancellable);
int local_list_size = (local_list != null) ? local_list.size : 0;
debug("local list found %d", local_list_size);
// block on do_list_email_async(), using an accumulator to gather the emails and return
// them all at once to the caller
Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
yield do_list_email_async(low, count, required_fields, accumulator, null, cancellable);
if (remote_folder == null || local_list_size == count)
return local_list;
return accumulator;
}
public override void lazy_list_email_async(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);
}
private async void do_list_email_async(int low, int count, Geary.Email.Field required_fields,
Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable = null)
throws Error {
assert(low >= 1);
assert(count >= 0);
if (!opened)
throw new EngineError.OPEN_REQUIRED("%s is not open", get_name());
if (count == 0) {
// signal finished
if (cb != null)
cb(null, null);
return;
}
Gee.List<Geary.Email>? local_list = null;
try {
local_list = yield local_folder.list_email_async(low, count, required_fields,
cancellable);
} catch (Error local_err) {
if (cb != null)
cb (null, local_err);
throw local_err;
}
int local_list_size = (local_list != null) ? local_list.size : 0;
if (local_list_size > 0) {
if (accumulator != null)
accumulator.add_all(local_list);
if (cb != null)
cb(local_list, null);
}
if (local_list_size == count) {
// signal finished
if (cb != null)
cb(null, null);
return;
}
// go through the positions from (low) to (low + count) and see if they're not already
// present in local_list; whatever isn't present needs to be fetched
@ -93,26 +196,91 @@ private class Geary.EngineFolder : Object, Geary.Folder {
needed_by_position += position;
}
if (needed_by_position.length == 0)
return local_list;
if (needed_by_position.length == 0) {
// signal finished
if (cb != null)
cb(null, null);
return;
}
Gee.List<Geary.Email>? remote_list = yield remote_list_email(needed_by_position,
required_fields, cancellable);
Gee.List<Geary.Email>? remote_list = null;
try {
// if cb != null, it will be called by remote_list_email(), so don't call again with
// returned list
remote_list = yield remote_list_email(needed_by_position, required_fields, cb, cancellable);
} catch (Error remote_err) {
if (cb != null)
cb(null, remote_err);
throw remote_err;
}
return combine_lists(local_list, remote_list);
if (accumulator != null && remote_list != null && remote_list.size > 0)
accumulator.add_all(remote_list);
// signal finished
if (cb != null)
cb(null, null);
}
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
public override async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
if (by_position.length == 0)
return null;
Gee.List<Geary.Email>? local_list = yield local_folder.list_email_sparse_async(by_position,
required_fields, cancellable);
Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
yield do_list_email_sparse_async(by_position, required_fields, accumulator, null,
cancellable);
return accumulator;
}
public override void lazy_list_email_sparse_async(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);
}
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 = null)
throws Error {
if (!opened)
throw new EngineError.OPEN_REQUIRED("%s is not open", get_name());
if (by_position.length == 0) {
// signal finished
if (cb != null)
cb(null, null);
return;
}
Gee.List<Geary.Email>? local_list = null;
try {
local_list = yield local_folder.list_email_sparse_async(by_position, required_fields,
cancellable);
} catch (Error local_err) {
if (cb != null)
cb(null, local_err);
throw local_err;
}
int local_list_size = (local_list != null) ? local_list.size : 0;
if (remote_folder == null || local_list_size == by_position.length)
return local_list;
if (local_list_size == by_position.length) {
if (accumulator != null)
accumulator.add_all(local_list);
// report and signal finished
if (cb != null) {
cb(local_list, null);
cb(null, null);
}
return;
}
// go through the list looking for anything not already in the sparse by_position list
// to fetch from the server; since by_position is not guaranteed to be sorted, the local
@ -136,28 +304,71 @@ private class Geary.EngineFolder : Object, Geary.Folder {
needed_by_position += position;
}
if (needed_by_position.length == 0)
return local_list;
if (needed_by_position.length == 0) {
if (local_list != null && local_list.size > 0) {
if (accumulator != null)
accumulator.add_all(local_list);
if (cb != null)
cb(local_list, null);
}
// signal finished
if (cb != null)
cb(null, null);
return;
}
Gee.List<Geary.Email>? remote_list = yield remote_list_email(needed_by_position,
required_fields, cancellable);
Gee.List<Geary.Email>? remote_list = null;
try {
// if cb != null, it will be called by remote_list_email(), so don't call again with
// returned list
remote_list = yield remote_list_email(needed_by_position, required_fields, cb, cancellable);
} catch (Error remote_err) {
if (cb != null)
cb(null, remote_err);
throw remote_err;
}
return combine_lists(local_list, remote_list);
if (accumulator != null && remote_list != null && remote_list.size > 0)
accumulator.add_all(remote_list);
// signal finished
if (cb != null)
cb(null, null);
}
private async Gee.List<Geary.Email>? remote_list_email(int[] needed_by_position,
Geary.Email.Field required_fields, Cancellable? cancellable) throws Error {
Geary.Email.Field required_fields, EmailCallback? cb, Cancellable? cancellable) throws Error {
// possible to call remote multiple times, wait for it to open once and go
yield wait_for_remote_to_open();
debug("Background fetching %d emails for %s", needed_by_position.length, get_name());
Gee.List<Geary.Email>? remote_list = yield remote_folder.list_email_sparse_async(
needed_by_position, required_fields, cancellable);
Gee.List<Geary.Email> full = new Gee.ArrayList<Geary.Email>();
if (remote_list != null && remote_list.size == 0)
remote_list = null;
// if any were fetched, store locally
// TODO: Bulk writing
if (remote_list != null) {
int index = 0;
while (index <= needed_by_position.length) {
// if a callback is specified, pull the messages down in chunks, so they can be reported
// incrementally
unowned int[] list;
if (cb != null) {
int list_count = int.min(REMOTE_FETCH_CHUNK_COUNT, needed_by_position.length - index);
list = needed_by_position[index:index + list_count];
} else {
list = needed_by_position;
}
Gee.List<Geary.Email>? remote_list = yield remote_folder.list_email_sparse_async(
list, required_fields, cancellable);
if (remote_list == null || remote_list.size == 0)
break;
// if any were fetched, store locally
// TODO: Bulk writing
foreach (Geary.Email email in remote_list) {
bool exists_in_system = false;
if (email.message_id != null) {
@ -193,14 +404,21 @@ private class Geary.EngineFolder : Object, Geary.Folder {
yield local_folder.update_email_async(email, false, cancellable);
}
}
if (cb != null)
cb(remote_list, null);
full.add_all(remote_list);
index += list.length;
}
return remote_list;
return full;
}
public async Geary.Email fetch_email_async(int num, Geary.Email.Field fields,
public override async Geary.Email fetch_email_async(int num, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (remote_folder == null)
if (!opened)
throw new EngineError.OPEN_REQUIRED("Folder %s not opened", get_name());
try {
@ -220,6 +438,7 @@ private class Geary.EngineFolder : Object, Geary.Folder {
fields = fields.set(Geary.Email.Field.REFERENCES);
// fetch from network
yield wait_for_remote_to_open();
Geary.Email email = yield remote_folder.fetch_email_async(num, fields, cancellable);
// save to local store
@ -233,19 +452,5 @@ private class Geary.EngineFolder : Object, Geary.Folder {
private void on_remote_updated() {
}
private Gee.List<Geary.Email>? combine_lists(Gee.List<Geary.Email>? a, Gee.List<Geary.Email>? b) {
if (a == null)
return b;
if (b == null)
return a;
Gee.List<Geary.Email> combined = new Gee.ArrayList<Geary.Email>();
combined.add_all(a);
combined.add_all(b);
return combined;
}
}

View file

@ -4,6 +4,8 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public delegate void Geary.EmailCallback(Gee.List<Geary.Email>? emails, Error? err);
public interface Geary.Folder : Object {
public enum CloseReason {
LOCAL_CLOSE,
@ -109,7 +111,8 @@ public interface Geary.Folder : Object {
*
* Note that this only returns the number of messages available to the backing medium. In the
* case of the local store, this might be less than the number on the network server. Folders
* created by Engine are aggregating objects and will return the true count.
* created by Engine are aggregating objects and will return the true count. However, this
* might require a round-trip to the server.
*
* Also note that local folders may be sparsely populated. get_count() returns the last position
* available, but not all emails from 1 to n may be available.
@ -151,7 +154,7 @@ public interface Geary.Folder : Object {
*
* In the case of a Folder returned by Engine, it will use what's available in the local store
* and fetch from the network only what it needs, so that the caller gets a full list.
* Note that this means the call may take some time to complete.
* Note that this means the call may require a round-trip to the server.
*
* TODO: Delayed listing methods (where what's available are reported via a callback after the
* async method has completed) will be implemented in the future for more responsive behavior.
@ -164,6 +167,19 @@ public interface Geary.Folder : Object {
public abstract async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
/**
* Similar in contract to list_email_async(), however instead of the emails being returned all
* at once at completion time, the emails are delivered to the caller in chunks via the
* EmailCallback. The method indicates when all the message have been fetched by passing a null
* for the first parameter. If an Error occurs while processing, it will be passed as the
* second parameter. There's no guarantess of the order the messages will be delivered to the
* caller.
*
* 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,
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,
@ -180,6 +196,18 @@ public interface Geary.Folder : Object {
public abstract async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
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
* 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
* message's order.
*
* The Folder must be opened prior to attempting this operation.
*/
public abstract void lazy_list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, EmailCallback cb, Cancellable? cancellable = null);
/**
* Returns a single email that fulfills the required_fields flag at the ordered position in
* the folder. If position is invalid for the folder's contents, an EngineError.NOT_FOUND
@ -196,8 +224,6 @@ public interface Geary.Folder : Object {
/**
* Used for debugging. Should not be used for user-visible labels.
*/
public virtual string to_string() {
return get_name();
}
public abstract string to_string();
}

View file

@ -0,0 +1,73 @@
/* Copyright 2011 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.
*/
public class Geary.Common.NonblockingSemaphore {
private class Pending {
public SourceFunc cb;
public Pending(SourceFunc cb) {
this.cb = cb;
}
}
private Cancellable? cancellable;
private bool passed = false;
private Gee.List<Pending> pending_queue = new Gee.LinkedList<Pending>();
public NonblockingSemaphore(Cancellable? cancellable = null) {
this.cancellable = cancellable;
if (cancellable != null)
cancellable.cancelled.connect(on_cancelled);
}
~NonblockingSemaphore() {
if (pending_queue.size > 0)
warning("Nonblocking semaphore destroyed with %d pending callers", pending_queue.size);
}
private void trigger_all() {
foreach (Pending pending in pending_queue)
Idle.add(pending.cb);
pending_queue.clear();
}
public void notify() throws Error {
check_cancelled();
passed = true;
trigger_all();
}
// TODO: Allow the caller to pass their own cancellable in if they want to be able to cancel
// this particular wait (and not all waiting threads of execution)
public async void wait_async() throws Error {
for (;;) {
check_cancelled();
if (passed)
return;
pending_queue.add(new Pending(wait_async.callback));
yield;
}
}
public bool is_cancelled() {
return (cancellable != null) ? cancellable.is_cancelled() : false;
}
private void check_cancelled() throws Error {
if (is_cancelled())
throw new IOError.CANCELLED("Semaphore cancelled");
}
private void on_cancelled() {
trigger_all();
}
}

View file

@ -4,7 +4,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder {
private ClientSessionManager session_mgr;
private MailboxInformation info;
private string name;
@ -21,7 +21,7 @@ public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
properties = new Imap.FolderProperties(null, info.attrs);
}
public string get_name() {
public override string get_name() {
return name;
}
@ -29,11 +29,11 @@ public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
return readonly;
}
public Geary.FolderProperties? get_properties() {
public override Geary.FolderProperties? get_properties() {
return properties;
}
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
public override async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
if (mailbox != null)
throw new EngineError.ALREADY_OPEN("%s already open", to_string());
@ -46,7 +46,7 @@ public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
notify_opened();
}
public async void close_async(Cancellable? cancellable = null) throws Error {
public override async void close_async(Cancellable? cancellable = null) throws Error {
if (mailbox == null)
return;
@ -57,21 +57,21 @@ public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
notify_closed(CloseReason.FOLDER_CLOSED);
}
public async int get_email_count(Cancellable? cancellable = null) throws Error {
public override async int get_email_count(Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
return mailbox.count;
}
public async void create_email_async(Geary.Email email, Cancellable? cancellable = null) throws Error {
public override async void create_email_async(Geary.Email email, Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
throw new EngineError.READONLY("IMAP currently read-only");
}
public async Gee.List<Geary.Email>? list_email_async(int low, int count, Geary.Email.Field fields,
public override async Gee.List<Geary.Email>? list_email_async(int low, int count, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
@ -79,7 +79,7 @@ public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
return yield mailbox.list_set_async(new MessageSet.range(low, count), fields, cancellable);
}
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
public override async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
@ -87,7 +87,7 @@ public class Geary.Imap.Folder : Object, Geary.Folder, Geary.RemoteFolder {
return yield mailbox.list_set_async(new MessageSet.sparse(by_position), fields, cancellable);
}
public async Geary.Email fetch_email_async(int position, Geary.Email.Field fields,
public override async Geary.Email fetch_email_async(int position, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());

View file

@ -5,6 +5,8 @@
*/
public class Geary.Imap.ClientSessionManager {
public const int MIN_POOL_SIZE = 2;
private Credentials cred;
private uint default_port;
private Gee.HashSet<ClientSession> sessions = new Gee.HashSet<ClientSession>();
@ -15,6 +17,20 @@ public class Geary.Imap.ClientSessionManager {
public ClientSessionManager(Credentials cred, uint default_port) {
this.cred = cred;
this.default_port = default_port;
adjust_session_pool.begin();
}
// TODO: Need a more thorough and bulletproof system for maintaining a pool of ready
// authorized sessions.
private async void adjust_session_pool() {
while (sessions.size < MIN_POOL_SIZE) {
try {
yield create_new_authorized_session(null);
} catch (Error err) {
debug("Unable to create authorized session to %s: %s", cred.server, err.message);
}
}
}
/**
@ -115,13 +131,7 @@ public class Geary.Imap.ClientSessionManager {
context.session.close_mailbox_async.begin();
}
private async ClientSession get_authorized_session(Cancellable? cancellable = null) throws Error {
foreach (ClientSession session in sessions) {
string? mailbox;
if (session.get_context(out mailbox) == ClientSession.Context.AUTHORIZED)
return session;
}
private async ClientSession create_new_authorized_session(Cancellable? cancellable) throws Error {
debug("Creating new session to %s", cred.server);
ClientSession new_session = new ClientSession(cred.server, default_port);
@ -135,11 +145,23 @@ public class Geary.Imap.ClientSessionManager {
sessions.add(new_session);
debug("Created new session to %s, %d total", cred.server, sessions.size);
return new_session;
}
private async ClientSession get_authorized_session(Cancellable? cancellable) throws Error {
foreach (ClientSession session in sessions) {
string? mailbox;
if (session.get_context(out mailbox) == ClientSession.Context.AUTHORIZED)
return session;
}
return yield create_new_authorized_session(cancellable);
}
private async ClientSession select_examine_async(string folder, bool is_select,
out SelectExamineResults results, Cancellable? cancellable = null) throws Error {
out SelectExamineResults results, Cancellable? cancellable) throws Error {
ClientSession.Context needed_context = (is_select) ? ClientSession.Context.SELECTED
: ClientSession.Context.EXAMINED;
foreach (ClientSession session in sessions) {

View file

@ -7,7 +7,7 @@
// TODO: This class currently deals with generic email storage as well as IMAP-specific issues; in
// the future, to support other email services, will need to break this up.
public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder {
private MailDatabase db;
private FolderRow folder_row;
private MessageTable message_table;
@ -32,15 +32,15 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
throw new EngineError.OPEN_REQUIRED("%s not open", to_string());
}
public string get_name() {
public override string get_name() {
return name;
}
public Geary.FolderProperties? get_properties() {
public override Geary.FolderProperties? get_properties() {
return null;
}
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
public override async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
if (opened)
throw new EngineError.ALREADY_OPEN("%s already open", to_string());
@ -48,7 +48,7 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
notify_opened();
}
public async void close_async(Cancellable? cancellable = null) throws Error {
public override async void close_async(Cancellable? cancellable = null) throws Error {
if (!opened)
return;
@ -56,14 +56,15 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
notify_closed(CloseReason.FOLDER_CLOSED);
}
public async int get_email_count(Cancellable? cancellable = null) throws Error {
public override async int get_email_count(Cancellable? cancellable = null) throws Error {
check_open();
// TODO
return 0;
}
public async void create_email_async(Geary.Email email, Cancellable? cancellable = null) throws Error {
public override async void create_email_async(Geary.Email email, Cancellable? cancellable = null)
throws Error {
check_open();
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
@ -90,7 +91,7 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
yield imap_location_table.create_async(imap_location_row, cancellable);
}
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field required_fields, Cancellable? cancellable) throws Error {
assert(low >= 1);
assert(count >= 1);
@ -103,7 +104,7 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
return yield list_email(list, required_fields, cancellable);
}
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
public override async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
check_open();
@ -146,7 +147,7 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
return (emails.size > 0) ? emails : null;
}
public async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
public override async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
Cancellable? cancellable = null) throws Error {
assert(position >= 1);

View file

@ -13,6 +13,7 @@ def build(bld):
bld.common_packages = ['glib-2.0', 'unique-1.0', 'posix' ]
bld.engine_src = [
'../engine/api/geary-abstract-folder.vala',
'../engine/api/geary-account.vala',
'../engine/api/geary-credentials.vala',
'../engine/api/geary-email-location.vala',
@ -29,6 +30,7 @@ def build(bld):
'../engine/common/common-interfaces.vala',
'../engine/common/common-message-data.vala',
'../engine/common/common-nonblocking-semaphore.vala',
'../engine/common/common-string.vala',
'../engine/imap/api/imap-account.vala',