Further work toward persisting messages locally (#3742).

This iteration now stores headers locally and fetches them first before going to the network.  Work done in the database to deal with IMAPisms.  More work on the GMime bindings (couple of mistakes in prior commit).
This commit is contained in:
Jim Nelson 2011-06-21 17:48:40 -07:00
parent 2fbe93afce
commit 4ccabcbd3e
27 changed files with 651 additions and 300 deletions

View file

@ -15,7 +15,7 @@ ENGINE_SRC := \
src/engine/api/Account.vala \
src/engine/api/Email.vala \
src/engine/api/EmailProperties.vala \
src/engine/api/EmailOrdering.vala \
src/engine/api/EmailLocation.vala \
src/engine/api/Folder.vala \
src/engine/api/FolderProperties.vala \
src/engine/api/Credentials.vala \
@ -30,6 +30,8 @@ ENGINE_SRC := \
src/engine/sqlite/MessageTable.vala \
src/engine/sqlite/MessageLocationRow.vala \
src/engine/sqlite/MessageLocationTable.vala \
src/engine/sqlite/ImapMessageLocationPropertiesTable.vala \
src/engine/sqlite/ImapMessageLocationPropertiesRow.vala \
src/engine/sqlite/api/Account.vala \
src/engine/sqlite/api/Folder.vala \
src/engine/state/Machine.vala \
@ -70,10 +72,12 @@ ENGINE_SRC := \
src/engine/imap/decoders/SelectExamineResults.vala \
src/engine/imap/decoders/StatusResults.vala \
src/engine/imap/api/Account.vala \
src/engine/imap/api/EmailLocation.vala \
src/engine/imap/api/EmailProperties.vala \
src/engine/imap/api/Folder.vala \
src/engine/imap/api/FolderProperties.vala \
src/engine/rfc822/MailboxAddress.vala \
src/engine/rfc822/MailboxAddresses.vala \
src/engine/rfc822/MessageData.vala \
src/engine/util/Memory.vala \
src/engine/util/ReferenceSemantics.vala \

View file

@ -50,7 +50,7 @@ CREATE TABLE MessageLocationTable (
id INTEGER PRIMARY KEY,
message_id INTEGER REFERENCES MessageTable ON DELETE CASCADE,
folder_id INTEGER REFERENCES FolderTable ON DELETE CASCADE,
ordering INTEGER
position INTEGER
);
CREATE INDEX MessageLocationTableMessageIDIndex ON MessageLocationTable(message_id);
@ -92,3 +92,15 @@ CREATE TABLE ImapMessagePropertiesTable (
CREATE INDEX ImapMessagePropertiesTableMessageIDIndex ON ImapMessagePropertiesTable(message_id);
--
-- ImapMessageLocationPropertiesTable
--
CREATE TABLE ImapMessageLocationPropertiesTable (
id INTEGER PRIMARY KEY,
location_id INTEGER UNIQUE REFERENCES MessageLocationTable ON DELETE CASCADE,
uid INTEGER
);
CREATE INDEX ImapMessageLocationPropertiesTableLocationIDIndex ON ImapMessageLocationPropertiesTable(location_id);

View file

@ -68,14 +68,6 @@ public class MainWindow : Gtk.Window {
do_start.begin();
}
private void on_folders_added_removed(Gee.Collection<Geary.Folder>? added,
Gee.Collection<Geary.Folder>? removed) {
if (added != null) {
folder_list_store.add_folders(added);
debug("%d folders added", added.size);
}
}
private async void do_start() {
try {
// pull down the root-level folders
@ -196,10 +188,13 @@ public class MainWindow : Gtk.Window {
private async void do_select_folder(Geary.Folder folder) throws Error {
message_list_store.clear();
if (current_folder != null)
if (current_folder != null) {
current_folder.email_added_removed.disconnect(on_email_added_removed);
yield current_folder.close_async();
}
current_folder = folder;
current_folder.email_added_removed.connect(on_email_added_removed);
yield current_folder.open_async(true);
@ -207,7 +202,7 @@ public class MainWindow : Gtk.Window {
Geary.Email.Field.ENVELOPE);
if (email != null && email.size > 0) {
foreach (Geary.Email envelope in email)
message_list_store.append_header(envelope);
message_list_store.append_envelope(envelope);
}
}
@ -236,7 +231,8 @@ public class MainWindow : Gtk.Window {
return;
}
Geary.Email text = yield current_folder.fetch_email_async(email.msg_num, Geary.Email.Field.BODY);
Geary.Email text = yield current_folder.fetch_email_async(email.location.position,
Geary.Email.Field.BODY);
message_buffer.set_text(text.body.buffer.to_ascii_string());
}
@ -247,5 +243,20 @@ public class MainWindow : Gtk.Window {
debug("Unable to select message: %s", err.message);
}
}
private void on_folders_added_removed(Gee.Collection<Geary.Folder>? added,
Gee.Collection<Geary.Folder>? removed) {
if (added != null) {
folder_list_store.add_folders(added);
debug("%d folders added", added.size);
}
}
private void on_email_added_removed(Gee.List<Geary.Email>? added, Gee.List<Geary.Folder>? removed) {
if (added != null) {
foreach (Geary.Email email in added)
message_list_store.append_envelope(email);
}
}
}

View file

@ -54,7 +54,8 @@ public class MessageListStore : Gtk.TreeStore {
set_column_types(Column.get_types());
}
public void append_header(Geary.Email envelope) {
// The Email should've been fetched with Geary.Email.Field.ENVELOPE, at least.
public void append_envelope(Geary.Email envelope) {
Gtk.TreeIter iter;
append(out iter, null);

View file

@ -30,7 +30,7 @@ private class Geary.EngineFolder : Object, Geary.Folder {
return null;
}
public async void create_email_async(Geary.Email email, Geary.EmailOrdering ordering,
public async void create_email_async(Geary.Email email, Geary.Email.Field fields,
Cancellable? cancellable) throws Error {
throw new EngineError.READONLY("Engine currently read-only");
}
@ -57,9 +57,111 @@ private class Geary.EngineFolder : Object, Geary.Folder {
return 0;
}
public async Gee.List<Geary.Email> list_email_async(int low, int count, Geary.Email.Field fields,
public async Gee.List<Geary.Email>? list_email_async(int low, int count, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
return yield net_folder.list_email_async(low, count, fields, cancellable);
assert(low >= 1);
assert(count >= 0);
if (count == 0)
return null;
Gee.List<Geary.Email>? local_list = yield local_folder.list_email_async(low, count, fields,
cancellable);
int local_list_size = (local_list != null) ? local_list.size : 0;
debug("local list found %d", local_list_size);
if (net_folder != null && local_list_size != count) {
// 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
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++;
if (index >= local_list_size || local_list[index].location.position != position)
needed_by_position += position;
}
if (needed_by_position.length != 0)
background_update_email_list.begin(needed_by_position, fields, cancellable);
}
return local_list;
}
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
if (by_position.length == 0)
return null;
Gee.List<Geary.Email>? local_list = yield local_folder.list_email_sparse_async(by_position,
fields, cancellable);
int local_list_size = (local_list != null) ? local_list.size : 0;
if (net_folder != null && local_list_size != by_position.length) {
// go through the list looking for anything not already in the sparse by_position list
// to fetch from the server; since by_position is not guaranteed to be sorted, the local
// list needs to be searched each iteration.
//
// TODO: Optimize this, especially if large lists/sparse sets are supplied
int[] needed_by_position = new int[0];
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) {
found = true;
break;
}
}
}
if (!found)
needed_by_position += position;
}
if (needed_by_position.length != 0)
background_update_email_list.begin(needed_by_position, fields, cancellable);
}
return local_list;
}
private async void background_update_email_list(int[] needed_by_position, Geary.Email.Field fields,
Cancellable? cancellable) {
debug("Background fetching %d emails for %s", needed_by_position.length, get_name());
Gee.List<Geary.Email>? net_list = null;
try {
net_list = yield net_folder.list_email_sparse_async(needed_by_position, fields,
cancellable);
} catch (Error net_err) {
message("Unable to fetch emails from server: %s", net_err.message);
if (net_err is IOError.CANCELLED)
return;
}
if (net_list != null && net_list.size == 0)
net_list = null;
if (net_list != null)
notify_email_added_removed(net_list, null);
if (net_list != null) {
foreach (Geary.Email email in net_list) {
try {
yield local_folder.create_email_async(email, fields, cancellable);
} catch (Error local_err) {
message("Unable to create email in local store: %s", local_err.message);
if (local_err is IOError.CANCELLED)
return;
}
}
}
}
public async Geary.Email fetch_email_async(int num, Geary.Email.Field fields,

View file

@ -33,7 +33,7 @@ public class Geary.Email : Object {
}
}
public int msg_num { get; private set; }
public Geary.EmailLocation location { get; private set; }
// DATE
public Geary.RFC822.Date? date = null;
@ -64,8 +64,8 @@ public class Geary.Email : Object {
// PROPERTIES
public Geary.EmailProperties? properties = null;
public Email(int msg_num) {
this.msg_num = msg_num;
public Email(Geary.EmailLocation location) {
this.location = location;
}
public string to_string() {

View file

@ -4,11 +4,11 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.EmailOrdering {
public int64 ordinal { get; private set; }
public class Geary.EmailLocation : Object {
public int position { get; private set; }
public EmailOrdering(int64 ordinal) {
this.ordinal = ordinal;
public EmailLocation(int position) {
this.position = position;
}
}

View file

@ -15,6 +15,9 @@ public interface Geary.Folder : Object {
public signal void closed(CloseReason reason);
public signal void email_added_removed(Gee.List<Geary.Email>? added,
Gee.List<Geary.Email>? removed);
public signal void updated();
public virtual void notify_opened() {
@ -25,6 +28,11 @@ public interface Geary.Folder : Object {
closed(reason);
}
public virtual void notify_email_added_removed(Gee.List<Geary.Email>? added,
Gee.List<Geary.Email>? removed) {
email_added_removed(added, removed);
}
public virtual void notify_updated() {
updated();
}
@ -39,16 +47,25 @@ public interface Geary.Folder : Object {
public abstract int get_message_count() throws Error;
public abstract async void create_email_async(Geary.Email email, Geary.EmailOrdering ordering,
Cancellable? cancellable = null) throws Error;
public abstract async void create_email_async(Geary.Email email,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error;
/**
* low is one-based.
*/
public abstract async Gee.List<Geary.Email> list_email_async(int low, int count,
public abstract async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error;
public abstract async Geary.Email fetch_email_async(int msg_num, Geary.Email.Field fields,
/**
* All positions are one-based.
*/
public abstract async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error;
/**
* position is one-based.
*/
public abstract async Geary.Email fetch_email_async(int position, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error;
}

View file

@ -30,7 +30,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
uid_validity = context.uid_validity;
}
public async Gee.List<Geary.Email>? list_async(int low, int count, Geary.Email.Field fields,
public async Gee.List<Geary.Email>? list_set_async(MessageSet msg_set, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (context.is_closed())
throw new ImapError.NOT_SELECTED("Mailbox %s closed", name);
@ -39,7 +39,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
throw new EngineError.BAD_PARAMETERS("No email fields specify for list");
CommandResponse resp = yield context.session.send_command_async(
new FetchCommand(context.session.generate_tag(), new MessageSet.range(low, count),
new FetchCommand(context.session.generate_tag(), msg_set,
fields_to_fetch_data_types(fields)), cancellable);
if (resp.status_response.status != Status.OK)
@ -49,12 +49,13 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
FetchResults[] results = FetchResults.decode(resp);
foreach (FetchResults res in results) {
Geary.Email email = new Geary.Email(res.msg_num);
// TODO: Add UID
Geary.Email email = new Geary.Email(new Geary.Imap.EmailLocation(res.msg_num, 0));
fetch_results_to_email(res, fields, email);
msgs.add(email);
}
return msgs;
return (msgs != null && msgs.size > 0) ? msgs : null;
}
public async Geary.Email fetch_async(int msg_num, Geary.Email.Field fields,
@ -78,7 +79,8 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
results[0].msg_num, msg_num);
}
Geary.Email email = new Geary.Email(msg_num);
// TODO: Add UID
Geary.Email email = new Geary.Email(new Geary.Imap.EmailLocation(results[0].msg_num, 0));
fetch_results_to_email(results[0], fields, email);
return email;

View file

@ -28,12 +28,12 @@ public class Geary.Imap.MessageSet {
value = "%d:*".printf(low_msg_num);
}
public MessageSet.scattered(int[] msg_nums) {
value = build_scattered_range(msg_nums);
public MessageSet.sparse(int[] msg_nums) {
value = build_sparse_range(msg_nums);
}
public MessageSet.scattered_to_highest(int[] msg_nums) {
value = "%s:*".printf(build_scattered_range(msg_nums));
public MessageSet.sparse_to_highest(int[] msg_nums) {
value = "%s:*".printf(build_sparse_range(msg_nums));
}
public MessageSet.multirange(MessageSet[] msg_sets) {
@ -50,7 +50,7 @@ public class Geary.Imap.MessageSet {
value = builder.str;
}
public MessageSet.multiscattered(MessageSet[] msg_sets) {
public MessageSet.multisparse(MessageSet[] msg_sets) {
StringBuilder builder = new StringBuilder();
for (int ctr = 0; ctr < msg_sets.length; ctr++) {
unowned MessageSet msg_set = msg_sets[ctr];
@ -68,7 +68,9 @@ public class Geary.Imap.MessageSet {
value = custom;
}
private static string build_scattered_range(int[] msg_nums) {
// TODO: It would be more efficient to look for runs in the numbers and form the set specifier
// with them.
private static string build_sparse_range(int[] msg_nums) {
assert(msg_nums.length > 0);
StringBuilder builder = new StringBuilder();

View file

@ -0,0 +1,16 @@
/* 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 class Geary.Imap.EmailLocation : Geary.EmailLocation {
public int64 uid { get; private set; }
public EmailLocation(int position, int64 uid) {
base (position);
this.uid = uid;
}
}

View file

@ -57,25 +57,33 @@ public class Geary.Imap.Folder : Object, Geary.Folder {
return mailbox.count;
}
public async void create_email_async(Geary.Email email, Geary.EmailOrdering ordring,
public async void create_email_async(Geary.Email email, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
throw new EngineError.READONLY("IMAP currently read-only");
}
public async Gee.List<Geary.Email> list_email_async(int low, int count, Geary.Email.Field fields,
public async Gee.List<Geary.Email>? list_email_async(int low, int count, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
return yield mailbox.list_async(low, count, fields, cancellable);
return yield mailbox.list_set_async(new MessageSet.range(low, count), fields, cancellable);
}
public async Geary.Email fetch_email_async(int msg_num, Geary.Email.Field fields,
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
return yield mailbox.list_set_async(new MessageSet.sparse(by_position), fields, cancellable);
}
public async Geary.Email fetch_email_async(int position, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
return yield mailbox.fetch_async(msg_num, fields, cancellable);
return yield mailbox.fetch_async(position, fields, cancellable);
}
public string to_string() {

View file

@ -138,7 +138,7 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder {
StringParameter mailbox = fields.get_as_string(2);
StringParameter domain = fields.get_as_string(3);
Geary.RFC822.MailboxAddress addr = new Geary.RFC822.MailboxAddress(
Geary.RFC822.MailboxAddress addr = new Geary.RFC822.MailboxAddress.imap(
(name != null) ? name.nullable_value : null,
(source_route != null) ? source_route.nullable_value : null,
mailbox.value,

View file

@ -11,7 +11,20 @@ public class Geary.RFC822.MailboxAddress {
public string domain { get; private set; }
public string address { get; private set; }
public MailboxAddress(string? name, string? source_route, string mailbox, string domain) {
public MailboxAddress(string? name, string address) {
this.name = name;
this.address = address;
source_route = null;
int atsign = address.index_of_char('@');
if (atsign > 0) {
mailbox = address.slice(0, atsign);
domain = address.slice(atsign + 1, address.length);
}
}
public MailboxAddress.imap(string? name, string? source_route, string mailbox, string domain) {
this.name = name;
this.source_route = source_route;
this.mailbox = mailbox;
@ -36,6 +49,10 @@ public class Geary.RFC822.MailboxAddress {
return name ?? mailbox;
}
public string to_rfc822_string() {
return get_full_address();
}
public string to_string() {
return get_full_address();
}

View file

@ -0,0 +1,85 @@
/* 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.RFC822.MailboxAddresses : Geary.Common.MessageData, Geary.RFC822.MessageData {
public int size { get { return addrs.size; } }
private Gee.List<MailboxAddress> addrs = new Gee.ArrayList<MailboxAddress>();
public MailboxAddresses(Gee.Collection<MailboxAddress> addrs) {
this.addrs.add_all(addrs);
}
public MailboxAddresses.from_rfc822_string(string rfc822) {
InternetAddressList addrlist = InternetAddressList.parse_string(rfc822);
int length = addrlist.length();
for (int ctr = 0; ctr < length; ctr++) {
InternetAddress? addr = addrlist.get_address(ctr);
// TODO: Handle group lists
InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox;
if (mbox_addr == null)
continue;
addrs.add(new MailboxAddress(mbox_addr.get_name(), mbox_addr.get_addr()));
}
}
public MailboxAddress? get(int index) {
return addrs.get(index);
}
public Gee.Iterator<MailboxAddress> iterator() {
return addrs.iterator();
}
public Gee.List<MailboxAddress> get_all() {
return addrs.read_only_view;
}
public string to_rfc822_string() {
switch (addrs.size) {
case 0:
return "";
case 1:
return addrs[0].to_rfc822_string();
default:
StringBuilder builder = new StringBuilder();
foreach (MailboxAddress addr in addrs) {
if (!String.is_empty(builder.str))
builder.append(", ");
builder.append(addr.to_rfc822_string());
}
return builder.str;
}
}
public override string to_string() {
switch (addrs.size) {
case 0:
return "(no addresses)";
case 1:
return addrs[0].to_string();
default:
StringBuilder builder = new StringBuilder();
foreach (MailboxAddress addr in addrs) {
if (!String.is_empty(builder.str))
builder.append(", ");
builder.append(addr.to_string());
}
return builder.str;
}
}
}

View file

@ -50,49 +50,6 @@ public class Geary.RFC822.Subject : Geary.Common.StringMessageData, Geary.RFC822
}
}
public class Geary.RFC822.MailboxAddresses : Geary.Common.MessageData, Geary.RFC822.MessageData {
public int size { get { return addrs.size; } }
private Gee.List<MailboxAddress> addrs = new Gee.ArrayList<MailboxAddress>();
public MailboxAddresses(Gee.Collection<MailboxAddress> addrs) {
this.addrs.add_all(addrs);
}
public MailboxAddress? get(int index) {
return addrs.get(index);
}
public Gee.Iterator<MailboxAddress> iterator() {
return addrs.iterator();
}
public Gee.List<MailboxAddress> get_all() {
return addrs.read_only_view;
}
public override string to_string() {
switch (addrs.size) {
case 0:
return "(no addresses)";
case 1:
return addrs[0].to_string();
default:
StringBuilder builder = new StringBuilder();
foreach (MailboxAddress addr in addrs) {
if (!String.is_empty(builder.str))
builder.append(", ");
builder.append(addr.to_string());
}
return builder.str;
}
}
}
public class Geary.RFC822.Header : Geary.Common.BlockMessageData, Geary.RFC822.MessageData {
public Header(Geary.Memory.AbstractBuffer buffer) {
base ("RFC822.Header", buffer);

View file

@ -0,0 +1,21 @@
/* 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.Sqlite.ImapMessageLocationPropertiesRow : Geary.Sqlite.Row {
public int64 id { get; private set; }
public int64 location_id { get; private set; }
public int64 uid { get; private set; }
public ImapMessageLocationPropertiesRow(ImapMessageLocationPropertiesTable table, int64 id,
int64 location_id, int64 uid) {
base (table);
this.id = id;
this.location_id = location_id;
this.uid = uid;
}
}

View file

@ -0,0 +1,43 @@
/* 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.Sqlite.ImapMessageLocationPropertiesTable : Geary.Sqlite.Table {
// This *must* be in the same order as the schema.
public enum Column {
ID,
LOCATION_ID,
UID
}
public ImapMessageLocationPropertiesTable(Geary.Sqlite.Database gdb, SQLHeavy.Table table) {
base (gdb, table);
}
public async int64 create_async(ImapMessageLocationPropertiesRow row,
Cancellable? cancellable = null) throws Error {
SQLHeavy.Query query = db.prepare(
"INSERT INTO ImapMessageLocationPropertiesTable (location_id, uid) VALUES (?, ?)");
query.bind_int64(0, row.location_id);
query.bind_int64(1, row.uid);
return yield query.execute_insert_async(cancellable);
}
public async ImapMessageLocationPropertiesRow? fetch_async(int64 location_id,
Cancellable? cancellable = null) throws Error {
SQLHeavy.Query query = db.prepare(
"SELECT id, uid FROM ImapMessageLocationPropertiesTable WHERE location_id = ?");
query.bind_int64(0, location_id);
SQLHeavy.QueryResult result = yield query.execute_async(cancellable);
if (result.finished)
return null;
return new ImapMessageLocationPropertiesRow(this, result.fetch_int64(0), location_id,
result.fetch_int64(1));
}
}

View file

@ -39,5 +39,15 @@ public class Geary.Sqlite.MailDatabase : Geary.Sqlite.Database {
? location_table
: (MessageLocationTable) add_table(new MessageLocationTable(this, heavy_table));
}
public Geary.Sqlite.ImapMessageLocationPropertiesTable get_imap_message_location_table() {
SQLHeavy.Table heavy_table;
ImapMessageLocationPropertiesTable? imap_location_table = get_table(
"ImapMessageLocationPropertiesTable", out heavy_table) as ImapMessageLocationPropertiesTable;
return (imap_location_table != null)
? imap_location_table
: (ImapMessageLocationPropertiesTable) add_table(new ImapMessageLocationPropertiesTable(this, heavy_table));
}
}

View file

@ -8,16 +8,16 @@ public class Geary.Sqlite.MessageLocationRow : Geary.Sqlite.Row {
public int64 id { get; private set; }
public int64 message_id { get; private set; }
public int64 folder_id { get; private set; }
public int64 ordering { get; private set; }
public int position { get; private set; }
public MessageLocationRow(MessageLocationTable table, int64 id, int64 message_id, int64 folder_id,
int64 ordering) {
int position) {
base (table);
this.id = id;
this.message_id = message_id;
this.folder_id = folder_id;
this.ordering = ordering;
this.position = position;
}
public MessageLocationRow.from_query_result(MessageLocationTable table,
@ -27,7 +27,7 @@ public class Geary.Sqlite.MessageLocationRow : Geary.Sqlite.Row {
id = fetch_int64_for(result, MessageLocationTable.Column.ID);
message_id = fetch_int64_for(result, MessageLocationTable.Column.MESSAGE_ID);
folder_id = fetch_int64_for(result, MessageLocationTable.Column.FOLDER_ID);
ordering = fetch_int64_for(result, MessageLocationTable.Column.ORDERING);
position = fetch_int_for(result, MessageLocationTable.Column.POSITION);
}
}

View file

@ -10,7 +10,7 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
ID,
MESSAGE_ID,
FOLDER_ID,
ORDERING
POSITION
}
public MessageLocationTable(Geary.Sqlite.Database db, SQLHeavy.Table table) {
@ -20,27 +20,27 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
public async int64 create_async(MessageLocationRow row, Cancellable? cancellable = null)
throws Error {
SQLHeavy.Query query = db.prepare(
"INSERT INTO MessageLocationTable (message_id, folder_id, ordering) VALUES (?, ?, ?)");
"INSERT INTO MessageLocationTable (message_id, folder_id, position) VALUES (?, ?, ?)");
query.bind_int64(0, row.message_id);
query.bind_int64(1, row.folder_id);
query.bind_int64(2, row.ordering);
query.bind_int(2, row.position);
return yield query.execute_insert_async(cancellable);
}
/**
* low is zero-based.
* low is one-based.
*/
public async Gee.List<MessageLocationRow>? list_async(int64 folder_id, int low, int count,
Cancellable? cancellable = null) throws Error {
assert(low >= 0);
assert(low >= 1);
SQLHeavy.Query query = db.prepare(
"SELECT id, message_id, ordering FROM MessageLocationTable WHERE folder_id = ? "
+ "LIMIT ? OFFSET ? ORDER BY ordering");
"SELECT id, message_id, position FROM MessageLocationTable WHERE folder_id = ? "
+ "ORDER BY position LIMIT ? OFFSET ?");
query.bind_int64(0, folder_id);
query.bind_int(1, count);
query.bind_int(2, low);
query.bind_int(2, low - 1);
SQLHeavy.QueryResult results = yield query.execute_async(cancellable);
if (results.finished)
@ -49,7 +49,7 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
Gee.List<MessageLocationRow> list = new Gee.ArrayList<MessageLocationRow>();
do {
list.add(new MessageLocationRow(this, results.fetch_int64(0), results.fetch_int64(1),
folder_id, results.fetch_int64(2)));
folder_id, results.fetch_int(2)));
yield results.next_async(cancellable);
} while (!results.finished);
@ -57,24 +57,60 @@ public class Geary.Sqlite.MessageLocationTable : Geary.Sqlite.Table {
}
/**
* num is zero-based.
* All positions are one-based.
*/
public async MessageLocationRow? fetch_async(int64 folder_id, int num,
public async Gee.List<MessageLocationRow>? list_sparse_async(int64 folder_id, int[] by_position,
Cancellable? cancellable = null) throws Error {
assert(num >= 0);
// build a vector for the IN expression
StringBuilder vector = new StringBuilder("(");
for (int ctr = 0; ctr < by_position.length; ctr++) {
assert(by_position[ctr] >= 1);
if (ctr < (by_position.length - 1))
vector.append_printf("%d, ", by_position[ctr]);
else
vector.append_printf("%d", by_position[ctr]);
}
vector.append(")");
SQLHeavy.Query query = db.prepare(
"SELECT id, message_id, ordering FROM MessageLocationTable WHERE folder_id = ? "
+ "LIMIT 1 OFFSET ? ORDER BY ordering");
"SELECT id, message_id, position FROM MessageLocationTable WHERE folder_id = ? AND position IN ?");
query.bind_int64(0, folder_id);
query.bind_int64(1, num);
query.bind_string(1, vector.str);
SQLHeavy.QueryResult results = yield query.execute_async(cancellable);
if (results.finished)
return null;
Gee.List<MessageLocationRow> list = new Gee.ArrayList<MessageLocationRow>();
do {
list.add(new MessageLocationRow(this, results.fetch_int64(0), results.fetch_int64(1),
folder_id, results.fetch_int(2)));
yield results.next_async(cancellable);
} while (!results.finished);
return list;
}
/**
* position is one-based.
*/
public async MessageLocationRow? fetch_async(int64 folder_id, int position,
Cancellable? cancellable = null) throws Error {
assert(position >= 1);
SQLHeavy.Query query = db.prepare(
"SELECT id, message_id, position FROM MessageLocationTable WHERE folder_id = ? "
+ "AND position = ?");
query.bind_int64(0, folder_id);
query.bind_int64(1, position);
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,
results.fetch_int64(2));
results.fetch_int(2));
}
}

View file

@ -93,8 +93,8 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row {
body = fetch_string_for(result, MessageTable.Column.BODY);
}
public Geary.Email to_email(int msg_num) throws Error {
Geary.Email email = new Geary.Email(msg_num);
public Geary.Email to_email(Geary.EmailLocation location) throws Error {
Geary.Email email = new Geary.Email(location);
email.date = (date != null) ? new RFC822.Date(date) : null;
@ -145,10 +145,7 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row {
}
public RFC822.MailboxAddresses? unflatten_addresses(string? str) {
if (str == null)
return null;
return null;
return String.is_empty(str) ? null : new RFC822.MailboxAddresses.from_rfc822_string(str);
}
}

View file

@ -82,7 +82,7 @@ public class Geary.Sqlite.MessageTable : Geary.Sqlite.Table {
assert(fields != Geary.Email.Field.NONE);
SQLHeavy.Query query = db.prepare(
"SELECT %s FROM MessageTable WHERE id = ?".printf(fields_to_columns(fields)));
"SELECT id, %s FROM MessageTable WHERE id = ?".printf(fields_to_columns(fields)));
query.bind_int64(0, id);
SQLHeavy.QueryResult results = yield query.execute_async(cancellable);
@ -90,7 +90,6 @@ public class Geary.Sqlite.MessageTable : Geary.Sqlite.Table {
return null;
MessageRow row = new MessageRow.from_query_result(this, fields, results);
row.id = id;
return row;
}
@ -99,6 +98,7 @@ public class Geary.Sqlite.MessageTable : Geary.Sqlite.Table {
StringBuilder builder = new StringBuilder();
foreach (Geary.Email.Field field in Geary.Email.Field.all()) {
string? append = null;
if ((fields & field) != 0) {
switch (field) {
case Geary.Email.Field.DATE:
append = "date_field, date_time_t";
@ -128,6 +128,7 @@ public class Geary.Sqlite.MessageTable : Geary.Sqlite.Table {
append = "body";
break;
}
}
if (append != null) {
if (!String.is_empty(builder.str))

View file

@ -9,6 +9,7 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder {
private FolderRow folder_row;
private MessageTable message_table;
private MessageLocationTable location_table;
private ImapMessageLocationPropertiesTable imap_location_table;
private string name;
internal Folder(MailDatabase db, FolderRow folder_row) throws Error {
@ -19,6 +20,7 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder {
message_table = db.get_message_table();
location_table = db.get_message_location_table();
imap_location_table = db.get_imap_message_location_table();
}
public string get_name() {
@ -39,57 +41,93 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder {
return 0;
}
public async void create_email_async(Geary.Email email, Geary.EmailOrdering ordering,
public async void create_email_async(Geary.Email email, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
int64 message_id = yield message_table.create_async(
new MessageRow.from_email(message_table, email),
cancellable);
Geary.Imap.EmailLocation location = (Geary.Imap.EmailLocation) email.location;
MessageLocationRow location_row = new MessageLocationRow(location_table, Row.INVALID_ID,
message_id, folder_row.id, ordering.ordinal);
yield location_table.create_async(location_row, cancellable);
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 fields,
public async Gee.List<Geary.Email>? list_email_async(int low, int count, Geary.Email.Field fields,
Cancellable? cancellable) throws Error {
assert(low >= 1);
assert(count >= 1);
// low is zero-based in the database.
Gee.List<MessageLocationRow>? list = yield location_table.list_async(folder_row.id, low - 1,
Gee.List<MessageLocationRow>? list = yield location_table.list_async(folder_row.id, low,
count, cancellable);
return yield list_email(list, fields, cancellable);
}
public async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
Geary.Email.Field fields, Cancellable? cancellable = null) throws Error {
Gee.List<MessageLocationRow>? list = yield location_table.list_sparse_async(folder_row.id,
by_position, cancellable);
return yield list_email(list, fields, cancellable);
}
private async Gee.List<Geary.Email>? list_email(Gee.List<MessageLocationRow>? list,
Geary.Email.Field fields, Cancellable? cancellable) throws Error {
if (list == null || list.size == 0)
throw new EngineError.NOT_FOUND("No messages found at position %d in %s", low, name);
return null;
Gee.List<Geary.Email> emails = new Gee.ArrayList<Geary.Email>();
int msg_num = 1;
foreach (MessageLocationRow location_row in list) {
ImapMessageLocationPropertiesRow? imap_location_row = yield imap_location_table.fetch_async(
location_row.id, cancellable);
assert(imap_location_row != null);
MessageRow? message_row = yield message_table.fetch_async(location_row.message_id,
fields, cancellable);
assert(message_row != null);
emails.add(message_row.to_email(msg_num++));
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 num, Geary.Email.Field fields,
public async Geary.Email fetch_email_async(int position, Geary.Email.Field fields,
Cancellable? cancellable = null) throws Error {
assert(num >= 0);
assert(position >= 1);
// num is zero-based in the database.
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id, num - 1,
MessageLocationRow? location_row = yield location_table.fetch_async(folder_row.id, position,
cancellable);
if (location_row == null)
throw new EngineError.NOT_FOUND("No message number %s in folder %s", num, name);
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, fields,
cancellable);
if (message_row == null)
throw new EngineError.NOT_FOUND("No message number %s in folde %s", num, name);
throw new EngineError.NOT_FOUND("No message at position %d in folder %s", position, name);
return message_row.to_email(num);
return message_row.to_email(new Geary.Imap.EmailLocation(location_row.position,
imap_location_row.uid));
}
}

View file

@ -299,80 +299,12 @@ namespace GMime {
public ssize_t write_to_stream (GMime.Stream stream);
}
[CCode (cheader_filename = "gmime/gmime.h")]
public class InternetAddress : GLib.Object {
public weak string name;
[CCode (has_construct_function = false)]
protected InternetAddress ();
[CCode (cname = "internet_address_get_name")]
public unowned string get_name ();
[CCode (cname = "internet_address_set_name")]
public void set_name (string name);
[CCode (cname = "internet_address_to_string")]
public virtual string to_string (bool encoded);
}
[CCode (cheader_filename = "gmime/gmime.h")]
public class InternetAddressGroup : GMime.InternetAddress {
public weak GMime.InternetAddressList members;
[CCode (cname = "internet_address_group_new")]
public InternetAddressGroup (string name);
[CCode (cname = "internet_address_group_add_member")]
public int add_member (GMime.InternetAddress member);
[CCode (cname = "internet_address_group_get_members")]
public GMime.InternetAddressList get_members ();
[CCode (cname = "internet_address_group_set_members")]
public void set_members (GMime.InternetAddressList members);
}
[CCode (cheader_filename = "gmime/gmime.h")]
public class InternetAddressList : GLib.Object {
public weak GLib.GenericArray array;
[CCode (cname = "internet_address_list_new")]
public InternetAddressList ();
[CCode (cname = "internet_address_list_add")]
public int add (GMime.InternetAddress addr);
[CCode (cname = "internet_address_list_append")]
public void append (GMime.InternetAddressList append);
[CCode (cname = "internet_address_list_clear")]
public void clear ();
[CCode (cname = "internet_address_list_contains")]
public bool contains (GMime.InternetAddress addr);
[CCode (cname = "internet_address_list_get_address")]
public GMime.InternetAddress get_address (int index);
[CCode (cname = "internet_address_list_index_of")]
public int index_of (GMime.InternetAddress addr);
[CCode (cname = "internet_address_list_insert")]
public void insert (int index, GMime.InternetAddress addr);
[CCode (cname = "internet_address_list_length")]
public int length ();
[CCode (cname = "internet_address_list_parse_string")]
public static GMime.InternetAddressList parse_string (string str);
[CCode (cname = "internet_address_list_prepend")]
public void prepend (GMime.InternetAddressList prepend);
[CCode (cname = "internet_address_list_remove")]
public bool remove (GMime.InternetAddress addr);
[CCode (cname = "internet_address_list_remove_at")]
public bool remove_at (int index);
[CCode (cname = "internet_address_list_set_address")]
public void set_address (int index, GMime.InternetAddress addr);
[CCode (cname = "internet_address_list_to_string")]
public string to_string (bool encode);
}
[CCode (cheader_filename = "gmime/gmime.h")]
public class InternetAddressMailbox : GMime.InternetAddress {
public weak string addr;
[CCode (cname = "internet_address_mailbox_new")]
public InternetAddressMailbox (string name, string addr);
[CCode (cname = "internet_address_mailbox_get_addr")]
public string get_addr ();
[CCode (cname = "internet_address_mailbox_set_addr")]
public void set_addr (string addr);
}
[CCode (cheader_filename = "gmime/gmime.h")]
public class Message : GMime.Object {
public ulong date;
public weak string from;
public weak string message_id;
public weak GMime.Object mime_part;
public weak GMime.InternetAddressList recipients;
public weak InternetAddressList recipients;
public weak string reply_to;
public weak string subject;
public int tz_offset;
@ -380,12 +312,12 @@ namespace GMime {
public Message (bool pretty_headers);
public void add_recipient (GMime.RecipientType type, string name, string addr);
public void @foreach (GMime.ObjectForeachFunc callback);
public unowned GMime.InternetAddressList get_all_recipients ();
public unowned InternetAddressList get_all_recipients ();
public void get_date (out ulong date, out int tz_offset);
public unowned string get_date_as_string ();
public unowned string get_message_id ();
public unowned GMime.Object get_mime_part ();
public unowned GMime.InternetAddressList get_recipients (GMime.RecipientType type);
public unowned InternetAddressList get_recipients (GMime.RecipientType type);
public unowned string get_reply_to ();
public unowned string get_sender ();
public unowned string get_subject ();
@ -1010,3 +942,40 @@ namespace GMime {
[CCode (cheader_filename = "gmime/gmime.h")]
public static size_t yencode_step (uint inbuf, size_t inlen, uint outbuf, int state, uint32 pcrc, uint32 crc);
}
[CCode (type_check_function = "IS_INTERNET_ADDRESS", type_id = "INTERNET_ADDRESS", cheader_filename = "gmime/gmime.h")]
public class InternetAddress : GLib.Object {
public unowned string? get_name ();
public void set_name (string? name);
public virtual string to_string (bool encoded);
}
[CCode (type_check_function = "INTERNET_ADDRESS_IS_GROUP", type_id = "INTERNET_ADDRESS_GROUP", cheader_filename = "gmime/gmime.h")]
public class InternetAddressGroup : InternetAddress {
public InternetAddressGroup (string name);
public int add_member (InternetAddress member);
public InternetAddressList get_members ();
public void set_members (InternetAddressList members);
}
[CCode (type_check_function = "INTERNET_ADDRESS_IS_MAILBOX", type_id = "INTERNET_ADDRESS_MAILBOX", cheader_filename = "gmime/gmime.h")]
public class InternetAddressMailbox : InternetAddress {
public InternetAddressMailbox (string? name, string addr);
public unowned string get_addr ();
public void set_addr (string addr);
}
[CCode (type_check_function = "IS_INTERNET_ADDRESS_LIST", type_id = "INTERNET_ADDRESS_LIST", cheader_filename = "gmime/gmime.h")]
public class InternetAddressList : GLib.Object {
public InternetAddressList ();
public int add (InternetAddress addr);
public void append (InternetAddressList append);
public void clear ();
public bool contains (InternetAddress addr);
public unowned InternetAddress get_address (int index);
public int index_of (InternetAddress addr);
public void insert (int index, InternetAddress addr);
public int length ();
public static InternetAddressList? parse_string (string str);
public void prepend (InternetAddressList prepend);
public bool remove (InternetAddress addr);
public bool remove_at (int index);
public void set_address (int index, InternetAddress addr);
public string? to_string (bool encode);
}

View file

@ -4,17 +4,18 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
namespace GMime {
public class InternetAddress {
[CCode (cname="InternetAddress", cheader_filename="gmime/gmime.h", type_id="INTERNET_ADDRESS", type_check_function="IS_INTERNET_ADDRESS")]
public class InternetAddress : GLib.Object {
[CCode (cname="internet_address_get_name")]
public unowned string get_name();
public unowned string? get_name();
[CCode (cname="internet_address_set_name")]
public void set_name(string name);
public void set_name(string? name);
[CCode (cname="internet_address_to_string")]
public virtual string to_string(bool encoded);
}
}
public class InternetAddressGroup {
[CCode (cname="InternetAddressGroup", cheader_filename="gmime/gmime.h", type_id="INTERNET_ADDRESS_GROUP", type_check_function="INTERNET_ADDRESS_IS_GROUP")]
public class InternetAddressGroup : InternetAddress {
[CCode (cname="internet_address_group_new")]
public InternetAddressGroup(string name);
[CCode (cname="internet_address_group_get_members")]
@ -23,18 +24,20 @@ namespace GMime {
public void set_members(InternetAddressList members);
[CCode (cname="internet_address_group_add_member")]
public int add_member(InternetAddress member);
}
}
public class InternetAddressMailbox {
[CCode (cname="InternetAddressMailbox", cheader_filename="gmime/gmime.h", type_id="INTERNET_ADDRESS_MAILBOX", type_check_function="INTERNET_ADDRESS_IS_MAILBOX")]
public class InternetAddressMailbox : InternetAddress {
[CCode (cname="internet_address_mailbox_new")]
public InternetAddressMailbox(string name, string addr);
public InternetAddressMailbox(string? name, string addr);
[CCode (cname="internet_address_mailbox_get_addr")]
public string get_addr();
public unowned string get_addr();
[CCode (cname="internet_address_mailbox_set_addr")]
public void set_addr(string addr);
}
}
public class InternetAddressList {
[CCode (cname="InternetAddressList", cheader_filename="gmime/gmime.h", type_id="INTERNET_ADDRESS_LIST", type_check_function="IS_INTERNET_ADDRESS_LIST")]
public class InternetAddressList : GLib.Object {
[CCode (cname="internet_address_list_new")]
public InternetAddressList();
[CCode (cname="internet_address_list_length")]
@ -54,7 +57,7 @@ namespace GMime {
[CCode (cname="internet_address_list_index_of")]
public int index_of(InternetAddress addr);
[CCode (cname="internet_address_list_get_address")]
public InternetAddress get_address(int index);
public unowned InternetAddress get_address(int index);
[CCode (cname="internet_address_list_set_address")]
public void set_address(int index, InternetAddress addr);
[CCode (cname="internet_address_list_prepend")]
@ -62,9 +65,8 @@ namespace GMime {
[CCode (cname="internet_address_list_append")]
public void append(InternetAddressList append);
[CCode (cname="internet_address_list_to_string")]
public string to_string(bool encode);
public string? to_string(bool encode);
[CCode (cname="internet_address_list_parse_string")]
public static InternetAddressList parse_string(string str);
}
public static InternetAddressList? parse_string(string str);
}

View file

@ -8,19 +8,19 @@ g_mime_signer_next name="get_next"
g_mime_utils_header_decode_date type_name="time_t"
g_mime_utils_header_decode_date.tz_offset is_out="1" nullable="1"
GMime.InternetAddress hidden="1"
InternetAddress hidden="1"
internet_address_* hidden="1"
GMime.InternetAddress.name hidden="1"
InternetAddress.name hidden="1"
GMime.InternetAddressGroup hidden="1"
InternetAddressGroup hidden="1"
internet_address_group_* hidden="1"
GMime.InternetAddressGroup.members hidden="1"
InternetAddressGroup.members hidden="1"
GMime.InternetAddressList hidden="1"
InternetAddressList hidden="1"
internet_address_list_* hidden="1"
GMime.InternetAddressList.array hidden="1"
InternetAddressList.array hidden="1"
GMime.InternetAddressMailbox hidden="1"
InternetAddressMailbox hidden="1"
internet_address_mailbox_* hidden="1"
GMime.InternetAddressMailbox.addr hidden="1"
InternetAddressMailbox.addr hidden="1"