2012-01-10 10:40:34 -08:00
|
|
|
/* Copyright 2011-2012 Yorba Foundation
|
2011-06-10 19:17:35 -07:00
|
|
|
*
|
|
|
|
|
* This software is licensed under the GNU Lesser General Public License
|
|
|
|
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
|
|
|
*/
|
|
|
|
|
|
2011-06-23 19:07:04 -07:00
|
|
|
// 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.
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
|
2012-05-03 20:24:44 -07:00
|
|
|
public const Geary.Email.Field REQUIRED_FOR_DUPLICATE_DETECTION = Geary.Email.Field.PROPERTIES;
|
2011-11-14 12:09:52 -08:00
|
|
|
|
2012-05-01 20:32:31 -07:00
|
|
|
public bool opened { get; private set; default = false; }
|
|
|
|
|
|
2011-07-18 11:48:42 -07:00
|
|
|
protected int manual_ref_count { get; protected set; }
|
|
|
|
|
|
2011-10-18 19:55:00 -07:00
|
|
|
private ImapDatabase db;
|
2011-06-16 16:27:08 -07:00
|
|
|
private FolderRow folder_row;
|
2011-07-15 13:39:02 -07:00
|
|
|
private Geary.Imap.FolderProperties? properties;
|
2011-06-16 16:27:08 -07:00
|
|
|
private MessageTable message_table;
|
|
|
|
|
private MessageLocationTable location_table;
|
2011-07-08 12:45:22 -07:00
|
|
|
private ImapMessagePropertiesTable imap_message_properties_table;
|
2011-07-01 15:40:20 -07:00
|
|
|
private Geary.FolderPath path;
|
2011-06-10 19:17:35 -07:00
|
|
|
|
2011-07-18 11:48:42 -07:00
|
|
|
internal Folder(ImapDatabase db, FolderRow folder_row, Geary.Imap.FolderProperties? properties,
|
2011-07-08 12:45:22 -07:00
|
|
|
Geary.FolderPath path) throws Error {
|
2011-06-16 16:27:08 -07:00
|
|
|
this.db = db;
|
|
|
|
|
this.folder_row = folder_row;
|
2011-07-18 11:48:42 -07:00
|
|
|
this.properties = properties;
|
2011-07-01 15:40:20 -07:00
|
|
|
this.path = path;
|
2011-06-16 16:27:08 -07:00
|
|
|
|
|
|
|
|
message_table = db.get_message_table();
|
|
|
|
|
location_table = db.get_message_location_table();
|
2011-07-08 12:45:22 -07:00
|
|
|
imap_message_properties_table = db.get_imap_message_properties_table();
|
2011-06-13 15:16:57 -07:00
|
|
|
}
|
|
|
|
|
|
2011-06-23 19:07:04 -07:00
|
|
|
private void check_open() throws Error {
|
|
|
|
|
if (!opened)
|
|
|
|
|
throw new EngineError.OPEN_REQUIRED("%s not open", to_string());
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public Geary.FolderPath get_path() {
|
2011-07-01 15:40:20 -07:00
|
|
|
return path;
|
2011-06-13 15:16:57 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public Geary.Imap.FolderProperties? get_properties() {
|
2011-07-18 11:48:42 -07:00
|
|
|
// TODO: TBD: alteration/updated signals for folders
|
2011-07-08 12:45:22 -07:00
|
|
|
return properties;
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
|
|
|
|
|
2011-07-18 11:48:42 -07:00
|
|
|
internal void update_properties(Geary.Imap.FolderProperties? properties) {
|
|
|
|
|
this.properties = properties;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
|
2011-06-23 19:07:04 -07:00
|
|
|
if (opened)
|
|
|
|
|
throw new EngineError.ALREADY_OPEN("%s already open", to_string());
|
|
|
|
|
|
|
|
|
|
opened = true;
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async void close_async(Cancellable? cancellable = null) throws Error {
|
2011-06-23 19:07:04 -07:00
|
|
|
if (!opened)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
opened = false;
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async int get_email_count_async(Cancellable? cancellable = null) throws Error {
|
2011-06-23 19:07:04 -07:00
|
|
|
check_open();
|
|
|
|
|
|
2011-07-15 13:39:02 -07:00
|
|
|
// TODO: This can be cached and updated when changes occur
|
2012-01-09 10:58:13 -08:00
|
|
|
return yield location_table.fetch_count_for_folder_async(null, folder_row.id, false,
|
|
|
|
|
cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async int get_email_count_including_removed_async(Cancellable? cancellable = null)
|
|
|
|
|
throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
|
|
|
|
// TODO: This can be cached and updated when changes occur
|
|
|
|
|
return yield location_table.fetch_count_for_folder_async(null, folder_row.id, true,
|
|
|
|
|
cancellable);
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
|
|
|
|
|
2011-11-09 16:40:28 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async bool create_email_async(Geary.Email email, Cancellable? cancellable = null)
|
2011-06-28 16:33:27 -07:00
|
|
|
throws Error {
|
2012-03-13 16:03:42 -07:00
|
|
|
return yield atomic_create_email_async(null, email, cancellable);
|
2011-10-12 16:46:23 -07:00
|
|
|
}
|
|
|
|
|
|
2011-11-14 12:09:52 -08:00
|
|
|
// TODO: Need to break out IMAP-specific functionality
|
|
|
|
|
private async int64 search_for_duplicate_async(Transaction transaction, Geary.Email email,
|
2011-10-12 16:46:23 -07:00
|
|
|
Cancellable? cancellable) throws Error {
|
2011-11-14 12:09:52 -08:00
|
|
|
// if fields not present, then no duplicate can reliably be found
|
|
|
|
|
if (!email.fields.is_all_set(REQUIRED_FOR_DUPLICATE_DETECTION))
|
|
|
|
|
return Sqlite.Row.INVALID_ID;
|
2011-06-16 16:27:08 -07:00
|
|
|
|
2012-05-03 20:24:44 -07:00
|
|
|
// 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(transaction, folder_row.id,
|
|
|
|
|
email.id.ordering, out message_id, cancellable)) {
|
|
|
|
|
return message_id;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-14 12:09:52 -08:00
|
|
|
// what's more, actually need all those fields to be available, not merely attempted,
|
|
|
|
|
// to err on the side of safety
|
|
|
|
|
Imap.EmailProperties? imap_properties = (Imap.EmailProperties) email.properties;
|
|
|
|
|
string? internaldate = (imap_properties != null && imap_properties.internaldate != null)
|
|
|
|
|
? imap_properties.internaldate.original : null;
|
2012-05-03 20:24:44 -07:00
|
|
|
long rfc822_size = (imap_properties != null && imap_properties.rfc822_size != null)
|
|
|
|
|
? imap_properties.rfc822_size.value : -1;
|
2011-11-14 12:09:52 -08:00
|
|
|
|
|
|
|
|
if (String.is_empty(internaldate) || rfc822_size < 0)
|
|
|
|
|
return Sqlite.Row.INVALID_ID;
|
2011-10-12 16:46:23 -07:00
|
|
|
|
2011-11-14 12:09:52 -08:00
|
|
|
// reset
|
|
|
|
|
message_id = Sqlite.Row.INVALID_ID;
|
|
|
|
|
|
|
|
|
|
// look for duplicate in IMAP message properties
|
|
|
|
|
Gee.List<int64?>? duplicate_ids = yield imap_message_properties_table.search_for_duplicates_async(
|
|
|
|
|
transaction, internaldate, rfc822_size, cancellable);
|
|
|
|
|
if (duplicate_ids != null && duplicate_ids.size > 0) {
|
2012-05-03 20:24:44 -07:00
|
|
|
if (duplicate_ids.size > 1) {
|
|
|
|
|
debug("Warning: Multiple messages with the same internaldate (%s) and size (%lu) found in %s",
|
|
|
|
|
internaldate, rfc822_size, to_string());
|
|
|
|
|
message_id = duplicate_ids[0];
|
2011-11-14 12:09:52 -08:00
|
|
|
} else if (duplicate_ids.size == 1) {
|
|
|
|
|
message_id = duplicate_ids[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return message_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns false if the message already exists at the specified position
|
|
|
|
|
private async bool associate_with_folder_async(Transaction transaction, int64 message_id,
|
|
|
|
|
Geary.Email email, Cancellable? cancellable) throws Error {
|
|
|
|
|
// see if an email exists at this position
|
2012-05-03 20:24:44 -07:00
|
|
|
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(transaction,
|
|
|
|
|
folder_row.id, email.id.ordering, cancellable);
|
2011-11-14 12:09:52 -08:00
|
|
|
if (location_row != null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// insert email at supplied position
|
|
|
|
|
location_row = new MessageLocationRow(location_table, Row.INVALID_ID, message_id,
|
|
|
|
|
folder_row.id, email.id.ordering, email.position);
|
|
|
|
|
yield location_table.create_async(transaction, location_row, cancellable);
|
2011-10-21 17:04:33 -07:00
|
|
|
|
2011-11-14 12:09:52 -08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-13 16:03:42 -07:00
|
|
|
private async bool atomic_create_email_async(Transaction? supplied_transaction, Geary.Email email,
|
2011-11-14 12:09:52 -08:00
|
|
|
Cancellable? cancellable) throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
|
|
|
|
Transaction transaction = supplied_transaction ?? yield db.begin_transaction_async(
|
|
|
|
|
"Folder.atomic_create_email_async", cancellable);
|
|
|
|
|
|
|
|
|
|
// See if this Email is already associated with the folder
|
|
|
|
|
int64 message_id;
|
|
|
|
|
bool associated = yield location_table.does_ordering_exist_async(transaction, folder_row.id,
|
|
|
|
|
email.id.ordering, out message_id, cancellable);
|
|
|
|
|
|
|
|
|
|
// if duplicate found, associate this email with this folder and merge in any new details
|
|
|
|
|
if (!associated || message_id == Sqlite.Row.INVALID_ID)
|
|
|
|
|
message_id = yield search_for_duplicate_async(transaction, email, cancellable);
|
|
|
|
|
|
2012-03-13 16:03:42 -07:00
|
|
|
// if already associated or a duplicate, merge and/or associate
|
2011-11-14 12:09:52 -08:00
|
|
|
if (message_id != Sqlite.Row.INVALID_ID) {
|
2012-05-03 20:24:44 -07:00
|
|
|
if (!associated) {
|
|
|
|
|
if (!yield associate_with_folder_async(transaction, message_id, email, cancellable)) {
|
|
|
|
|
debug("Warning: Unable to associate %s (%lld) with %s", email.id.to_string(), message_id,
|
|
|
|
|
to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-11-14 12:09:52 -08:00
|
|
|
|
|
|
|
|
yield merge_email_async(transaction, message_id, email, cancellable);
|
|
|
|
|
|
|
|
|
|
if (supplied_transaction == null)
|
|
|
|
|
yield transaction.commit_if_required_async(cancellable);
|
|
|
|
|
|
2012-03-13 16:03:42 -07:00
|
|
|
return false;
|
2011-11-14 12:09:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// not found, so create and associate with this folder
|
2011-10-12 16:46:23 -07:00
|
|
|
message_id = yield message_table.create_async(transaction,
|
2011-10-21 17:04:33 -07:00
|
|
|
new MessageRow.from_email(message_table, email), cancellable);
|
2011-06-23 19:07:04 -07:00
|
|
|
|
2011-11-09 16:40:28 -08:00
|
|
|
// create the message location in the location lookup table
|
2011-06-16 16:27:08 -07:00
|
|
|
MessageLocationRow location_row = new MessageLocationRow(location_table, Row.INVALID_ID,
|
2011-11-09 16:40:28 -08:00
|
|
|
message_id, folder_row.id, email.id.ordering, email.position);
|
2011-10-12 16:46:23 -07:00
|
|
|
yield location_table.create_async(transaction, location_row, cancellable);
|
2011-07-08 12:45:22 -07:00
|
|
|
|
|
|
|
|
// only write out the IMAP email properties if they're supplied and there's something to
|
|
|
|
|
// write out -- no need to create an empty row
|
|
|
|
|
Geary.Imap.EmailProperties? properties = (Geary.Imap.EmailProperties?) email.properties;
|
2011-07-15 13:39:02 -07:00
|
|
|
if (email.fields.fulfills(Geary.Email.Field.PROPERTIES) && properties != null) {
|
2011-07-08 12:45:22 -07:00
|
|
|
ImapMessagePropertiesRow properties_row = new ImapMessagePropertiesRow.from_imap_properties(
|
|
|
|
|
imap_message_properties_table, message_id, properties);
|
2011-10-12 16:46:23 -07:00
|
|
|
yield imap_message_properties_table.create_async(transaction, properties_row, cancellable);
|
2011-07-08 12:45:22 -07:00
|
|
|
}
|
2011-07-26 15:29:08 -07:00
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
// only commit if not supplied a transaction
|
|
|
|
|
if (supplied_transaction == null)
|
|
|
|
|
yield transaction.commit_async(cancellable);
|
|
|
|
|
|
2012-03-13 16:03:42 -07:00
|
|
|
return true;
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
2011-10-04 18:44:18 -07:00
|
|
|
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable)
|
|
|
|
|
throws Error {
|
2011-06-23 19:07:04 -07:00
|
|
|
check_open();
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
Geary.Folder.normalize_span_specifiers(ref low, ref count,
|
|
|
|
|
yield get_email_count_async(cancellable));
|
2011-07-15 13:39:02 -07:00
|
|
|
|
2011-07-08 12:45:22 -07:00
|
|
|
if (count == 0)
|
|
|
|
|
return null;
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.list_email_async",
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
Gee.List<MessageLocationRow>? list = yield location_table.list_async(transaction,
|
2012-01-09 10:58:13 -08:00
|
|
|
folder_row.id, low, count, false, cancellable);
|
2011-06-21 17:48:40 -07:00
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
return yield do_list_email_async(transaction, list, required_fields, false, cancellable);
|
2012-01-09 10:58:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Gee.List<Geary.Email>? list_email_including_removed_async(int low, int count,
|
|
|
|
|
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable)
|
|
|
|
|
throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
Geary.Folder.normalize_span_specifiers(ref low, ref count,
|
|
|
|
|
yield get_email_count_including_removed_async(cancellable));
|
2012-01-09 10:58:13 -08:00
|
|
|
|
|
|
|
|
if (count == 0)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
Transaction transaction = yield db.begin_transaction_async(
|
|
|
|
|
"Folder.list_email_including_removed_async", cancellable);
|
|
|
|
|
|
|
|
|
|
Gee.List<MessageLocationRow>? list = yield location_table.list_async(transaction,
|
|
|
|
|
folder_row.id, low, count, true, cancellable);
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
return yield do_list_email_async(transaction, list, required_fields, true, cancellable);
|
2011-06-21 17:48:40 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
2011-11-09 16:40:28 -08:00
|
|
|
int count, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags,
|
|
|
|
|
Cancellable? cancellable = null) throws Error {
|
2011-11-10 13:20:48 -08:00
|
|
|
if (count == 0 || count == 1) {
|
2011-11-09 16:40:28 -08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-15 13:39:02 -07:00
|
|
|
check_open();
|
|
|
|
|
|
2011-11-09 16:40:28 -08:00
|
|
|
Geary.Imap.UID uid = ((Geary.Imap.EmailIdentifier) initial_id).uid;
|
2011-11-10 13:20:48 -08:00
|
|
|
bool excluding_id = flags.is_all_set(Geary.Folder.ListFlags.EXCLUDING_ID);
|
2011-11-09 16:40:28 -08:00
|
|
|
|
|
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.list_email_by_id_async",
|
2011-10-12 16:46:23 -07:00
|
|
|
cancellable);
|
|
|
|
|
|
2011-11-09 16:40:28 -08:00
|
|
|
int64 low, high;
|
|
|
|
|
if (count < 0) {
|
2011-11-10 13:20:48 -08:00
|
|
|
high = excluding_id ? uid.value - 1 : uid.value;
|
2011-11-09 16:40:28 -08:00
|
|
|
low = (count != int.MIN) ? (high + count).clamp(1, uint32.MAX) : -1;
|
|
|
|
|
} else {
|
2011-11-10 13:20:48 -08:00
|
|
|
// count > 1
|
|
|
|
|
low = excluding_id ? uid.value + 1 : uid.value;
|
2011-11-09 16:40:28 -08:00
|
|
|
high = (count != int.MAX) ? (low + count).clamp(1, uint32.MAX) : -1;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
Gee.List<MessageLocationRow>? list = yield location_table.list_ordering_async(transaction,
|
2011-11-09 16:40:28 -08:00
|
|
|
folder_row.id, low, high, cancellable);
|
2011-07-15 13:39:02 -07:00
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
return yield do_list_email_async(transaction, list, required_fields, false, cancellable);
|
2011-07-15 13:39:02 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
private async Gee.List<Geary.Email>? do_list_email_async(Transaction transaction,
|
2012-01-09 10:58:13 -08:00
|
|
|
Gee.List<MessageLocationRow>? list, Geary.Email.Field required_fields,
|
|
|
|
|
bool include_removed, Cancellable? cancellable) throws Error {
|
2011-06-23 19:07:04 -07:00
|
|
|
check_open();
|
|
|
|
|
|
2011-06-16 16:27:08 -07:00
|
|
|
if (list == null || list.size == 0)
|
2011-06-21 17:48:40 -07:00
|
|
|
return null;
|
2011-06-16 16:27:08 -07:00
|
|
|
|
2011-06-23 19:07:04 -07:00
|
|
|
// TODO: As this loop involves multiple database operations to form an email, might make
|
|
|
|
|
// sense in the future to launch each async method separately, putting the final results
|
|
|
|
|
// together when all the information is fetched
|
2011-06-16 16:27:08 -07:00
|
|
|
Gee.List<Geary.Email> emails = new Gee.ArrayList<Geary.Email>();
|
|
|
|
|
foreach (MessageLocationRow location_row in list) {
|
2011-06-23 19:07:04 -07:00
|
|
|
// fetch the message itself
|
2011-11-10 13:20:48 -08:00
|
|
|
MessageRow? message_row = null;
|
|
|
|
|
if (required_fields != Geary.Email.Field.NONE && required_fields != Geary.Email.Field.PROPERTIES) {
|
|
|
|
|
message_row = yield message_table.fetch_async(transaction, location_row.message_id,
|
|
|
|
|
required_fields, cancellable);
|
|
|
|
|
assert(message_row != null);
|
|
|
|
|
|
|
|
|
|
// only add to the list if the email contains all the required fields (because
|
|
|
|
|
// properties comes out of a separate table, skip this if properties are requested)
|
|
|
|
|
if (!message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES)))
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2011-06-16 16:27:08 -07:00
|
|
|
|
2011-07-08 12:45:22 -07:00
|
|
|
ImapMessagePropertiesRow? properties = null;
|
2011-07-18 15:57:55 -07:00
|
|
|
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
|
2011-10-12 16:46:23 -07:00
|
|
|
properties = yield imap_message_properties_table.fetch_async(transaction,
|
|
|
|
|
location_row.message_id, cancellable);
|
2011-07-18 15:57:55 -07:00
|
|
|
if (properties == null)
|
|
|
|
|
continue;
|
2011-07-08 12:45:22 -07:00
|
|
|
}
|
|
|
|
|
|
2011-07-19 15:55:56 -07:00
|
|
|
Geary.Imap.UID uid = new Geary.Imap.UID(location_row.ordering);
|
2012-01-09 10:58:13 -08:00
|
|
|
int position = yield location_row.get_position_async(transaction, include_removed,
|
|
|
|
|
cancellable);
|
2011-07-29 20:10:43 -07:00
|
|
|
if (position == -1) {
|
|
|
|
|
debug("Unable to locate position of email during list of %s, dropping", to_string());
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2011-07-19 15:55:56 -07:00
|
|
|
|
2011-11-10 13:20:48 -08:00
|
|
|
Geary.Imap.EmailIdentifier email_id = new Geary.Imap.EmailIdentifier(uid);
|
|
|
|
|
|
|
|
|
|
Geary.Email email = (message_row != null)
|
|
|
|
|
? message_row.to_email(position, email_id)
|
|
|
|
|
: new Geary.Email(position, email_id);
|
|
|
|
|
|
2011-07-08 12:45:22 -07:00
|
|
|
if (properties != null)
|
|
|
|
|
email.set_email_properties(properties.get_imap_email_properties());
|
|
|
|
|
|
|
|
|
|
emails.add(email);
|
2011-06-16 16:27:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (emails.size > 0) ? emails : null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
|
2011-07-19 15:55:56 -07:00
|
|
|
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
|
2011-06-23 19:07:04 -07:00
|
|
|
check_open();
|
|
|
|
|
|
2011-07-19 15:55:56 -07:00
|
|
|
Geary.Imap.UID uid = ((Imap.EmailIdentifier) id).uid;
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.fetch_email_async",
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(transaction,
|
|
|
|
|
folder_row.id, uid.value, cancellable);
|
2011-07-01 15:40:20 -07:00
|
|
|
if (location_row == null) {
|
2011-07-19 15:55:56 -07:00
|
|
|
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
|
2011-07-01 15:40:20 -07:00
|
|
|
to_string());
|
|
|
|
|
}
|
2011-06-21 17:48:40 -07:00
|
|
|
|
2012-01-09 10:58:13 -08:00
|
|
|
int position = yield location_row.get_position_async(transaction, false, cancellable);
|
2011-11-10 13:20:48 -08:00
|
|
|
if (position == -1) {
|
|
|
|
|
throw new EngineError.NOT_FOUND("Unable to determine position of email %s in %s",
|
|
|
|
|
id.to_string(), to_string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// loopback on perverse case
|
|
|
|
|
if (required_fields == Geary.Email.Field.NONE)
|
|
|
|
|
return new Geary.Email(position, id);
|
|
|
|
|
|
2011-11-15 17:31:36 -08:00
|
|
|
// Only fetch message row if we have fields other than Properties.
|
|
|
|
|
MessageRow? message_row = null;
|
|
|
|
|
if (required_fields != Geary.Email.Field.PROPERTIES) {
|
|
|
|
|
message_row = yield message_table.fetch_async(transaction,
|
|
|
|
|
location_row.message_id, required_fields, cancellable);
|
|
|
|
|
if (message_row == null) {
|
|
|
|
|
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
|
|
|
|
|
to_string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// see if the message row fulfills everything but properties, which are held in
|
|
|
|
|
// separate table
|
|
|
|
|
if (!message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES))) {
|
|
|
|
|
throw new EngineError.INCOMPLETE_MESSAGE(
|
|
|
|
|
"Message %s in folder %s only fulfills %Xh fields (required: %Xh)", id.to_string(),
|
|
|
|
|
to_string(), message_row.fields, required_fields);
|
|
|
|
|
}
|
2011-06-23 19:07:04 -07:00
|
|
|
}
|
|
|
|
|
|
2011-07-08 12:45:22 -07:00
|
|
|
ImapMessagePropertiesRow? properties = null;
|
2011-07-15 14:29:33 -07:00
|
|
|
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
|
2011-10-12 16:46:23 -07:00
|
|
|
properties = yield imap_message_properties_table.fetch_async(transaction,
|
|
|
|
|
location_row.message_id, cancellable);
|
2011-07-19 15:55:56 -07:00
|
|
|
if (properties == null) {
|
|
|
|
|
throw new EngineError.INCOMPLETE_MESSAGE(
|
|
|
|
|
"Message %s in folder %s does not have PROPERTIES field", id.to_string(),
|
|
|
|
|
to_string());
|
|
|
|
|
}
|
2011-07-08 12:45:22 -07:00
|
|
|
}
|
|
|
|
|
|
2011-11-15 17:31:36 -08:00
|
|
|
Geary.Email email;
|
|
|
|
|
email = message_row != null ? message_row.to_email(position, id) : email =
|
|
|
|
|
new Geary.Email(position, id);
|
|
|
|
|
|
2011-07-08 12:45:22 -07:00
|
|
|
if (properties != null)
|
|
|
|
|
email.set_email_properties(properties.get_imap_email_properties());
|
|
|
|
|
|
|
|
|
|
return email;
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
2011-06-23 19:07:04 -07:00
|
|
|
|
2011-07-15 13:39:02 -07:00
|
|
|
public async Geary.Imap.UID? get_earliest_uid_async(Cancellable? cancellable = null) throws Error {
|
2012-05-02 21:22:52 -07:00
|
|
|
return yield get_uid_extremes_async(true, cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Geary.Imap.UID? get_latest_uid_async(Cancellable? cancellable = null) throws Error {
|
|
|
|
|
return yield get_uid_extremes_async(false, cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Geary.Imap.UID? get_uid_extremes_async(bool earliest, Cancellable? cancellable)
|
|
|
|
|
throws Error {
|
2011-07-15 13:39:02 -07:00
|
|
|
check_open();
|
|
|
|
|
|
2012-05-02 21:22:52 -07:00
|
|
|
int64 ordering = yield location_table.get_ordering_extremes_async(null, folder_row.id,
|
|
|
|
|
earliest, cancellable);
|
2011-07-15 13:39:02 -07:00
|
|
|
|
|
|
|
|
return (ordering >= 1) ? new Geary.Imap.UID(ordering) : null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async void remove_single_email_async(Geary.EmailIdentifier email_id,
|
2011-12-15 16:16:27 -08:00
|
|
|
Cancellable? cancellable = null) throws Error {
|
2011-07-15 13:39:02 -07:00
|
|
|
// TODO: Right now, deleting an email is merely detaching its association with a folder
|
|
|
|
|
// (since it may be located in multiple folders). This means at some point in the future
|
|
|
|
|
// a vacuum will be required to remove emails that are completely unassociated with the
|
|
|
|
|
// account
|
2012-03-14 11:05:32 -07:00
|
|
|
if (!yield location_table.remove_by_ordering_async(null, folder_row.id, email_id.ordering,
|
|
|
|
|
cancellable)) {
|
|
|
|
|
throw new EngineError.NOT_FOUND("Message %s not found in %s", email_id.to_string(),
|
|
|
|
|
to_string());
|
2011-07-29 20:10:43 -07:00
|
|
|
}
|
2011-07-15 13:39:02 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-14 11:05:32 -07:00
|
|
|
public async void mark_email_async(
|
2011-12-13 16:13:20 -08:00
|
|
|
Gee.List<Geary.EmailIdentifier> to_mark, Geary.EmailFlags? flags_to_add,
|
|
|
|
|
Geary.EmailFlags? flags_to_remove, Cancellable? cancellable = null) throws Error {
|
|
|
|
|
|
2012-01-09 10:58:13 -08:00
|
|
|
Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map = yield get_email_flags_async(
|
|
|
|
|
to_mark, cancellable);
|
|
|
|
|
|
|
|
|
|
foreach (Geary.EmailIdentifier id in map.keys) {
|
|
|
|
|
if (flags_to_add != null)
|
|
|
|
|
foreach (Geary.EmailFlag flag in flags_to_add.get_all())
|
|
|
|
|
((Geary.Imap.EmailFlags) map.get(id)).add(flag);
|
|
|
|
|
|
|
|
|
|
if (flags_to_remove != null)
|
|
|
|
|
foreach (Geary.EmailFlag flag in flags_to_remove.get_all())
|
|
|
|
|
((Geary.Imap.EmailFlags) map.get(id)).remove(flag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield set_email_flags_async(map, cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> get_email_flags_async(
|
|
|
|
|
Gee.List<Geary.EmailIdentifier> to_get, Cancellable? cancellable) throws Error {
|
|
|
|
|
|
2012-03-13 16:03:42 -07:00
|
|
|
Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map = new Gee.HashMap<
|
|
|
|
|
Geary.EmailIdentifier, Geary.EmailFlags>(Hashable.hash_func, Equalable.equal_func);
|
2012-01-09 10:58:13 -08:00
|
|
|
|
|
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.get_email_flags_async",
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
foreach (Geary.EmailIdentifier id in to_get) {
|
|
|
|
|
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(
|
|
|
|
|
transaction, folder_row.id, ((Geary.Imap.EmailIdentifier) id).uid.value,
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
if (location_row == null) {
|
|
|
|
|
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
|
|
|
|
|
to_string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImapMessagePropertiesRow? row = yield imap_message_properties_table.fetch_async(
|
2012-04-17 10:29:41 -07:00
|
|
|
transaction, location_row.message_id, cancellable);
|
2012-01-09 10:58:13 -08:00
|
|
|
if (row == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
map.set(id, row.get_imap_email_properties().email_flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield transaction.commit_async(cancellable);
|
|
|
|
|
|
|
|
|
|
return map;
|
2011-12-13 16:13:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async void set_email_flags_async(Gee.Map<Geary.EmailIdentifier,
|
|
|
|
|
Geary.EmailFlags> map, Cancellable? cancellable) throws Error {
|
2011-12-07 18:46:05 -08:00
|
|
|
check_open();
|
|
|
|
|
|
2012-01-09 10:58:13 -08:00
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.set_email_flags_async",
|
2011-12-07 18:46:05 -08:00
|
|
|
cancellable);
|
|
|
|
|
|
2011-12-13 16:13:20 -08:00
|
|
|
foreach (Geary.EmailIdentifier id in map.keys) {
|
2011-12-07 18:46:05 -08:00
|
|
|
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(
|
|
|
|
|
transaction, folder_row.id, ((Geary.Imap.EmailIdentifier) id).uid.value, cancellable);
|
|
|
|
|
if (location_row == null) {
|
|
|
|
|
throw new EngineError.NOT_FOUND("No message with ID %s in folder %s", id.to_string(),
|
|
|
|
|
to_string());
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-13 16:13:20 -08:00
|
|
|
Geary.Imap.MessageFlags flags = ((Geary.Imap.EmailFlags) map.get(id)).message_flags;
|
2011-12-07 18:46:05 -08:00
|
|
|
|
2012-04-17 10:29:41 -07:00
|
|
|
yield imap_message_properties_table.update_flags_async(transaction, location_row.message_id,
|
2011-12-13 16:13:20 -08:00
|
|
|
flags.serialize(), cancellable);
|
2011-12-07 18:46:05 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield transaction.commit_async(cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
public async bool is_email_present_async(Geary.EmailIdentifier id, out Geary.Email.Field available_fields,
|
2011-06-23 19:07:04 -07:00
|
|
|
Cancellable? cancellable = null) throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
2011-07-19 15:55:56 -07:00
|
|
|
Geary.Imap.UID uid = ((Imap.EmailIdentifier) id).uid;
|
|
|
|
|
|
2011-06-23 19:07:04 -07:00
|
|
|
available_fields = Geary.Email.Field.NONE;
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.is_email_present",
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
MessageLocationRow? location_row = yield location_table.fetch_by_ordering_async(transaction,
|
|
|
|
|
folder_row.id, uid.value, cancellable);
|
2011-06-23 19:07:04 -07:00
|
|
|
if (location_row == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
return yield message_table.fetch_fields_async(transaction, location_row.message_id,
|
|
|
|
|
out available_fields, cancellable);
|
2011-06-23 19:07:04 -07:00
|
|
|
}
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
private async void merge_email_async(Transaction transaction, int64 message_id, Geary.Email email,
|
2011-06-23 19:07:04 -07:00
|
|
|
Cancellable? cancellable = null) throws Error {
|
|
|
|
|
assert(message_id != Row.INVALID_ID);
|
|
|
|
|
|
|
|
|
|
// if nothing to merge, nothing to do
|
|
|
|
|
if (email.fields == Geary.Email.Field.NONE)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-11-10 13:20:48 -08:00
|
|
|
if (email.fields != Geary.Email.Field.PROPERTIES) {
|
|
|
|
|
MessageRow? message_row = yield message_table.fetch_async(transaction, message_id, email.fields,
|
|
|
|
|
cancellable);
|
|
|
|
|
assert(message_row != null);
|
2011-07-08 12:45:22 -07:00
|
|
|
|
2011-11-14 12:09:52 -08:00
|
|
|
message_row.merge_from_remote(email);
|
2011-11-10 13:20:48 -08:00
|
|
|
|
|
|
|
|
// possible nothing has changed or been added
|
|
|
|
|
if (message_row.fields != Geary.Email.Field.NONE)
|
|
|
|
|
yield message_table.merge_async(transaction, message_row, cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-08 12:45:22 -07:00
|
|
|
// update IMAP properties
|
|
|
|
|
if (email.fields.fulfills(Geary.Email.Field.PROPERTIES)) {
|
2011-07-15 13:39:02 -07:00
|
|
|
Geary.Imap.EmailProperties properties = (Geary.Imap.EmailProperties) email.properties;
|
|
|
|
|
string? internaldate =
|
|
|
|
|
(properties.internaldate != null) ? properties.internaldate.original : null;
|
|
|
|
|
long rfc822_size =
|
|
|
|
|
(properties.rfc822_size != null) ? properties.rfc822_size.value : -1;
|
|
|
|
|
|
2011-10-12 16:46:23 -07:00
|
|
|
yield imap_message_properties_table.update_async(transaction, message_id,
|
2011-12-13 16:13:20 -08:00
|
|
|
properties.get_message_flags().serialize(), internaldate, rfc822_size, cancellable);
|
2011-07-08 12:45:22 -07:00
|
|
|
}
|
2011-06-23 19:07:04 -07:00
|
|
|
}
|
2012-01-09 10:58:13 -08:00
|
|
|
|
|
|
|
|
public async void remove_marked_email_async(Geary.EmailIdentifier id, out bool marked,
|
|
|
|
|
Cancellable? cancellable) throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
|
|
|
|
Transaction transaction = yield db.begin_transaction_async(
|
|
|
|
|
"Folder.remove_marked_email_async", cancellable);
|
|
|
|
|
|
|
|
|
|
// Get marked status.
|
|
|
|
|
marked = yield location_table.is_marked_removed_async(transaction, folder_row.id,
|
|
|
|
|
id.ordering, cancellable);
|
|
|
|
|
|
|
|
|
|
// Detaching email's association with a folder.
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield transaction.commit_async(cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async void mark_removed_async(Geary.EmailIdentifier id, bool remove,
|
|
|
|
|
Cancellable? cancellable) throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
|
|
|
|
Transaction transaction = yield db.begin_transaction_async("Folder.mark_removed_async",
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
yield location_table.mark_removed_async(transaction, folder_row.id, id.ordering,
|
|
|
|
|
remove, cancellable);
|
|
|
|
|
yield transaction.commit_async(cancellable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Geary.EmailIdentifier? id_from_remote_position(int remote_position,
|
|
|
|
|
int remote_count) throws Error {
|
|
|
|
|
Geary.EmailIdentifier? id = null;
|
|
|
|
|
|
|
|
|
|
debug("id from remote position: pos = %d, count = %d", remote_position, remote_count);
|
|
|
|
|
|
|
|
|
|
// Get local count, convert remote to local position.
|
|
|
|
|
int local_count = yield get_email_count_including_removed_async();
|
|
|
|
|
int local_position = remote_position - (remote_count - local_count);
|
|
|
|
|
|
|
|
|
|
// possible we don't have the remote email locally
|
|
|
|
|
if (local_position >= 1) {
|
|
|
|
|
// get EmailIdentifier
|
|
|
|
|
Gee.List<Geary.Email>? local = yield list_email_including_removed_async(local_position, 1,
|
|
|
|
|
Geary.Email.Field.NONE, Geary.Folder.ListFlags.NONE, null);
|
|
|
|
|
if (local != null && local.size == 1) {
|
|
|
|
|
id = local[0].id;
|
|
|
|
|
} else {
|
|
|
|
|
debug("list_email_async unable to convert position %d into id (count=%d)",
|
|
|
|
|
local_position, local_count);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
debug("Unable to get local position for remote position %d (local_count=%d remote_count=%d)",
|
|
|
|
|
remote_position, local_count, remote_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return id;
|
|
|
|
|
}
|
2012-03-13 16:03:42 -07:00
|
|
|
|
2012-03-22 16:57:18 -07:00
|
|
|
public async Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>? list_email_fields_by_id_async(
|
2012-03-13 16:03:42 -07:00
|
|
|
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
|
|
|
|
|
check_open();
|
|
|
|
|
|
|
|
|
|
if (ids.size == 0)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
Gee.HashMap<Geary.EmailIdentifier, Geary.Email.Field> map = new Gee.HashMap<
|
|
|
|
|
Geary.EmailIdentifier, Geary.Email.Field>(Hashable.hash_func, Equalable.equal_func);
|
|
|
|
|
|
|
|
|
|
Transaction transaction = yield db.begin_transaction_async("get_email_fields_by_id_async",
|
|
|
|
|
cancellable);
|
|
|
|
|
|
|
|
|
|
foreach (Geary.EmailIdentifier id in ids) {
|
|
|
|
|
MessageLocationRow? row = yield location_table.fetch_by_ordering_async(transaction,
|
|
|
|
|
folder_row.id, ((Geary.Imap.EmailIdentifier) id).uid.value, cancellable);
|
|
|
|
|
if (row == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
Geary.Email.Field fields;
|
|
|
|
|
if (yield message_table.fetch_fields_async(transaction, row.message_id, out fields,
|
|
|
|
|
cancellable)) {
|
|
|
|
|
map.set(id, fields);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (map.size > 0) ? map : null;
|
|
|
|
|
}
|
2012-03-14 11:05:32 -07:00
|
|
|
|
|
|
|
|
public string to_string() {
|
|
|
|
|
return path.to_string();
|
|
|
|
|
}
|
2011-06-10 19:17:35 -07:00
|
|
|
}
|
|
|
|
|
|