Some cleanup dealing with commit for #3851.

The commit for #3851 changed the semantics of how messages are stored in the local cache,
and not all the code was properly updated to recognize that, particularly in EngineFolder.

This patch also introduces EmailIdentifier, which allows for a mail message to be fetched by
a unique identifier rather than its position in the folder list.  This is much more
efficient and less error-prone, and means that Email objects don't have to be updated if
their position changes (although there are still some questions in that area).  For IMAP,
this means being able to use the message's UID.
This commit is contained in:
Jim Nelson 2011-07-19 15:55:56 -07:00
parent 9adabb21ed
commit e812ef6166
17 changed files with 213 additions and 95 deletions

View file

@ -250,7 +250,7 @@ public class MainWindow : Gtk.Window {
return;
}
Geary.Email for_buffer = yield current_folder.fetch_email_async(email.location.position,
Geary.Email for_buffer = yield current_folder.fetch_email_async(email.id,
MessageBuffer.REQUIRED_FIELDS);
message_buffer.display_email(for_buffer);

View file

@ -81,8 +81,8 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
}
}
public abstract async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
Cancellable? cancellable = null) throws Error;
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(Geary.Email email, Cancellable? cancellable = null)
throws Error;

View file

@ -0,0 +1,20 @@
/* 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.
*/
/**
* Each mail system muse have its own method for uniquely identifying an email message. The only
* limitation upon an EmailIdentifier is that it's only considered valid within the Folder the
* 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.)
*/
public abstract class Geary.EmailIdentifier : Object, Geary.Equalable {
public abstract bool equals(Geary.Equalable other);
public abstract string to_string();
}

View file

@ -6,9 +6,11 @@
public class Geary.EmailLocation : Object {
public int position { get; private set; }
public int64 ordering { get; private set; }
public EmailLocation(int position) {
public EmailLocation(int position, int64 ordering) {
this.position = position;
this.ordering = ordering;
}
}

View file

@ -58,6 +58,7 @@ public class Geary.Email : Object {
}
public Geary.EmailLocation location { get; private set; }
public Geary.EmailIdentifier id { get; private set; }
// DATE
public Geary.RFC822.Date? date { get; private set; default = null; }
@ -92,7 +93,12 @@ public class Geary.Email : Object {
private Geary.RFC822.Message? message = null;
public Email(Geary.EmailLocation location) {
public Email(Geary.EmailLocation location, Geary.EmailIdentifier id) {
this.location = location;
this.id = id;
}
public void update_location(Geary.EmailLocation location) {
this.location = location;
}

View file

@ -146,7 +146,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
if (!opened)
throw new EngineError.OPEN_REQUIRED("%s is not open", to_string());
if (remote_folder != null)
if (yield wait_for_remote_to_open())
return yield remote_folder.get_email_count(cancellable);
return yield local_folder.get_email_count(cancellable);
@ -189,14 +189,24 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
// normalize the position (ordering) of what's available locally with the situation on
// the server
int remote_count = yield normalize_email_positions_async(low, count, cancellable);
int local_count, remote_count;
yield normalize_email_positions_async(low, count, out local_count, out remote_count,
cancellable);
// normalize the arguments to match what's on the remote server
normalize_span_specifiers(ref low, ref count, remote_count);
debug("do_list_email_async: low=%d count=%d remote_count=%d", low, count, remote_count);
// because the local store caches messages starting from the newest (at the end of the list)
// to the earliest fetched by the user, need to adjust the low value to match its offset
// and range
int local_low = (low - (remote_count - local_count)).clamp(1, local_count);
debug("do_list_email_async: low=%d count=%d local_count=%d remote_count=%d local_low=%d",
low, count, local_count, remote_count, local_low);
Gee.List<Geary.Email>? local_list = null;
try {
local_list = yield local_folder.list_email_async(low, count, required_fields,
local_list = yield local_folder.list_email_async(local_low, count, required_fields,
cancellable);
} catch (Error local_err) {
if (cb != null)
@ -207,6 +217,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
int local_list_size = (local_list != null) ? local_list.size : 0;
debug("Fetched %d emails from local store for %s", local_list_size, to_string());
if (local_list_size > 0) {
if (accumulator != null)
accumulator.add_all(local_list);
@ -223,16 +235,35 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
return;
}
// fixup local email positions to match server's positions
if (local_list_size > 0) {
foreach (Geary.Email email in local_list) {
int new_position = email.location.position + (low - local_low);
email.update_location(new Geary.EmailLocation(new_position, email.location.ordering));
}
}
// 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
//
// 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];
int position = low;
for (int index = 0; (index < count) && (position <= (low + (count - 1))); position++) {
while ((index < local_list_size) && (local_list[index].location.position < position))
index++;
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) {
found = true;
break;
}
}
if (index >= local_list_size || local_list[index].location.position != position)
if (!found) {
debug("Need email at %d in %s", position, to_string());
needed_by_position += position;
}
}
if (needed_by_position.length == 0) {
@ -299,11 +330,21 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
// the server
int low, high;
Arrays.int_find_high_low(by_position, out low, out high);
yield normalize_email_positions_async(low, high - low + 1, cancellable);
int local_count, remote_count;
yield normalize_email_positions_async(low, high - low + 1, out local_count, out remote_count,
cancellable);
int local_offset = (remote_count > local_count) ? (remote_count - local_count - 1) : 0;
// Fixup all the positions to match the local store's notions
int[] local_by_position = new int[by_position.length];
for (int ctr = 0; ctr < by_position.length; ctr++)
local_by_position[ctr] = by_position[ctr] - local_offset;
Gee.List<Geary.Email>? local_list = null;
try {
local_list = yield local_folder.list_email_sparse_async(by_position, required_fields,
local_list = yield local_folder.list_email_sparse_async(local_by_position, required_fields,
cancellable);
} catch (Error local_err) {
if (cb != null)
@ -314,6 +355,14 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
int local_list_size = (local_list != null) ? local_list.size : 0;
// reverse the process, fixing up all the returned messages to match the server's notions
if (local_list_size > 0) {
foreach (Geary.Email email in local_list) {
int new_position = email.location.position + local_offset;
email.update_location(new Geary.EmailLocation(new_position, email.location.ordering));
}
}
if (local_list_size == by_position.length) {
if (accumulator != null)
accumulator.add_all(local_list);
@ -336,8 +385,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
foreach (int position in by_position) {
bool found = false;
if (local_list != null) {
foreach (Geary.Email email in local_list) {
if (email.location.position == position) {
foreach (Geary.Email email2 in local_list) {
if (email2.location.position == position) {
found = true;
break;
@ -464,13 +513,13 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
return full;
}
public override async Geary.Email fetch_email_async(int num, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
if (!opened)
throw new EngineError.OPEN_REQUIRED("Folder %s not opened", to_string());
try {
return yield local_folder.fetch_email_async(num, fields, cancellable);
return yield local_folder.fetch_email_async(id, fields, cancellable);
} catch (Error err) {
// TODO: Better parsing of error; currently merely falling through and trying network
// for copy
@ -481,7 +530,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
// or it's simply not present. If it's not present, want to ensure that the Message-ID
// is requested, as that's a good way to manage duplicate messages in the system
Geary.Email.Field available_fields;
bool is_present = yield local_folder.is_email_present_at(num, out available_fields, cancellable);
bool is_present = yield local_folder.is_email_present(id, out available_fields, cancellable);
if (!is_present)
fields = fields.set(Geary.Email.Field.REFERENCES);
@ -489,7 +538,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
if (!yield wait_for_remote_to_open())
throw new EngineError.SERVER_UNAVAILABLE("No connection to %s", remote.to_string());
Geary.Email email = yield remote_folder.fetch_email_async(num, fields, cancellable);
Geary.Email email = yield remote_folder.fetch_email_async(id, fields, cancellable);
// save to local store
yield local_folder.update_email_async(email, false, cancellable);
@ -521,15 +570,14 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
// 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.
//
// Returns the email count on remote_folder.
private async int normalize_email_positions_async(int low, int count, Cancellable? cancellable)
private async void normalize_email_positions_async(int low, int count, out int local_count,
out int remote_count, Cancellable? cancellable)
throws Error {
if (!yield wait_for_remote_to_open())
throw new EngineError.SERVER_UNAVAILABLE("No connection to %s", remote.to_string());
int local_count = yield local_folder.get_email_count(cancellable);
int remote_count = yield remote_folder.get_email_count(cancellable);
local_count = yield local_folder.get_email_count(cancellable);
remote_count = yield remote_folder.get_email_count(cancellable);
// fixup span specifier
normalize_span_specifiers(ref low, ref count, remote_count);
@ -541,7 +589,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
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)
return remote_count;
return;
int prefetch_count = local_low - high;
@ -563,8 +611,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
yield local_folder.create_email_async(email, cancellable);
debug("prefetched %d for %s", prefetch_count, to_string());
return remote_count;
}
}

View file

@ -228,8 +228,8 @@ public interface Geary.Folder : Object {
*
* position is one-based.
*/
public abstract async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
Cancellable? cancellable = null) throws Error;
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 from the folder, determined by its EmailLocation. If the email location

View file

@ -114,9 +114,9 @@ private class Geary.GenericImapFolder : Geary.EngineFolder {
break;
Geary.Imap.UID remote_uid =
((Geary.Imap.EmailLocation) old_remote[remote_ctr].location).uid;
((Geary.Imap.EmailIdentifier) old_remote[remote_ctr].id).uid;
Geary.Imap.UID local_uid =
((Geary.Imap.EmailLocation) old_local[local_ctr].location).uid;
((Geary.Imap.EmailIdentifier) old_local[local_ctr].id).uid;
if (remote_uid.value == local_uid.value) {
// same, update flags and move on

View file

@ -24,13 +24,8 @@ public interface Geary.LocalAccount : Object, Geary.Account {
}
public interface Geary.LocalFolder : Object, Geary.Folder {
/**
* Unlike the remote store, the local store can be sparsely populated, both by fields within
* an email and position (ordering) within the list. This checks if the email at position
* is available. If it returns true, the available_fields indicate what is stored locally.
*/
public async abstract bool is_email_present_at(int position, out Geary.Email.Field available_fields,
Cancellable? cancellable = null) throws Error;
public async abstract bool is_email_present(Geary.EmailIdentifier id,
out Geary.Email.Field available_fields, Cancellable? cancellable = null) throws Error;
/**
* Geary allows for a single message to exist in multiple folders. This method checks if the

View file

@ -0,0 +1,29 @@
/* Copyright 2011 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.Imap.EmailIdentifier : Geary.EmailIdentifier {
public Imap.UID uid { get; private set; }
public EmailIdentifier(Imap.UID uid) {
this.uid = uid;
}
public override bool equals(Equalable o) {
Geary.Imap.EmailIdentifier? other = o as Geary.Imap.EmailIdentifier;
if (other == null)
return false;
if (this == other)
return true;
return uid.value == other.uid.value;
}
public override string to_string() {
return uid.to_string();
}
}

View file

@ -4,18 +4,13 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
//
// The EmailLocation for any message originating from an ImapAccount is guaranteed to have its
// UID attached to it, whether or not it was requested in the FETCH operation.
//
/*
* The IMAP implementation of Geary.EmailLocation uses the email's UID to order the messages.
*/
private class Geary.Imap.EmailLocation : Geary.EmailLocation {
public Geary.Imap.UID uid { get; private set; }
public EmailLocation(int position, Geary.Imap.UID uid) {
base (position);
this.uid = uid;
base (position, uid.value);
}
}

View file

@ -112,14 +112,14 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Geary
return yield mailbox.list_set_async(msg_set, fields, cancellable);
}
public override async Geary.Email fetch_email_async(int position, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
// TODO: If position out of range, throw EngineError.NOT_FOUND
return yield mailbox.fetch_async(position, fields, cancellable);
return yield mailbox.fetch_async(((Imap.EmailIdentifier) id).uid, fields, cancellable);
}
public override async void remove_email_async(Geary.Email email, Cancellable? cancellable = null)
@ -127,7 +127,7 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder, Geary
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
Geary.Imap.UID? uid = ((Geary.Imap.EmailLocation) email.location).uid;
Geary.Imap.UID? uid = ((Geary.Imap.EmailIdentifier) email.id).uid;
if (uid == null)
throw new EngineError.NOT_FOUND("Removing email requires UID");

View file

@ -65,15 +65,17 @@ 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(res.msg_num, uid));
Geary.Email email = new Geary.Email(new Geary.Imap.EmailLocation(res.msg_num, uid),
new Geary.Imap.EmailIdentifier(uid));
fetch_results_to_email(res, fields, email);
msgs.add(email);
}
return (msgs != null && msgs.size > 0) ? msgs : null;
}
public async Geary.Email fetch_async(int msg_num, Geary.Email.Field fields,
public async Geary.Email fetch_async(Geary.Imap.UID uid, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (context.is_closed())
throw new ImapError.NOT_SELECTED("Mailbox %s closed", name);
@ -81,8 +83,11 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
Gee.Set<FetchDataType> data_type_set = new Gee.HashSet<FetchDataType>();
fields_to_fetch_data_types(fields, data_type_set);
// no need to fetch the UID we're asking for
data_type_set.remove(FetchDataType.UID);
FetchCommand fetch_cmd = new FetchCommand.from_collection(context.session.generate_tag(),
new MessageSet(msg_num), data_type_set);
new MessageSet.uid(uid), data_type_set);
CommandResponse resp = yield context.session.send_command_async(fetch_cmd, cancellable);
@ -95,15 +100,8 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
if (results.length != 1)
throw new ImapError.SERVER_ERROR("Too many responses from server: %d", results.length);
if (results[0].msg_num != msg_num) {
throw new ImapError.SERVER_ERROR("Server returns message #%d, requested %d",
results[0].msg_num, msg_num);
}
UID? uid = results[0].get_data(FetchDataType.UID) as UID;
assert(uid != null);
Geary.Email email = new Geary.Email(new Geary.Imap.EmailLocation(results[0].msg_num, uid));
Geary.Email email = new Geary.Email(new Geary.Imap.EmailLocation(results[0].msg_num, uid),
new Geary.Imap.EmailIdentifier(uid));
fetch_results_to_email(results[0], fields, email);
return email;

View file

@ -79,15 +79,15 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
throws Error {
check_open();
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
Geary.Imap.EmailIdentifier id = (Geary.Imap.EmailIdentifier) email.id;
// See if it already exists; first by UID (which is only guaranteed to be unique in a folder,
// not account-wide)
int64 message_id;
if (yield location_table.does_ordering_exist_async(folder_row.id, location.uid.value,
if (yield location_table.does_ordering_exist_async(folder_row.id, email.location.ordering,
out message_id, cancellable)) {
throw new EngineError.ALREADY_EXISTS("Email with UID %s already exists in %s",
location.uid.to_string(), to_string());
id.uid.to_string(), to_string());
}
// TODO: The following steps should be atomic
@ -98,7 +98,7 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
// create the message location in the location lookup table using its UID for the ordering
// (which fulfills the requirements for the ordering column)
MessageLocationRow location_row = new MessageLocationRow(location_table, Row.INVALID_ID,
message_id, folder_row.id, location.uid.value, location.position);
message_id, folder_row.id, email.location.ordering, email.location.position);
yield location_table.create_async(location_row, cancellable);
// only write out the IMAP email properties if they're supplied and there's something to
@ -176,8 +176,11 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
continue;
}
Geary.Email email = message_row.to_email(new Geary.Imap.EmailLocation(location_row.position,
new Geary.Imap.UID(location_row.ordering)));
Geary.Imap.UID uid = new Geary.Imap.UID(location_row.ordering);
Geary.Email email = message_row.to_email(
new Geary.Imap.EmailLocation(location_row.position, uid),
new Geary.Imap.EmailIdentifier(uid));
if (properties != null)
email.set_email_properties(properties.get_imap_email_properties());
@ -187,25 +190,23 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
return (emails.size > 0) ? emails : null;
}
public override async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
Cancellable? cancellable = null) throws Error {
assert(position >= 1);
public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
check_open();
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id, position,
cancellable);
Geary.Imap.UID uid = ((Imap.EmailIdentifier) id).uid;
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(folder_row.id,
uid.value, cancellable);
if (location_row == null) {
throw new EngineError.NOT_FOUND("No message at position %d in folder %s", position,
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
to_string());
}
assert(location_row.position == position);
MessageRow? message_row = yield message_table.fetch_async(location_row.message_id,
required_fields, cancellable);
if (message_row == null) {
throw new EngineError.NOT_FOUND("No message at position %d in folder %s", position,
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
to_string());
}
@ -213,7 +214,7 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
// separate table
if (!message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES))) {
throw new EngineError.INCOMPLETE_MESSAGE(
"Message at position %d in folder %s only fulfills %Xh fields", position, to_string(),
"Message %s in folder %s only fulfills %Xh fields", id.to_string(), to_string(),
message_row.fields);
}
@ -221,10 +222,16 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
properties = yield imap_message_properties_table.fetch_async(location_row.message_id,
cancellable);
if (properties == null) {
throw new EngineError.INCOMPLETE_MESSAGE(
"Message %s in folder %s does not have PROPERTIES field", id.to_string(),
to_string());
}
}
Geary.Email email = message_row.to_email(new Geary.Imap.EmailLocation(location_row.position,
new Geary.Imap.UID(location_row.ordering)));
// TODO: Would be helpful if proper position was known
Geary.Email email = message_row.to_email(
new Geary.Imap.EmailLocation(location_row.position, uid), id);
if (properties != null)
email.set_email_properties(properties.get_imap_email_properties());
@ -247,21 +254,23 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
// (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
Geary.Imap.UID? uid = ((Geary.Imap.EmailLocation) email.location).uid;
Geary.Imap.UID? uid = ((Geary.Imap.EmailIdentifier) email.id).uid;
if (uid == null)
throw new EngineError.NOT_FOUND("UID required to delete local email");
yield location_table.remove_by_ordering_async(folder_row.id, uid.value, cancellable);
}
public async bool is_email_present_at(int position, out Geary.Email.Field available_fields,
public async bool is_email_present(Geary.EmailIdentifier id, out Geary.Email.Field available_fields,
Cancellable? cancellable = null) throws Error {
check_open();
Geary.Imap.UID uid = ((Imap.EmailIdentifier) id).uid;
available_fields = Geary.Email.Field.NONE;
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id, position,
cancellable);
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(folder_row.id,
uid.value, cancellable);
if (location_row == null)
return false;
@ -275,7 +284,7 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
int64 message_id;
return yield location_table.does_ordering_exist_async(folder_row.id,
((Geary.Imap.EmailLocation) email.location).uid.value, out message_id, cancellable);
((Geary.Imap.EmailIdentifier) email.id).uid.value, out message_id, cancellable);
}
public async void update_email_async(Geary.Email email, bool duplicate_okay,
@ -283,12 +292,13 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
check_open();
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
Geary.Imap.EmailIdentifier id = (Geary.Imap.EmailIdentifier) email.id;
// See if the message can be identified in the folder (which both reveals association and
// 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(folder_row.id,
location.uid.value, out message_id, cancellable);
id.uid.value, 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
@ -341,7 +351,7 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder, Gear
// insert email at supplied position
location_row = new MessageLocationRow(location_table, Row.INVALID_ID, message_id,
folder_row.id, location.uid.value, location.position);
folder_row.id, id.uid.value, location.position);
yield location_table.create_async(location_row, cancellable);
}

View file

@ -163,6 +163,21 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
results.fetch_int64(2), position);
}
public async MessageLocationRow? fetch_by_ordering_async(int64 folder_id, int64 ordering,
Cancellable? cancellable = null) throws Error {
SQLHeavy.Query query = db.prepare(
"SELECT id, message_id FROM MessageLocationTable WHERE folder_id = ? AND ordering = ? ");
query.bind_int64(0, folder_id);
query.bind_int64(1, ordering);
SQLHeavy.QueryResult results = yield query.execute_async(cancellable);
if (results.finished)
return null;
return new MessageLocationRow(this, results.fetch_int64(0), results.fetch_int64(1),
folder_id, ordering, -1);
}
public async int fetch_count_for_folder_async(int64 folder_id, Cancellable? cancellable = null)
throws Error {
SQLHeavy.Query query = db.prepare(

View file

@ -80,8 +80,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) throws Error {
Geary.Email email = new Geary.Email(location);
public Geary.Email to_email(Geary.EmailLocation location, Geary.EmailIdentifier id) throws Error {
Geary.Email email = new Geary.Email(location, id);
if (((fields & Geary.Email.Field.DATE) != 0) && (date != null))
email.set_send_date(new RFC822.Date(date));

View file

@ -19,6 +19,7 @@ def build(bld):
'../engine/api/geary-abstract-folder.vala',
'../engine/api/geary-account.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',
@ -40,6 +41,7 @@ def build(bld):
'../engine/common/common-message-data.vala',
'../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',