Commit 6b8951bf authored by Jim Nelson's avatar Jim Nelson

Implemented IMAP-specific folder and message properties tables: #3805

This commit adds support for IMAP-specific properties, of which UIDValidity is crucial toward completing #3805.  The additional code is to integrate these tables into the SQLite Geary backend and to make sure this information is requested from the IMAP server.

NOTE: This commit changes the database schema.  Old databases will need to be blown away before running.
parent 9792780e
......@@ -69,8 +69,7 @@ CREATE TABLE ImapFolderPropertiesTable (
id INTEGER PRIMARY KEY,
folder_id INTEGER UNIQUE REFERENCES FolderTable ON DELETE CASCADE,
uid_validity INTEGER,
supports_children INTEGER,
is_openable INTEGER
attributes TEXT
);
CREATE INDEX ImapFolderPropertiesTableFolderIDIndex ON ImapFolderPropertiesTable(folder_id);
......@@ -82,13 +81,7 @@ CREATE INDEX ImapFolderPropertiesTableFolderIDIndex ON ImapFolderPropertiesTable
CREATE TABLE ImapMessagePropertiesTable (
id INTEGER PRIMARY KEY,
message_id INTEGER UNIQUE REFERENCES MessageTable ON DELETE CASCADE,
answered INTEGER,
deleted INTEGER,
draft INTEGER,
flagged INTEGER,
recent INTEGER,
seen INTEGER,
all_flags TEXT
flags TEXT
);
CREATE INDEX ImapMessagePropertiesTableMessageIDIndex ON ImapMessagePropertiesTable(message_id);
......
......@@ -93,7 +93,7 @@ public class MainWindow : Gtk.Window {
else
debug("no folders");
} catch (Error err) {
error("%s", err.message);
warning("%s", err.message);
}
}
......
......@@ -91,7 +91,8 @@ class ImapConsole : Gtk.Window {
"exit",
"quit",
"gmail",
"keepalive"
"keepalive",
"status"
};
private void exec(string input) {
......@@ -180,6 +181,10 @@ class ImapConsole : Gtk.Window {
keepalive(cmd, args);
break;
case "status":
folder_status(cmd, args);
break;
default:
status("Unknown command \"%s\"".printf(cmd));
break;
......@@ -393,6 +398,28 @@ class ImapConsole : Gtk.Window {
}
}
private void folder_status(string cmd, string[] args) throws Error {
check_min_connected(cmd, args, 2, "<folder> <data-item...>");
status("Status %s".printf(args[0]));
Geary.Imap.StatusDataType[] data_items = new Geary.Imap.StatusDataType[0];
for (int ctr = 1; ctr < args.length; ctr++)
data_items += Geary.Imap.StatusDataType.decode(args[ctr]);
cx.send_async.begin(new Geary.Imap.StatusCommand(cx.generate_tag(), args[0], data_items),
null, on_get_status);
}
private void on_get_status(Object? source, AsyncResult result) {
try {
cx.send_async.end(result);
status("Get status");
} catch (Error err) {
exception(err);
}
}
private void quit(string cmd, string[] args) throws Error {
Gtk.main_quit();
}
......
......@@ -5,8 +5,8 @@
*/
public abstract class Geary.AbstractFolder : Object, Geary.Folder {
protected virtual void notify_opened() {
opened();
protected virtual void notify_opened(Geary.Folder.OpenState state) {
opened(state);
}
protected virtual void notify_closed(Geary.Folder.CloseReason reason) {
......
......@@ -7,10 +7,11 @@
private class Geary.EngineFolder : Geary.AbstractFolder {
private const int REMOTE_FETCH_CHUNK_COUNT = 10;
private RemoteAccount remote;
private LocalAccount local;
private RemoteFolder? remote_folder = null;
private LocalFolder local_folder;
protected RemoteAccount remote;
protected LocalAccount local;
protected RemoteFolder? remote_folder = null;
protected LocalFolder local_folder;
private bool opened = false;
private Geary.Common.NonblockingSemaphore remote_semaphore =
new Geary.Common.NonblockingSemaphore(true);
......@@ -59,8 +60,6 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
open_remote_async.begin(readonly, cancellable, on_open_remote_completed);
opened = true;
notify_opened();
}
private async void open_remote_async(bool readonly, Cancellable? cancellable) throws Error {
......@@ -77,6 +76,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
private void on_open_remote_completed(Object? source, AsyncResult result) {
try {
open_remote_async.end(result);
notify_opened(Geary.Folder.OpenState.BOTH);
} catch (Error err) {
debug("Unable to open remote folder %s: %s", to_string(), err.message);
......@@ -86,6 +87,8 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
} catch (Error err) {
debug("Unable to notify remote folder ready: %s", err.message);
}
notify_opened(Geary.Folder.OpenState.LOCAL);
}
}
......@@ -142,7 +145,7 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable = null)
throws Error {
assert(low >= 1);
assert(count >= 0);
assert(count >= 0 || count == -1);
if (!opened)
throw new EngineError.OPEN_REQUIRED("%s is not open", to_string());
......@@ -361,8 +364,10 @@ private class Geary.EngineFolder : Geary.AbstractFolder {
list = needed_by_position;
}
// Always get the flags, and the generic end-user won't know to ask for them until they
// need them
Gee.List<Geary.Email>? remote_list = yield remote_folder.list_email_sparse_async(
list, required_fields, cancellable);
list, required_fields | Geary.Email.Field.PROPERTIES, cancellable);
if (remote_list == null || remote_list.size == 0)
break;
......
......@@ -7,6 +7,12 @@
public delegate void Geary.EmailCallback(Gee.List<Geary.Email>? emails, Error? err);
public interface Geary.Folder : Object {
public enum OpenState {
REMOTE,
LOCAL,
BOTH
}
public enum CloseReason {
LOCAL_CLOSE,
REMOTE_CLOSE,
......@@ -15,9 +21,9 @@ public interface Geary.Folder : Object {
/**
* This is fired when the Folder is successfully opened by a caller. It will only fire once
* until the Folder is closed.
* until the Folder is closed, with the OpenState indicating what has been opened.
*/
public signal void opened();
public signal void opened(OpenState state);
/**
* This is fired when the Folder is successfully closed by a caller. It will only fire once
......@@ -50,8 +56,8 @@ public interface Geary.Folder : Object {
* directly. This allows subclasses and superclasses the opportunity to inspect the email
* and update state before and/or after the signal has been fired.
*/
protected virtual void notify_opened() {
opened();
protected virtual void notify_opened(OpenState state) {
opened(state);
}
/**
......@@ -140,7 +146,8 @@ public interface Geary.Folder : Object {
/**
* Returns a list of messages that fulfill the required_fields flags starting at the low
* position and moving up to (low + count). The list is not guaranteed to be in any
* position and moving up to (low + count). If count is -1, the returned list starts at low
* and proceeds to all available emails. The returned list is not guaranteed to be in any
* particular order.
*
* If any position in low to (low + count) are out of range, only the email within range are
......
......@@ -50,7 +50,7 @@ private class Geary.GenericImapAccount : Geary.EngineAccount {
Gee.Collection<Geary.Folder> engine_list = new Gee.ArrayList<Geary.Folder>();
if (local_list != null && local_list.size > 0) {
foreach (Geary.Folder local_folder in local_list)
engine_list.add(new EngineFolder(remote, local, (LocalFolder) local_folder));
engine_list.add(new GenericImapFolder(remote, local, (LocalFolder) local_folder));
}
background_update_folders.begin(parent, engine_list);
......@@ -72,7 +72,7 @@ private class Geary.GenericImapAccount : Geary.EngineAccount {
try {
local_folder = (LocalFolder) yield local.fetch_folder_async(path, cancellable);
return new EngineFolder(remote, local, local_folder);
return new GenericImapFolder(remote, local, local_folder);
} catch (EngineError err) {
// don't thrown NOT_FOUND's, that means we need to fall through and clone from the
// server
......@@ -94,10 +94,10 @@ private class Geary.GenericImapAccount : Geary.EngineAccount {
yield local.clone_folder_async(remote_folder, cancellable);
}
// Fetch the local account's version of the folder for the EngineFolder
// Fetch the local account's version of the folder for the GenericImapFolder
local_folder = (LocalFolder) yield local.fetch_folder_async(path, cancellable);
return new EngineFolder(remote, local, local_folder);
return new GenericImapFolder(remote, local, local_folder);
}
private Gee.Set<string> get_folder_names(Gee.Collection<Geary.Folder> folders) {
......@@ -140,11 +140,15 @@ private class Geary.GenericImapAccount : Geary.EngineAccount {
if (to_remove.size == 0)
to_remove = null;
try {
if (to_add != null)
yield local.clone_many_folders_async(to_add);
} catch (Error err) {
error("Unable to add/remove folders: %s", err.message);
if (to_add != null) {
foreach (Geary.Folder folder in to_add) {
try {
yield local.clone_folder_async(folder);
} catch (Error err) {
debug("Unable to add/remove folder %s: %s", folder.get_path().to_string(),
err.message);
}
}
}
Gee.Collection<Geary.Folder> engine_added = null;
......@@ -154,7 +158,7 @@ private class Geary.GenericImapAccount : Geary.EngineAccount {
try {
LocalFolder local_folder = (LocalFolder) yield local.fetch_folder_async(
remote_folder.get_path());
engine_added.add(new EngineFolder(remote, local, local_folder));
engine_added.add(new GenericImapFolder(remote, local, local_folder));
} catch (Error convert_err) {
error("Unable to fetch local folder: %s", convert_err.message);
}
......
/* 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.GenericImapFolder : Geary.EngineFolder {
public GenericImapFolder(RemoteAccount remote, LocalAccount local, LocalFolder local_folder) {
base (remote, local, local_folder);
}
}
......@@ -8,9 +8,6 @@ public interface Geary.LocalAccount : Object, Geary.Account {
public abstract async void clone_folder_async(Geary.Folder folder, Cancellable? cancellable = null)
throws Error;
public abstract async void clone_many_folders_async(Gee.Collection<Geary.Folder> folders,
Cancellable? cancellable = null) throws Error;
/**
* Returns true if the email (identified by its Message-ID) already exists in the account's
* local store, no matter the folder.
......
......@@ -66,7 +66,18 @@ public class Geary.Imap.Account : Geary.AbstractAccount, Geary.RemoteAccount {
if (processed == null)
delims.set(path.get_root().basename, mbox.delim);
folders.add(new Geary.Imap.Folder(session_mgr, path, mbox));
UIDValidity? uid_validity = null;
if (!mbox.attrs.contains(MailboxAttribute.NO_SELECT)) {
try {
StatusResults results = yield session_mgr.status_async(path.get_fullpath(),
{ StatusDataType.UIDVALIDITY }, cancellable);
uid_validity = results.uidvalidity;
} catch (Error status_err) {
message("Unable to fetch UID Validity for %s: %s", path.to_string(), status_err.message);
}
}
folders.add(new Geary.Imap.Folder(session_mgr, path, uid_validity, mbox));
}
return folders;
......@@ -93,7 +104,14 @@ public class Geary.Imap.Account : Geary.AbstractAccount, Geary.RemoteAccount {
if (mbox == null)
throw_not_found(path);
return new Geary.Imap.Folder(session_mgr, processed, mbox);
UIDValidity? uid_validity = null;
if (!mbox.attrs.contains(MailboxAttribute.NO_SELECT)) {
StatusResults results = yield session_mgr.status_async(processed.get_fullpath(),
{ StatusDataType.UIDVALIDITY }, cancellable);
uid_validity = results.uidvalidity;
}
return new Geary.Imap.Folder(session_mgr, processed, uid_validity, mbox);
} catch (ImapError err) {
if (err is ImapError.SERVER_ERROR)
throw_not_found(path);
......
......@@ -5,10 +5,27 @@
*/
public class Geary.Imap.EmailProperties : Geary.EmailProperties {
public bool answered { get; private set; }
public bool deleted { get; private set; }
public bool draft { get; private set; }
public bool flagged { get; private set; }
public bool recent { get; private set; }
public bool seen { get; private set; }
public MessageFlags flags { get; private set; }
public EmailProperties(MessageFlags flags) {
this.flags = flags;
answered = flags.contains(MessageFlag.ANSWERED);
deleted = flags.contains(MessageFlag.DELETED);
draft = flags.contains(MessageFlag.DRAFT);
flagged = flags.contains(MessageFlag.FLAGGED);
recent = flags.contains(MessageFlag.RECENT);
seen = flags.contains(MessageFlag.SEEN);
}
public bool is_empty() {
return (flags.size == 0);
}
public override bool is_unread() {
......
......@@ -5,13 +5,13 @@
*/
public class Geary.Imap.FolderProperties : Geary.FolderProperties {
public UID? uid_validity { get; set; }
public UIDValidity? uid_validity { get; set; }
public MailboxAttributes attrs { get; private set; }
public Trillian supports_children { get; private set; }
public Trillian has_children { get; private set; }
public Trillian is_openable { get; private set; }
public FolderProperties(UID? uid_validity, MailboxAttributes attrs) {
public FolderProperties(UIDValidity? uid_validity, MailboxAttributes attrs) {
this.uid_validity = uid_validity;
this.attrs = attrs;
......
......@@ -14,13 +14,14 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder {
private Imap.FolderProperties properties;
private Mailbox? mailbox = null;
internal Folder(ClientSessionManager session_mgr, Geary.FolderPath path, MailboxInformation info) {
internal Folder(ClientSessionManager session_mgr, Geary.FolderPath path, UIDValidity? uid_validity,
MailboxInformation info) {
this.session_mgr = session_mgr;
this.info = info;
this.path = path;
readonly = Trillian.UNKNOWN;
properties = new Imap.FolderProperties(null, info.attrs);
properties = new Imap.FolderProperties(uid_validity, info.attrs);
}
public override Geary.FolderPath get_path() {
......@@ -46,7 +47,7 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder {
this.readonly = Trillian.from_boolean(readonly);
properties.uid_validity = mailbox.uid_validity;
notify_opened();
notify_opened(Geary.Folder.OpenState.REMOTE);
}
public override async void close_async(Cancellable? cancellable = null) throws Error {
......@@ -79,7 +80,11 @@ public class Geary.Imap.Folder : Geary.AbstractFolder, Geary.RemoteFolder {
if (mailbox == null)
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
return yield mailbox.list_set_async(new MessageSet.range(low, count), fields, cancellable);
MessageSet msg_set = (count != -1)
? new MessageSet.range(low, count)
: new MessageSet.range_to_highest(low);
return yield mailbox.list_set_async(msg_set, fields, cancellable);
}
public override async Gee.List<Geary.Email>? list_email_sparse_async(int[] by_position,
......
......@@ -17,13 +17,13 @@ public class Geary.Imap.SelectExamineResults : Geary.Imap.CommandResults {
* -1 if not specified.
*/
public int unseen { get; private set; }
public UID? uid_validity { get; private set; }
public UIDValidity? uid_validity { get; private set; }
public Flags? flags { get; private set; }
public Flags? permanentflags { get; private set; }
public bool readonly { get; private set; }
private SelectExamineResults(StatusResponse status_response, int exists, int recent, int unseen,
UID? uidvalidity, Flags? flags, Flags? permanentflags, bool readonly) {
UIDValidity? uidvalidity, Flags? flags, Flags? permanentflags, bool readonly) {
base (status_response);
this.exists = exists;
......@@ -41,7 +41,7 @@ public class Geary.Imap.SelectExamineResults : Geary.Imap.CommandResults {
int exists = -1;
int recent = -1;
int unseen = -1;
UID? uidvalidity = null;
UIDValidity? uidvalidity = null;
UID? uidnext = null;
MessageFlags? flags = null;
MessageFlags? permanentflags = null;
......@@ -75,7 +75,7 @@ public class Geary.Imap.SelectExamineResults : Geary.Imap.CommandResults {
break;
case ResponseCodeType.UIDVALIDITY:
uidvalidity = new UID(
uidvalidity = new UIDValidity(
ok_response.response_code.get_as_string(1).as_int());
break;
......
......@@ -15,14 +15,14 @@ public class Geary.Imap.StatusResults : Geary.Imap.CommandResults {
*/
public int recent { get; private set; }
public UID? uidnext { get; private set; }
public UID? uidvalidity { get; private set; }
public UIDValidity? uidvalidity { get; private set; }
/**
* -1 if not set.
*/
public int unseen { get; private set; }
public StatusResults(StatusResponse status_response, string mailbox, int messages, int recent,
UID? uidnext, UID? uidvalidity, int unseen) {
UID? uidnext, UIDValidity? uidvalidity, int unseen) {
base (status_response);
this.mailbox = mailbox;
......@@ -54,7 +54,7 @@ public class Geary.Imap.StatusResults : Geary.Imap.CommandResults {
int messages = -1;
int recent = -1;
UID? uidnext = null;
UID? uidvalidity = null;
UIDValidity? uidvalidity = null;
int unseen = -1;
for (int ctr = 0; ctr < values.get_count(); ctr += 2) {
......@@ -76,7 +76,7 @@ public class Geary.Imap.StatusResults : Geary.Imap.CommandResults {
break;
case StatusDataType.UIDVALIDITY:
uidvalidity = new UID(valuep.as_int());
uidvalidity = new UIDValidity(valuep.as_int());
break;
case StatusDataType.UNSEEN:
......
......@@ -25,6 +25,12 @@ public class Geary.Imap.UID : Geary.Common.Int64MessageData, Geary.Imap.MessageD
}
}
public class Geary.Imap.UIDValidity : Geary.Common.Int64MessageData, Geary.Imap.MessageData {
public UIDValidity(int64 value) {
base (value);
}
}
public class Geary.Imap.MessageNumber : Geary.Common.IntMessageData, Geary.Imap.MessageData {
public MessageNumber(int value) {
base (value);
......@@ -32,6 +38,8 @@ public class Geary.Imap.MessageNumber : Geary.Common.IntMessageData, Geary.Imap.
}
public abstract class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.MessageData {
public int size { get { return list.size; } }
private Gee.Set<Flag> list;
public Flags(Gee.Collection<Flag> flags) {
......@@ -47,6 +55,14 @@ public abstract class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.Me
return list.read_only_view;
}
/**
* Returns the flags in serialized form, which is each flag separated by a space (legal in
* IMAP, as flags must be atoms and atoms prohibit spaces).
*/
public virtual string serialize() {
return to_string();
}
public override string to_string() {
StringBuilder builder = new StringBuilder();
foreach (Flag flag in list) {
......@@ -79,12 +95,32 @@ public class Geary.Imap.MessageFlags : Geary.Imap.Flags {
return new MessageFlags(list);
}
public static MessageFlags deserialize(string str) {
string[] tokens = str.split(" ");
Gee.Collection<MessageFlag> flags = new Gee.ArrayList<MessageFlag>();
foreach (string token in tokens)
flags.add(new MessageFlag(token));
return new MessageFlags(flags);
}
}
public class Geary.Imap.MailboxAttributes : Geary.Imap.Flags {
public MailboxAttributes(Gee.Collection<MailboxAttribute> attrs) {
base (attrs);
}
public static MailboxAttributes deserialize(string str) {
string[] tokens = str.split(" ");
Gee.Collection<MailboxAttribute> attrs = new Gee.ArrayList<MailboxAttribute>();
foreach (string token in tokens)
attrs.add(new MailboxAttribute(token));
return new MailboxAttributes(attrs);
}
}
public class Geary.Imap.InternalDate : Geary.RFC822.Date, Geary.Imap.MessageData {
......
......@@ -98,6 +98,19 @@ public class Geary.Imap.ClientSessionManager {
return (results.get_count() > 0) ? results.get_all()[0] : null;
}
public async Geary.Imap.StatusResults status_async(string path, StatusDataType[] types,
Cancellable? cancellable = null) throws Error {
ClientSession session = yield get_authorized_session(cancellable);
StatusResults results = StatusResults.decode(yield session.send_command_async(
new StatusCommand(session.generate_tag(), path, types), cancellable));
if (results.status_response.status != Status.OK)
throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string());
return results;
}
public async Mailbox select_mailbox(string path, Cancellable? cancellable = null) throws Error {
return yield select_examine_mailbox(path, true, cancellable);
}
......
......@@ -8,7 +8,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
public string name { get; private set; }
public int count { get; private set; }
public bool is_readonly { get; private set; }
public UID uid_validity { get; private set; }
public UIDValidity uid_validity { get; private set; }
private SelectedContext context;
......@@ -203,7 +203,7 @@ internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
public int exists { get; protected set; }
public int recent { get; protected set; }
public bool is_readonly { get; protected set; }
public UID uid_validity { get; protected set; }
public UIDValidity uid_validity { get; protected set; }
public signal void exists_changed(int exists);
......
......@@ -23,6 +23,14 @@ public abstract class Geary.Sqlite.Table {
return table.field_name(col);
}
protected inline static int bool_to_int(bool b) {
return b ? 1 : 0;
}
protected inline static bool int_to_bool(int i) {
return !(i == 0);
}
public string to_string() {
return table.name;
}
......
......@@ -7,6 +7,7 @@
public class Geary.Sqlite.Account : Geary.AbstractAccount, Geary.LocalAccount {
private MailDatabase db;
private FolderTable folder_table;
private ImapFolderPropertiesTable folder_properties_table;
private MessageTable message_table;
public Account(Geary.Credentials cred) {
......@@ -19,6 +20,7 @@ public class Geary.Sqlite.Account : Geary.AbstractAccount, Geary.LocalAccount {
}
folder_table = db.get_folder_table();
folder_properties_table = db.get_imap_folder_properties_table();
message_table = db.get_message_table();
}
......@@ -42,20 +44,21 @@ public class Geary.Sqlite.Account : Geary.AbstractAccount, Geary.LocalAccount {
public async void clone_folder_async(Geary.Folder folder, Cancellable? cancellable = null)
throws Error {
Geary.Imap.Folder imap_folder = (Geary.Imap.Folder) folder;
Geary.Imap.FolderProperties? imap_folder_properties = (Geary.Imap.FolderProperties?)
imap_folder.get_properties();
// properties *must* be available to perform a clone
assert(imap_folder_properties != null);
int64 parent_id = yield fetch_parent_id_async(folder.get_path(), cancellable);
yield folder_table.create_async(new FolderRow(folder_table, folder.get_path().basename,
parent_id), cancellable);
}
public async void clone_many_folders_async(Gee.Collection<Geary.Folder> folders,
Cancellable? cancellable = null) throws Error {
Gee.List<FolderRow> rows = new Gee.ArrayList<FolderRow>();
foreach (Geary.Folder folder in folders) {
int64 parent_id = yield fetch_parent_id_async(folder.get_path(), cancellable);
rows.add(new FolderRow(db.get_folder_table(), folder.get_path().basename, parent_id));
}
yield folder_table.create_many_async(rows, cancellable);
int64 folder_id = yield folder_table.create_async(new FolderRow(folder_table,
imap_folder.get_path().basename, parent_id), cancellable);
yield folder_properties_table.create_async(
new ImapFolderPropertiesRow.from_imap_properties(folder_properties_table, folder_id,
imap_folder_properties));
}
public override async Gee.Collection<Geary.Folder> list_folders_async(Geary.FolderPath? parent,
......@@ -75,11 +78,14 @@ public class Geary.Sqlite.Account : Geary.AbstractAccount, Geary.LocalAccount {
Gee.Collection<Geary.Folder> folders = new Gee.ArrayList<Geary.Sqlite.Folder>();
foreach (FolderRow row in rows) {
ImapFolderPropertiesRow? properties = yield folder_properties_table.fetch_async(row.id,
cancellable);
Geary.FolderPath path = (parent != null)
? parent.get_child(row.name)
: new Geary.FolderRoot(row.name, "/", Geary.Imap.Folder.CASE_SENSITIVE);
folders.add(new Geary.Sqlite.Folder(db, row, path));
folders.add(new Geary.Sqlite.Folder(db, row, properties, path));
}
return folders;
......@@ -105,7 +111,10 @@ public class Geary.Sqlite.Account : Geary.AbstractAccount, Geary.LocalAccount {
if (row == null)
throw new EngineError.NOT_FOUND("%s not found in local database", path.to_string());
return new Geary.Sqlite.Folder(db, row, path);
ImapFolderPropertiesRow? properties = yield folder_properties_table.fetch_async(row.id,
cancellable);
return new Geary.Sqlite.Folder(db, row, properties, path);
}
public async bool has_message_id_async(Geary.RFC822.MessageID message_id, out int count,
......
......@@ -10,20 +10,25 @@
public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder {
private MailDatabase db;
private FolderRow folder_row;
private Geary.FolderProperties? properties;
private MessageTable message_table;
private MessageLocationTable location_table;
private ImapMessageLocationPropertiesTable imap_location_table;
private ImapMessagePropertiesTable imap_message_properties_table;
private Geary.FolderPath path;
private bool opened = false;
internal Folder(MailDatabase db, FolderRow folder_row, Geary.FolderPath path) throws Error {
internal Folder(MailDatabase db, FolderRow folder_row, ImapFolderPropertiesRow? properties,
Geary.FolderPath path) throws Error {
this.db = db;
this.folder_row = folder_row;
this.properties = (properties != null) ? properties.get_imap_folder_properties() : null;
this.path = path;
message_table = db.get_message_table();
location_table = db.get_message_location_table();
imap_location_table = db.get_imap_message_location_table();
imap_message_properties_table = db.get_imap_message_properties_table();