New file naming scheme and organization for the Engine.
The code base is growing much faster than expected, faster than Shotwell it seems (not necessarily line count, but files and necessary organization of the library vs. Shotwell's initial flat directory). After some thought decided to move to a more standard Vala/GTK naming scheme of all lowercase with dashes for spaces starting with namespace (minus the "geary-", unless the class was in the topmost namespace). Three motivations: 1. Often confusing when working on code to see three "Folder.vala" in the gedit tabs: one IMAP, one SQLite, and one the interface definition. 2. This paves the way for waf integration, as right now we're held up using it because it barfs on projects with two files of the same name in different directories. 3. I find the CamelCase in the file browser becoming hard on the eyes, and this scheme seems a little more browsable.
This commit is contained in:
parent
f94018474b
commit
41faa36103
78 changed files with 83 additions and 77 deletions
310
src/engine/sqlite/api/sqlite-folder.vala
Normal file
310
src/engine/sqlite/api/sqlite-folder.vala
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
// TODO: This class currently deals with generic email storage as well as IMAP-specific issues; in
|
||||
// the future, to support other email services, will need to break this up.
|
||||
|
||||
public class Geary.Sqlite.Folder : Object, Geary.Folder, Geary.LocalFolder {
|
||||
private MailDatabase db;
|
||||
private FolderRow folder_row;
|
||||
private MessageTable message_table;
|
||||
private MessageLocationTable location_table;
|
||||
private ImapMessageLocationPropertiesTable imap_location_table;
|
||||
private string name;
|
||||
private bool opened = false;
|
||||
|
||||
internal Folder(MailDatabase db, FolderRow folder_row) throws Error {
|
||||
this.db = db;
|
||||
this.folder_row = folder_row;
|
||||
|
||||
name = folder_row.name;
|
||||
|
||||
message_table = db.get_message_table();
|
||||
location_table = db.get_message_location_table();
|
||||
imap_location_table = db.get_imap_message_location_table();
|
||||
}
|
||||
|
||||
private void check_open() throws Error {
|
||||
if (!opened)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not open", to_string());
|
||||
}
|
||||
|
||||
public string get_name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Geary.FolderProperties? get_properties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
|
||||
if (opened)
|
||||
throw new EngineError.ALREADY_OPEN("%s already open", to_string());
|
||||
|
||||
opened = true;
|
||||
notify_opened();
|
||||
}
|
||||
|
||||
public async void close_async(Cancellable? cancellable = null) throws Error {
|
||||
if (!opened)
|
||||
return;
|
||||
|
||||
opened = false;
|
||||
notify_closed(CloseReason.FOLDER_CLOSED);
|
||||
}
|
||||
|
||||
public async int get_email_count(Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
public async void create_email_async(Geary.Email email, Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
|
||||
|
||||
// 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 imap_location_table.search_uid_in_folder(location.uid, folder_row.id, out message_id,
|
||||
cancellable)) {
|
||||
throw new EngineError.ALREADY_EXISTS("Email with UID %s already exists in %s",
|
||||
location.uid.to_string(), get_name());
|
||||
}
|
||||
|
||||
message_id = yield message_table.create_async(
|
||||
new MessageRow.from_email(message_table, email),
|
||||
cancellable);
|
||||
|
||||
MessageLocationRow location_row = new MessageLocationRow(location_table, Row.INVALID_ID,
|
||||
message_id, folder_row.id, location.position);
|
||||
int64 location_id = yield location_table.create_async(location_row, cancellable);
|
||||
|
||||
ImapMessageLocationPropertiesRow imap_location_row = new ImapMessageLocationPropertiesRow(
|
||||
imap_location_table, Row.INVALID_ID, location_id, location.uid);
|
||||
yield imap_location_table.create_async(imap_location_row, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
||||
Geary.Email.Field required_fields, Cancellable? cancellable) throws Error {
|
||||
assert(low >= 1);
|
||||
assert(count >= 1);
|
||||
|
||||
check_open();
|
||||
|
||||
Gee.List<MessageLocationRow>? list = yield location_table.list_async(folder_row.id, low,
|
||||
count, cancellable);
|
||||
|
||||
return yield list_email(list, required_fields, cancellable);
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
|
||||
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Gee.List<MessageLocationRow>? list = yield location_table.list_sparse_async(folder_row.id,
|
||||
by_position, cancellable);
|
||||
|
||||
return yield list_email(list, required_fields, cancellable);
|
||||
}
|
||||
|
||||
private async Gee.List<Geary.Email>? list_email(Gee.List<MessageLocationRow>? list,
|
||||
Geary.Email.Field required_fields, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
if (list == null || list.size == 0)
|
||||
return null;
|
||||
|
||||
// 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
|
||||
Gee.List<Geary.Email> emails = new Gee.ArrayList<Geary.Email>();
|
||||
foreach (MessageLocationRow location_row in list) {
|
||||
// fetch the IMAP message location properties that are associated with the generic
|
||||
// message location
|
||||
ImapMessageLocationPropertiesRow? imap_location_row = yield imap_location_table.fetch_async(
|
||||
location_row.id, cancellable);
|
||||
assert(imap_location_row != null);
|
||||
|
||||
// fetch the message itself
|
||||
MessageRow? message_row = yield message_table.fetch_async(location_row.message_id,
|
||||
required_fields, cancellable);
|
||||
assert(message_row != null);
|
||||
// only add to the list if the email contains all the required fields
|
||||
if (!message_row.fields.is_set(required_fields))
|
||||
continue;
|
||||
|
||||
emails.add(message_row.to_email(new Geary.Imap.EmailLocation(location_row.position,
|
||||
imap_location_row.uid)));
|
||||
}
|
||||
|
||||
return (emails.size > 0) ? emails : null;
|
||||
}
|
||||
|
||||
public async Geary.Email fetch_email_async(int position, Geary.Email.Field required_fields,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
assert(position >= 1);
|
||||
|
||||
check_open();
|
||||
|
||||
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id, position,
|
||||
cancellable);
|
||||
if (location_row == null)
|
||||
throw new EngineError.NOT_FOUND("No message at position %d in folder %s", position, name);
|
||||
|
||||
assert(location_row.position == position);
|
||||
|
||||
ImapMessageLocationPropertiesRow? imap_location_row = yield imap_location_table.fetch_async(
|
||||
location_row.id, cancellable);
|
||||
if (imap_location_row == null) {
|
||||
throw new EngineError.NOT_FOUND("No IMAP location properties at position %d in %s",
|
||||
position, name);
|
||||
}
|
||||
|
||||
assert(imap_location_row.location_id == location_row.id);
|
||||
|
||||
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, name);
|
||||
|
||||
if (!message_row.fields.is_set(required_fields)) {
|
||||
throw new EngineError.INCOMPLETE_MESSAGE(
|
||||
"Message at position %d in folder %s only fulfills %Xh fields", position, to_string(),
|
||||
message_row.fields);
|
||||
}
|
||||
|
||||
return message_row.to_email(new Geary.Imap.EmailLocation(location_row.position,
|
||||
imap_location_row.uid));
|
||||
}
|
||||
|
||||
public async bool is_email_present_at(int position, out Geary.Email.Field available_fields,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
available_fields = Geary.Email.Field.NONE;
|
||||
|
||||
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id, position,
|
||||
cancellable);
|
||||
if (location_row == null)
|
||||
return false;
|
||||
|
||||
return yield message_table.fetch_fields_async(location_row.message_id, out available_fields,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public async bool is_email_associated_async(Geary.Email email, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
int64 message_id;
|
||||
return yield imap_location_table.search_uid_in_folder(
|
||||
((Geary.Imap.EmailLocation) email.location).uid, folder_row.id, out message_id,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public async void update_email_async(Geary.Email email, bool duplicate_okay,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
|
||||
|
||||
// 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 imap_location_table.search_uid_in_folder(location.uid, folder_row.id,
|
||||
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
|
||||
// a merge
|
||||
if (email.message_id == null) {
|
||||
if (!associated) {
|
||||
if (!duplicate_okay)
|
||||
throw new EngineError.INCOMPLETE_MESSAGE("No Message-ID");
|
||||
|
||||
yield create_email_async(email, cancellable);
|
||||
} else {
|
||||
yield merge_email_async(message_id, email, cancellable);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If not associated, find message with matching Message-ID
|
||||
if (!associated) {
|
||||
Gee.List<int64?>? list = yield message_table.search_message_id_async(email.message_id,
|
||||
cancellable);
|
||||
|
||||
// If none found, this operation is a create
|
||||
if (list == null || list.size == 0) {
|
||||
yield create_email_async(email, cancellable);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Too many found turns this operation into a create
|
||||
if (list.size != 1) {
|
||||
yield create_email_async(email, cancellable);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
message_id = list[0];
|
||||
}
|
||||
|
||||
// Found a message. If not associated with this folder, associate now.
|
||||
// TODO: Need to lock the database during this operation, as these steps should be atomic.
|
||||
if (!associated) {
|
||||
// see if an email exists at this position
|
||||
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id,
|
||||
location.position);
|
||||
if (location_row != null) {
|
||||
throw new EngineError.ALREADY_EXISTS("Email already exists at position %d in %s",
|
||||
email.location.position, to_string());
|
||||
}
|
||||
|
||||
// insert email at supplied position
|
||||
location_row = new MessageLocationRow(location_table, Row.INVALID_ID, message_id,
|
||||
folder_row.id, location.position);
|
||||
int64 location_id = yield location_table.create_async(location_row, cancellable);
|
||||
|
||||
// update position propeties
|
||||
ImapMessageLocationPropertiesRow imap_location_row = new ImapMessageLocationPropertiesRow(
|
||||
imap_location_table, Row.INVALID_ID, location_id, location.uid);
|
||||
yield imap_location_table.create_async(imap_location_row, cancellable);
|
||||
}
|
||||
|
||||
// Merge any new information with the existing message in the local store
|
||||
yield merge_email_async(message_id, email, cancellable);
|
||||
|
||||
// Done.
|
||||
}
|
||||
|
||||
// TODO: The database should be locked around this method, as it should be atomic.
|
||||
// TODO: Merge email properties
|
||||
private async void merge_email_async(int64 message_id, Geary.Email email,
|
||||
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;
|
||||
|
||||
MessageRow? message_row = yield message_table.fetch_async(message_id, email.fields,
|
||||
cancellable);
|
||||
assert(message_row != null);
|
||||
|
||||
message_row.merge_from_network(email);
|
||||
|
||||
// possible nothing has changed or been added
|
||||
if (message_row.fields != Geary.Email.Field.NONE)
|
||||
yield message_table.merge_async(message_row, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue