Remove positional listing operations from Geary.Folder: Closes #7249
This simplifies the various Folder implementations, removes one (large) replay queue operation, makes vector expansion _much_ simpler, and generally makes the Geary API a bit cleaner. Because the code affected included some of the proposed fixes for operation. That plus the vector expansion changes may have some affect on that ticket.
This commit is contained in:
parent
3270edb1fa
commit
da8c95c29a
22 changed files with 787 additions and 1469 deletions
|
|
@ -192,10 +192,10 @@ engine/imap-engine/gmail/imap-engine-gmail-account.vala
|
|||
engine/imap-engine/gmail/imap-engine-gmail-folder.vala
|
||||
engine/imap-engine/other/imap-engine-other-account.vala
|
||||
engine/imap-engine/other/imap-engine-other-folder.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-copy-email.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-expunge-email.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-list-email.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala
|
||||
engine/imap-engine/replay-ops/imap-engine-mark-email.vala
|
||||
|
|
|
|||
|
|
@ -77,42 +77,18 @@ public abstract class Geary.AbstractFolder : BaseObject, Geary.Folder {
|
|||
|
||||
public abstract async void close_async(Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public virtual void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
|
||||
Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
|
||||
do_lazy_list_email_async.begin(low, count, required_fields, flags, cb, cancellable);
|
||||
}
|
||||
|
||||
private async void do_lazy_list_email_async(int low, int count, Geary.Email.Field required_fields,
|
||||
Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
|
||||
try {
|
||||
Gee.List<Geary.Email>? list = yield list_email_async(low, count, required_fields, flags,
|
||||
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_by_id_async(Geary.EmailIdentifier initial_id,
|
||||
public abstract async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
|
||||
int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public virtual void lazy_list_email_by_id(Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb,
|
||||
Cancellable? cancellable = null) {
|
||||
do_lazy_list_email_by_id_async.begin(initial_id, count, required_fields, flags, cb, cancellable);
|
||||
public virtual void lazy_list_email_by_id(Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
|
||||
do_lazy_list_email_by_id_async.begin(initial_id, count, required_fields, flags, cb,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb,
|
||||
Cancellable? cancellable) {
|
||||
private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable) {
|
||||
try {
|
||||
Gee.List<Geary.Email>? list = yield list_email_by_id_async(initial_id, count,
|
||||
required_fields, flags, cancellable);
|
||||
|
|
|
|||
|
|
@ -101,19 +101,6 @@ public class Geary.Email : BaseObject {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* position is the one-based addressing of the email in the folder, in the notion that messages
|
||||
* are "stacked" from 1 to n, earliest to newest. "Earliest" and "newest" do not necessarily
|
||||
* correspond to the emails' send or receive time, merely how they've been arranged on the stack.
|
||||
*
|
||||
* This value is only good at the time the Email is requested from the folder. Subsequent
|
||||
* operations may change the Email's position in the folder (or simply remove it). This value
|
||||
* is *not* updated to reflect this.
|
||||
*
|
||||
* This field is not always returned valid, depending on the Folder it originates from.
|
||||
*/
|
||||
internal int position { get; set; default = -1; }
|
||||
|
||||
/**
|
||||
* id is a unique identifier for the Email in the Folder. It is guaranteed to be unique for
|
||||
* as long as the Folder is open. Once closed, guarantees are no longer made.
|
||||
|
|
|
|||
|
|
@ -93,10 +93,14 @@ public interface Geary.Folder : BaseObject {
|
|||
*/
|
||||
FORCE_UPDATE,
|
||||
/**
|
||||
* Exclude the provided EmailIdentifier (only respected by {@link list_email_by_id_async} and
|
||||
* Include the provided EmailIdentifier (only respected by {@link list_email_by_id_async} and
|
||||
* {@link lazy_list_email_by_id}).
|
||||
*/
|
||||
EXCLUDING_ID;
|
||||
INCLUDING_ID,
|
||||
/**
|
||||
* Direction of list traversal (if not set, from newest to oldest).
|
||||
*/
|
||||
OLDEST_TO_NEWEST;
|
||||
|
||||
public bool is_any_set(ListFlags flags) {
|
||||
return (this & flags) != 0;
|
||||
|
|
@ -105,6 +109,26 @@ public interface Geary.Folder : BaseObject {
|
|||
public bool is_all_set(ListFlags flags) {
|
||||
return (this & flags) == flags;
|
||||
}
|
||||
|
||||
public bool is_local_only() {
|
||||
return is_all_set(LOCAL_ONLY);
|
||||
}
|
||||
|
||||
public bool is_force_update() {
|
||||
return is_all_set(FORCE_UPDATE);
|
||||
}
|
||||
|
||||
public bool is_including_id() {
|
||||
return is_all_set(INCLUDING_ID);
|
||||
}
|
||||
|
||||
public bool is_oldest_to_newest() {
|
||||
return is_all_set(OLDEST_TO_NEWEST);
|
||||
}
|
||||
|
||||
public bool is_newest_to_oldest() {
|
||||
return !is_oldest_to_newest();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Geary.Account account { get; }
|
||||
|
|
@ -163,7 +187,6 @@ public interface Geary.Folder : BaseObject {
|
|||
* Fired when email has been appended to the list of messages in the folder.
|
||||
*
|
||||
* The {@link EmailIdentifier} for all appended messages is supplied as a signal parameter.
|
||||
* Email positions remain valid, but the total count of the messages in the folder has changed.
|
||||
*
|
||||
* @see email_locally_appended
|
||||
*/
|
||||
|
|
@ -177,9 +200,6 @@ public interface Geary.Folder : BaseObject {
|
|||
* have not been seen prior. Hence, an email that is removed from the folder and returned
|
||||
* later will not be listed here (unless it was removed from the local store in the meantime).
|
||||
*
|
||||
* Note that these messages were appended as well, hence their positional addressing may have
|
||||
* changed since last seen in this folder.
|
||||
*
|
||||
* @see email_appended
|
||||
*/
|
||||
public signal void email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids);
|
||||
|
|
@ -321,98 +341,28 @@ public interface Geary.Folder : BaseObject {
|
|||
public abstract async void close_async(Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Returns a list of messages that fulfill the required_fields flags starting at the low
|
||||
* position and moving up to (low + count). If count is -1, the returned list starts at low
|
||||
* and proceeds to all available emails. If low is -1, the *last* (most recent) 'count' emails
|
||||
* are returned. If both low and count are -1, it's no different than calling with low as
|
||||
* 1 and count -1, that is, all emails are returned. (See normalize_span_specifiers() for
|
||||
* a utility function that handles all aspects of these requirements.) low is one-based, unless
|
||||
* -1 is specified, as explained above.
|
||||
* List emails from the {@link Folder} starting at a particular location within the vector
|
||||
* and moving either direction along the mail stack.
|
||||
*
|
||||
* The returned list is not guaranteed to be in any particular order. The position index
|
||||
* (starting from low) *is* ordered, however, from oldest to newest (in terms of receipt by the
|
||||
* SMTP server, not necessarily the Sent: field), so if the caller wants the latest emails,
|
||||
* they should calculate low by subtracting from get_email_count() or set low to -1 and use
|
||||
* count to fetch the last n emails.
|
||||
* If the {@link EmailIdentifier} is null, it indicates the end of the vector. Which end
|
||||
* depends on the {@link ListFlags.OLDEST_TO_NEWEST} flag. Without, the default is to traverse
|
||||
* from newest to oldest, with null being the newest email. If set, the direction is reversed
|
||||
* and null indicates the oldest email.
|
||||
*
|
||||
* If any position in low to (low + count) are out of range, only the email within range are
|
||||
* reported. No error is thrown. This allows callers to blindly request the first or last n
|
||||
* emails in a folder without determining the count first.
|
||||
* If not null, the EmailIdentifier ''must'' have originated from this Folder.
|
||||
*
|
||||
* If the caller would prefer the Folder return emails it has immediately available rather than
|
||||
* make an expensive network call to "properly" fetch the emails, it should pass ListFlags.LOCAL_ONLY.
|
||||
* However, this also means avoiding a full synchronization, so it's possible the fetched
|
||||
* emails do not correspond to what's actually available on the server. The best use of this
|
||||
* method is to quickly retrieve a block of email for display or processing purposes,
|
||||
* immediately followed by a non-fast list operation and then merging the two results.
|
||||
* To fetch all available messages in one call, use a count of int.MAX.
|
||||
*
|
||||
* Likewise, if this is called while Folder is in an OPENING or LOCAL state (that is, the remote
|
||||
* server is not yet available), only local mail will be returned. This is to avoid two poor
|
||||
* situations: (a) waiting to connect to the server to ensure that positional addressing is
|
||||
* correctly calculated (and potentially missing the opportunity to return available local data)
|
||||
* and (b) fetching locally, waiting, then fetching remotely, which means the returned emails
|
||||
* could potentially mix stale and fresh data. A ListFlag may be offered in the future to allow
|
||||
* the caller to force the engine to wait for a server connection before continuing. See
|
||||
* get_open_state() and "opened" for more information.
|
||||
*
|
||||
* Note that LOCAL_ONLY only returns the emails with the required fields that are available in
|
||||
* the Folder's local store. It may have fewer or incomplete messages, meaning that this will
|
||||
* return an incomplete list.
|
||||
*
|
||||
* Similarly, if the caller wants the Folder to always go out to the network to retrieve the
|
||||
* information (even if it is already present in the local store), use ListFlags.FORCE_UPDATE.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, 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(int low, int count, Geary.Email.Field required_fields,
|
||||
ListFlags flags, EmailCallback cb, Cancellable? cancellable = null);
|
||||
|
||||
/**
|
||||
* Similar in contract to list_email_async(), but uses Geary.EmailIdentifier rather than
|
||||
* positional addressing. This allows for a batch of messages to be listed from a starting
|
||||
* identifier, going up and down the stack depending on the count parameter.
|
||||
*
|
||||
* The count parameter is exclusive of the Email at initial_id. That is, if count is one,
|
||||
* two Emails may be returned: the one for initial_id and the next one. If count is zero,
|
||||
* only the Email with the specified initial_id will be listed, making this method operate
|
||||
* like fetch_email_async().
|
||||
*
|
||||
* If count is positive, initial_id is the *lowest* identifier and the returned list is going
|
||||
* up the stack (toward the most recently added). If the count is negative, initial_id is
|
||||
* the *highest* identifier and the returned list is going down the stack (toward the earliest
|
||||
* added).
|
||||
*
|
||||
* To fetch all available messages in one direction or another, use int.MIN or int.MAX.
|
||||
*
|
||||
* initial_id *must* be an EmailIdentifier available to the Folder for this to work, as listing
|
||||
* a range inevitably requires positional addressing under the covers. However, since it's
|
||||
* some times desirable to list messages excluding the specified EmailIdentifier, callers may
|
||||
* use ListFlags.EXCLUDING_ID (which is a flag only recognized by this method and
|
||||
* lazy_list_email_by_id()). If the count is zero or one (or the number of messages remaining
|
||||
* on the stack from the initial ID's position is zero or one) *and* this flag is set, no
|
||||
* messages will be returned.
|
||||
* Use {@link ListFlags.INCLUDING_ID} to include the {@link Email} for the particular identifier
|
||||
* in the results. Otherwise, the specified email will not be included. A null
|
||||
* EmailIdentifier implies that the top most email is included in the result (i.e.
|
||||
* ListFlags.INCLUDING_ID is not required);
|
||||
*
|
||||
* There's no guarantee of the returned messages' order.
|
||||
*
|
||||
* There is (currently) no sparse version of list_email_by_id_async().
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
||||
public abstract async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
|
||||
int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
|
|
@ -426,18 +376,16 @@ public interface Geary.Folder : BaseObject {
|
|||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract void lazy_list_email_by_id(Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb,
|
||||
Cancellable? cancellable = null);
|
||||
public abstract void lazy_list_email_by_id(Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb, Cancellable? cancellable = null);
|
||||
|
||||
/**
|
||||
* Similar in contract to list_email_async(), but uses a list of Geary.EmailIdentifiers rather
|
||||
* than positional addressing, much like list_email_by_id_async(). See that method for more
|
||||
* information on its contract and how the flags parameter works.
|
||||
* Similar in contract to {@link list_email_by_id_async}, but uses a list of
|
||||
* {@link Geary.EmailIdentifier}s rather than a range.
|
||||
*
|
||||
* Any Gee.Collection is accepted for EmailIdentifiers, but the returned list will only contain
|
||||
* one email for each requested; duplicates are ignored. ListFlags.EXCLUDING_ID is ignored
|
||||
* for this call and lazy_list_email_by_sparse_id().
|
||||
* one email for each requested; duplicates are ignored. ListFlags.INCLUDING_ID is ignored
|
||||
* for this call and {@link lazy_list_email_by_sparse_id}.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
|
|
@ -446,11 +394,11 @@ public interface Geary.Folder : BaseObject {
|
|||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Similar in contract to lazy_list_email(), but uses a list of Geary.EmailIdentifiers rather
|
||||
* than positional addressing. See list_email_by_id_async() and list_email_by_sparse_id_async()
|
||||
* for more information on their contracts and how the flags and callback parameter works.
|
||||
* See {@link list_email_by_id_async} and {@link list_email_by_sparse_id_async}
|
||||
* for more information on {@link EmailIdentifier}s and how the flags and callback parameter
|
||||
* works.
|
||||
*
|
||||
* Like the other "lazy" methods, this method will call EmailCallback while the operation is
|
||||
* Like the other "lazy" method, this method will call EmailCallback while the operation is
|
||||
* processing. This method does not block.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
|
|
@ -492,56 +440,6 @@ public interface Geary.Folder : BaseObject {
|
|||
public abstract async Geary.Email fetch_email_async(Geary.EmailIdentifier email_id,
|
||||
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* check_span_specifiers() verifies that the span specifiers match the requirements set by
|
||||
* list_email_async() and lazy_list_email_async(). If not, this method throws
|
||||
* EngineError.BAD_PARAMETERS.
|
||||
*/
|
||||
protected static void check_span_specifiers(int low, int count) throws EngineError {
|
||||
if ((low < 1 && low != -1) || (count < 0 && count != -1))
|
||||
throw new EngineError.BAD_PARAMETERS("low=%d count=%d", low, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* normalize_span_specifiers() deals with the varieties of span specifiers that can be passed
|
||||
* to list_email_async() and lazy_list_email_async(). Note that this function is for
|
||||
* implementations to convert 'low' and 'count' into positive values (1-based in the case of
|
||||
* low) that are within an appropriate range.
|
||||
*
|
||||
* If total is zero, low and count will return as zero as well.
|
||||
*
|
||||
* The caller should plug in 'low' and 'count' passed from the user as well as the total
|
||||
* number of emails available (i.e. the complete span is 1..total).
|
||||
*/
|
||||
internal static void normalize_span_specifiers(ref int low, ref int count, int total)
|
||||
throws EngineError {
|
||||
check_span_specifiers(low, count);
|
||||
|
||||
if (total < 0)
|
||||
throw new EngineError.BAD_PARAMETERS("total=%d", total);
|
||||
|
||||
if (total == 0) {
|
||||
low = 0;
|
||||
count = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if both are -1, it's no different than low=1 count=-1 (that is, return all email)
|
||||
if (low == -1 && count == -1)
|
||||
low = 1;
|
||||
|
||||
// if count is -1, it's like a globbed star (return everything starting at low)
|
||||
if (count == -1)
|
||||
count = total;
|
||||
|
||||
if (low == -1)
|
||||
low = ((total - count) + 1).clamp(1, total);
|
||||
|
||||
if ((low + (count - 1)) > total)
|
||||
count = ((total - low) + 1).clamp(1, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for debugging. Should not be used for user-visible labels.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -266,43 +266,12 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
|
|||
throw error;
|
||||
}
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (low >= 0)
|
||||
error("Search folder can't list email positionally");
|
||||
|
||||
// TODO:
|
||||
// * This is a temporary implementation that can't handle positional addressing.
|
||||
// * Fetch emails as a batch, not one at a time.
|
||||
int result_mutex_token = yield result_mutex.claim_async();
|
||||
|
||||
Gee.List<Geary.Email> results = new Gee.ArrayList<Geary.Email>();
|
||||
Error? error = null;
|
||||
try {
|
||||
int i = 0;
|
||||
foreach(Geary.Email email in search_results) {
|
||||
results.add(yield fetch_email_async(email.id, required_fields, flags, cancellable));
|
||||
|
||||
i++;
|
||||
if (count > 0 && i >= count)
|
||||
break;
|
||||
}
|
||||
} catch(Error e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
result_mutex.release(ref result_mutex_token);
|
||||
|
||||
if (error != null)
|
||||
throw error;
|
||||
|
||||
return (results.size == 0 ? null : results);
|
||||
}
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
|
||||
int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (count <= 0)
|
||||
return null;
|
||||
|
||||
// TODO: as above, this is incomplete and inefficient.
|
||||
int result_mutex_token = yield result_mutex.claim_async();
|
||||
|
||||
|
|
@ -310,38 +279,34 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
|
|||
int initial_index = -1;
|
||||
int i = 0;
|
||||
foreach (Geary.Email email in search_results) {
|
||||
if (email.id.equal_to(initial_id))
|
||||
if (initial_id != null && email.id.equal_to(initial_id))
|
||||
initial_index = i;
|
||||
ids[i++] = email.id;
|
||||
}
|
||||
|
||||
if (initial_id == null)
|
||||
initial_index = ids.length - 1;
|
||||
|
||||
Gee.List<Geary.Email> results = new Gee.ArrayList<Geary.Email>();
|
||||
Error? error = null;
|
||||
if (initial_index >= 0) {
|
||||
try {
|
||||
// A negative count means we walk forwards in our array and
|
||||
// vice versa.
|
||||
int real_count = count.abs();
|
||||
int increment = (count < 0 ? 1 : -1);
|
||||
i = initial_index;
|
||||
if ((flags & Folder.ListFlags.EXCLUDING_ID) != 0)
|
||||
i += increment;
|
||||
else
|
||||
++real_count;
|
||||
int end = i + real_count * increment;
|
||||
|
||||
for (; i >= 0 && i < search_results.size && i != end; i += increment)
|
||||
int increment = flags.is_oldest_to_newest() ? 1 : -1;
|
||||
i = initial_index;
|
||||
if (!flags.is_including_id() && initial_id != null)
|
||||
i += increment;
|
||||
int end = i + (count * increment);
|
||||
|
||||
for (; i >= 0 && i < search_results.size && i != end; i += increment) {
|
||||
try {
|
||||
results.add(yield fetch_email_async(ids[i], required_fields, flags, cancellable));
|
||||
} catch (Error e) {
|
||||
error = e;
|
||||
} catch (Error err) {
|
||||
if (!(err is EngineError.NOT_FOUND))
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result_mutex.release(ref result_mutex_token);
|
||||
|
||||
if (error != null)
|
||||
throw error;
|
||||
|
||||
return (results.size == 0 ? null : results);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
|
|||
internal async void local_load_async() {
|
||||
debug("ConversationMonitor seeding with local email for %s", folder.to_string());
|
||||
try {
|
||||
yield load_async(-1, min_window_count, Folder.ListFlags.LOCAL_ONLY, cancellable_monitor);
|
||||
yield load_by_id_async(null, min_window_count, Folder.ListFlags.LOCAL_ONLY, cancellable_monitor);
|
||||
} catch (Error e) {
|
||||
debug("Error loading local messages: %s", e.message);
|
||||
}
|
||||
|
|
@ -391,28 +391,12 @@ public class Geary.App.ConversationMonitor : BaseObject {
|
|||
throw close_err;
|
||||
}
|
||||
|
||||
/**
|
||||
* See Geary.Folder.list_email_async() for details of how these parameters operate. Instead
|
||||
* of returning emails, this method will load the Conversations object with them sorted into
|
||||
* Conversation objects.
|
||||
*/
|
||||
private async void load_async(int low, int count, Geary.Folder.ListFlags flags,
|
||||
Cancellable? cancellable) throws Error {
|
||||
notify_scan_started();
|
||||
try {
|
||||
yield process_email_async(yield folder.list_email_async(low, count,
|
||||
required_fields, flags, cancellable), new ProcessJobContext(true));
|
||||
} catch (Error err) {
|
||||
list_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See Geary.Folder.list_email_by_id_async() for details of how these parameters operate. Instead
|
||||
* of returning emails, this method will load the Conversations object with them sorted into
|
||||
* Conversation objects.
|
||||
*/
|
||||
private async void load_by_id_async(Geary.EmailIdentifier initial_id, int count,
|
||||
private async void load_by_id_async(Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Folder.ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
notify_scan_started();
|
||||
try {
|
||||
|
|
@ -734,12 +718,12 @@ public class Geary.App.ConversationMonitor : BaseObject {
|
|||
if (earliest_id != null) {
|
||||
debug("ConversationMonitor (%s) reseeding starting from Email ID %s on opened %s", why,
|
||||
earliest_id.to_string(), folder.to_string());
|
||||
yield load_by_id_async(earliest_id, int.MAX, Geary.Folder.ListFlags.NONE,
|
||||
yield load_by_id_async(earliest_id, int.MAX, Geary.Folder.ListFlags.OLDEST_TO_NEWEST,
|
||||
cancellable_monitor);
|
||||
} else {
|
||||
debug("ConversationMonitor (%s) reseeding latest %d emails on opened %s", why,
|
||||
min_window_count, folder.to_string());
|
||||
yield load_async(-1, min_window_count, Geary.Folder.ListFlags.NONE, cancellable_monitor);
|
||||
yield load_by_id_async(null, min_window_count, Geary.Folder.ListFlags.NONE, cancellable_monitor);
|
||||
}
|
||||
} catch (Error e) {
|
||||
debug("Reseed error: %s", e.message);
|
||||
|
|
@ -872,8 +856,8 @@ public class Geary.App.ConversationMonitor : BaseObject {
|
|||
num_to_load = WINDOW_FILL_MESSAGE_COUNT;
|
||||
|
||||
try {
|
||||
yield load_by_id_async(low_id, -num_to_load,
|
||||
Geary.Folder.ListFlags.EXCLUDING_ID, cancellable_monitor);
|
||||
yield load_by_id_async(low_id, num_to_load,
|
||||
Geary.Folder.ListFlags.NONE, cancellable_monitor);
|
||||
} catch(Error e) {
|
||||
debug("Error filling conversation window: %s", e.message);
|
||||
}
|
||||
|
|
@ -881,7 +865,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
|
|||
// No existing messages or an insert invalidated our existing list,
|
||||
// need to start from scratch.
|
||||
try {
|
||||
yield load_async(-1, min_window_count, Folder.ListFlags.NONE, cancellable_monitor);
|
||||
yield load_by_id_async(null, min_window_count, Folder.ListFlags.NONE, cancellable_monitor);
|
||||
} catch(Error e) {
|
||||
debug("Error filling conversation window: %s", e.message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -532,7 +532,7 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
|
||||
// Ignore any messages that don't have the required fields.
|
||||
if (partial_ok || row.fields.fulfills(requested_fields)) {
|
||||
Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id, null));
|
||||
Geary.Email email = row.to_email(new Geary.ImapDB.EmailIdentifier(id, null));
|
||||
Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable);
|
||||
|
||||
Gee.Set<Geary.FolderPath>? folders = do_find_email_folders(cx, id, cancellable);
|
||||
|
|
@ -683,8 +683,8 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
cx, id, requested_fields, out db_fields, cancellable);
|
||||
|
||||
if (partial_ok || row.fields.fulfills(requested_fields)) {
|
||||
Geary.Email email = row.to_email(-1,
|
||||
new Geary.ImapDB.EmailIdentifier(id, email_id_folder_path));
|
||||
Geary.Email email = row.to_email(new Geary.ImapDB.EmailIdentifier(id,
|
||||
email_id_folder_path));
|
||||
Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable);
|
||||
search_results.add(email);
|
||||
}
|
||||
|
|
@ -769,8 +769,8 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
"Message %s only fulfills %Xh fields (required: %Xh)",
|
||||
email_id.to_string(), row.fields, required_fields);
|
||||
|
||||
email = row.to_email(-1,
|
||||
new Geary.ImapDB.EmailIdentifier(email_id.ordering, email_id.folder_path));
|
||||
email = row.to_email(new Geary.ImapDB.EmailIdentifier(email_id.ordering,
|
||||
email_id.folder_path));
|
||||
Geary.ImapDB.Folder.do_add_attachments(cx, email, email_id.ordering, cancellable);
|
||||
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
|
@ -876,7 +876,7 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
Geary.Email.Field db_fields;
|
||||
MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row(
|
||||
cx, id, search_fields, out db_fields, cancellable);
|
||||
Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id, null));
|
||||
Geary.Email email = row.to_email(new Geary.ImapDB.EmailIdentifier(id, null));
|
||||
Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable);
|
||||
|
||||
Geary.ImapDB.Folder.do_add_email_to_search_table(cx, id, email, cancellable);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
NONE = 0,
|
||||
PARTIAL_OK,
|
||||
INCLUDE_MARKED_FOR_REMOVE,
|
||||
EXCLUDING_ID;
|
||||
INCLUDING_ID,
|
||||
OLDEST_TO_NEWEST;
|
||||
|
||||
public bool is_all_set(ListFlags flags) {
|
||||
return (this & flags) == flags;
|
||||
|
|
@ -28,22 +29,30 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
public bool include_marked_for_remove() {
|
||||
return is_all_set(INCLUDE_MARKED_FOR_REMOVE);
|
||||
}
|
||||
|
||||
public static ListFlags from_folder_flags(Geary.Folder.ListFlags flags) {
|
||||
ListFlags result = NONE;
|
||||
|
||||
if (flags.is_all_set(Geary.Folder.ListFlags.INCLUDING_ID))
|
||||
result |= INCLUDING_ID;
|
||||
|
||||
if (flags.is_all_set(Geary.Folder.ListFlags.OLDEST_TO_NEWEST))
|
||||
result |= OLDEST_TO_NEWEST;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class LocationIdentifier {
|
||||
public int64 message_id;
|
||||
public int position;
|
||||
public int64 ordering;
|
||||
public Geary.EmailIdentifier email_id;
|
||||
|
||||
// If EmailIdentifier has already been built, it can be supplied rather then auto-created
|
||||
// by LocationIdentifier
|
||||
public LocationIdentifier(int64 message_id, int position, int64 ordering,
|
||||
Geary.FolderPath path, Geary.EmailIdentifier? email_id) {
|
||||
assert(position >= 1);
|
||||
|
||||
public LocationIdentifier(int64 message_id, int64 ordering, Geary.FolderPath path,
|
||||
Geary.EmailIdentifier? email_id) {
|
||||
this.message_id = message_id;
|
||||
this.position = position;
|
||||
this.ordering = ordering;
|
||||
this.email_id = email_id ?? new Imap.EmailIdentifier(new Imap.UID(ordering), path);
|
||||
|
||||
|
|
@ -52,8 +61,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
}
|
||||
}
|
||||
|
||||
public bool opened { get; private set; default = false; }
|
||||
|
||||
protected int manual_ref_count { get; protected set; }
|
||||
|
||||
private ImapDB.Database db;
|
||||
|
|
@ -82,11 +89,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
this.properties = properties;
|
||||
}
|
||||
|
||||
private void check_open() throws Error {
|
||||
if (!opened)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not open", to_string());
|
||||
}
|
||||
|
||||
public unowned Geary.FolderPath get_path() {
|
||||
return path;
|
||||
}
|
||||
|
|
@ -105,22 +107,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
this.properties = properties;
|
||||
}
|
||||
|
||||
public async void open_async(Cancellable? cancellable = null) throws Error {
|
||||
if (opened)
|
||||
throw new EngineError.ALREADY_OPEN("%s already open", to_string());
|
||||
|
||||
opened = true;
|
||||
|
||||
lock (marked_removed)
|
||||
marked_removed.clear();
|
||||
}
|
||||
|
||||
public async void close_async(Cancellable? cancellable = null) throws Error {
|
||||
opened = false;
|
||||
|
||||
// anything marked as removed is dropped rather than actually deleted from the database;
|
||||
// folder synchronization the next time the folder is opened will take care of anything
|
||||
// not properly sync'd
|
||||
// Should be called whenever the main Geary.Folder interface is opened or closed
|
||||
public void reset(){
|
||||
lock (marked_removed) {
|
||||
marked_removed.clear();
|
||||
}
|
||||
|
|
@ -175,8 +163,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
}
|
||||
|
||||
public async int get_email_count_async(ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
int count = 0;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
count = do_get_email_count(cx, flags, cancellable);
|
||||
|
|
@ -190,8 +176,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
// Updates both the FolderProperties and the value in the local store. Must be called while
|
||||
// open.
|
||||
public async void update_remote_status_message_count(int count, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
if (count < 0)
|
||||
return;
|
||||
|
||||
|
|
@ -212,8 +196,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
// Updates both the FolderProperties and the value in the local store. Must be called while
|
||||
// open.
|
||||
public async void update_remote_selected_message_count(int count, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
if (count < 0)
|
||||
return;
|
||||
|
||||
|
|
@ -233,8 +215,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
public async int get_id_position_async(Geary.EmailIdentifier id, ListFlags flags,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
int position = -1;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
position = do_get_message_position(cx, id, flags, cancellable);
|
||||
|
|
@ -249,8 +229,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
// (true if created, false if merged) as the value
|
||||
public async Gee.Map<Geary.Email, bool> create_or_merge_email_async(Gee.Collection<Geary.Email> emails,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Gee.HashMap<Geary.Email, bool> results = new Gee.HashMap<Geary.Email, bool>();
|
||||
Gee.ArrayList<Geary.EmailIdentifier> complete_ids = new Gee.ArrayList<Geary.EmailIdentifier>();
|
||||
Gee.Collection<Contact> updated_contacts = new Gee.ArrayList<Contact>();
|
||||
|
|
@ -302,53 +280,66 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
return results;
|
||||
}
|
||||
|
||||
// NOTE: This can be used to check local messages without opening the folder, useful since
|
||||
// opening a Geary.Folder implies remote connection ... this skips check_open() (and, by
|
||||
// implication, means the ImapDB.Folder can be in an odd state), so USE CAREFULLY.
|
||||
public async Gee.List<Geary.Email>? local_list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
return yield internal_list_email_async(low, count, required_fields, flags, true, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
return yield internal_list_email_async(low, count, required_fields, flags, false, cancellable);
|
||||
}
|
||||
|
||||
private async Gee.List<Geary.Email>? internal_list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, ListFlags flags, bool skip_open_check,
|
||||
Cancellable? cancellable) throws Error {
|
||||
if (!skip_open_check)
|
||||
check_open();
|
||||
public async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
|
||||
int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable)
|
||||
throws Error {
|
||||
if (count == 0)
|
||||
return null;
|
||||
|
||||
// Break up this work a bit so the database is not held on one long continuous transaction
|
||||
// First, pull in all the message locations that correspond to the list criteria
|
||||
//
|
||||
// TODO: A more efficient way to do this would be to pull in all the columns at once in
|
||||
// a single SELECT operation ... this might be less efficient than current practice if
|
||||
// a lot of messages are marked for removal, but that's an edge case
|
||||
Gee.List<LocationIdentifier> ids = new Gee.ArrayList<LocationIdentifier>();
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => {
|
||||
Geary.Folder.normalize_span_specifiers(ref low, ref count,
|
||||
do_get_email_count(cx, flags, cancellable));
|
||||
if (count == 0)
|
||||
return Db.TransactionOutcome.SUCCESS;
|
||||
bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID);
|
||||
bool oldest_to_newest = flags.is_all_set(ListFlags.OLDEST_TO_NEWEST);
|
||||
|
||||
int64 start;
|
||||
if (initial_id != null) {
|
||||
start = ((Geary.Imap.EmailIdentifier) initial_id).uid.value;
|
||||
if (!including_id)
|
||||
start += oldest_to_newest ? 1 : -1;
|
||||
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"SELECT message_id, ordering FROM MessageLocationTable WHERE folder_id=? "
|
||||
+ "ORDER BY ordering LIMIT ? OFFSET ?");
|
||||
// watch for underrun and overrun, which indicates BOL/EOL
|
||||
if (start < Imap.UID.MIN || start > Imap.UID.MAX)
|
||||
return null;
|
||||
} else if (oldest_to_newest) {
|
||||
start = Imap.UID.MIN;
|
||||
} else {
|
||||
start = Imap.UID.MAX;
|
||||
}
|
||||
|
||||
// Break up work so all reading isn't done in single transaction that locks up the
|
||||
// database ... first, gather locations of all emails in database
|
||||
Gee.List<LocationIdentifier> ids = new Gee.ArrayList<LocationIdentifier>();
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
Db.Statement stmt;
|
||||
if (oldest_to_newest) {
|
||||
stmt = cx.prepare("""
|
||||
SELECT message_id, ordering
|
||||
FROM MessageLocationTable
|
||||
WHERE folder_id = ? AND ordering >= ?
|
||||
ORDER BY ordering ASC
|
||||
LIMIT ?
|
||||
""");
|
||||
} else {
|
||||
stmt = cx.prepare("""
|
||||
SELECT message_id, ordering
|
||||
FROM MessageLocationTable
|
||||
WHERE folder_id = ? AND ordering <= ?
|
||||
ORDER BY ordering DESC
|
||||
LIMIT ?
|
||||
""");
|
||||
}
|
||||
stmt.bind_rowid(0, folder_id);
|
||||
stmt.bind_int(1, count);
|
||||
stmt.bind_int(2, low - 1);
|
||||
stmt.bind_int64(1, start);
|
||||
stmt.bind_int(2, count);
|
||||
|
||||
Db.Result results = stmt.exec(cancellable);
|
||||
if (results.finished)
|
||||
return Db.TransactionOutcome.SUCCESS;
|
||||
|
||||
int position = low;
|
||||
do {
|
||||
LocationIdentifier location = new LocationIdentifier(results.rowid_at(0), position++,
|
||||
results.int64_at(1), path, null);
|
||||
int64 ordering = results.int64_at(1);
|
||||
Geary.EmailIdentifier email_id = new Imap.EmailIdentifier(new Imap.UID(ordering), path);
|
||||
|
||||
LocationIdentifier location = new LocationIdentifier(results.rowid_at(0), ordering,
|
||||
path, email_id);
|
||||
if (!flags.include_marked_for_remove() && is_marked_removed(location.email_id))
|
||||
continue;
|
||||
|
||||
|
|
@ -358,97 +349,52 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
return Db.TransactionOutcome.SUCCESS;
|
||||
}, cancellable);
|
||||
|
||||
// Next, pull in email from locations in chunks (rather than all in one transaction)
|
||||
// Next, read in email in chunks
|
||||
return yield list_email_in_chunks_async(ids, required_fields, flags, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
||||
int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
|
||||
// ListFlags.OLDEST_TO_NEWEST is ignored
|
||||
public async Gee.List<Geary.Email>? list_email_by_range_async(Geary.EmailIdentifier start_id,
|
||||
Geary.EmailIdentifier end_id, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable)
|
||||
throws Error {
|
||||
check_open();
|
||||
bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID);
|
||||
|
||||
if (count == 0 || count == 1) {
|
||||
try {
|
||||
Geary.Email email = yield fetch_email_async(initial_id, required_fields, flags,
|
||||
cancellable);
|
||||
|
||||
Gee.List<Geary.Email> singleton = new Gee.ArrayList<Geary.Email>();
|
||||
singleton.add(email);
|
||||
|
||||
return singleton;
|
||||
} catch (EngineError engine_err) {
|
||||
// list_email variants don't return NOT_FOUND or INCOMPLETE_MESSAGE
|
||||
if ((engine_err is EngineError.NOT_FOUND) || (engine_err is EngineError.INCOMPLETE_MESSAGE))
|
||||
return null;
|
||||
|
||||
throw engine_err;
|
||||
}
|
||||
}
|
||||
int64 start = ((Geary.Imap.EmailIdentifier) start_id).uid.value;
|
||||
if (!including_id)
|
||||
start++;
|
||||
|
||||
Geary.Imap.UID uid = ((Geary.Imap.EmailIdentifier) initial_id).uid;
|
||||
bool excluding_id = flags.is_all_set(ListFlags.EXCLUDING_ID);
|
||||
int64 end = ((Geary.Imap.EmailIdentifier) end_id).uid.value;
|
||||
if (!including_id)
|
||||
end--;
|
||||
|
||||
int64 low, high;
|
||||
if (count < 0) {
|
||||
high = excluding_id ? uid.value - 1 : uid.value;
|
||||
low = (count != int.MIN) ? (high + count).clamp(1, uint32.MAX) : -1;
|
||||
} else {
|
||||
// count > 1
|
||||
low = excluding_id ? uid.value + 1 : uid.value;
|
||||
high = (count != int.MAX) ? (low + count).clamp(1, uint32.MAX) : -1;
|
||||
}
|
||||
if (start > end || start < Imap.UID.MIN || end > Imap.UID.MAX)
|
||||
return null;
|
||||
|
||||
// Break up work so all reading isn't done in single transaction that locks up the
|
||||
// database ... first, gather locations of all emails in database
|
||||
Gee.List<LocationIdentifier> ids = new Gee.ArrayList<LocationIdentifier>();
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
Db.Statement stmt;
|
||||
if (high != -1 && low != -1) {
|
||||
stmt = cx.prepare(
|
||||
"SELECT message_id, ordering FROM MessageLocationTable WHERE folder_id=? "
|
||||
+ "AND ordering >= ? AND ordering <= ? ORDER BY ordering ASC");
|
||||
stmt.bind_rowid(0, folder_id);
|
||||
stmt.bind_int64(1, low);
|
||||
stmt.bind_int64(2, high);
|
||||
} else if (high == -1) {
|
||||
stmt = cx.prepare(
|
||||
"SELECT message_id, ordering FROM MessageLocationTable WHERE folder_id=? "
|
||||
+ "AND ordering >= ? ORDER BY ordering ASC");
|
||||
stmt.bind_rowid(0, folder_id);
|
||||
stmt.bind_int64(1, low);
|
||||
} else {
|
||||
assert(low == -1);
|
||||
|
||||
stmt = cx.prepare(
|
||||
"SELECT message_id, ordering FROM MessageLocationTable WHERE folder_id=? "
|
||||
+ "AND ordering <= ? ORDER BY ordering ASC");
|
||||
stmt.bind_rowid(0, folder_id);
|
||||
stmt.bind_int64(1, high);
|
||||
}
|
||||
Db.Statement stmt = cx.prepare("""
|
||||
SELECT message_id, ordering
|
||||
FROM MessageLocationTable
|
||||
WHERE folder_id = ? AND ordering >= ? AND ordering <= ?
|
||||
""");
|
||||
stmt.bind_rowid(0, folder_id);
|
||||
stmt.bind_int64(1, start);
|
||||
stmt.bind_int64(2, end);
|
||||
|
||||
Db.Result results = stmt.exec(cancellable);
|
||||
if (results.finished)
|
||||
return Db.TransactionOutcome.SUCCESS;
|
||||
|
||||
int position = -1;
|
||||
do {
|
||||
int64 ordering = results.int64_at(1);
|
||||
Geary.EmailIdentifier email_id = new Imap.EmailIdentifier(new Imap.UID(ordering), path);
|
||||
|
||||
// get position of first message and roll from there
|
||||
if (position == -1) {
|
||||
position = do_get_message_position(cx, email_id, flags, cancellable);
|
||||
assert(position >= 1);
|
||||
}
|
||||
|
||||
LocationIdentifier location = new LocationIdentifier(results.rowid_at(0), position++,
|
||||
ordering, path, email_id);
|
||||
if (!flags.include_marked_for_remove() && is_marked_removed(location.email_id)) {
|
||||
// don't count this in the positional addressing
|
||||
position--;
|
||||
|
||||
LocationIdentifier location = new LocationIdentifier(results.rowid_at(0), ordering,
|
||||
path, email_id);
|
||||
if (!flags.include_marked_for_remove() && is_marked_removed(location.email_id))
|
||||
continue;
|
||||
}
|
||||
|
||||
ids.add(location);
|
||||
} while (results.next(cancellable));
|
||||
|
|
@ -495,8 +441,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
public async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
|
||||
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.Email? email = null;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
// get the message and its position
|
||||
|
|
@ -504,12 +448,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
if (message_id == Db.INVALID_ROWID)
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
||||
int position = do_get_message_position(cx, id, flags, cancellable);
|
||||
if (position < 1)
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
||||
LocationIdentifier location = new LocationIdentifier(message_id, position, id.ordering,
|
||||
path, null);
|
||||
LocationIdentifier location = new LocationIdentifier(message_id, id.ordering, path, null);
|
||||
if (!flags.include_marked_for_remove() && is_marked_removed(location.email_id))
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
||||
|
|
@ -526,6 +465,33 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
return email;
|
||||
}
|
||||
|
||||
// pos is 1-based
|
||||
public async Geary.Imap.UID? get_uid_at_async(int pos, Cancellable? cancellable) throws Error {
|
||||
assert(pos >= 1);
|
||||
|
||||
int64 ordering = Imap.UID.INVALID;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
Db.Statement stmt = cx.prepare("""
|
||||
SELECT ordering
|
||||
FROM MessageLocationTable
|
||||
WHERE folder_id=?
|
||||
ORDER BY ordering
|
||||
LIMIT 1
|
||||
OFFSET ?
|
||||
""");
|
||||
stmt.bind_rowid(0, folder_id);
|
||||
stmt.bind_int(1, pos - 1);
|
||||
|
||||
Db.Result results = stmt.exec(cancellable);
|
||||
if (!results.finished)
|
||||
ordering = results.int64_at(0);
|
||||
|
||||
return Db.TransactionOutcome.DONE;
|
||||
}, cancellable);
|
||||
|
||||
return Imap.UID.is_value_valid(ordering) ? new Imap.UID(ordering) : null;
|
||||
}
|
||||
|
||||
public async Geary.Imap.UID? get_earliest_uid_async(Cancellable? cancellable = null) throws Error {
|
||||
return yield get_uid_extremes_async(true, cancellable);
|
||||
}
|
||||
|
|
@ -536,8 +502,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
private async Geary.Imap.UID? get_uid_extremes_async(bool earliest, Cancellable? cancellable)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
int64 ordering = Imap.UID.INVALID;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
Db.Statement stmt;
|
||||
|
|
@ -560,8 +524,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
// TODO: Rename to detach_email_async().
|
||||
public async void remove_email_async(Gee.Collection<Geary.EmailIdentifier> ids,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
// TODO: Right now, deleting an email is merely detaching its association with a folder
|
||||
// (since it may be located in multiple folders). This means at some point in the future
|
||||
// a vacuum will be required to remove emails that are completely unassociated with the
|
||||
|
|
@ -587,8 +549,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
}
|
||||
|
||||
public async void detach_all_emails_async(Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => {
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"DELETE FROM MessageLocationTable WHERE folder_id=?");
|
||||
|
|
@ -603,7 +563,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
public async void mark_email_async(Gee.Collection<Geary.EmailIdentifier> to_mark,
|
||||
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, Cancellable? cancellable)
|
||||
throws Error {
|
||||
check_open();
|
||||
Error? error = null;
|
||||
int unread_change = 0; // Negative means messages are read, positive means unread.
|
||||
|
||||
|
|
@ -660,8 +619,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
public async Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags>? get_email_flags_async(
|
||||
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags>? map = null;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => {
|
||||
map = do_get_email_flags(cx, ids, cancellable);
|
||||
|
|
@ -674,8 +631,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
public async void set_email_flags_async(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
Error? error = null;
|
||||
int unread_change = 0; // Negative means messages are read, positive means unread.
|
||||
|
||||
|
|
@ -723,8 +678,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
public async bool is_email_present_async(Geary.EmailIdentifier id, out Geary.Email.Field available_fields,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.Email.Field internal_available_fields = Geary.Email.Field.NONE;
|
||||
bool is_present = false;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => {
|
||||
|
|
@ -745,8 +698,6 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
public async void remove_marked_email_async(Geary.EmailIdentifier id, out bool is_marked,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
bool internal_is_marked = false;
|
||||
bool was_unread = false;
|
||||
yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => {
|
||||
|
|
@ -779,15 +730,11 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
// TODO: Need to verify each EmailIdentifier before adding to marked_removed collection.
|
||||
public async void mark_removed_async(Gee.Collection<Geary.EmailIdentifier> ids, bool mark_removed,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
mark_unmark_removed(ids, mark_removed);
|
||||
}
|
||||
|
||||
public async Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>? list_email_fields_by_id_async(
|
||||
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
if (ids.size == 0)
|
||||
return null;
|
||||
|
||||
|
|
@ -1145,12 +1092,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
}
|
||||
|
||||
// look for perverse case
|
||||
if (required_fields == Geary.Email.Field.NONE) {
|
||||
Geary.Email email = new Geary.Email(location.email_id);
|
||||
email.position = location.position;
|
||||
|
||||
return email;
|
||||
}
|
||||
if (required_fields == Geary.Email.Field.NONE)
|
||||
return new Geary.Email(location.email_id);
|
||||
|
||||
Geary.Email.Field db_fields;
|
||||
MessageRow row = do_fetch_message_row(cx, location.message_id, required_fields,
|
||||
|
|
@ -1161,7 +1104,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
location.email_id.to_string(), to_string(), row.fields, required_fields);
|
||||
}
|
||||
|
||||
Geary.Email email = row.to_email(location.position, location.email_id);
|
||||
Geary.Email email = row.to_email(location.email_id);
|
||||
|
||||
return do_add_attachments(cx, email, location.message_id, cancellable);
|
||||
}
|
||||
|
|
@ -1545,7 +1488,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
return;
|
||||
|
||||
// Build the combined email from the merge, which will be used to save the attachments
|
||||
Geary.Email combined_email = row.to_email(email.position, email.id);
|
||||
Geary.Email combined_email = row.to_email(email.id);
|
||||
do_add_attachments(cx, combined_email, message_id, cancellable);
|
||||
|
||||
// Merge in any fields in the submitted email that aren't already in the database or are mutable
|
||||
|
|
|
|||
|
|
@ -97,12 +97,11 @@ private class Geary.ImapDB.MessageRow {
|
|||
}
|
||||
}
|
||||
|
||||
public Geary.Email to_email(int position, Geary.EmailIdentifier id) throws Error {
|
||||
public Geary.Email to_email(Geary.EmailIdentifier id) throws Error {
|
||||
// Important to set something in the Email object if the field bit is set ... for example,
|
||||
// if the caller expects to see a DATE field, that field is set in the Email's bitmask,
|
||||
// even if the Date object is null
|
||||
Geary.Email email = new Geary.Email(id);
|
||||
email.position = position;
|
||||
|
||||
if (fields.is_all_set(Geary.Email.Field.DATE))
|
||||
email.set_send_date(!String.is_empty(date) ? new RFC822.Date(date) : null);
|
||||
|
|
|
|||
|
|
@ -283,62 +283,36 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu
|
|||
return FolderSupport.Create.Result.CREATED;
|
||||
}
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
Gee.List<Geary.Email>? list = null;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
Geary.Folder.normalize_span_specifiers(ref low, ref count,
|
||||
do_get_email_count(cx, cancellable));
|
||||
|
||||
if (count == 0)
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"SELECT id, ordering, message FROM SmtpOutboxTable ORDER BY ordering LIMIT ? OFFSET ?");
|
||||
stmt.bind_int(0, count);
|
||||
stmt.bind_int(1, low - 1);
|
||||
|
||||
Db.Result results = stmt.exec(cancellable);
|
||||
if (results.finished)
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
||||
list = new Gee.ArrayList<Geary.Email>();
|
||||
int position = low;
|
||||
do {
|
||||
list.add(row_to_email(new OutboxRow(results.rowid_at(0), position++, results.int64_at(1),
|
||||
results.string_buffer_at(2), _path)));
|
||||
} while (results.next());
|
||||
|
||||
return Db.TransactionOutcome.DONE;
|
||||
}, cancellable);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(
|
||||
Geary.EmailIdentifier initial_id, int count, Geary.Email.Field required_fields,
|
||||
Geary.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields,
|
||||
Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
SmtpOutboxEmailIdentifier? id = initial_id as SmtpOutboxEmailIdentifier;
|
||||
if (id == null) {
|
||||
if (initial_id != null && !(initial_id is SmtpOutboxEmailIdentifier)) {
|
||||
throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not for Outbox",
|
||||
initial_id.to_string());
|
||||
}
|
||||
|
||||
if (count <= 0)
|
||||
return null;
|
||||
|
||||
Gee.List<Geary.Email>? list = null;
|
||||
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
|
||||
count = int.min(count, do_get_email_count(cx, cancellable));
|
||||
string dir = flags.is_newest_to_oldest() ? "DESC" : "ASC";
|
||||
|
||||
Db.Statement stmt = cx.prepare(
|
||||
"SELECT id, ordering, message FROM SmtpOutboxTable WHERE ordering >= ? "
|
||||
+ "ORDER BY ordering LIMIT ?");
|
||||
stmt.bind_int64(0,
|
||||
flags.is_all_set(Folder.ListFlags.EXCLUDING_ID) ? id.ordering + 1 : id.ordering);
|
||||
stmt.bind_int(1, count);
|
||||
Db.Statement stmt;
|
||||
if (initial_id != null) {
|
||||
stmt = cx.prepare(
|
||||
"SELECT id, ordering, message FROM SmtpOutboxTable WHERE ordering >= ? "
|
||||
+ "ORDER BY ordering %s LIMIT ?".printf(dir));
|
||||
stmt.bind_int64(0,
|
||||
flags.is_including_id() ? initial_id.ordering : initial_id.ordering + 1);
|
||||
stmt.bind_int(1, count);
|
||||
} else {
|
||||
stmt = cx.prepare(
|
||||
"SELECT id, ordering, message FROM SmtpOutboxTable ORDER BY ordering %s LIMIT ?".printf(dir));
|
||||
stmt.bind_int(1, count);
|
||||
}
|
||||
|
||||
Db.Result results = stmt.exec(cancellable);
|
||||
if (results.finished)
|
||||
|
|
@ -353,8 +327,10 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu
|
|||
assert(position >= 1);
|
||||
}
|
||||
|
||||
list.add(row_to_email(new OutboxRow(results.rowid_at(0), position++, ordering,
|
||||
list.add(row_to_email(new OutboxRow(results.rowid_at(0), position, ordering,
|
||||
results.string_buffer_at(2), _path)));
|
||||
position += flags.is_newest_to_oldest() ? -1 : 1;
|
||||
assert(position >= 1);
|
||||
} while (results.next());
|
||||
|
||||
return Db.TransactionOutcome.DONE;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
private GenericFolder? current_folder = null;
|
||||
private Cancellable? bg_cancellable = null;
|
||||
private Nonblocking.Semaphore stopped = new Nonblocking.Semaphore();
|
||||
private Nonblocking.Semaphore prefetcher_semaphore = new Nonblocking.Semaphore();
|
||||
|
||||
public AccountSynchronizer(GenericAccount account) {
|
||||
this.account = account;
|
||||
|
|
@ -221,8 +220,9 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
DateTime? oldest_local = null;
|
||||
Geary.EmailIdentifier? oldest_local_id = null;
|
||||
try {
|
||||
Gee.List<Geary.Email>? list = yield folder.local_folder.local_list_email_async(1, 1,
|
||||
Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.NONE, bg_cancellable);
|
||||
Gee.List<Geary.Email>? list = yield folder.local_folder.list_email_by_id_async(null, 1,
|
||||
Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.NONE | ImapDB.Folder.ListFlags.OLDEST_TO_NEWEST,
|
||||
bg_cancellable);
|
||||
if (list != null && list.size > 0) {
|
||||
oldest_local = list[0].properties.date_received;
|
||||
oldest_local_id = list[0].id;
|
||||
|
|
@ -265,12 +265,6 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
return true;
|
||||
}
|
||||
|
||||
// set up monitoring the Folder's prefetcher so an exception doesn't leave dangling
|
||||
// signal subscriptions
|
||||
prefetcher_semaphore = new Nonblocking.Semaphore();
|
||||
folder.email_prefetcher.halting.connect(on_email_prefetcher_completed);
|
||||
folder.closed.connect(on_email_prefetcher_completed);
|
||||
|
||||
// turn off the flag watcher whilst synchronizing, as that can cause addt'l load on the
|
||||
// CPU
|
||||
folder.email_flag_watcher.enabled = false;
|
||||
|
|
@ -285,9 +279,6 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
|
||||
// fallthrough and close
|
||||
} finally {
|
||||
folder.email_prefetcher.halting.disconnect(on_email_prefetcher_completed);
|
||||
folder.closed.disconnect(on_email_prefetcher_completed);
|
||||
|
||||
folder.email_flag_watcher.enabled = true;
|
||||
}
|
||||
|
||||
|
|
@ -307,59 +298,27 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
|
||||
// only perform vector expansion if oldest isn't old enough
|
||||
if (oldest_local == null || oldest_local.compare(epoch) > 0) {
|
||||
yield expand_folder_async(folder, epoch, oldest_local, oldest_local_id);
|
||||
Geary.EmailIdentifier? epoch_id = yield folder.find_earliest_email_async(epoch,
|
||||
oldest_local_id, bg_cancellable);
|
||||
if (epoch_id == null) {
|
||||
debug("Unable to locate epoch messages on remote folder %s%s", folder.to_string(),
|
||||
(oldest_local_id != null) ? " earlier than oldest local" : "");
|
||||
}
|
||||
} else {
|
||||
debug("No expansion necessary for %s, oldest local (%s) is before epoch (%s)",
|
||||
folder.to_string(), oldest_local.to_string(), epoch.to_string());
|
||||
}
|
||||
|
||||
// always give email prefetcher time to finish its work
|
||||
if (folder.email_prefetcher.has_work()) {
|
||||
// expanding an already opened folder doesn't guarantee the prefetcher will start
|
||||
debug("Waiting for email prefetcher to complete %s...", folder.to_string());
|
||||
try {
|
||||
yield prefetcher_semaphore.wait_async(bg_cancellable);
|
||||
} catch (Error err) {
|
||||
debug("Error waiting for email prefetcher to complete %s: %s", folder.to_string(),
|
||||
err.message);
|
||||
}
|
||||
debug("Waiting for email prefetcher to complete %s...", folder.to_string());
|
||||
try {
|
||||
yield folder.email_prefetcher.active_sem.wait_async(bg_cancellable);
|
||||
} catch (Error err) {
|
||||
debug("Error waiting for email prefetcher to complete %s: %s", folder.to_string(),
|
||||
err.message);
|
||||
}
|
||||
|
||||
debug("Done background sync'ing %s", folder.to_string());
|
||||
}
|
||||
|
||||
private async void expand_folder_async(GenericFolder folder, DateTime epoch, DateTime? oldest_local,
|
||||
Geary.EmailIdentifier? oldest_local_id) throws Error {
|
||||
// use IMAP SEARCH to discover the epoch email
|
||||
Imap.SearchCriteria criteria = new Imap.SearchCriteria();
|
||||
criteria.is_(Imap.SearchCriterion.since_internaldate(new Imap.InternalDate.from_date_time(epoch)));
|
||||
|
||||
// if local email available, only search for messages before it
|
||||
if (oldest_local_id != null) {
|
||||
Imap.UID oldest_uid = ((Imap.EmailIdentifier) oldest_local_id).uid;
|
||||
criteria.and(Imap.SearchCriterion.message_set(
|
||||
new Imap.MessageSet.uid_range(new Imap.UID(Imap.UID.MIN), oldest_uid.previous())));
|
||||
}
|
||||
|
||||
Gee.SortedSet<Geary.EmailIdentifier>? since_ids = yield folder.remote_folder.search_async(
|
||||
criteria, null);
|
||||
if (since_ids == null || since_ids.size == 0) {
|
||||
debug("Unable to locate epoch messages on remote folder %s", folder.to_string());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
debug("%s: %d discovered since epoch %s, fetching from %s", folder.to_string(), since_ids.size,
|
||||
epoch.to_string(), since_ids.first().to_string());
|
||||
|
||||
// fetch the oldest one; the Folder will perform the vector expansion and the email
|
||||
// prefetcher will pull them all in
|
||||
yield folder.fetch_email_async(since_ids.first(), Email.Field.NONE, Folder.ListFlags.NONE);
|
||||
}
|
||||
|
||||
private void on_email_prefetcher_completed() {
|
||||
debug("on_email_prefetcher_completed");
|
||||
prefetcher_semaphore.blind_notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,19 +101,9 @@ private class Geary.ImapEngine.EmailFlagWatcher : BaseObject {
|
|||
Geary.EmailIdentifier? lowest = null;
|
||||
int total = 0;
|
||||
do {
|
||||
// Fetch a chunk of email flags in local folder.
|
||||
Gee.List<Geary.Email>? list_local;
|
||||
if (lowest == null) {
|
||||
list_local = yield folder.list_email_async(-1, PULL_CHUNK_COUNT,
|
||||
Geary.Email.Field.FLAGS, Geary.Folder.ListFlags.LOCAL_ONLY, cancellable);
|
||||
} else {
|
||||
// note using negative count to move down the stack
|
||||
list_local = yield folder.list_email_by_id_async(lowest, 0 - PULL_CHUNK_COUNT,
|
||||
Geary.Email.Field.FLAGS, Geary.Folder.ListFlags.LOCAL_ONLY | Geary.Folder.ListFlags.EXCLUDING_ID,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
if (list_local == null || list_local.size == 0)
|
||||
Gee.List<Geary.Email>? list_local = yield folder.list_email_by_id_async(lowest,
|
||||
PULL_CHUNK_COUNT, Geary.Email.Field.FLAGS, Geary.Folder.ListFlags.LOCAL_ONLY, cancellable);
|
||||
if (list_local == null || list_local.is_empty)
|
||||
break;
|
||||
|
||||
total += list_local.size;
|
||||
|
|
@ -134,7 +124,7 @@ private class Geary.ImapEngine.EmailFlagWatcher : BaseObject {
|
|||
local_map.keys.size, folder.to_string());
|
||||
Gee.List<Geary.Email>? list_remote = yield folder.list_email_by_sparse_id_async(local_map.keys,
|
||||
Email.Field.FLAGS, Geary.Folder.ListFlags.FORCE_UPDATE, cancellable);
|
||||
if (list_remote == null || list_remote.size == 0)
|
||||
if (list_remote == null || list_remote.is_empty)
|
||||
break;
|
||||
|
||||
// Build map of emails that have changed.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
private const int PREFETCH_IDS_CHUNKS = 500;
|
||||
private const int PREFETCH_CHUNK_BYTES = 128 * 1024;
|
||||
|
||||
private unowned Geary.Folder folder;
|
||||
public Nonblocking.CountingSemaphore active_sem { get; private set;
|
||||
default = new Nonblocking.CountingSemaphore(null); }
|
||||
|
||||
private unowned ImapEngine.GenericFolder folder;
|
||||
private int start_delay_sec;
|
||||
private Nonblocking.Mutex mutex = new Nonblocking.Mutex();
|
||||
private Gee.TreeSet<Geary.Email> prefetch_emails = new Collection.FixedTreeSet<Geary.Email>(
|
||||
|
|
@ -26,9 +29,7 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
private uint schedule_id = 0;
|
||||
private Cancellable cancellable = new Cancellable();
|
||||
|
||||
public signal void halting();
|
||||
|
||||
public EmailPrefetcher(Geary.Folder folder, int start_delay_sec = PREFETCH_DELAY_SEC) {
|
||||
public EmailPrefetcher(ImapEngine.GenericFolder folder, int start_delay_sec = PREFETCH_DELAY_SEC) {
|
||||
assert(start_delay_sec > 0);
|
||||
|
||||
this.folder = folder;
|
||||
|
|
@ -36,7 +37,7 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
|
||||
folder.opened.connect(on_opened);
|
||||
folder.closed.connect(on_closed);
|
||||
folder.email_locally_appended.connect(on_locally_appended);
|
||||
folder.local_expansion.connect(on_local_expansion);
|
||||
}
|
||||
|
||||
~EmailPrefetcher() {
|
||||
|
|
@ -45,11 +46,7 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
|
||||
folder.opened.disconnect(on_opened);
|
||||
folder.closed.disconnect(on_closed);
|
||||
folder.email_locally_appended.disconnect(on_locally_appended);
|
||||
}
|
||||
|
||||
public bool has_work() {
|
||||
return prefetch_emails.size > 0;
|
||||
folder.local_expansion.disconnect(on_local_expansion);
|
||||
}
|
||||
|
||||
private void on_opened(Geary.Folder.OpenState open_state) {
|
||||
|
|
@ -57,6 +54,9 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
return;
|
||||
|
||||
cancellable = new Cancellable();
|
||||
|
||||
// acquire here since .begin() only schedules for later
|
||||
active_sem.acquire();
|
||||
do_prepare_all_local_async.begin();
|
||||
}
|
||||
|
||||
|
|
@ -71,16 +71,22 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
}
|
||||
}
|
||||
|
||||
private void on_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids) {
|
||||
private void on_local_expansion(Gee.Collection<Geary.EmailIdentifier> ids) {
|
||||
// acquire here since .begin() only schedules for later
|
||||
active_sem.acquire();
|
||||
do_prepare_new_async.begin(ids);
|
||||
}
|
||||
|
||||
// emails should include PROPERTIES
|
||||
private void schedule_prefetch(Gee.Collection<Geary.Email> emails) {
|
||||
debug("%s: scheduling %d emails for prefetch", folder.to_string(), emails.size);
|
||||
prefetch_emails.add_all(emails);
|
||||
|
||||
// only increment active state if not rescheduling
|
||||
if (schedule_id != 0)
|
||||
Source.remove(schedule_id);
|
||||
else
|
||||
active_sem.acquire();
|
||||
|
||||
schedule_id = Timeout.add_seconds(start_delay_sec, on_start_prefetch);
|
||||
}
|
||||
|
|
@ -94,15 +100,12 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
}
|
||||
|
||||
private async void do_prepare_all_local_async() {
|
||||
int low = -1;
|
||||
bool finished = false;
|
||||
do {
|
||||
finished = (low == 1);
|
||||
|
||||
Geary.EmailIdentifier? lowest = null;
|
||||
for (;;) {
|
||||
Gee.List<Geary.Email>? list = null;
|
||||
try {
|
||||
list = yield folder.list_email_async(low, PREFETCH_IDS_CHUNKS, Geary.Email.Field.PROPERTIES,
|
||||
Geary.Folder.ListFlags.LOCAL_ONLY, cancellable);
|
||||
list = yield folder.list_email_by_id_async(lowest, PREFETCH_IDS_CHUNKS,
|
||||
Geary.Email.Field.PROPERTIES, Geary.Folder.ListFlags.LOCAL_ONLY, cancellable);
|
||||
} catch (Error err) {
|
||||
debug("Error while list local emails for %s: %s", folder.to_string(), err.message);
|
||||
}
|
||||
|
|
@ -110,10 +113,16 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
if (list == null || list.size == 0)
|
||||
break;
|
||||
|
||||
schedule_prefetch(list);
|
||||
// find lowest for next iteration
|
||||
foreach (Geary.Email email in list) {
|
||||
if (lowest == null || email.id.compare_to(lowest) < 0)
|
||||
lowest = email.id;
|
||||
}
|
||||
|
||||
low = Numeric.int_floor(low - PREFETCH_IDS_CHUNKS, 1);
|
||||
} while (!finished);
|
||||
schedule_prefetch(list);
|
||||
}
|
||||
|
||||
active_sem.blind_notify();
|
||||
}
|
||||
|
||||
private async void do_prepare_new_async(Gee.Collection<Geary.EmailIdentifier> ids) {
|
||||
|
|
@ -127,6 +136,8 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
|
||||
if (list != null && list.size > 0)
|
||||
schedule_prefetch(list);
|
||||
|
||||
active_sem.blind_notify();
|
||||
}
|
||||
|
||||
private async void do_prefetch_async() {
|
||||
|
|
@ -139,9 +150,8 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
|
|||
debug("Error while prefetching emails for %s: %s", folder.to_string(), err.message);
|
||||
}
|
||||
|
||||
// only signal "halting" if it looks like nothing more is waiting for another round
|
||||
if (prefetch_emails.size == 0)
|
||||
halting();
|
||||
// this round is done
|
||||
active_sem.blind_notify();
|
||||
|
||||
if (token != Nonblocking.Mutex.INVALID_TOKEN) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -43,9 +43,20 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
private int open_count = 0;
|
||||
private Nonblocking.ReportingSemaphore<bool>? remote_semaphore = null;
|
||||
private ReplayQueue? replay_queue = null;
|
||||
private Nonblocking.Mutex normalize_email_positions_mutex = new Nonblocking.Mutex();
|
||||
private int remote_count = -1;
|
||||
|
||||
/**
|
||||
* Indicates new messages have been added to the local store via vector expansion.
|
||||
*
|
||||
* This does ''not'' necessarily mean the messages are "new" (i.e. recently arrived), merely
|
||||
* that a request was made for email not in the database and it's now been added.
|
||||
*
|
||||
* @see appended
|
||||
* @see locally_appended
|
||||
*/
|
||||
public virtual signal void local_expansion(Gee.Collection<Geary.EmailIdentifier> ids) {
|
||||
}
|
||||
|
||||
public GenericFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local,
|
||||
ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
|
||||
_account = account;
|
||||
|
|
@ -70,6 +81,10 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
local_folder.email_complete.disconnect(on_email_complete);
|
||||
}
|
||||
|
||||
internal virtual void notify_local_expansion(Gee.Collection<Geary.EmailIdentifier> ids) {
|
||||
local_expansion(ids);
|
||||
}
|
||||
|
||||
public void set_special_folder_type(SpecialFolderType new_type) {
|
||||
_special_folder_type = new_type;
|
||||
}
|
||||
|
|
@ -78,13 +93,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
if (open_count == 0)
|
||||
return Geary.Folder.OpenState.CLOSED;
|
||||
|
||||
if (local_folder.opened)
|
||||
return (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL;
|
||||
else if (remote_folder != null)
|
||||
return Geary.Folder.OpenState.REMOTE;
|
||||
|
||||
// opened flag set but neither open; indicates opening state
|
||||
return Geary.Folder.OpenState.OPENING;
|
||||
return (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL;
|
||||
}
|
||||
|
||||
// Returns the synchronized remote count (-1 if not opened) and the last seen remote count (stored
|
||||
|
|
@ -92,6 +101,9 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
//
|
||||
// Return value is the remote_count, unless the remote is unopened, in which case it's the
|
||||
// last_seen_remote_count (which may be -1).
|
||||
//
|
||||
// remote_count, last_seen_remote_count, and returned value do not reflect any notion of
|
||||
// messages marked for removal
|
||||
internal int get_remote_counts(out int remote_count, out int last_seen_remote_count) {
|
||||
remote_count = this.remote_count;
|
||||
last_seen_remote_count = local_folder.get_properties().select_examine_messages;
|
||||
|
|
@ -207,10 +219,15 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
Geary.Email.Field normalization_fields = is_fast_open ? FAST_NORMALIZATION_FIELDS : NORMALIZATION_FIELDS;
|
||||
|
||||
for (;;) {
|
||||
Geary.Imap.EmailIdentifier current_end_id = new Geary.Imap.EmailIdentifier(
|
||||
new Imap.UID(current_start_id.uid.value + NORMALIZATION_CHUNK_COUNT),
|
||||
current_start_id.folder_path);
|
||||
|
||||
// Get the local emails in the range ... use PARTIAL_OK to ensure all emails are normalized
|
||||
Gee.List<Geary.Email>? old_local = yield local_folder.list_email_by_id_async(
|
||||
current_start_id, NORMALIZATION_CHUNK_COUNT, normalization_fields,
|
||||
ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable);
|
||||
Gee.List<Geary.Email>? old_local = yield local_folder.list_email_by_range_async(
|
||||
current_start_id, current_end_id, normalization_fields,
|
||||
ImapDB.Folder.ListFlags.PARTIAL_OK | ImapDB.Folder.ListFlags.INCLUDING_ID,
|
||||
cancellable);
|
||||
|
||||
// verify still open
|
||||
check_open("normalize_folders (list local)");
|
||||
|
|
@ -222,9 +239,6 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
int local_length = (old_local != null) ? old_local.size : 0;
|
||||
|
||||
// if nothing, keep going because there could be remote messages to pull down
|
||||
Geary.Imap.EmailIdentifier current_end_id = new Geary.Imap.EmailIdentifier(
|
||||
new Imap.UID(current_start_id.uid.value + NORMALIZATION_CHUNK_COUNT),
|
||||
current_start_id.folder_path);
|
||||
Imap.MessageSet msg_set = new Imap.MessageSet.uid_range(current_start_id.uid, current_end_id.uid);
|
||||
|
||||
// Get the remote emails in the range to either add any not known, remove deleted messages,
|
||||
|
|
@ -436,6 +450,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
debug("Notifying of %d locally appended emails since %s last seen", all_locally_appended_ids.size,
|
||||
to_string());
|
||||
notify_email_locally_appended(all_locally_appended_ids);
|
||||
notify_local_expansion(all_locally_appended_ids);
|
||||
}
|
||||
|
||||
// notify additions
|
||||
|
|
@ -476,18 +491,8 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
// start the replay queue
|
||||
replay_queue = new ReplayQueue(path.to_string(), remote_semaphore);
|
||||
|
||||
try {
|
||||
yield local_folder.open_async(cancellable);
|
||||
} catch (Error err) {
|
||||
debug("Not opening %s: unable to open local folder: %s", to_string(), err.message);
|
||||
|
||||
notify_open_failed(OpenFailed.LOCAL_FAILED, err);
|
||||
|
||||
// schedule close now
|
||||
close_internal_async.begin(CloseReason.LOCAL_ERROR, CloseReason.REMOTE_CLOSE, cancellable);
|
||||
|
||||
throw err;
|
||||
}
|
||||
// reset state in the local (database) folder
|
||||
local_folder.reset();
|
||||
|
||||
// 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
|
||||
|
|
@ -597,6 +602,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
|
||||
if (remote_folder != null)
|
||||
_properties.remove(remote_folder.properties);
|
||||
|
||||
yield close_internal_async(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, cancellable);
|
||||
}
|
||||
|
||||
|
|
@ -633,12 +639,8 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
// time, even if remote was not fully opened, as some callers rely on order of signals
|
||||
notify_closed(remote_reason);
|
||||
|
||||
// close local store
|
||||
try {
|
||||
yield local_folder.close_async(cancellable);
|
||||
} catch (Error local_err) {
|
||||
debug("Error closing %s local store: %s", to_string(), local_err.message);
|
||||
}
|
||||
// close local store (reset its state)
|
||||
local_folder.reset();
|
||||
|
||||
// see above note for why this must be called every time
|
||||
notify_closed(local_reason);
|
||||
|
|
@ -754,8 +756,10 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
if (appended.size > 0)
|
||||
notify_email_appended(appended);
|
||||
|
||||
if (created.size > 0)
|
||||
if (created.size > 0) {
|
||||
notify_email_locally_appended(created);
|
||||
notify_local_expansion(created);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
notify_email_count_changed(remote_count, CountChangeReason.APPENDED);
|
||||
|
|
@ -789,10 +793,9 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
|
||||
debug("do_replay_remove_message: local_count=%d local_position=%d", local_count, local_position);
|
||||
|
||||
Gee.List<Geary.Email>? list = yield local_folder.list_email_async(local_position,
|
||||
1, Geary.Email.Field.NONE, ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null);
|
||||
if (list != null && list.size > 0)
|
||||
owned_id = list[0].id;
|
||||
Imap.UID? uid = yield local_folder.get_uid_at_async(local_position, null);
|
||||
if (uid != null)
|
||||
owned_id = new Imap.EmailIdentifier(uid, path);
|
||||
} catch (Error err) {
|
||||
debug("Unable to determine ID of removed message #%d from %s: %s", remote_position,
|
||||
to_string(), err.message);
|
||||
|
|
@ -868,79 +871,27 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
});
|
||||
}
|
||||
|
||||
//
|
||||
// list_email variants
|
||||
//
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
|
||||
yield do_list_email_async("list_email_async", low, count, required_fields, flags, accumulator,
|
||||
null, cancellable);
|
||||
|
||||
return (accumulator.size > 0) ? accumulator : null;
|
||||
}
|
||||
|
||||
public override void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
|
||||
Geary.Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
|
||||
do_lazy_list_email_async.begin(low, count, required_fields, flags, cb, cancellable);
|
||||
}
|
||||
|
||||
private async void do_lazy_list_email_async(int low, int count, Geary.Email.Field required_fields,
|
||||
Geary.Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable) {
|
||||
try {
|
||||
yield do_list_email_async("lazy_list_email", low, count, required_fields, flags,
|
||||
null, cb, cancellable);
|
||||
} catch (Error err) {
|
||||
cb(null, err);
|
||||
}
|
||||
}
|
||||
|
||||
private async void do_list_email_async(string method, int low, int count, Geary.Email.Field required_fields,
|
||||
Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator, EmailCallback? cb,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open(method);
|
||||
check_flags(method, flags);
|
||||
check_span_specifiers(low, count);
|
||||
|
||||
if (count == 0) {
|
||||
// signal finished
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule list operation and wait for completion.
|
||||
ListEmail op = new ListEmail(this, low, count, required_fields, flags, accumulator, cb,
|
||||
cancellable);
|
||||
replay_queue.schedule(op);
|
||||
|
||||
yield op.wait_for_ready_async(cancellable);
|
||||
}
|
||||
|
||||
//
|
||||
// list_email_by_id variants
|
||||
//
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
|
||||
int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
|
||||
yield do_list_email_by_id_async("list_email_by_id_async", initial_id, count, required_fields,
|
||||
flags, accumulator, null, cancellable);
|
||||
|
||||
return (accumulator.size > 0) ? accumulator : null;
|
||||
return !accumulator.is_empty ? accumulator : null;
|
||||
}
|
||||
|
||||
public override void lazy_list_email_by_id(Geary.EmailIdentifier initial_id, int count,
|
||||
public override void lazy_list_email_by_id(Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb,
|
||||
Cancellable? cancellable = null) {
|
||||
do_lazy_list_email_by_id_async.begin(initial_id, count, required_fields, flags, cb, cancellable);
|
||||
}
|
||||
|
||||
private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier initial_id, int count,
|
||||
private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable) {
|
||||
try {
|
||||
yield do_list_email_by_id_async("lazy_list_email_by_id", initial_id, count, required_fields,
|
||||
|
|
@ -950,12 +901,13 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
}
|
||||
}
|
||||
|
||||
private async void do_list_email_by_id_async(string method, Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator,
|
||||
EmailCallback? cb, Cancellable? cancellable) throws Error {
|
||||
private async void do_list_email_by_id_async(string method, Geary.EmailIdentifier? initial_id,
|
||||
int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
|
||||
Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable) throws Error {
|
||||
check_open(method);
|
||||
check_flags(method, flags);
|
||||
check_id(method, initial_id);
|
||||
if (initial_id != null)
|
||||
check_id(method, initial_id);
|
||||
|
||||
if (count == 0) {
|
||||
// signal finished
|
||||
|
|
@ -1088,118 +1040,6 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
check_id(method, id);
|
||||
}
|
||||
|
||||
// Converts a remote position to a local position. remote_pos is 1-based.
|
||||
//
|
||||
// Returns negative value if remote_count is smaller than local_count or remote_pos is out of
|
||||
// range.
|
||||
internal static int remote_position_to_local_position(int remote_pos, int local_count, int remote_count) {
|
||||
assert(remote_pos >= 1);
|
||||
assert(local_count >= 0);
|
||||
assert(remote_count >= 0);
|
||||
|
||||
if (remote_count < local_count) {
|
||||
debug("remote_position_to_local_position: remote_count=%d < local_count=%d (remote_pos=%d)",
|
||||
remote_count, local_count, remote_pos);
|
||||
}
|
||||
|
||||
if (remote_pos > remote_count) {
|
||||
debug("remote_position_to_local_position: remote_pos=%d > remote_count=%d (local_count=%d)",
|
||||
remote_pos, remote_count, local_count);
|
||||
}
|
||||
|
||||
return (remote_pos <= remote_count) ? remote_pos - (remote_count - local_count) : -1;
|
||||
}
|
||||
|
||||
// Converts a local position to a remote position. local_pos is 1-based.
|
||||
//
|
||||
// Returns negative value if remote_count is smaller than local_count or if local_pos is out
|
||||
// of range.
|
||||
internal static int local_position_to_remote_position(int local_pos, int local_count, int remote_count) {
|
||||
assert(local_pos >= 1);
|
||||
assert(local_count >= 0);
|
||||
assert(remote_count >= 0);
|
||||
|
||||
if (remote_count < local_count) {
|
||||
debug("local_position_to_remote_position: remote_count=%d < local_count=%d",
|
||||
remote_count, local_count);
|
||||
} else if (local_pos > local_count) {
|
||||
debug("local_position_to_remote_position: local_pos=%d > local_count=%d",
|
||||
local_pos, local_count);
|
||||
}
|
||||
|
||||
return (local_pos <= local_count) ? remote_count - (local_count - local_pos) : -1;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// This method takes care of that if that range needs to expand.
|
||||
//
|
||||
// Note that this method doesn't return a remote_count because that's maintained by the
|
||||
// EngineFolder as a member variable.
|
||||
internal async void normalize_email_positions_async(int low, int count, out int local_count,
|
||||
Cancellable? cancellable) throws Error {
|
||||
if (!yield remote_semaphore.wait_for_result_async(cancellable))
|
||||
throw new EngineError.SERVER_UNAVAILABLE("no connection to %s", remote.to_string());
|
||||
|
||||
int mutex_token = yield normalize_email_positions_mutex.claim_async(cancellable);
|
||||
|
||||
Gee.HashSet<Geary.EmailIdentifier> created_ids = new Gee.HashSet<Geary.EmailIdentifier>();
|
||||
Error? error = null;
|
||||
try {
|
||||
local_count = yield local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
|
||||
// fixup span specifier
|
||||
normalize_span_specifiers(ref low, ref count, remote_count);
|
||||
|
||||
// Only prefetch properties for messages not being asked for by the user
|
||||
// (any messages that may be between the user's high and the remote's high, assuming that
|
||||
// all messages in local_count are contiguous from the highest email position, which is
|
||||
// taken care of my prepare_opened_folder_async())
|
||||
int high = (low + (count - 1)).clamp(1, remote_count);
|
||||
int local_low = (local_count > 0) ? (remote_count - local_count) + 1 : remote_count;
|
||||
if (high >= local_low) {
|
||||
normalize_email_positions_mutex.release(ref mutex_token);
|
||||
return;
|
||||
}
|
||||
|
||||
int prefetch_count = local_low - high;
|
||||
|
||||
// Normalize the local folder by fetching EmailIdentifiers for all missing email as well
|
||||
// as fields for duplicate detection
|
||||
Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(
|
||||
new Imap.MessageSet.range_by_count(new Imap.SequenceNumber(high), prefetch_count),
|
||||
ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION, cancellable);
|
||||
if (list == null || list.size != prefetch_count) {
|
||||
throw new EngineError.BAD_PARAMETERS("Unable to prefetch %d email starting at %d in %s",
|
||||
count, low, to_string());
|
||||
}
|
||||
|
||||
Gee.Map<Geary.Email, bool> created_or_merged = yield local_folder.create_or_merge_email_async(
|
||||
list, cancellable);
|
||||
|
||||
// Collect which EmailIdentifiers were created and report them
|
||||
foreach (Geary.Email email in created_or_merged.keys) {
|
||||
// true means created
|
||||
if (created_or_merged.get(email))
|
||||
created_ids.add(email.id);
|
||||
}
|
||||
} catch (Error e) {
|
||||
local_count = 0; // prevent compiler warning
|
||||
error = e;
|
||||
}
|
||||
|
||||
normalize_email_positions_mutex.release(ref mutex_token);
|
||||
|
||||
// report created outside of mutex, to avoid reentrancy issues
|
||||
if (created_ids.size > 0)
|
||||
notify_email_locally_appended(created_ids);
|
||||
|
||||
if (error != null)
|
||||
throw error;
|
||||
}
|
||||
|
||||
public virtual async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
|
||||
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
|
|
@ -1226,5 +1066,46 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
|
|||
private void on_email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> changed) {
|
||||
notify_email_flags_changed(changed);
|
||||
}
|
||||
|
||||
// TODO: A proper public search mechanism; note that this always round-trips to the remote,
|
||||
// doesn't go through the replay queue, and doesn't deal with messages marked for deletion
|
||||
internal async Geary.EmailIdentifier? find_earliest_email_async(DateTime datetime,
|
||||
Geary.EmailIdentifier? before_id, Cancellable? cancellable) throws Error {
|
||||
check_open("find_earliest_email_async");
|
||||
if (before_id != null)
|
||||
check_id("find_earliest_email_async", before_id);
|
||||
|
||||
Imap.SearchCriteria criteria = new Imap.SearchCriteria();
|
||||
criteria.is_(Imap.SearchCriterion.since_internaldate(new Imap.InternalDate.from_date_time(datetime)));
|
||||
|
||||
// if before_id available, only search for messages before it
|
||||
if (before_id != null) {
|
||||
Imap.UID before_uid = ((Imap.EmailIdentifier) before_id).uid;
|
||||
criteria.and(Imap.SearchCriterion.message_set(
|
||||
new Imap.MessageSet.uid_range(new Imap.UID(Imap.UID.MIN), before_uid.previous())));
|
||||
}
|
||||
|
||||
Gee.SortedSet<Imap.UID>? since_uids = yield remote_folder.search_async(criteria, null);
|
||||
if (since_uids == null || since_uids.size == 0)
|
||||
return null;
|
||||
|
||||
debug("%s: Found %d emails after %s", to_string(), since_uids.size, datetime.to_string());
|
||||
|
||||
Imap.EmailIdentifier found_id = new Imap.EmailIdentifier(since_uids.first(), path);
|
||||
|
||||
// list NONE from the earliest in order to perform vector expansion and ensure the local
|
||||
// store has no gaps in it
|
||||
//
|
||||
// NOTE: This relies on side-effects and known behavior of the implementation of this
|
||||
// class. Magically generating EmailIdentifiers is a bad idea. (That's why this uses
|
||||
// list_email_by_id_async() and not fetch_email_async(), and why OLDEST_TO_NEWEST is set.)
|
||||
Gee.List<Geary.Email>? list = yield list_email_by_id_async(found_id, 1, Geary.Email.Field.NONE,
|
||||
ListFlags.OLDEST_TO_NEWEST, cancellable);
|
||||
if (list == null || list.size == 0)
|
||||
return null;
|
||||
|
||||
// now the EmailIdentifier should be valid, but use the one generated by the list operation
|
||||
return list[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
/* Copyright 2012-2013 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.
|
||||
*/
|
||||
|
||||
private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.SendReplayOperation {
|
||||
private class RemoteBatchOperation : Nonblocking.BatchOperation {
|
||||
// IN
|
||||
public GenericFolder owner;
|
||||
public Imap.MessageSet msg_set;
|
||||
public Geary.Email.Field unfulfilled_fields;
|
||||
public Geary.Email.Field required_fields;
|
||||
|
||||
// OUT
|
||||
public Gee.Set<Geary.EmailIdentifier> created_ids = new Gee.HashSet<Geary.EmailIdentifier>();
|
||||
|
||||
public RemoteBatchOperation(GenericFolder owner, Imap.MessageSet msg_set,
|
||||
Geary.Email.Field unfulfilled_fields, Geary.Email.Field required_fields) {
|
||||
this.owner = owner;
|
||||
this.msg_set = msg_set;
|
||||
this.unfulfilled_fields = unfulfilled_fields;
|
||||
this.required_fields = required_fields;
|
||||
}
|
||||
|
||||
public override async Object? execute_async(Cancellable? cancellable) throws Error {
|
||||
// fetch from remote folder
|
||||
Gee.List<Geary.Email>? list = yield owner.remote_folder.list_email_async(msg_set,
|
||||
unfulfilled_fields, cancellable);
|
||||
if (list == null || list.size == 0)
|
||||
return null;
|
||||
|
||||
// TODO: create_or_merge_email_async() should only write if something has changed
|
||||
Gee.Map<Geary.Email, bool> created_or_merged = yield owner.local_folder.create_or_merge_email_async(
|
||||
list, cancellable);
|
||||
for (int ctr = 0; ctr < list.size; ctr++) {
|
||||
Geary.Email email = list[ctr];
|
||||
|
||||
// if created, add to id pool
|
||||
if (created_or_merged.get(email))
|
||||
created_ids.add(email.id);
|
||||
|
||||
// if remote email doesn't fulfills all required fields, fetch full and return that
|
||||
// TODO: Need a sparse ID fetch in ImapDB.Folder to do this all at once
|
||||
if (!email.fields.fulfills(required_fields)) {
|
||||
email = yield owner.local_folder.fetch_email_async(email.id, required_fields,
|
||||
ImapDB.Folder.ListFlags.NONE, cancellable);
|
||||
list[ctr] = email;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
protected GenericFolder owner;
|
||||
protected Geary.Email.Field required_fields;
|
||||
protected Gee.List<Geary.Email>? accumulator = null;
|
||||
protected weak EmailCallback? cb;
|
||||
protected Cancellable? cancellable;
|
||||
protected Folder.ListFlags flags;
|
||||
protected Gee.HashMultiMap<Geary.Email.Field, Geary.EmailIdentifier> unfulfilled = new Gee.HashMultiMap<
|
||||
Geary.Email.Field, Geary.EmailIdentifier>();
|
||||
|
||||
public AbstractListEmail(string name, GenericFolder owner, Geary.Email.Field required_fields,
|
||||
Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator, EmailCallback? cb,
|
||||
Cancellable? cancellable) {
|
||||
base(name);
|
||||
|
||||
this.owner = owner;
|
||||
this.required_fields = required_fields;
|
||||
this.accumulator = accumulator;
|
||||
this.cb = cb;
|
||||
this.cancellable = cancellable;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public override bool query_local_writebehind_operation(ReplayOperation.WritebehindOperation op,
|
||||
EmailIdentifier id, Imap.EmailFlags? flags) {
|
||||
// don't need to check if id is present here, all paths deal with this possibility
|
||||
// correctly
|
||||
|
||||
switch (op) {
|
||||
case ReplayOperation.WritebehindOperation.REMOVE:
|
||||
// remove email already picked up from local store ... for email reported via the
|
||||
// callback, too late
|
||||
if (accumulator != null) {
|
||||
Gee.HashSet<Geary.Email> wb_removed = new Gee.HashSet<Geary.Email>();
|
||||
foreach (Geary.Email email in accumulator) {
|
||||
if (email.id.equal_to(id))
|
||||
wb_removed.add(email);
|
||||
}
|
||||
|
||||
accumulator.remove_all(wb_removed);
|
||||
}
|
||||
|
||||
// remove from unfulfilled list, as there's nothing to fetch from the server
|
||||
foreach (Geary.Email.Field field in unfulfilled.get_keys())
|
||||
unfulfilled.remove(field, id);
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
// ignored
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Child class should execute its own calls *before* calling this base method
|
||||
public override async ReplayOperation.Status replay_remote_async() throws Error {
|
||||
// only deal with unfulfilled email, child class must deal with everything else
|
||||
if (unfulfilled.size == 0)
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
|
||||
// schedule operations to remote for each set of email with unfulfilled fields and merge
|
||||
// in results, pulling out the entire email
|
||||
Nonblocking.Batch batch = new Nonblocking.Batch();
|
||||
foreach (Geary.Email.Field unfulfilled_fields in unfulfilled.get_keys()) {
|
||||
Gee.Collection<EmailIdentifier> email_ids = unfulfilled.get(unfulfilled_fields);
|
||||
if (email_ids.size == 0)
|
||||
continue;
|
||||
|
||||
Imap.MessageSet msg_set = new Imap.MessageSet.email_id_collection(email_ids);
|
||||
RemoteBatchOperation remote_op = new RemoteBatchOperation(owner, msg_set, unfulfilled_fields,
|
||||
required_fields);
|
||||
batch.add(remote_op);
|
||||
}
|
||||
|
||||
yield batch.execute_all_async(cancellable);
|
||||
batch.throw_first_exception();
|
||||
|
||||
Gee.ArrayList<Geary.Email> result_list = new Gee.ArrayList<Geary.Email>();
|
||||
Gee.HashSet<Geary.EmailIdentifier> created_ids = new Gee.HashSet<Geary.EmailIdentifier>();
|
||||
foreach (int batch_id in batch.get_ids()) {
|
||||
Gee.List<Geary.Email>? list = (Gee.List<Geary.Email>?) batch.get_result(batch_id);
|
||||
if (list != null && list.size > 0) {
|
||||
result_list.add_all(list);
|
||||
|
||||
RemoteBatchOperation op = (RemoteBatchOperation) batch.get_operation(batch_id);
|
||||
created_ids.add_all(op.created_ids);
|
||||
}
|
||||
}
|
||||
|
||||
// report merged emails
|
||||
if (result_list.size > 0) {
|
||||
if (accumulator != null)
|
||||
accumulator.add_all(result_list);
|
||||
|
||||
if (cb != null)
|
||||
cb(result_list, null);
|
||||
}
|
||||
|
||||
// done
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
// signal
|
||||
if (created_ids.size > 0)
|
||||
owner.notify_local_expansion(created_ids);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
public override async void backout_local_async() throws Error {
|
||||
// R/O, no backout
|
||||
}
|
||||
|
||||
public override string describe_state() {
|
||||
return "required_fields=%Xh local_only=%s force_update=%s".printf(required_fields,
|
||||
flags.is_local_only().to_string(), flags.is_force_update().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,16 +47,13 @@ private class Geary.ImapEngine.FetchEmail : Geary.ImapEngine.SendReplayOperation
|
|||
throw err;
|
||||
}
|
||||
|
||||
int remote_count;
|
||||
engine.get_remote_counts(out remote_count, null);
|
||||
|
||||
// If returned in full, done
|
||||
if (email != null && email.fields.fulfills(required_fields))
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
|
||||
// If local only (or not connected) and not found fully in local store, throw NOT_FOUND;
|
||||
// there is no fallback
|
||||
if (flags.is_all_set(Folder.ListFlags.LOCAL_ONLY) || remote_count < 0) {
|
||||
if (flags.is_all_set(Folder.ListFlags.LOCAL_ONLY)) {
|
||||
throw new EngineError.NOT_FOUND("Email %s with fields %Xh not found in %s", id.to_string(),
|
||||
required_fields, to_string());
|
||||
}
|
||||
|
|
@ -109,18 +106,13 @@ private class Geary.ImapEngine.FetchEmail : Geary.ImapEngine.SendReplayOperation
|
|||
email = list[0];
|
||||
assert(email != null);
|
||||
|
||||
// use position to normalize any missing emails in the span (prevent holes in the local
|
||||
// email vector)
|
||||
assert(email.position >= 1);
|
||||
yield engine.normalize_email_positions_async(email.position, 1, null, cancellable);
|
||||
|
||||
Gee.Map<Geary.Email, bool> created_or_merged =
|
||||
yield engine.local_folder.create_or_merge_email_async(new Collection.SingleItem<Geary.Email>(email),
|
||||
cancellable);
|
||||
|
||||
// true means created
|
||||
if (created_or_merged.get(email))
|
||||
engine.notify_email_locally_appended(new Collection.SingleItem<Geary.EmailIdentifier>(email.id));
|
||||
engine.notify_local_expansion(new Collection.SingleItem<Geary.EmailIdentifier>(email.id));
|
||||
|
||||
// if remote_email doesn't fulfill all required, pull from local database, which should now
|
||||
// be able to do all of that
|
||||
|
|
|
|||
|
|
@ -4,93 +4,197 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
private class Geary.ImapEngine.ListEmailByID : Geary.ImapEngine.ListEmail {
|
||||
private Geary.EmailIdentifier initial_id;
|
||||
private class Geary.ImapEngine.ListEmailByID : Geary.ImapEngine.AbstractListEmail {
|
||||
private Imap.EmailIdentifier? initial_id;
|
||||
private int count;
|
||||
private int local_list_count = 0;
|
||||
|
||||
public ListEmailByID(GenericFolder engine, Geary.EmailIdentifier initial_id, int count,
|
||||
public ListEmailByID(GenericFolder owner, Geary.EmailIdentifier? initial_id, int count,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator,
|
||||
EmailCallback? cb, Cancellable? cancellable) {
|
||||
base(engine, 0, count, required_fields, flags, accumulator, cb, cancellable);
|
||||
base ("ListEmailByID", owner, required_fields, flags, accumulator, cb, cancellable);
|
||||
|
||||
name = "ListEmailByID";
|
||||
|
||||
this.initial_id = initial_id;
|
||||
this.initial_id = (Imap.EmailIdentifier?) initial_id;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_local_async() throws Error {
|
||||
int local_count = yield engine.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
if (flags.is_force_update())
|
||||
return ReplayOperation.Status.CONTINUE;
|
||||
|
||||
int initial_position = yield engine.local_folder.get_id_position_async(initial_id,
|
||||
ImapDB.Folder.ListFlags.NONE, cancellable);
|
||||
if (initial_position <= 0) {
|
||||
throw new EngineError.NOT_FOUND("Email ID %s in %s not known to local store",
|
||||
initial_id.to_string(), engine.to_string());
|
||||
// get everything from local store, even partial matches, that fit range
|
||||
ImapDB.Folder.ListFlags list_flags = ImapDB.Folder.ListFlags.from_folder_flags(flags);
|
||||
list_flags |= ImapDB.Folder.ListFlags.PARTIAL_OK;
|
||||
Gee.List<Geary.Email>? list = yield owner.local_folder.list_email_by_id_async(initial_id,
|
||||
count, required_fields, list_flags, cancellable);
|
||||
|
||||
// walk list, breaking out unfulfilled items from fulfilled items
|
||||
Gee.ArrayList<Geary.Email> fulfilled = new Gee.ArrayList<Geary.Email>();
|
||||
if (list != null) {
|
||||
foreach (Geary.Email email in list) {
|
||||
if (email.fields.fulfills(required_fields))
|
||||
fulfilled.add(email);
|
||||
else
|
||||
unfulfilled.set(required_fields.clear(email.fields), email.id);
|
||||
}
|
||||
}
|
||||
|
||||
int remote_count;
|
||||
int last_seen_remote_count;
|
||||
int usable_remote_count = engine.get_remote_counts(out remote_count, out last_seen_remote_count);
|
||||
|
||||
// use local count if both remote counts unavailable
|
||||
if (usable_remote_count < 0)
|
||||
usable_remote_count = local_count;
|
||||
|
||||
// normalize the initial position to the remote folder's addressing
|
||||
initial_position = GenericFolder.local_position_to_remote_position(initial_position, local_count, usable_remote_count);
|
||||
if (initial_position <= 0) {
|
||||
throw new EngineError.NOT_FOUND("Cannot map email ID %s in %s to remote folder",
|
||||
initial_id.to_string(), engine.to_string());
|
||||
}
|
||||
|
||||
// since count can also indicate "to earliest" or "to latest", normalize
|
||||
// (count is exclusive of initial_id, hence adding/subtracting one, meaning that a count
|
||||
// of zero or one are accepted)
|
||||
int low, high;
|
||||
if (count < 0) {
|
||||
low = (count != int.MIN) ? (initial_position + count + 1) : 1;
|
||||
high = excluding_id ? initial_position - 1 : initial_position;
|
||||
} else if (count > 0) {
|
||||
low = excluding_id ? initial_position + 1 : initial_position;
|
||||
high = (count != int.MAX) ? (initial_position + count - 1) : usable_remote_count;
|
||||
} else {
|
||||
// count == 0
|
||||
low = initial_position;
|
||||
high = initial_position;
|
||||
}
|
||||
|
||||
// low should never be -1, so don't need to check for that
|
||||
low = low.clamp(1, int.MAX);
|
||||
|
||||
int actual_count = ((high - low) + 1);
|
||||
|
||||
// one more check
|
||||
if (actual_count == 0) {
|
||||
Logging.debug(Logging.Flag.REPLAY,
|
||||
"ListEmailByID %s: no actual count to return (%d) (excluding=%s %s)",
|
||||
engine.to_string(), actual_count, excluding_id.to_string(), initial_id.to_string());
|
||||
// report fulfilled items
|
||||
if (fulfilled.size > 0) {
|
||||
if (accumulator != null)
|
||||
accumulator.add_all(fulfilled);
|
||||
|
||||
if (cb != null)
|
||||
cb(fulfilled, null);
|
||||
}
|
||||
|
||||
// local-only operations stop here; also, since the local store is normalized from the top
|
||||
// of the vector on down, if enough items came back fulfilled, then done
|
||||
local_list_count = (list != null) ? list.size : 0;
|
||||
if (flags.is_local_only() || (unfulfilled.size == 0 && local_list_count >= count)) {
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
this.low = low;
|
||||
this.count = actual_count;
|
||||
return ReplayOperation.Status.CONTINUE;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_remote_async() throws Error {
|
||||
// To get this far, either the local store doesn't have all the contents of the items in
|
||||
// the request range, or it doesn't have any row for items in the range (i.e. the vector
|
||||
// is too short).
|
||||
if (local_list_count + unfulfilled.size < count) {
|
||||
Gee.List<Geary.Email>? expanded = yield expand_vector_async();
|
||||
if (expanded != null) {
|
||||
// take all the IDs from the expanded vector and call them unfulfilled; base class
|
||||
// does the rest. Add duplicate detection fields so that can be determined
|
||||
// immediately
|
||||
foreach (Geary.Email email in expanded) {
|
||||
unfulfilled.set(required_fields | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION,
|
||||
email.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always return completed if the base class says so
|
||||
if ((yield base.replay_local_async()) == ReplayOperation.Status.COMPLETED)
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
// Even after expansion it's possible for the local_list_count + unfulfilled to be less
|
||||
// than count if the folder has fewer messages or the user is requesting a span near
|
||||
// either end of the vector, so don't do that kind of sanity checking here
|
||||
|
||||
// Only return CONTINUE if connected to the remote (otherwise possibility of mixing stale
|
||||
// and fresh email data in single call)
|
||||
return (remote_count >= 0) ? ReplayOperation.Status.CONTINUE : ReplayOperation.Status.COMPLETED;
|
||||
return yield base.replay_remote_async();
|
||||
}
|
||||
|
||||
private async Gee.List<Geary.Email>? expand_vector_async() throws Error {
|
||||
// watch out for situations where the entire folder is represented locally (i.e. no
|
||||
// expansion necessary)
|
||||
int remote_count = owner.get_remote_counts(null, null);
|
||||
if (remote_count < 0)
|
||||
return null;
|
||||
|
||||
// include marked for removed in the count in case this is being called while a removal
|
||||
// is in process, in which case don't want to expand vector this moment because the
|
||||
// vector is in flux
|
||||
int local_count = yield owner.local_folder.get_email_count_async(
|
||||
ImapDB.Folder.ListFlags.NONE, cancellable);
|
||||
int local_count_with_marked = yield owner.local_folder.get_email_count_async(
|
||||
ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
|
||||
|
||||
if (local_count_with_marked >= remote_count) {
|
||||
// watch for sync discrepencies ... this is not something that can be fixed up here
|
||||
if (local_count_with_marked > remote_count) {
|
||||
message("%s: not expanding vector remote_count=%d local_count=%d", to_string(),
|
||||
remote_count, local_count);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// determine low and high position for expansion ... default in most code paths for high
|
||||
// is the SequenceNumber just below the lowest known message, unless no local messages
|
||||
// are present
|
||||
Imap.SequenceNumber? low_pos = null;
|
||||
Imap.SequenceNumber? high_pos = null;
|
||||
if (local_count > 0)
|
||||
high_pos = new Imap.SequenceNumber(Numeric.int_floor(remote_count - local_count, 1));
|
||||
|
||||
if (flags.is_oldest_to_newest()) {
|
||||
if (initial_id == null) {
|
||||
// if oldest to newest and initial-id is null, then start at the bottom
|
||||
low_pos = new Imap.SequenceNumber(1);
|
||||
} else {
|
||||
// since initial_id is not null and contract requires that the ID come from this
|
||||
// folder, it must be known locally; the span for oldest-to-newest is always locally
|
||||
// available because the partial vector always is from the newest messages on
|
||||
// the remote down toward the oldest; so no vector expansion is necessary in this
|
||||
// case
|
||||
//
|
||||
// However ... there is some internal code (search, specifically) that relies on
|
||||
// being able to pass in an EmailIdentifier with a UID unknown locally, and so that
|
||||
// needs to be taken accounted of
|
||||
Gee.Map<Imap.UID, Imap.SequenceNumber>? map = yield owner.remote_folder.uid_to_position_async(
|
||||
new Imap.MessageSet.uid(initial_id.uid), cancellable);
|
||||
if (map == null || map.size == 0 || !map.has_key(initial_id.uid)) {
|
||||
debug("%s: Unable to expand vector for initial_id=%s: unable to convert to position",
|
||||
to_string(), initial_id.to_string());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
low_pos = map.get(initial_id.uid);
|
||||
}
|
||||
} else {
|
||||
// newest to oldest
|
||||
//
|
||||
// if initial_id is null or no local earliest UID, then vector expansion is simple:
|
||||
// merely count backwards from the top of the vector
|
||||
if (initial_id == null || local_count == 0) {
|
||||
low_pos = new Imap.SequenceNumber(Numeric.int_floor((remote_count - count) + 1, 1));
|
||||
// don't set high_pos, leave null to use symbolic "highest" in MessageSet
|
||||
high_pos = null;
|
||||
} else {
|
||||
// not so simple; need to determine the *remote* position of the earliest local
|
||||
// UID and count backward from that; if no UIDs present, then it's as if no initial_id
|
||||
// is specified.
|
||||
//
|
||||
// low position: count backwards; note that it's possible this will overshoot and
|
||||
// pull in more email than technically required, but without a round-trip to the
|
||||
// server to determine the position number of a particular UID, this makes sense
|
||||
assert(high_pos != null);
|
||||
low_pos = new Imap.SequenceNumber(
|
||||
Numeric.int_floor((high_pos.value - count) + 1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// low_pos must be defined by this point
|
||||
assert(low_pos != null);
|
||||
|
||||
Imap.MessageSet msg_set;
|
||||
if (high_pos != null)
|
||||
msg_set = new Imap.MessageSet.range_by_first_last(low_pos, high_pos);
|
||||
else
|
||||
msg_set = new Imap.MessageSet.range_to_highest(low_pos);
|
||||
|
||||
debug("%s: Performing vector expansion using %s for %s/%u", owner.to_string(), msg_set.to_string(),
|
||||
(initial_id != null) ? initial_id.to_string() : "(null)", count);
|
||||
|
||||
Gee.List<Geary.Email>? list = yield owner.remote_folder.list_email_async(msg_set,
|
||||
Geary.Email.Field.NONE, cancellable);
|
||||
|
||||
debug("%s: Vector expansion completed (%d new email)", owner.to_string(),
|
||||
(list != null) ? list.size : 0);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override async void backout_local_async() throws Error {
|
||||
// R/O, nothing to backout
|
||||
}
|
||||
|
||||
public override string describe_state() {
|
||||
return "%s initial_id=%s excl=%s".printf(base.describe_state(), initial_id.to_string(),
|
||||
excluding_id.to_string());
|
||||
return "%s initial_id=%s count=%u incl=%s newest_to_oldest=%s".printf(base.describe_state(),
|
||||
(initial_id != null) ? initial_id.to_string() : "(null)", count,
|
||||
flags.is_including_id().to_string(), flags.is_newest_to_oldest().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.SendReplayOperation {
|
||||
private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.AbstractListEmail {
|
||||
private class LocalBatchOperation : Nonblocking.BatchOperation {
|
||||
public GenericFolder owner;
|
||||
public Geary.EmailIdentifier id;
|
||||
|
|
@ -33,82 +33,18 @@ private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.SendReplay
|
|||
}
|
||||
}
|
||||
|
||||
private class RemoteBatchOperation : Nonblocking.BatchOperation {
|
||||
public GenericFolder owner;
|
||||
public Imap.MessageSet msg_set;
|
||||
public Geary.Email.Field unfulfilled_fields;
|
||||
public Geary.Email.Field required_fields;
|
||||
|
||||
public RemoteBatchOperation(GenericFolder owner, Imap.MessageSet msg_set,
|
||||
Geary.Email.Field unfulfilled_fields, Geary.Email.Field required_fields) {
|
||||
this.owner = owner;
|
||||
this.msg_set = msg_set;
|
||||
this.unfulfilled_fields = unfulfilled_fields;
|
||||
this.required_fields = required_fields;
|
||||
}
|
||||
|
||||
public override async Object? execute_async(Cancellable? cancellable) throws Error {
|
||||
// fetch from remote folder
|
||||
Gee.List<Geary.Email>? list = yield owner.remote_folder.list_email_async(msg_set,
|
||||
unfulfilled_fields, cancellable);
|
||||
if (list == null || list.size == 0)
|
||||
return null;
|
||||
|
||||
// TODO: create_or_merge_email_async() should only write if something has changed
|
||||
yield owner.local_folder.create_or_merge_email_async(list, cancellable);
|
||||
for (int ctr = 0; ctr < list.size; ctr++) {
|
||||
Geary.Email email = list[ctr];
|
||||
|
||||
// if remote email doesn't fulfills all required fields, fetch full and return that
|
||||
// TODO: Need a sparse ID fetch in ImapDB.Folder to do this all at once
|
||||
if (!email.fields.fulfills(required_fields)) {
|
||||
email = yield owner.local_folder.fetch_email_async(email.id, required_fields,
|
||||
ImapDB.Folder.ListFlags.NONE, cancellable);
|
||||
list[ctr] = email;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
private GenericFolder owner;
|
||||
private Gee.HashSet<Geary.EmailIdentifier> ids = new Gee.HashSet<Geary.EmailIdentifier>();
|
||||
private Geary.Email.Field required_fields;
|
||||
private Folder.ListFlags flags;
|
||||
private bool local_only;
|
||||
private bool force_update;
|
||||
private Gee.Collection<Geary.Email>? accumulator;
|
||||
private unowned EmailCallback cb;
|
||||
private Cancellable? cancellable;
|
||||
private Gee.HashMultiMap<Geary.Email.Field, Geary.EmailIdentifier> unfulfilled = new Gee.HashMultiMap<
|
||||
Geary.Email.Field, Geary.EmailIdentifier>();
|
||||
|
||||
public ListEmailBySparseID(GenericFolder owner, Gee.Collection<Geary.EmailIdentifier> ids,
|
||||
Geary.Email.Field required_fields, Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator,
|
||||
EmailCallback cb, Cancellable? cancellable) {
|
||||
base ("ListEmailBySparseID");
|
||||
base ("ListEmailBySparseID", owner, required_fields, flags, accumulator, cb, cancellable);
|
||||
|
||||
this.owner = owner;
|
||||
this.ids.add_all(ids);
|
||||
this.required_fields = required_fields;
|
||||
this.flags = flags;
|
||||
this.accumulator = accumulator;
|
||||
this.cb = cb;
|
||||
this.cancellable = cancellable;
|
||||
|
||||
local_only = flags.is_all_set(Folder.ListFlags.LOCAL_ONLY);
|
||||
force_update = flags.is_all_set(Folder.ListFlags.FORCE_UPDATE);
|
||||
|
||||
// always fetch required fields unless a modified list, in which case only fetch the fields
|
||||
// requested by user ... this ensures the local store is seeded with certain fields required
|
||||
// for it to operate properly
|
||||
if (!force_update && !local_only)
|
||||
this.required_fields |= ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_local_async() throws Error {
|
||||
if (force_update) {
|
||||
if (flags.is_force_update()) {
|
||||
foreach (EmailIdentifier id in ids)
|
||||
unfulfilled.set(required_fields, id);
|
||||
|
||||
|
|
@ -131,8 +67,11 @@ private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.SendReplay
|
|||
LocalBatchOperation local_op = (LocalBatchOperation) batch.get_operation(batch_id);
|
||||
Geary.Email? email = (Geary.Email?) batch.get_result(batch_id);
|
||||
|
||||
// if completely unknown, make sure duplicate detection fields are included; otherwise,
|
||||
// if known, then they were pulled down during folder normalization and during
|
||||
// vector expansion
|
||||
if (email == null)
|
||||
unfulfilled.set(required_fields, local_op.id);
|
||||
unfulfilled.set(required_fields | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION, local_op.id);
|
||||
else if (!email.fields.fulfills(required_fields))
|
||||
unfulfilled.set(required_fields.clear(email.fields), local_op.id);
|
||||
else
|
||||
|
|
@ -147,11 +86,7 @@ private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.SendReplay
|
|||
cb(fulfilled, null);
|
||||
}
|
||||
|
||||
int remote_count;
|
||||
int last_seen_remote_count;
|
||||
owner.get_remote_counts(out remote_count, out last_seen_remote_count);
|
||||
|
||||
if (local_only || unfulfilled.size == 0 || remote_count < 0) {
|
||||
if (flags.is_local_only() || unfulfilled.size == 0) {
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
|
|
@ -161,78 +96,6 @@ private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.SendReplay
|
|||
return ReplayOperation.Status.CONTINUE;
|
||||
}
|
||||
|
||||
public override bool query_local_writebehind_operation(ReplayOperation.WritebehindOperation op,
|
||||
EmailIdentifier id, Imap.EmailFlags? flags) {
|
||||
// don't need to check if id is present here, all paths deal with this correctly
|
||||
|
||||
switch (op) {
|
||||
case ReplayOperation.WritebehindOperation.REMOVE:
|
||||
// remove email already picked up from local store ... for email reported via the
|
||||
// callback, too late
|
||||
if (accumulator != null) {
|
||||
Gee.HashSet<Geary.Email> wb_removed = new Gee.HashSet<Geary.Email>();
|
||||
foreach (Geary.Email email in accumulator) {
|
||||
if (email.id.equal_to(id))
|
||||
wb_removed.add(email);
|
||||
}
|
||||
|
||||
accumulator.remove_all(wb_removed);
|
||||
}
|
||||
|
||||
// remove from unfulfilled list, as there's nothing to fetch from the server
|
||||
foreach (Geary.Email.Field field in unfulfilled.get_keys())
|
||||
unfulfilled.remove(field, id);
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
// ignored
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_remote_async() throws Error {
|
||||
Nonblocking.Batch batch = new Nonblocking.Batch();
|
||||
|
||||
// schedule operations to remote for each set of email with unfulfilled fields and merge
|
||||
// in results, pulling out the entire email
|
||||
foreach (Geary.Email.Field unfulfilled_fields in unfulfilled.get_keys()) {
|
||||
Gee.Collection<EmailIdentifier> email_ids = unfulfilled.get(unfulfilled_fields);
|
||||
if (email_ids.size == 0)
|
||||
continue;
|
||||
|
||||
Imap.MessageSet msg_set = new Imap.MessageSet.email_id_collection(email_ids);
|
||||
RemoteBatchOperation remote_op = new RemoteBatchOperation(owner, msg_set, unfulfilled_fields,
|
||||
required_fields);
|
||||
batch.add(remote_op);
|
||||
}
|
||||
|
||||
yield batch.execute_all_async(cancellable);
|
||||
batch.throw_first_exception();
|
||||
|
||||
Gee.ArrayList<Geary.Email> result_list = new Gee.ArrayList<Geary.Email>();
|
||||
foreach (int batch_id in batch.get_ids()) {
|
||||
Gee.List<Geary.Email>? list = (Gee.List<Geary.Email>?) batch.get_result(batch_id);
|
||||
if (list != null && list.size > 0)
|
||||
result_list.add_all(list);
|
||||
}
|
||||
|
||||
// report merged emails
|
||||
if (result_list.size > 0) {
|
||||
if (accumulator != null)
|
||||
accumulator.add_all(result_list);
|
||||
|
||||
if (cb != null)
|
||||
cb(result_list, null);
|
||||
}
|
||||
|
||||
// done
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
public override async void backout_local_async() throws Error {
|
||||
// R/O, nothing to backout
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,394 +0,0 @@
|
|||
/* Copyright 2012-2013 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.
|
||||
*/
|
||||
|
||||
private class Geary.ImapEngine.ListEmail : Geary.ImapEngine.SendReplayOperation {
|
||||
private class RemoteListPositional : Nonblocking.BatchOperation {
|
||||
private ListEmail owner;
|
||||
private int[] needed_by_position;
|
||||
|
||||
public RemoteListPositional(ListEmail owner, int[] needed_by_position) {
|
||||
this.owner = owner;
|
||||
this.needed_by_position = needed_by_position;
|
||||
}
|
||||
|
||||
public override async Object? execute_async(Cancellable? cancellable) throws Error {
|
||||
yield owner.remote_list_positional(needed_by_position);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoteListPartial : Nonblocking.BatchOperation {
|
||||
private ListEmail owner;
|
||||
private Geary.Email.Field remaining_fields;
|
||||
private Gee.Collection<EmailIdentifier> ids;
|
||||
|
||||
public RemoteListPartial(ListEmail owner, Geary.Email.Field remaining_fields,
|
||||
Gee.Collection<EmailIdentifier> ids) {
|
||||
this.owner = owner;
|
||||
this.remaining_fields = remaining_fields;
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
public override async Object? execute_async(Cancellable? cancellable) throws Error {
|
||||
yield owner.remote_list_partials(ids, remaining_fields);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected GenericFolder engine;
|
||||
protected int low;
|
||||
protected int count;
|
||||
protected Geary.Email.Field required_fields;
|
||||
protected Gee.List<Geary.Email>? accumulator = null;
|
||||
protected weak EmailCallback? cb;
|
||||
protected Cancellable? cancellable;
|
||||
protected Folder.ListFlags flags;
|
||||
protected bool local_only;
|
||||
protected bool remote_only;
|
||||
protected bool excluding_id;
|
||||
|
||||
private Gee.List<Geary.Email>? local_list = null;
|
||||
private int local_list_size = 0;
|
||||
private Gee.HashMultiMap<Geary.Email.Field, Geary.EmailIdentifier> unfulfilled = new Gee.HashMultiMap<
|
||||
Geary.Email.Field, Geary.EmailIdentifier>();
|
||||
|
||||
public ListEmail(GenericFolder engine, int low, int count, Geary.Email.Field required_fields,
|
||||
Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable) {
|
||||
base("ListEmail");
|
||||
|
||||
this.engine = engine;
|
||||
this.low = low;
|
||||
this.count = count;
|
||||
this.required_fields = required_fields;
|
||||
this.accumulator = accumulator;
|
||||
this.cb = cb;
|
||||
this.cancellable = cancellable;
|
||||
this.flags = flags;
|
||||
|
||||
local_only = flags.is_all_set(Folder.ListFlags.LOCAL_ONLY);
|
||||
remote_only = flags.is_all_set(Folder.ListFlags.FORCE_UPDATE);
|
||||
excluding_id = flags.is_all_set(Folder.ListFlags.EXCLUDING_ID);
|
||||
|
||||
// always fetch required fields unless a modified list, in which case only fetch the fields
|
||||
// requested by user ... this ensures the local store is seeded with certain fields required
|
||||
// for it to operate properly
|
||||
if (!remote_only && !local_only)
|
||||
this.required_fields |= ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_local_async() throws Error {
|
||||
int local_count = yield engine.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
|
||||
int remote_count;
|
||||
int last_seen_remote_count;
|
||||
int usable_remote_count = engine.get_remote_counts(out remote_count, out last_seen_remote_count);
|
||||
|
||||
if (usable_remote_count <= 0) {
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
Logging.debug(Logging.Flag.REPLAY,
|
||||
"ListEmail.replay_local_async %s: No usable remote count, completed", engine.to_string());
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
Folder.normalize_span_specifiers(ref low, ref count, usable_remote_count);
|
||||
|
||||
//
|
||||
// Convert the requested low/count values into values that correspond do the number of
|
||||
// emails in the database and their lowest position value relative to the remote's list.
|
||||
//
|
||||
|
||||
// convert remote low position to the low position relative to the database's contents ...
|
||||
// this can return zero or a negative value if the position is not present in the local store
|
||||
int local_low = GenericFolder.remote_position_to_local_position(low, local_count,
|
||||
usable_remote_count);
|
||||
|
||||
// from local_low and the user's request count, calculate the low position in the database
|
||||
// for what is available (again, can be zero if nothing is available in the database)
|
||||
int local_available_low = 0;
|
||||
if (local_low >= 1)
|
||||
local_available_low = local_low;
|
||||
else if ((local_low + count) >= 1)
|
||||
local_available_low = 1;
|
||||
|
||||
// finally, determine how much in the requested span is available locally, if any
|
||||
int local_available_count;
|
||||
if (local_low >= 1)
|
||||
local_available_count = (local_count - local_low) + 1;
|
||||
else
|
||||
local_available_count = count + local_low;
|
||||
local_available_count = local_available_count.clamp(0, count);
|
||||
|
||||
Logging.debug(Logging.Flag.REPLAY,
|
||||
"ListEmail.replay_local_async %s: low=%d count=%d local_low=%d local_count=%d "
|
||||
+ "local_available_low=%d local_available_count=%d remote_count=%d "
|
||||
+ "last_seen_remote_count=%d usable_remote_count=%d local_only=%s remote_only=%s",
|
||||
engine.to_string(), low, count, local_low, local_count, local_available_low,
|
||||
local_available_count, remote_count, last_seen_remote_count,
|
||||
usable_remote_count, local_only.to_string(), remote_only.to_string());
|
||||
|
||||
if (!remote_only && local_available_count > 0 && local_available_low > 0) {
|
||||
try {
|
||||
local_list = yield engine.local_folder.list_email_async(local_available_low,
|
||||
local_available_count, required_fields, ImapDB.Folder.ListFlags.PARTIAL_OK,
|
||||
cancellable);
|
||||
} catch (Error local_err) {
|
||||
if (cb != null && !(local_err is IOError.CANCELLED))
|
||||
cb (null, local_err);
|
||||
throw local_err;
|
||||
}
|
||||
}
|
||||
|
||||
local_list_size = (local_list != null) ? local_list.size : 0;
|
||||
|
||||
// Break into two pools: a list of emails where all field requirements are met and a hash
|
||||
// table of messages keyed by what fields are required
|
||||
Gee.List<Geary.Email> fulfilled = new Gee.ArrayList<Geary.Email>();
|
||||
if (local_list_size > 0) {
|
||||
foreach (Geary.Email email in local_list) {
|
||||
if (email.fields.fulfills(required_fields)) {
|
||||
fulfilled.add(email);
|
||||
} else {
|
||||
// strip fulfilled fields so only remaining are fetched from server
|
||||
Geary.Email.Field remaining = required_fields.clear(email.fields);
|
||||
assert(remaining != Geary.Email.Field.NONE);
|
||||
unfulfilled.set(remaining, email.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logging.debug(Logging.Flag.REPLAY,
|
||||
"ListEmail.replay_local_async %s: local_list_size=%d fulfilled=%d", to_string(),
|
||||
local_list_size, fulfilled.size);
|
||||
|
||||
// report fulfilled
|
||||
if (fulfilled.size > 0) {
|
||||
if (accumulator != null)
|
||||
accumulator.add_all(fulfilled);
|
||||
|
||||
if (cb != null)
|
||||
cb(fulfilled, null);
|
||||
}
|
||||
|
||||
// if local list matches total asked for, if only returning local versions, or if not
|
||||
// connected to the remote, operation is completed
|
||||
//
|
||||
// NOTE: Do NOT want to wait for remote to open in replay_remote_async() if work was done
|
||||
// here using last_seen_remote_count, as there's a high possibility that positional
|
||||
// addressing will be out of sync will return bogus email to caller; in other words, it
|
||||
// means returning a combination of dirty local email and validated remote email, which is
|
||||
// bad news
|
||||
if (fulfilled.size == count || local_only || remote_count < 0) {
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
return ReplayOperation.Status.CONTINUE;
|
||||
}
|
||||
|
||||
public override bool query_local_writebehind_operation(ReplayOperation.WritebehindOperation op,
|
||||
EmailIdentifier id, Imap.EmailFlags? flags) {
|
||||
// don't need to check if id is present here, all paths deal with this possibility
|
||||
// correctly
|
||||
|
||||
switch (op) {
|
||||
case ReplayOperation.WritebehindOperation.REMOVE:
|
||||
// remove email already picked up from local store ... for email reported via the
|
||||
// callback, too late
|
||||
if (accumulator != null) {
|
||||
Gee.HashSet<Geary.Email> wb_removed = new Gee.HashSet<Geary.Email>();
|
||||
foreach (Geary.Email email in accumulator) {
|
||||
if (email.id.equal_to(id))
|
||||
wb_removed.add(email);
|
||||
}
|
||||
|
||||
accumulator.remove_all(wb_removed);
|
||||
}
|
||||
|
||||
// remove from unfulfilled list, as there's nothing to fetch from the server
|
||||
foreach (Geary.Email.Field field in unfulfilled.get_keys())
|
||||
unfulfilled.remove(field, id);
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
// ignored
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_remote_async() throws Error {
|
||||
// normalize the email positions in the local store, so the positions being requested
|
||||
// from the server are available in the database
|
||||
int local_count;
|
||||
yield engine.normalize_email_positions_async(low, count, out local_count, cancellable);
|
||||
|
||||
// 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 in full
|
||||
//
|
||||
// TODO: This is inefficient because we can't assume the returned emails are sorted or
|
||||
// contiguous (it's possible local email is present but doesn't fulfill all the fields).
|
||||
// A better search method is probably possible, but this will do for now
|
||||
int[] needed_by_position = new int[0];
|
||||
for (int position = low; position <= (low + (count - 1)); position++) {
|
||||
bool found = false;
|
||||
for (int ctr = 0; ctr < local_list_size; ctr++) {
|
||||
if (local_list[ctr].position == position) {
|
||||
found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
needed_by_position += position;
|
||||
}
|
||||
|
||||
Logging.debug(Logging.Flag.REPLAY, "ListEmail.replay_remote %s: %d by position, %d unfulfilled",
|
||||
engine.to_string(), needed_by_position.length, unfulfilled.get_values().size);
|
||||
|
||||
Nonblocking.Batch batch = new Nonblocking.Batch();
|
||||
|
||||
// fetch in full whatever is needed wholesale
|
||||
if (needed_by_position.length > 0)
|
||||
batch.add(new RemoteListPositional(this, needed_by_position));
|
||||
|
||||
// fetch the partial emails that do not fulfill all required fields, getting only those
|
||||
// fields that are missing for each email
|
||||
if (unfulfilled.size > 0) {
|
||||
foreach (Geary.Email.Field remaining_fields in unfulfilled.get_keys()) {
|
||||
Gee.Collection<EmailIdentifier> email_ids = unfulfilled.get(remaining_fields);
|
||||
if (email_ids.size > 0)
|
||||
batch.add(new RemoteListPartial(this, remaining_fields, email_ids));
|
||||
}
|
||||
}
|
||||
|
||||
Logging.debug(Logging.Flag.REPLAY, "ListEmail.replay_remote %s: Scheduling %d partial list operations",
|
||||
engine.to_string(), batch.size);
|
||||
|
||||
yield batch.execute_all_async(cancellable);
|
||||
|
||||
// Notify of first error encountered before throwing
|
||||
if (cb != null && batch.first_exception != null)
|
||||
cb(null, batch.first_exception);
|
||||
|
||||
batch.throw_first_exception();
|
||||
|
||||
// signal finished
|
||||
if (cb != null)
|
||||
cb(null, null);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
public override async void backout_local_async() throws Error {
|
||||
// R/O, no backout
|
||||
}
|
||||
|
||||
private async void remote_list_positional(int[] needed_by_position) throws Error {
|
||||
// pull in reverse order because callers to this method tend to order messages from oldest
|
||||
// to newest, but for user satisfaction, should be fetched from newest to oldest
|
||||
int remaining = needed_by_position.length;
|
||||
while (remaining > 0) {
|
||||
// if a callback is specified, pull the messages down in chunks, so they can be reported
|
||||
// incrementally
|
||||
int[] list;
|
||||
if (cb != null) {
|
||||
int list_count = int.min(GenericFolder.REMOTE_FETCH_CHUNK_COUNT, remaining);
|
||||
list = needed_by_position[remaining - list_count:remaining];
|
||||
assert(list.length == list_count);
|
||||
} else {
|
||||
list = needed_by_position;
|
||||
}
|
||||
|
||||
Imap.SequenceNumber[] seq_list = Imap.SequenceNumber.to_list(list);
|
||||
|
||||
// pull from server
|
||||
Gee.List<Geary.Email>? remote_list = yield engine.remote_folder.list_email_async(
|
||||
new Imap.MessageSet.sparse(seq_list), required_fields, cancellable);
|
||||
if (remote_list == null || remote_list.size == 0)
|
||||
break;
|
||||
|
||||
// if any were fetched, store locally ... must be stored before they can be reported
|
||||
// via the callback because if, in the context of the callback, these messages are
|
||||
// requested, they won't be found in the database, causing another remote fetch to
|
||||
// occur
|
||||
remote_list = yield merge_emails(remote_list, cancellable);
|
||||
|
||||
if (accumulator != null && remote_list != null && remote_list.size > 0)
|
||||
accumulator.add_all(remote_list);
|
||||
|
||||
if (cb != null && remote_list.size > 0)
|
||||
cb(remote_list, null);
|
||||
|
||||
remaining -= list.length;
|
||||
}
|
||||
}
|
||||
|
||||
private async void remote_list_partials(Gee.Collection<Geary.EmailIdentifier> ids,
|
||||
Geary.Email.Field remaining_fields) throws Error {
|
||||
Gee.List<Geary.Email>? remote_list = yield engine.remote_folder.list_email_async(
|
||||
new Imap.MessageSet.email_id_collection(ids), remaining_fields, cancellable);
|
||||
if (remote_list == null || remote_list.size == 0)
|
||||
return;
|
||||
|
||||
remote_list = yield merge_emails(remote_list, cancellable);
|
||||
|
||||
if (accumulator != null && remote_list != null && remote_list.size > 0)
|
||||
accumulator.add_all(remote_list);
|
||||
|
||||
if (cb != null && remote_list.size > 0)
|
||||
cb(remote_list, null);
|
||||
}
|
||||
|
||||
private async Gee.List<Geary.Email> merge_emails(Gee.List<Geary.Email> list,
|
||||
Cancellable? cancellable) throws Error {
|
||||
CreateLocalEmailOperation create_op = new CreateLocalEmailOperation(engine.local_folder,
|
||||
list, required_fields);
|
||||
|
||||
Nonblocking.Batch batch = new Nonblocking.Batch();
|
||||
batch.add(create_op);
|
||||
|
||||
yield batch.execute_all_async(cancellable);
|
||||
|
||||
batch.throw_first_exception();
|
||||
|
||||
assert(create_op.created != null);
|
||||
assert(create_op.merged != null);
|
||||
|
||||
// report locally added (non-duplicate, not unknown) emails & collect emails post-merge
|
||||
Gee.HashSet<Geary.EmailIdentifier> created_ids = new Gee.HashSet<Geary.EmailIdentifier>();
|
||||
foreach (Geary.Email email in create_op.created.keys) {
|
||||
// true means created
|
||||
if (create_op.created.get(email))
|
||||
created_ids.add(email.id);
|
||||
}
|
||||
|
||||
if (created_ids.size > 0)
|
||||
engine.notify_email_locally_appended(created_ids);
|
||||
|
||||
Gee.ArrayList<Geary.Email> merged_list = new Gee.ArrayList<Geary.Email>();
|
||||
merged_list.add_all(create_op.merged.values);
|
||||
|
||||
if (cb != null && merged_list.size > 0)
|
||||
cb(merged_list, null);
|
||||
|
||||
return merged_list;
|
||||
}
|
||||
|
||||
public override string describe_state() {
|
||||
return "low=%d count=%d required_fields=%Xh local_only=%s remote_only=%s".printf(low, count,
|
||||
required_fields, local_only.to_string(), remote_only.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
private Nonblocking.Mutex cmd_mutex = new Nonblocking.Mutex();
|
||||
private Gee.HashMap<SequenceNumber, FetchedData> fetch_accumulator = new Gee.HashMap<
|
||||
SequenceNumber, FetchedData>();
|
||||
private Gee.TreeSet<Geary.EmailIdentifier> search_accumulator = new Gee.TreeSet<Geary.EmailIdentifier>();
|
||||
private Gee.TreeSet<Imap.UID> search_accumulator = new Gee.TreeSet<Imap.UID>();
|
||||
|
||||
/**
|
||||
* A (potentially unsolicited) response from the server.
|
||||
|
|
@ -197,7 +197,7 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
// All SEARCH from this class are UID SEARCH, so can reliably convert and add to
|
||||
// accumulator
|
||||
foreach (int uid in seq_or_uid)
|
||||
search_accumulator.add(new Imap.EmailIdentifier(new UID(uid), path));
|
||||
search_accumulator.add(new UID(uid));
|
||||
}
|
||||
|
||||
private void on_status_response(StatusResponse status_response) {
|
||||
|
|
@ -263,7 +263,7 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
// All commands must executed inside the cmd_mutex; returns FETCH or STORE results
|
||||
private async void exec_commands_async(Gee.Collection<Command> cmds,
|
||||
out Gee.HashMap<SequenceNumber, FetchedData>? fetched,
|
||||
out Gee.TreeSet<Geary.EmailIdentifier>? search_results, Cancellable? cancellable) throws Error {
|
||||
out Gee.TreeSet<Imap.UID>? search_results, Cancellable? cancellable) throws Error {
|
||||
int token = yield cmd_mutex.claim_async(cancellable);
|
||||
|
||||
// execute commands with mutex locked
|
||||
|
|
@ -285,7 +285,7 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
|
||||
if (search_accumulator.size > 0) {
|
||||
search_results = search_accumulator;
|
||||
search_accumulator = new Gee.TreeSet<Geary.EmailIdentifier>();
|
||||
search_accumulator = new Gee.TreeSet<Imap.UID>();
|
||||
} else {
|
||||
search_results = null;
|
||||
}
|
||||
|
|
@ -449,6 +449,29 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
return (email_list.size > 0) ? email_list : null;
|
||||
}
|
||||
|
||||
public async Gee.Map<UID, SequenceNumber>? uid_to_position_async(MessageSet msg_set,
|
||||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
// MessageSet better be UID addressing
|
||||
assert(msg_set.is_uid);
|
||||
|
||||
Gee.List<Command> cmds = new Gee.ArrayList<Command>();
|
||||
cmds.add(new FetchCommand.data_type(msg_set, FetchDataType.UID));
|
||||
|
||||
Gee.HashMap<SequenceNumber, FetchedData>? fetched;
|
||||
yield exec_commands_async(cmds, out fetched, null, cancellable);
|
||||
|
||||
if (fetched == null || fetched.size == 0)
|
||||
return null;
|
||||
|
||||
Gee.Map<UID, SequenceNumber> map = new Gee.HashMap<UID, SequenceNumber>();
|
||||
foreach (SequenceNumber seq_num in fetched.keys)
|
||||
map.set((UID) fetched.get(seq_num).data_map.get(FetchDataType.UID), seq_num);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public async void remove_email_async(MessageSet msg_set, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
|
|
@ -526,15 +549,15 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
yield exec_commands_async(cmds, null, null, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.SortedSet<Geary.EmailIdentifier>? search_async(SearchCriteria criteria,
|
||||
Cancellable? cancellable) throws Error {
|
||||
public async Gee.SortedSet<Imap.UID>? search_async(SearchCriteria criteria, Cancellable? cancellable)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
// always perform a UID SEARCH
|
||||
Gee.Collection<Command> cmds = new Gee.ArrayList<Command>();
|
||||
cmds.add(new SearchCommand(criteria, true));
|
||||
|
||||
Gee.TreeSet<Geary.EmailIdentifier>? search_results;
|
||||
Gee.TreeSet<Imap.UID>? search_results;
|
||||
yield exec_commands_async(cmds, null, out search_results, cancellable);
|
||||
|
||||
return (search_results != null && search_results.size > 0) ? search_results : null;
|
||||
|
|
@ -621,7 +644,6 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
FetchBodyDataIdentifier? preview_identifier, FetchBodyDataIdentifier? preview_charset_identifier)
|
||||
throws Error {
|
||||
Geary.Email email = new Geary.Email(new Imap.EmailIdentifier(uid, path));
|
||||
email.position = fetched_data.seq_num.value;
|
||||
|
||||
// accumulate these to submit Imap.EmailProperties all at once
|
||||
InternalDate? internaldate = null;
|
||||
|
|
|
|||
|
|
@ -85,36 +85,6 @@ public class Geary.Imap.MessageSet : BaseObject {
|
|||
value = "%s:*".printf(low_seq_num.serialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* A positive count yields a range going from initial up the stack (toward the most recently
|
||||
* added message). A negative count yields a range going from initial down the stack (toward
|
||||
* the earliest added message). A count of zero yields a message range for one UID, initial.
|
||||
*
|
||||
* Underflows and overflows are accounted for by clamping the arithmetic result to the possible
|
||||
* range of UID's.
|
||||
*/
|
||||
public MessageSet.uid_range_by_count(UID initial, int count) {
|
||||
assert(initial.value > 0);
|
||||
|
||||
if (count == 0) {
|
||||
value = initial.serialize();
|
||||
} else {
|
||||
int64 low, high;
|
||||
if (count < 0) {
|
||||
high = initial.value;
|
||||
low = (high + count).clamp(1, uint32.MAX);
|
||||
} else {
|
||||
// count > 0
|
||||
low = initial.value;
|
||||
high = (low + count).clamp(1, uint32.MAX);
|
||||
}
|
||||
|
||||
value = "%s:%s".printf(low.to_string(), high.to_string());
|
||||
}
|
||||
|
||||
is_uid = true;
|
||||
}
|
||||
|
||||
public MessageSet.uid_range_to_highest(UID low) {
|
||||
assert(low.value > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,14 @@ public class Geary.Nonblocking.CountingSemaphore : Geary.Nonblocking.AbstractSem
|
|||
*/
|
||||
public int count { get; private set; default = 0; }
|
||||
|
||||
/**
|
||||
* Indicates that the {@link count} has changed due to either {@link acquire} or
|
||||
* {@link notify} being invoked.
|
||||
*/
|
||||
public signal void count_changed(int count);
|
||||
|
||||
public CountingSemaphore(Cancellable? cancellable) {
|
||||
base (true, false, cancellable);
|
||||
base (true, true, cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -27,7 +33,14 @@ public class Geary.Nonblocking.CountingSemaphore : Geary.Nonblocking.AbstractSem
|
|||
* @return Number of acquired tasks, including the one that made this call.
|
||||
*/
|
||||
public int acquire() {
|
||||
return ++count;
|
||||
count++;
|
||||
|
||||
// store on stack in case of reentrancy from signal handler; also note that Vala doesn't
|
||||
// deal well with properties, pre/post-inc, and assignment on same line
|
||||
int new_count = count;
|
||||
count_changed(new_count);
|
||||
|
||||
return new_count;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -43,7 +56,14 @@ public class Geary.Nonblocking.CountingSemaphore : Geary.Nonblocking.AbstractSem
|
|||
if (count == 0)
|
||||
throw new NonblockingError.INVALID("notify() on a zeroed CountingSemaphore");
|
||||
|
||||
if (count-- == 0)
|
||||
count--;
|
||||
|
||||
// store on stack in case of reentrancy from signal handler; also note that Vala doesn't
|
||||
// deal well with properties, pre/post-inc, and assignment on same line
|
||||
int new_count = count;
|
||||
count_changed(new_count);
|
||||
|
||||
if (new_count == 0)
|
||||
base.notify();
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +74,7 @@ public class Geary.Nonblocking.CountingSemaphore : Geary.Nonblocking.AbstractSem
|
|||
*/
|
||||
public async override void wait_async(Cancellable? cancellable = null) throws Error {
|
||||
if (count != 0)
|
||||
yield wait_async(cancellable);
|
||||
yield base.wait_async(cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue