Moving away from positional addressing. Greater emphasis on EmailIdentifiers for
addressing. Positional addressing is a nightmare for a lot of reasons, especially keeping positions up-to-date as the Folder mutates. Now, positions are returned with the email but for advisory reasons only, and do not keep up-to-date. It is expected that a client will use positional addressing to "bootstrap" the application's data store, then use EmailIdentifier addressing to traverse the Folder's contents.
This commit is contained in:
parent
0c4d6ed8e9
commit
3f6d8eac5a
22 changed files with 602 additions and 372 deletions
|
|
@ -200,7 +200,7 @@ public class MainWindow : Gtk.Window {
|
|||
current_conversations.lazy_load(-1, -1, Geary.Folder.ListFlags.FAST, cancellable_folder);
|
||||
}
|
||||
|
||||
public void on_scan_started(int low, int count) {
|
||||
public void on_scan_started() {
|
||||
debug("on scan started");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
public int compare_email(Geary.Email aenvelope, Geary.Email benvelope) {
|
||||
int diff = aenvelope.date.value.compare(benvelope.date.value);
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
|
||||
// stabilize sort by using the mail's position, which is always unique in a folder
|
||||
return aenvelope.location.position - benvelope.location.position;
|
||||
// stabilize sort by using the mail's ordering, which is always unique in a folder
|
||||
return (diff != 0) ? diff : aenvelope.id.compare(benvelope.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,13 @@ public class Geary.Conversations : Object {
|
|||
private bool monitor_new = false;
|
||||
private Cancellable? cancellable_monitor = null;
|
||||
|
||||
public virtual signal void scan_started(int low, int count) {
|
||||
/**
|
||||
* "scan-started" is fired whenever beginning to load messages into the Conversations object.
|
||||
* If id is not null, then the scan is starting at an identifier and progressing according to
|
||||
* count (see Geary.Folder.list_email_by_id_async()). Otherwise, the scan is using positional
|
||||
* addressing and low is a valid one-based position (see Geary.Folder.list_email_async()).
|
||||
*/
|
||||
public virtual signal void scan_started(Geary.EmailIdentifier? id, int low, int count) {
|
||||
}
|
||||
|
||||
public virtual signal void scan_error(Error err) {
|
||||
|
|
@ -224,8 +230,8 @@ public class Geary.Conversations : Object {
|
|||
folder.messages_appended.disconnect(on_folder_messages_appended);
|
||||
}
|
||||
|
||||
protected virtual void notify_scan_started(int low, int count) {
|
||||
scan_started(low, count);
|
||||
protected virtual void notify_scan_started(Geary.EmailIdentifier? id, int low, int count) {
|
||||
scan_started(id, low, count);
|
||||
}
|
||||
|
||||
protected virtual void notify_scan_error(Error err) {
|
||||
|
|
@ -254,11 +260,28 @@ public class Geary.Conversations : Object {
|
|||
return conversations.read_only_view;
|
||||
}
|
||||
|
||||
public bool monitor_new_messages(Cancellable? cancellable = null) {
|
||||
if (monitor_new)
|
||||
return false;
|
||||
|
||||
monitor_new = true;
|
||||
cancellable_monitor = cancellable;
|
||||
folder.messages_appended.connect(on_folder_messages_appended);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public async void load_async(int low, int count, Geary.Folder.ListFlags flags,
|
||||
Cancellable? cancellable) throws Error {
|
||||
notify_scan_started(low, count);
|
||||
notify_scan_started(null, low, count);
|
||||
try {
|
||||
Gee.List<Email>? list = yield folder.list_email_async(low, count, required_fields, flags);
|
||||
Gee.List<Email>? list = yield folder.list_email_async(low, count, required_fields, flags,
|
||||
cancellable);
|
||||
on_email_listed(list, null);
|
||||
if (list != null)
|
||||
on_email_listed(null, null);
|
||||
|
|
@ -267,20 +290,45 @@ public class Geary.Conversations : Object {
|
|||
}
|
||||
}
|
||||
|
||||
public void lazy_load(int low, int count, Geary.Folder.ListFlags flags, Cancellable? cancellable)
|
||||
throws Error {
|
||||
notify_scan_started(low, count);
|
||||
/**
|
||||
* See Geary.Folder.lazy_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.
|
||||
*/
|
||||
public void lazy_load(int low, int count, Geary.Folder.ListFlags flags, Cancellable? cancellable) {
|
||||
notify_scan_started(null, low, count);
|
||||
folder.lazy_list_email(low, count, required_fields, flags, on_email_listed, cancellable);
|
||||
}
|
||||
|
||||
public bool monitor_new_messages(Cancellable? cancellable = null) {
|
||||
if (monitor_new)
|
||||
return false;
|
||||
|
||||
monitor_new = true;
|
||||
cancellable_monitor = cancellable;
|
||||
folder.messages_appended.connect(on_folder_messages_appended);
|
||||
return true;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public async void load_by_id_async(Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Folder.ListFlags flags, Cancellable? cancellable) throws Error {
|
||||
notify_scan_started(initial_id, -1, count);
|
||||
try {
|
||||
Gee.List<Email>? list = yield folder.list_email_by_id_async(initial_id, count,
|
||||
required_fields, flags, cancellable);
|
||||
on_email_listed(list, null);
|
||||
if (list != null)
|
||||
on_email_listed(null, null);
|
||||
} catch (Error err) {
|
||||
on_email_listed(null, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See Geary.Folder.lazy_list_email_by_id() for details of how these parameters operate. Instead
|
||||
* of returning emails, this method will load the Conversations object with them sorted into
|
||||
* Conversation objects.
|
||||
*/
|
||||
public void lazy_load_by_id(Geary.EmailIdentifier initial_id, int count, Geary.Folder.ListFlags flags,
|
||||
Cancellable? cancellable) {
|
||||
notify_scan_started(initial_id, -1, count);
|
||||
folder.lazy_list_email_by_id(initial_id, count, required_fields, flags, on_email_listed,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
private void on_email_listed(Gee.List<Geary.Email>? emails, Error? err) {
|
||||
|
|
@ -518,29 +566,27 @@ public class Geary.Conversations : Object {
|
|||
}
|
||||
|
||||
private void on_folder_messages_appended() {
|
||||
// Find highest position.
|
||||
// Find highest identifier by ordering
|
||||
// TODO: optimize.
|
||||
int high = -1;
|
||||
foreach (Conversation c in conversations)
|
||||
foreach (Email e in c.get_pool())
|
||||
if (e.location.position > high)
|
||||
high = e.location.position;
|
||||
Geary.EmailIdentifier? highest = null;
|
||||
foreach (Conversation c in conversations) {
|
||||
foreach (Email e in c.get_pool()) {
|
||||
if (highest == null || (e.id.compare(highest) > 0))
|
||||
highest = e.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (high < 0) {
|
||||
if (highest == null) {
|
||||
debug("Unable to find highest message position in %s", folder.to_string());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Message(s) appended to %s, fetching email at %d and above", folder.to_string(),
|
||||
high + 1);
|
||||
debug("Message(s) appended to %s, fetching email at %s and above", folder.to_string(),
|
||||
highest.to_string());
|
||||
|
||||
// Want to get the one *after* the highest position in the list
|
||||
try {
|
||||
lazy_load(high + 1, -1, Folder.ListFlags.NONE, cancellable_monitor);
|
||||
} catch (Error e) {
|
||||
warning("Error getting new mail: %s", e.message);
|
||||
}
|
||||
lazy_load_by_id(highest.next(), int.MAX, Folder.ListFlags.NONE, cancellable_monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,41 @@
|
|||
* message is located in; an EmailIdentifier cannot be used in another Folder to determine if the
|
||||
* message is duplicated there. (Either EmailIdentifier will be expanded to allow for this or
|
||||
* another system will be offered.)
|
||||
*
|
||||
* EmailIdentifier is Comparable because it can be used to compare against other EmailIdentifiers
|
||||
* (in the same Folder) for sort order that corresponds to their position in the Folder. It does
|
||||
* this through an ordering field that provides an integer that can be compared to other ordering
|
||||
* fields in the same Folder that match the email's position within it. The ordering field may
|
||||
* or may not be the actual unique identifier; in IMAP, for example, it is, while in other systems
|
||||
* it may not be.
|
||||
*/
|
||||
|
||||
public abstract class Geary.EmailIdentifier : Object, Geary.Equalable {
|
||||
public abstract class Geary.EmailIdentifier : Object, Geary.Equalable, Geary.Comparable {
|
||||
public abstract int64 ordering { get; protected set; }
|
||||
|
||||
public abstract EmailIdentifier next();
|
||||
|
||||
public abstract EmailIdentifier previous();
|
||||
|
||||
public abstract bool equals(Geary.Equalable other);
|
||||
|
||||
public virtual int compare(Geary.Comparable o) {
|
||||
Geary.EmailIdentifier? other = o as Geary.EmailIdentifier;
|
||||
if (other == null)
|
||||
return -1;
|
||||
|
||||
if (this == other)
|
||||
return 0;
|
||||
|
||||
int64 diff = ordering - other.ordering;
|
||||
if (diff < 0)
|
||||
return -1;
|
||||
else if (diff > 0)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
public abstract string to_string();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
/* Copyright 2011 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An EmailLocation represents an Email's position (cardinal ordering) in a Folder. Unlike other
|
||||
* elements in an Email, it may change over time, depending on events within the Folder itself.
|
||||
* It reports these changes via signals.
|
||||
*
|
||||
* An EmailLocation's position is a cardinal (1 to n) of an Email's ordering within the Folder.
|
||||
* Its ordering is an opaque number that was used to determine this ordering -- it may be exactly
|
||||
* equal to its position, or it may not. This value is particular to the message provider and
|
||||
* should not be depended on.
|
||||
*
|
||||
* When the Folder closes, the EmailLocation will fire its "invalidated" signal and go into an
|
||||
* invalidated state. Its position will be -1. The EmailLocation will never go back to a valid
|
||||
* state; the owner should discard the EmailLocation (and, by extension, its Email) after the Folder
|
||||
* has closed and fetch fresh ones once its re-opened.
|
||||
*
|
||||
* Note the differences between an EmailLocation, which gives a cardinal way of addressing mail in
|
||||
* a folder, an an EmailIdentifier, which is unique and immutable, but is only addressable as a
|
||||
* singleton, and not by a range.
|
||||
*/
|
||||
public class Geary.EmailLocation : Object {
|
||||
/**
|
||||
* Returns -1 if invalidated.
|
||||
*/
|
||||
public int position { get; private set; }
|
||||
public int64 ordering { get; private set; }
|
||||
|
||||
private Geary.Folder folder;
|
||||
private int local_adjustment;
|
||||
|
||||
public signal void position_altered(int old_position, int new_position);
|
||||
|
||||
public signal void position_deleted(int position);
|
||||
|
||||
/**
|
||||
* "invalidated" is fired when the EmailLocation object is no longer valid due to the Folder
|
||||
* closing. At this point, its position will be -1.
|
||||
*/
|
||||
public signal void invalidated();
|
||||
|
||||
public EmailLocation(Geary.Folder folder, int position, int64 ordering) {
|
||||
init(folder, position, ordering, -1);
|
||||
}
|
||||
|
||||
public EmailLocation.local(Geary.Folder folder, int position, int64 ordering, int local_adjustment) {
|
||||
init(folder, position, ordering, local_adjustment);
|
||||
}
|
||||
|
||||
~EmailLocation() {
|
||||
invalidate(false);
|
||||
}
|
||||
|
||||
private void init(Geary.Folder folder, int position, int64 ordering, int local_adjustment) {
|
||||
assert(position >= 1);
|
||||
|
||||
this.folder = folder;
|
||||
this.position = position;
|
||||
this.ordering = ordering;
|
||||
this.local_adjustment = local_adjustment;
|
||||
|
||||
folder.message_removed.connect(on_message_removed);
|
||||
folder.opened.connect(on_folder_opened);
|
||||
folder.closed.connect(on_folder_closed);
|
||||
}
|
||||
|
||||
private void invalidate(bool signalled) {
|
||||
if (position < 0)
|
||||
return;
|
||||
|
||||
position = -1;
|
||||
|
||||
folder.message_removed.disconnect(on_message_removed);
|
||||
folder.opened.disconnect(on_folder_opened);
|
||||
folder.closed.disconnect(on_folder_closed);
|
||||
|
||||
if (signalled)
|
||||
invalidated();
|
||||
}
|
||||
|
||||
private void on_message_removed(int position, int total) {
|
||||
// bail out if invalidated
|
||||
if (position < 1)
|
||||
return;
|
||||
|
||||
// if the removed position is greater than this one's, no change in this position
|
||||
if (this.position < position)
|
||||
return;
|
||||
|
||||
// if the same, can't adjust (adjust it to what?), but notify that this EmailLocation has
|
||||
// been removed
|
||||
if (this.position == position) {
|
||||
position_deleted(position);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// adjust this position downward
|
||||
int old_position = this.position;
|
||||
this.position--;
|
||||
assert(this.position >= 1);
|
||||
|
||||
position_altered(old_position, this.position);
|
||||
}
|
||||
|
||||
private void on_folder_closed() {
|
||||
invalidate(true);
|
||||
}
|
||||
|
||||
private void on_folder_opened(Geary.Folder.OpenState state, int count) {
|
||||
// If no local_adjustment, nothing needs to be done; if the local_adjustment is greater or
|
||||
// equal to the remote folder's count, that indicates messages have been removed, in which
|
||||
// case let the adjustments occur via on_message_removed().
|
||||
if (local_adjustment < 0 || count <= local_adjustment) {
|
||||
local_adjustment = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int old_position = position;
|
||||
position = count - local_adjustment;
|
||||
assert(position >= 1);
|
||||
|
||||
// mark as completed, to prevent this from happening again
|
||||
local_adjustment = -1;
|
||||
|
||||
if (position != old_position)
|
||||
position_altered(old_position, position);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,25 @@ public class Geary.Email : Object {
|
|||
}
|
||||
}
|
||||
|
||||
public Geary.EmailLocation location { get; private set; }
|
||||
/**
|
||||
* 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 always returned, no matter what Fields are used to retrieve the Email.
|
||||
*/
|
||||
public int position { get; private set; }
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This field is always returned, no matter what Fields are used to retrieve the Email.
|
||||
*/
|
||||
public Geary.EmailIdentifier id { get; private set; }
|
||||
|
||||
// DATE
|
||||
|
|
@ -94,13 +112,17 @@ public class Geary.Email : Object {
|
|||
|
||||
private Geary.RFC822.Message? message = null;
|
||||
|
||||
public Email(Geary.EmailLocation location, Geary.EmailIdentifier id) {
|
||||
this.location = location;
|
||||
public Email(int position, Geary.EmailIdentifier id) {
|
||||
assert(position >= 1);
|
||||
|
||||
this.position = position;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void update_location(Geary.EmailLocation location) {
|
||||
this.location = location;
|
||||
internal void update_position(int position) {
|
||||
assert(position >= 1);
|
||||
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public void set_send_date(Geary.RFC822.Date date) {
|
||||
|
|
@ -185,10 +207,10 @@ public class Geary.Email : Object {
|
|||
public string to_string() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append_printf("[#%d/%s] ", location.position, id.to_string());
|
||||
builder.append_printf("[#%d/%s] ", position, id.to_string());
|
||||
|
||||
if (date != null)
|
||||
builder.append_printf("[%s]", date.to_string());
|
||||
builder.append_printf("%s/", date.to_string());
|
||||
|
||||
return builder.str;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ public interface Geary.Folder : Object {
|
|||
FOLDER_CLOSED
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
BEFORE,
|
||||
AFTER
|
||||
public enum CountChangeReason {
|
||||
ADDED,
|
||||
REMOVED
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
|
@ -67,20 +67,21 @@ public interface Geary.Folder : Object {
|
|||
* "message-removed" is fired when a message has been removed (deleted or moved) from the
|
||||
* folder (and therefore old message position numbers may no longer be valid, i.e. those after
|
||||
* the removed message).
|
||||
*
|
||||
* NOTE: It's possible for the remote server to report a message has been removed that is not
|
||||
* known locally (and therefore the caller could not have record of). If this happens, this
|
||||
* signal will *not* fire, although "email-count-changed" will.
|
||||
*/
|
||||
public signal void message_removed(int position, int total);
|
||||
public signal void message_removed(Geary.EmailIdentifier id);
|
||||
|
||||
/**
|
||||
* "positions-reordered" is fired when message positions on emails in the folder may no longer
|
||||
* be valid, which may happen even if a message has not been removed. In other words, if a
|
||||
* message is removed and it causes positions to change, "message-remove" will be fired followed
|
||||
* by this signal.
|
||||
* "email-count-changed" is fired when the total count of email in a folder has changed in any way.
|
||||
*
|
||||
* Although reordering may be rare (positions shifting is a better description), it is possible
|
||||
* for messages in a folder to change positions completely. This signal covers both
|
||||
* circumstances.
|
||||
* Note that this signal will be fired alongside "messages-appended" or "message-removed".
|
||||
* That is, do not use both signals to process email count changes; one will suffice.
|
||||
* This signal will fire after those (although see the note at "message-removed").
|
||||
*/
|
||||
public signal void positions_reordered();
|
||||
public signal void email_count_changed(int new_count, CountChangeReason reason);
|
||||
|
||||
/**
|
||||
* This helper method should be called by implementors of Folder rather than firing the signal
|
||||
|
|
@ -108,14 +109,14 @@ public interface Geary.Folder : Object {
|
|||
* directly. This allows subclasses and superclasses the opportunity to inspect the email
|
||||
* and update state before and/or after the signal has been fired.
|
||||
*/
|
||||
protected abstract void notify_positions_reordered();
|
||||
protected abstract void notify_message_removed(Geary.EmailIdentifier id);
|
||||
|
||||
/**
|
||||
* This helper method should be called by implementors of Folder rather than firing the signal
|
||||
* directly. This allows subclasses and superclasses the opportunity to inspect the email
|
||||
* and update state before and/or after the signal has been fired.
|
||||
*/
|
||||
protected abstract void notify_message_removed(int position, int total);
|
||||
protected abstract void notify_email_count_changed(int new_count, CountChangeReason reason);
|
||||
|
||||
public abstract Geary.FolderPath get_path();
|
||||
|
||||
|
|
@ -267,7 +268,7 @@ public interface Geary.Folder : Object {
|
|||
* messages are passed back to the caller in chunks as they're retrieved. When null is passed
|
||||
* as the first parameter, all the messages have been fetched. If an Error occurs during
|
||||
* processing, it's passed as the second parameter. There's no guarantee of the returned
|
||||
* message's order.
|
||||
* messages' order.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
|
|
@ -275,27 +276,73 @@ public interface Geary.Folder : Object {
|
|||
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().
|
||||
*
|
||||
* There is no guarantee that a message with the initial_id will be returned however.
|
||||
* (It is up to the implementation to deal with spans starting from a non-existant or
|
||||
* unavailable EmailIdentifier.) To fetch email exclusive of the initial_id, use
|
||||
* EmailIdentifier.next() or EmailIdentifier.previous().
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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,
|
||||
int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
/**
|
||||
* Similar in contract to lazy_list_email_async(), but uses Geary.EmailIdentifier rather than
|
||||
* positional addressing, much like list_email_by_id_async(). See that method for more
|
||||
* information on its contract and how the count parameter works.
|
||||
*
|
||||
* Like the other "lazy" methods, this method will call EmailCallback while the operation is
|
||||
* processing. This method does not block.
|
||||
*
|
||||
* There is (currently) no sparse version of lazy_list_email_by_id().
|
||||
*
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Returns a single email that fulfills the required_fields flag at the ordered position in
|
||||
* the folder. If position is invalid for the folder's contents, an EngineError.NOT_FOUND
|
||||
* the folder. If the email_id is invalid for the folder's contents, an EngineError.NOT_FOUND
|
||||
* error is thrown. If the requested fields are not available, EngineError.INCOMPLETE_MESSAGE
|
||||
* is thrown.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*
|
||||
* position is one-based.
|
||||
*/
|
||||
public abstract async Geary.Email fetch_email_async(Geary.EmailIdentifier email_id,
|
||||
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Removes the email at the supplied position from the folder. If the email position is
|
||||
* invalid for any reason, EngineError.NOT_FOUND is thrown.
|
||||
* Removes the email at the supplied position from the folder. If the email_id is invalid for
|
||||
* any reason, EngineError.NOT_FOUND is thrown.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract async void remove_email_async(int position, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
public abstract async void remove_email_async(Geary.EmailIdentifier email_id,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* check_span_specifiers() verifies that the span specifiers match the requirements set by
|
||||
|
|
|
|||
|
|
@ -5,10 +5,21 @@
|
|||
*/
|
||||
|
||||
private class Geary.Imap.EmailIdentifier : Geary.EmailIdentifier {
|
||||
public override int64 ordering { get; protected set; }
|
||||
|
||||
public Imap.UID uid { get; private set; }
|
||||
|
||||
public override Geary.EmailIdentifier next() {
|
||||
return new Geary.Imap.EmailIdentifier(new Imap.UID((uid.value + 1).clamp(1, uint32.MAX)));
|
||||
}
|
||||
|
||||
public override Geary.EmailIdentifier previous() {
|
||||
return new Geary.Imap.EmailIdentifier(new Imap.UID((uid.value - 1).clamp(1, uint32.MAX)));
|
||||
}
|
||||
|
||||
public EmailIdentifier(Imap.UID uid) {
|
||||
this.uid = uid;
|
||||
ordering = uid.value;
|
||||
}
|
||||
|
||||
public override bool equals(Equalable o) {
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
/* Copyright 2011 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IMAP implementation of Geary.EmailLocation uses the email's UID to order the messages.
|
||||
*/
|
||||
|
||||
private class Geary.Imap.EmailLocation : Geary.EmailLocation {
|
||||
public EmailLocation(Geary.Folder folder, int position, Geary.Imap.UID uid) {
|
||||
base (folder, position, uid.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/* Copyright 2011 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
private interface Geary.Imap.FolderExtensions : Geary.Folder {
|
||||
/**
|
||||
* Much like Geary.Folder.list_email_async(), but this list operation allows for a range of
|
||||
* emails to be specified by their UID rather than position (message number). If low is null
|
||||
* that indicates to search from the lowest UID (1) to high. Likewise, if high is null it
|
||||
* indicates to search from low to the highest UID. Setting both to null will return all
|
||||
* emails in the folder.
|
||||
*
|
||||
* Unlike list_email_async(), this call guarantees that the messages will be returned in UID
|
||||
* order, from lowest to highest.
|
||||
*
|
||||
* The folder must be open before making this call.
|
||||
*/
|
||||
public abstract async Gee.List<Geary.Email>? list_email_uid_async(Geary.Imap.UID? low,
|
||||
Geary.Imap.UID? high, Geary.Email.Field fields, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
}
|
||||
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Geary.Imap.FolderExtensions {
|
||||
private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder {
|
||||
public const bool CASE_SENSITIVE = true;
|
||||
|
||||
private ClientSessionManager session_mgr;
|
||||
|
|
@ -27,6 +27,10 @@ private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Gear
|
|||
: new Imap.FolderProperties(0, 0, 0, null, null, info.attrs);
|
||||
}
|
||||
|
||||
protected void notify_message_at_removed(int position, int total) {
|
||||
message_at_removed(position, total);
|
||||
}
|
||||
|
||||
public override Geary.FolderPath get_path() {
|
||||
return path;
|
||||
}
|
||||
|
|
@ -92,7 +96,7 @@ private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Gear
|
|||
private void on_expunged(MessageNumber expunged, int total) {
|
||||
assert(mailbox != null);
|
||||
|
||||
notify_message_removed(expunged.value, total);
|
||||
notify_message_at_removed(expunged.value, total);
|
||||
}
|
||||
|
||||
public override async int get_email_count_async(Cancellable? cancellable = null) throws Error {
|
||||
|
|
@ -128,14 +132,27 @@ private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Gear
|
|||
return yield mailbox.list_set_async(this, new MessageSet.sparse(by_position), fields, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_uid_async(Geary.Imap.UID? low,
|
||||
Geary.Imap.UID? high, Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier email_id,
|
||||
int count, Geary.Email.Field fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
||||
MessageSet msg_set = (high != null)
|
||||
? new MessageSet.uid_range((low != null) ? low : new Geary.Imap.UID(1), high)
|
||||
: new MessageSet.uid_range_to_highest(low);
|
||||
UID uid = ((Imap.EmailIdentifier) email_id).uid;
|
||||
|
||||
MessageSet msg_set;
|
||||
if (count > 0) {
|
||||
msg_set = (count == int.MAX)
|
||||
? new MessageSet.uid_range_to_highest(uid)
|
||||
: new MessageSet.uid_range_by_count(uid, count);
|
||||
} else if (count < 0) {
|
||||
msg_set = (count != int.MIN)
|
||||
? new MessageSet.uid_range(new UID(1), uid)
|
||||
: new MessageSet.uid_range_by_count(uid, count);
|
||||
} else {
|
||||
// count == 0
|
||||
msg_set = new MessageSet.uid(uid);
|
||||
}
|
||||
|
||||
return yield mailbox.list_set_async(this, msg_set, fields, cancellable);
|
||||
}
|
||||
|
|
@ -150,7 +167,7 @@ private class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Gear
|
|||
return yield mailbox.fetch_async(this, ((Imap.EmailIdentifier) id).uid, fields, cancellable);
|
||||
}
|
||||
|
||||
public override async void remove_email_async(int position, Cancellable? cancellable = null)
|
||||
public override async void remove_email_async(Geary.EmailIdentifier email_id, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
|
|
|||
|
|
@ -45,6 +45,37 @@ public class Geary.Imap.MessageSet {
|
|||
value = "%d:*".printf(low_msg_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
MessageSet.uid(initial);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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 = "%lld:%lld".printf(low, high);
|
||||
is_uid = true;
|
||||
}
|
||||
|
||||
public MessageSet.uid_range_to_highest(UID low) {
|
||||
assert(low.value > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -78,9 +78,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
|
|||
// see fields_to_fetch_data_types() for why this is guaranteed
|
||||
assert(uid != null);
|
||||
|
||||
Geary.Email email = new Geary.Email(
|
||||
new Geary.Imap.EmailLocation(folder, res.msg_num, uid),
|
||||
new Geary.Imap.EmailIdentifier(uid));
|
||||
Geary.Email email = new Geary.Email(res.msg_num, new Geary.Imap.EmailIdentifier(uid));
|
||||
fetch_results_to_email(res, fields, email);
|
||||
|
||||
msgs.add(email);
|
||||
|
|
@ -111,9 +109,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
|
|||
if (results.length != 1)
|
||||
throw new ImapError.SERVER_ERROR("Too many responses from server: %d", results.length);
|
||||
|
||||
Geary.Email email = new Geary.Email(
|
||||
new Geary.Imap.EmailLocation(folder, results[0].msg_num, uid),
|
||||
new Geary.Imap.EmailIdentifier(uid));
|
||||
Geary.Email email = new Geary.Email(results[0].msg_num, new Geary.Imap.EmailIdentifier(uid));
|
||||
fetch_results_to_email(results[0], fields, email);
|
||||
|
||||
return email;
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
|
|||
messages_appended(total);
|
||||
}
|
||||
|
||||
protected virtual void notify_message_removed(int position, int total) {
|
||||
message_removed(position, total);
|
||||
protected virtual void notify_message_removed(Geary.EmailIdentifier id) {
|
||||
message_removed(id);
|
||||
}
|
||||
|
||||
protected virtual void notify_positions_reordered() {
|
||||
positions_reordered();
|
||||
protected virtual void notify_email_count_changed(int new_count, Folder.CountChangeReason reason) {
|
||||
email_count_changed(new_count, reason);
|
||||
}
|
||||
|
||||
public abstract Geary.FolderPath get_path();
|
||||
|
|
@ -90,10 +90,36 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (list != null && list.size > 0)
|
||||
cb(list, null);
|
||||
|
||||
cb(null, null);
|
||||
} catch (Error err) {
|
||||
cb(null, err);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
|
||||
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async void remove_email_async(int position, Cancellable? cancellable = null)
|
||||
public abstract async void remove_email_async(Geary.EmailIdentifier email_id, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public virtual string to_string() {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
public EngineFolder owner;
|
||||
public int position;
|
||||
public int new_remote_count;
|
||||
public EmailIdentifier? id;
|
||||
|
||||
public ReplayRemoval(EngineFolder owner, int position, int new_remote_count) {
|
||||
base ("Removal");
|
||||
|
|
@ -34,10 +35,20 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
this.owner = owner;
|
||||
this.position = position;
|
||||
this.new_remote_count = new_remote_count;
|
||||
id = null;
|
||||
}
|
||||
|
||||
public ReplayRemoval.with_id(EngineFolder owner, EmailIdentifier id) {
|
||||
base ("Removal.with_id");
|
||||
|
||||
this.owner = owner;
|
||||
position = -1;
|
||||
new_remote_count = -1;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public override async void replay() {
|
||||
yield owner.do_replay_remove_message(position, new_remote_count);
|
||||
yield owner.do_replay_remove_message(position, new_remote_count, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,6 +139,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
// signals
|
||||
folder.messages_appended.connect(on_remote_messages_appended);
|
||||
folder.message_removed.connect(on_remote_message_removed);
|
||||
folder.message_at_removed.connect(on_remote_message_at_removed);
|
||||
|
||||
// state
|
||||
remote_count = yield folder.get_email_count_async(cancellable);
|
||||
|
|
@ -260,38 +272,52 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
}
|
||||
}
|
||||
|
||||
private void on_remote_message_removed(int position, int total) {
|
||||
debug("on_remote_message_removed: position=%d total=%d", position, total);
|
||||
private void on_remote_message_removed(Geary.EmailIdentifier id) {
|
||||
debug("on_remote_message_removed: %s", id.to_string());
|
||||
replay_queue.schedule(new ReplayRemoval.with_id(this, id));
|
||||
}
|
||||
|
||||
private void on_remote_message_at_removed(int position, int total) {
|
||||
debug("on_remote_message_at_removed: position=%d total=%d", position, total);
|
||||
replay_queue.schedule(new ReplayRemoval(this, position, total));
|
||||
}
|
||||
|
||||
// This MUST only be called from ReplayRemoval.
|
||||
private async void do_replay_remove_message(int remote_position, int new_remote_count) {
|
||||
try {
|
||||
// calculate the local position of the message in the local store
|
||||
int local_count = yield local_folder.get_email_count_async();
|
||||
int local_low = ((remote_count - local_count) + 1).clamp(1, remote_count);
|
||||
|
||||
if (remote_position < local_low) {
|
||||
debug("do_replay_remove_message: Not removing message at %d from local store, not present",
|
||||
remote_position);
|
||||
} else {
|
||||
// Adjust remote position to local position
|
||||
yield local_folder.remove_email_async((remote_position - local_low) + 1);
|
||||
private async void do_replay_remove_message(int remote_position, int new_remote_count,
|
||||
Geary.EmailIdentifier? id) {
|
||||
if (remote_position < 1)
|
||||
assert(id != null);
|
||||
else
|
||||
assert(new_remote_count >= 0);
|
||||
|
||||
if (id == null) {
|
||||
try {
|
||||
Gee.List<Geary.Email>? local = yield local_folder.list_email_async(remote_position, 1,
|
||||
Geary.Email.Field.NONE, Geary.Folder.ListFlags.NONE, null);
|
||||
if (local != null && local.size > 0)
|
||||
id = local[0].id;
|
||||
} catch (Error err) {
|
||||
debug("Unable to determine ID of removed message #%d from %s: %s", remote_position,
|
||||
to_string(), err.message);
|
||||
}
|
||||
|
||||
// save new remote count
|
||||
remote_count = new_remote_count;
|
||||
|
||||
notify_message_removed(remote_position, new_remote_count);
|
||||
|
||||
// only fire "positions-altered" if indeed positions have been altered
|
||||
if (remote_position != new_remote_count)
|
||||
notify_positions_reordered();
|
||||
} catch (Error err) {
|
||||
debug("Unable to remove message #%d from %s: %s", remote_position, to_string(),
|
||||
err.message);
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
try {
|
||||
// Reflect change in the local store and notify subscribers
|
||||
yield local_folder.remove_email_async(id, null);
|
||||
|
||||
notify_message_removed(id);
|
||||
} catch (Error err2) {
|
||||
debug("Unable to remove message #%d from %s: %s", remote_position, to_string(),
|
||||
err2.message);
|
||||
}
|
||||
}
|
||||
|
||||
// save new remote count and notify of change
|
||||
remote_count = new_remote_count;
|
||||
|
||||
notify_email_count_changed(remote_count, CountChangeReason.REMOVED);
|
||||
}
|
||||
|
||||
public override async int get_email_count_async(Cancellable? cancellable = null) throws Error {
|
||||
|
|
@ -322,6 +348,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
return accumulator;
|
||||
}
|
||||
|
||||
// TODO: Capture Error and report via EmailCallback.
|
||||
public override void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
|
||||
Geary.Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
|
||||
// schedule do_list_email_async(), using the callback to drive availability of email
|
||||
|
|
@ -392,19 +419,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
// fixup local email positions to match server's positions
|
||||
if (local_list_size > 0 && remote_count > 0 && local_count < remote_count) {
|
||||
int adjustment = remote_count - local_count;
|
||||
foreach (Geary.Email email in local_list) {
|
||||
email.update_location(new Geary.EmailLocation(this,
|
||||
email.location.position + adjustment, email.location.ordering));
|
||||
}
|
||||
} else if (local_list_size > 0 && local_only) {
|
||||
// if remote_count is -1, the remote folder hasn't been opened so the true count hasn't
|
||||
// been determined; create local EmailLocations that update themselves when the
|
||||
// folder is opened and the count is known (adjusted by the local_offset passed in)
|
||||
foreach (Geary.Email local_email in local_list) {
|
||||
local_email.update_location(new Geary.EmailLocation.local(this,
|
||||
local_email.location.position, local_email.location.ordering,
|
||||
(count + low - 1) - local_email.location.position));
|
||||
}
|
||||
foreach (Geary.Email email in local_list)
|
||||
email.update_position(email.position + adjustment);
|
||||
}
|
||||
|
||||
// report list
|
||||
|
|
@ -434,7 +450,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
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].location.position == position) {
|
||||
if (local_list[ctr].position == position) {
|
||||
found = true;
|
||||
|
||||
break;
|
||||
|
|
@ -486,6 +502,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
return accumulator;
|
||||
}
|
||||
|
||||
// TODO: Capture Error and report via EmailCallback.
|
||||
public override void lazy_list_email_sparse(int[] by_position, Geary.Email.Field required_fields,
|
||||
Folder.ListFlags flags, EmailCallback cb, Cancellable? cancellable = null) {
|
||||
// schedule listing in the background, using the callback to drive availability of email
|
||||
|
|
@ -546,11 +563,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
|
||||
// reverse the process, fixing up all the returned messages to match the server's notions
|
||||
if (local_list_size > 0 && local_offset > 0) {
|
||||
foreach (Geary.Email email in local_list) {
|
||||
int new_position = email.location.position + local_offset;
|
||||
email.update_location(new Geary.EmailLocation(this, new_position,
|
||||
email.location.ordering));
|
||||
}
|
||||
foreach (Geary.Email email in local_list)
|
||||
email.update_position(email.position + local_offset);
|
||||
}
|
||||
|
||||
if (local_list_size == by_position.length || local_only) {
|
||||
|
|
@ -576,7 +590,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
bool found = false;
|
||||
if (local_list != null) {
|
||||
foreach (Geary.Email email2 in local_list) {
|
||||
if (email2.location.position == position) {
|
||||
if (email2.position == position) {
|
||||
found = true;
|
||||
|
||||
break;
|
||||
|
|
@ -624,6 +638,84 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
cb(null, null);
|
||||
}
|
||||
|
||||
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> list = new Gee.ArrayList<Geary.Email>();
|
||||
yield do_list_email_by_id_async(initial_id, count, required_fields, list, null, cancellable,
|
||||
flags.is_all_set(Folder.ListFlags.FAST));
|
||||
|
||||
return (list.size > 0) ? list : null;
|
||||
}
|
||||
|
||||
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, cb, cancellable,
|
||||
flags.is_all_set(Folder.ListFlags.FAST));
|
||||
}
|
||||
|
||||
private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Email.Field required_fields, EmailCallback cb, Cancellable? cancellable, bool local_only) {
|
||||
try {
|
||||
yield do_list_email_by_id_async(initial_id, count, required_fields, null, cb, cancellable,
|
||||
local_only);
|
||||
} catch (Error err) {
|
||||
cb(null, err);
|
||||
}
|
||||
}
|
||||
|
||||
// STRATEGY: Determine position number of message at initial_id then work via positions from
|
||||
// that point on.
|
||||
private async void do_list_email_by_id_async(Geary.EmailIdentifier initial_id, int count,
|
||||
Geary.Email.Field required_fields, Gee.List<Geary.Email>? accumulator, EmailCallback? cb,
|
||||
Cancellable? cancellable, bool local_only) throws Error {
|
||||
if (!opened)
|
||||
throw new EngineError.OPEN_REQUIRED("%s is not open", to_string());
|
||||
|
||||
// listing by ID requires the remote to be open and fully synchronized, as there's no
|
||||
// reliable way to determine certain counts and positions without it
|
||||
//
|
||||
// TODO: Need to deal with this in a sane manner when offline
|
||||
if (!yield wait_for_remote_to_open())
|
||||
throw new EngineError.SERVER_UNAVAILABLE("Must be synchronized with server for listing by ID");
|
||||
|
||||
assert(remote_count >= 0);
|
||||
|
||||
int local_count = yield local_folder.get_email_count_async(cancellable);
|
||||
|
||||
int initial_position = yield local_folder.get_id_position_async(initial_id, cancellable);
|
||||
if (initial_position <= 0) {
|
||||
throw new EngineError.NOT_FOUND("Email ID %s in %s not known to local store",
|
||||
initial_id.to_string(), to_string);
|
||||
}
|
||||
|
||||
// since count can also indicate "to earliest" or "to latest", normalize
|
||||
// (count is exclusive of initial_id, hence adding/substracting 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 = initial_position;
|
||||
} else if (count > 0) {
|
||||
low = initial_position;
|
||||
high = (count != int.MAX) ? (initial_position + count - 1) : remote_count;
|
||||
} else {
|
||||
// count == 0
|
||||
low = initial_position;
|
||||
high = initial_position;
|
||||
}
|
||||
|
||||
int actual_count = (high - low + 1);
|
||||
|
||||
debug("do_list_email_by_id_async: initial_id=%s initial_position=%d count=%d actual_count=%d low=%d high=%d local_count=%d remote_count=%d",
|
||||
initial_id.to_string(), initial_position, count, actual_count, low, high, local_count,
|
||||
remote_count);
|
||||
|
||||
yield do_list_email_async(low, actual_count, required_fields, accumulator, cb, cancellable,
|
||||
local_only);
|
||||
}
|
||||
|
||||
private async Gee.List<Geary.Email>? remote_list_email(int[] needed_by_position,
|
||||
Geary.Email.Field required_fields, EmailCallback? cb, Cancellable? cancellable) throws Error {
|
||||
// possible to call remote multiple times, wait for it to open once and go
|
||||
|
|
@ -737,8 +829,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
|
|||
return email;
|
||||
}
|
||||
|
||||
public override async void remove_email_async(int position, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
public override async void remove_email_async(Geary.EmailIdentifier email_id,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
if (!opened)
|
||||
throw new EngineError.OPEN_REQUIRED("Folder %s not opened", to_string());
|
||||
|
||||
|
|
|
|||
|
|
@ -83,10 +83,12 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
|
|||
}
|
||||
|
||||
if (uid_start_value < remote_properties.uid_next.value) {
|
||||
Imap.UID uid_start = new Imap.UID(uid_start_value);
|
||||
Geary.Imap.EmailIdentifier uid_start = new Geary.Imap.EmailIdentifier(
|
||||
new Geary.Imap.UID(uid_start_value));
|
||||
|
||||
Gee.List<Geary.Email>? newest = yield imap_remote_folder.list_email_uid_async(
|
||||
uid_start, null, Geary.Email.Field.PROPERTIES, cancellable);
|
||||
Gee.List<Geary.Email>? newest = yield imap_remote_folder.list_email_by_id_async(
|
||||
uid_start, int.MAX, Geary.Email.Field.PROPERTIES, Geary.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
|
||||
if (newest != null && newest.size > 0) {
|
||||
debug("saving %d newest emails in %s", newest.size, to_string());
|
||||
|
|
@ -103,8 +105,8 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
|
|||
|
||||
// fetch email from earliest email to last to (a) remove any deletions and (b) update
|
||||
// any flags that may have changed
|
||||
Geary.Imap.UID last_uid = new Geary.Imap.UID(local_properties.uid_next.value - 1);
|
||||
Geary.Imap.UID? earliest_uid = yield imap_local_folder.get_earliest_uid_async(cancellable);
|
||||
int64 full_uid_count = local_properties.uid_next.value - 1 - earliest_uid.value;
|
||||
|
||||
// if no earliest UID, that means no messages in local store, so nothing to update
|
||||
if (earliest_uid == null || !earliest_uid.is_valid()) {
|
||||
|
|
@ -113,8 +115,21 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
|
|||
return true;
|
||||
}
|
||||
|
||||
Gee.List<Geary.Email>? old_local = yield imap_local_folder.list_email_uid_async(earliest_uid,
|
||||
last_uid, Geary.Email.Field.PROPERTIES, cancellable);
|
||||
// If no UID's, nothing to update
|
||||
if (full_uid_count <= 0 || (full_uid_count > int.MAX)) {
|
||||
debug("No valid UID range in local folder %s (count=%lld), nothing to update", to_string(),
|
||||
full_uid_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Geary.Imap.EmailIdentifier earliest_id = new Geary.Imap.EmailIdentifier(earliest_uid);
|
||||
int full_id_count = (int) full_uid_count;
|
||||
|
||||
// Get the local emails in the range
|
||||
Gee.List<Geary.Email>? old_local = yield imap_local_folder.list_email_by_id_async(
|
||||
earliest_id, full_id_count, Geary.Email.Field.PROPERTIES, Geary.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
int local_length = (old_local != null) ? old_local.size : 0;
|
||||
|
||||
// as before, if empty folder, nothing to update
|
||||
|
|
@ -124,8 +139,10 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
|
|||
return true;
|
||||
}
|
||||
|
||||
Gee.List<Geary.Email>? old_remote = yield imap_remote_folder.list_email_uid_async(earliest_uid,
|
||||
last_uid, Geary.Email.Field.PROPERTIES, cancellable);
|
||||
// Get the remote emails in the range
|
||||
Gee.List<Geary.Email>? old_remote = yield imap_remote_folder.list_email_by_id_async(
|
||||
earliest_id, full_id_count, Geary.Email.Field.PROPERTIES, Geary.Folder.ListFlags.NONE,
|
||||
cancellable);
|
||||
int remote_length = (old_remote != null) ? old_remote.size : 0;
|
||||
|
||||
int remote_ctr = 0;
|
||||
|
|
@ -164,34 +181,42 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
|
|||
|
||||
// local's email on the server has been removed, remove locally
|
||||
try {
|
||||
yield local_folder.remove_email_async(old_local[local_ctr].location.position,
|
||||
cancellable);
|
||||
yield local_folder.remove_email_async(old_local[local_ctr].id, cancellable);
|
||||
} catch (Error remove_err) {
|
||||
debug("Unable to remove discarded email from %s: %s", to_string(),
|
||||
remove_err.message);
|
||||
}
|
||||
|
||||
notify_message_removed(old_local[local_ctr].id);
|
||||
|
||||
local_ctr++;
|
||||
}
|
||||
}
|
||||
|
||||
// add newly-discovered emails to local store
|
||||
int appended = 0;
|
||||
for (; remote_ctr < remote_length; remote_ctr++) {
|
||||
try {
|
||||
yield local_folder.create_email_async(old_remote[remote_ctr], cancellable);
|
||||
appended++;
|
||||
} catch (Error append_err) {
|
||||
debug("Unable to append new email to %s: %s", to_string(), append_err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// remove anything left over
|
||||
if (appended > 0)
|
||||
notify_messages_appended(appended);
|
||||
|
||||
// remove anything left over ... use local count rather than remote as we're still in a stage
|
||||
// where only the local messages are available
|
||||
for (; local_ctr < local_length; local_ctr++) {
|
||||
try {
|
||||
yield local_folder.remove_email_async(old_local[local_ctr].location.position,
|
||||
cancellable);
|
||||
yield local_folder.remove_email_async(old_local[local_ctr].id, cancellable);
|
||||
} catch (Error discard_err) {
|
||||
debug("Unable to discard email from %s: %s", to_string(), discard_err.message);
|
||||
}
|
||||
|
||||
notify_message_removed(old_local[local_ctr].id);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,21 @@ private interface Geary.LocalFolder : Object, Geary.Folder {
|
|||
public async abstract bool is_email_present_async(Geary.EmailIdentifier id,
|
||||
out Geary.Email.Field available_fields, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Converts an EmailIdentifier into positional addressing in the Folder. This call relies on
|
||||
* the fact that when a Folder is fully opened, the local stores' tail list of messages (the
|
||||
* messages located at the top of the stack, i.e. the latest ones added) are synchronized with
|
||||
* the server and is gap-free, even if all the fields for those messages is not entirely
|
||||
* available.
|
||||
*
|
||||
* Returns a positive value locating the position of the email. Other values (zero, negative)
|
||||
* indicate the EmailIdentifier is unknown, which could mean the message is not associated with
|
||||
* the folder, or is buried so far down the list on the remote server that it's not known
|
||||
* locally (yet).
|
||||
*/
|
||||
public async abstract int get_id_position_async(Geary.EmailIdentifier id, Cancellable? cancellable)
|
||||
throws Error;
|
||||
|
||||
/**
|
||||
* Geary allows for a single message to exist in multiple folders. This method checks if the
|
||||
* email is associated with this folder. It may rely on a Message-ID being present, in which
|
||||
|
|
|
|||
|
|
@ -16,5 +16,13 @@ private interface Geary.RemoteAccount : Object, Geary.Account {
|
|||
}
|
||||
|
||||
private interface Geary.RemoteFolder : Object, Geary.Folder {
|
||||
/**
|
||||
* A remote folder may report *either* a message has been removed by its EmailIdentifier
|
||||
* (in which case it should use "message-removed") or by its position (in which case it should
|
||||
* use this signal, "message-at-removed"), but never both for the same removal.
|
||||
*/
|
||||
public signal void message_at_removed(int position, int total);
|
||||
|
||||
protected abstract void notify_message_at_removed(int position, int total);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
// TODO: This class currently deals with generic email storage as well as IMAP-specific issues; in
|
||||
// the future, to support other email services, will need to break this up.
|
||||
|
||||
private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Geary.Imap.FolderExtensions,
|
||||
Geary.ReferenceSemantics {
|
||||
private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Geary.ReferenceSemantics {
|
||||
protected int manual_ref_count { get; protected set; }
|
||||
|
||||
private ImapDatabase db;
|
||||
|
|
@ -79,6 +78,23 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
return yield location_table.fetch_count_for_folder_async(null, folder_row.id, cancellable);
|
||||
}
|
||||
|
||||
public async int get_id_position_async(Geary.EmailIdentifier id, Cancellable? cancellable)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
Transaction transaction = yield db.begin_transaction_async("Folder.get_id_position_async",
|
||||
cancellable);
|
||||
|
||||
int64 message_id;
|
||||
if (!yield location_table.does_ordering_exist_async(transaction, folder_row.id,
|
||||
id.ordering, out message_id, cancellable)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return yield location_table.fetch_message_position_async(transaction, message_id, folder_row.id,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public override async void create_email_async(Geary.Email email, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
yield atomic_create_email_async(null, email, cancellable);
|
||||
|
|
@ -88,8 +104,6 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.Imap.EmailIdentifier id = (Geary.Imap.EmailIdentifier) email.id;
|
||||
|
||||
Transaction transaction = supplied_transaction ?? yield db.begin_transaction_async(
|
||||
"Folder.atomic_create_email_async", cancellable);
|
||||
|
||||
|
|
@ -97,9 +111,9 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
// not account-wide)
|
||||
int64 message_id;
|
||||
if (yield location_table.does_ordering_exist_async(transaction, folder_row.id,
|
||||
email.location.ordering, out message_id, cancellable)) {
|
||||
throw new EngineError.ALREADY_EXISTS("Email with UID %s already exists in %s",
|
||||
id.uid.to_string(), to_string());
|
||||
email.id.ordering, out message_id, cancellable)) {
|
||||
throw new EngineError.ALREADY_EXISTS("Email with ID %s already exists in %s",
|
||||
email.id.to_string(), to_string());
|
||||
}
|
||||
|
||||
// TODO: Also check by Message-ID (and perhaps other EmailProperties) to link an existing
|
||||
|
|
@ -108,10 +122,9 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
message_id = yield message_table.create_async(transaction,
|
||||
new MessageRow.from_email(message_table, email), cancellable);
|
||||
|
||||
// create the message location in the location lookup table using its UID for the ordering
|
||||
// (which fulfills the requirements for the ordering column)
|
||||
// create the message location in the location lookup table
|
||||
MessageLocationRow location_row = new MessageLocationRow(location_table, Row.INVALID_ID,
|
||||
message_id, folder_row.id, email.location.ordering, email.location.position);
|
||||
message_id, folder_row.id, email.id.ordering, email.position);
|
||||
yield location_table.create_async(transaction, location_row, cancellable);
|
||||
|
||||
// only write out the IMAP email properties if they're supplied and there's something to
|
||||
|
|
@ -166,16 +179,37 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
return yield list_email(transaction, list, required_fields, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_uid_async(Geary.Imap.UID? low, Geary.Imap.UID? high,
|
||||
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
||||
int count, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
if (count == 0) {
|
||||
Geary.Email email = yield fetch_email_async(initial_id, required_fields, cancellable);
|
||||
|
||||
Gee.List<Geary.Email> singleton = new Gee.ArrayList<Geary.Email>();
|
||||
singleton.add(email);
|
||||
|
||||
return singleton;
|
||||
}
|
||||
|
||||
check_open();
|
||||
|
||||
Transaction transaction = yield db.begin_transaction_async("Folder.list_email_uid_async",
|
||||
Geary.Imap.UID uid = ((Geary.Imap.EmailIdentifier) initial_id).uid;
|
||||
|
||||
Transaction transaction = yield db.begin_transaction_async("Folder.list_email_by_id_async",
|
||||
cancellable);
|
||||
|
||||
int64 low, high;
|
||||
if (count < 0) {
|
||||
high = uid.value;
|
||||
low = (count != int.MIN) ? (high + count).clamp(1, uint32.MAX) : -1;
|
||||
} else {
|
||||
// count > 0
|
||||
low = uid.value;
|
||||
high = (count != int.MAX) ? (low + count).clamp(1, uint32.MAX) : -1;
|
||||
}
|
||||
|
||||
Gee.List<MessageLocationRow>? list = yield location_table.list_ordering_async(transaction,
|
||||
folder_row.id,(low != null) ? low.value : 1, (high != null) ? high.value : -1,
|
||||
cancellable);
|
||||
folder_row.id, low, high, cancellable);
|
||||
|
||||
return yield list_email(transaction, list, required_fields, cancellable);
|
||||
}
|
||||
|
|
@ -219,9 +253,7 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
continue;
|
||||
}
|
||||
|
||||
Geary.Email email = message_row.to_email(
|
||||
new Geary.Imap.EmailLocation(this, position, uid),
|
||||
new Geary.Imap.EmailIdentifier(uid));
|
||||
Geary.Email email = message_row.to_email(position, new Geary.Imap.EmailIdentifier(uid));
|
||||
if (properties != null)
|
||||
email.set_email_properties(properties.get_imap_email_properties());
|
||||
|
||||
|
|
@ -279,7 +311,7 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
id.to_string(), to_string());
|
||||
}
|
||||
|
||||
Geary.Email email = message_row.to_email(new Geary.Imap.EmailLocation(this, position, uid), id);
|
||||
Geary.Email email = message_row.to_email(position, id);
|
||||
if (properties != null)
|
||||
email.set_email_properties(properties.get_imap_email_properties());
|
||||
|
||||
|
|
@ -295,7 +327,7 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
return (ordering >= 1) ? new Geary.Imap.UID(ordering) : null;
|
||||
}
|
||||
|
||||
public override async void remove_email_async(int position, Cancellable? cancellable = null)
|
||||
public override async void remove_email_async(Geary.EmailIdentifier id, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
|
|
@ -306,17 +338,15 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
// (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
|
||||
// account
|
||||
if (!yield location_table.remove_by_position_async(transaction, folder_row.id, position, cancellable)) {
|
||||
throw new EngineError.NOT_FOUND("Message #%d in local store of %s not found", position,
|
||||
if (!yield location_table.remove_by_ordering_async(transaction, folder_row.id, id.ordering,
|
||||
cancellable)) {
|
||||
throw new EngineError.NOT_FOUND("Message %s in local store of %s not found", id.to_string(),
|
||||
to_string());
|
||||
}
|
||||
|
||||
int count = yield location_table.fetch_count_for_folder_async(transaction, folder_row.id,
|
||||
cancellable);
|
||||
|
||||
yield transaction.commit_async(cancellable);
|
||||
|
||||
notify_message_removed(position, count);
|
||||
notify_message_removed(id);
|
||||
}
|
||||
|
||||
public async bool is_email_present_async(Geary.EmailIdentifier id, out Geary.Email.Field available_fields,
|
||||
|
|
@ -352,9 +382,6 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
|
||||
Geary.Imap.EmailIdentifier id = (Geary.Imap.EmailIdentifier) email.id;
|
||||
|
||||
Transaction transaction = yield db.begin_transaction_async("Folder.update_email_async",
|
||||
cancellable);
|
||||
|
||||
|
|
@ -362,7 +389,7 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
// a message_id that can be used for a merge; note that this works without a Message-ID)
|
||||
int64 message_id;
|
||||
bool associated = yield location_table.does_ordering_exist_async(transaction, folder_row.id,
|
||||
id.uid.value, out message_id, cancellable);
|
||||
email.id.ordering, out message_id, cancellable);
|
||||
|
||||
// If working around the lack of a Message-ID and not associated with this folder, treat
|
||||
// this operation as a create; otherwise, since a folder-association is determined, do
|
||||
|
|
@ -413,15 +440,15 @@ private class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gea
|
|||
if (!associated) {
|
||||
// see if an email exists at this position
|
||||
MessageLocationRow? location_row = yield location_table.fetch_async(transaction,
|
||||
folder_row.id, location.position, cancellable);
|
||||
folder_row.id, email.position, cancellable);
|
||||
if (location_row != null) {
|
||||
throw new EngineError.ALREADY_EXISTS("Email already exists at position %d in %s",
|
||||
email.location.position, to_string());
|
||||
email.position, to_string());
|
||||
}
|
||||
|
||||
// insert email at supplied position
|
||||
location_row = new MessageLocationRow(location_table, Row.INVALID_ID, message_id,
|
||||
folder_row.id, id.uid.value, location.position);
|
||||
folder_row.id, email.id.ordering, email.position);
|
||||
yield location_table.create_async(transaction, location_row, cancellable);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,30 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
|
|||
return -1;
|
||||
}
|
||||
|
||||
public async int fetch_message_position_async(Transaction? transaction, int64 message_id,
|
||||
int64 folder_id, Cancellable? cancellable) throws Error {
|
||||
Transaction locked = yield obtain_lock_async(transaction,
|
||||
"MessageLocationTable.fetch_message_position_async", cancellable);
|
||||
|
||||
SQLHeavy.Query query = locked.prepare(
|
||||
"SELECT message_id FROM MessageLocationTable WHERE folder_id=? ORDER BY ordering");
|
||||
query.bind_int64(0, folder_id);
|
||||
|
||||
SQLHeavy.QueryResult results = yield query.execute_async(cancellable);
|
||||
|
||||
int position = 1;
|
||||
while (!results.finished) {
|
||||
if (results.fetch_int64(0) == message_id)
|
||||
return position;
|
||||
|
||||
yield results.next_async(cancellable);
|
||||
position++;
|
||||
}
|
||||
|
||||
// not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
public async int fetch_count_for_folder_async(Transaction? transaction,
|
||||
int64 folder_id, Cancellable? cancellable) throws Error {
|
||||
Transaction locked = yield obtain_lock_async(transaction,
|
||||
|
|
@ -278,33 +302,26 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
|
|||
return (!result.finished) ? result.fetch_int64(0) : -1;
|
||||
}
|
||||
|
||||
public async bool remove_by_position_async(Transaction? transaction, int64 folder_id,
|
||||
int position, Cancellable? cancellable) throws Error {
|
||||
assert(position >= 1);
|
||||
|
||||
public async bool remove_by_ordering_async(Transaction? transaction, int64 folder_id,
|
||||
int64 ordering, Cancellable? cancellable) throws Error {
|
||||
Transaction locked = yield obtain_lock_async(transaction,
|
||||
"MessageLocationTable.remove_by_position_async", cancellable);
|
||||
"MessageLocationTable.remove_by_ordering_async", cancellable);
|
||||
|
||||
SQLHeavy.Query query = locked.prepare(
|
||||
"SELECT id FROM MessageLocationTable WHERE folder_id = ? ORDER BY ordering LIMIT 1 OFFSET ?");
|
||||
"SELECT id FROM MessageLocationTable WHERE folder_id=? AND ordering=?");
|
||||
query.bind_int64(0, folder_id);
|
||||
query.bind_int(1, position - 1);
|
||||
query.bind_int64(1, ordering);
|
||||
|
||||
SQLHeavy.QueryResult results = yield query.execute_async(cancellable);
|
||||
if (results.finished)
|
||||
return false;
|
||||
|
||||
query = locked.prepare(
|
||||
"DELETE FROM MessageLocationTable WHERE id = ?");
|
||||
query = locked.prepare("DELETE FROM MessageLocationTable WHERE id=?");
|
||||
query.bind_int64(0, results.fetch_int(0));
|
||||
|
||||
yield query.execute_async(cancellable);
|
||||
locked.set_commit_required();
|
||||
|
||||
// only commit if performing our own transaction
|
||||
if (transaction == null)
|
||||
yield locked.commit_async(cancellable);
|
||||
|
||||
yield release_lock_async(transaction, locked, cancellable);
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row {
|
|||
body = fetch_string_for(result, MessageTable.Column.BODY);
|
||||
}
|
||||
|
||||
public Geary.Email to_email(Geary.EmailLocation location, Geary.EmailIdentifier id) throws Error {
|
||||
Geary.Email email = new Geary.Email(location, id);
|
||||
public Geary.Email to_email(int position, Geary.EmailIdentifier id) throws Error {
|
||||
Geary.Email email = new Geary.Email(position, id);
|
||||
|
||||
if (((fields & Geary.Email.Field.DATE) != 0) && (date != null))
|
||||
email.set_send_date(new RFC822.Date(date));
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ def build(bld):
|
|||
'../engine/api/geary-conversations.vala',
|
||||
'../engine/api/geary-credentials.vala',
|
||||
'../engine/api/geary-email-identifier.vala',
|
||||
'../engine/api/geary-email-location.vala',
|
||||
'../engine/api/geary-email-properties.vala',
|
||||
'../engine/api/geary-email.vala',
|
||||
'../engine/api/geary-endpoint.vala',
|
||||
|
|
@ -38,9 +37,7 @@ def build(bld):
|
|||
|
||||
'../engine/imap/api/imap-account.vala',
|
||||
'../engine/imap/api/imap-email-identifier.vala',
|
||||
'../engine/imap/api/imap-email-location.vala',
|
||||
'../engine/imap/api/imap-email-properties.vala',
|
||||
'../engine/imap/api/imap-folder-extensions.vala',
|
||||
'../engine/imap/api/imap-folder-properties.vala',
|
||||
'../engine/imap/api/imap-folder.vala',
|
||||
'../engine/imap/command/imap-command-response.vala',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue