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:
parent
708b3d754a
commit
a774034aff
9 changed files with 531 additions and 105 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
91
src/engine/api/geary-abstract-folder.vala
Normal file
91
src/engine/api/geary-abstract-folder.vala
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
||||
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);
|
||||
|
||||
if (remote_folder != null) {
|
||||
remote_folder.updated.disconnect(on_remote_updated);
|
||||
yield remote_folder.close_async(cancellable);
|
||||
remote_folder = null;
|
||||
|
||||
notify_closed(CloseReason.FOLDER_CLOSED);
|
||||
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);
|
||||
|
||||
Gee.List<Geary.Email>? remote_list = yield remote_list_email(needed_by_position,
|
||||
required_fields, cancellable);
|
||||
return;
|
||||
}
|
||||
|
||||
return combine_lists(local_list, remote_list);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Gee.List<Geary.Email>? remote_list = yield remote_list_email(needed_by_position,
|
||||
required_fields, cancellable);
|
||||
if (cb != null)
|
||||
cb(local_list, null);
|
||||
}
|
||||
|
||||
return combine_lists(local_list, remote_list);
|
||||
// signal finished
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
// if any were fetched, store locally
|
||||
// TODO: Bulk writing
|
||||
if (remote_list != null) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
73
src/engine/common/common-nonblocking-semaphore.vala
Normal file
73
src/engine/common/common-nonblocking-semaphore.vala
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue