[geary/wip/181-special-folder-dupes: 6/7] Convert Geary.FolderRoot to be an actual root, not just a top-level
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/181-special-folder-dupes: 6/7] Convert Geary.FolderRoot to be an actual root, not just a top-level
- Date: Mon, 14 Jan 2019 05:28:28 +0000 (UTC)
commit 960ed1994d78ccab99bfcb581914594a89e47037
Author: Michael Gratton <mike vee net>
Date: Mon Jan 14 12:10:48 2019 +1100
Convert Geary.FolderRoot to be an actual root, not just a top-level
Instead of each top-level IMAP folder being a FolderRoot object, then
children of that being FolderPath objects, this makes FolderRoot an
"empty" FolderPath, so that both top-level and descendant folders are
plain FolderPath objects. Aside from being more technically correct,
this means that empty namespace roots can now be used interchangably
with non-empty namespace roots (addressing issue #181), and custom
folder implementations no longer need to provide their own trivial,
custom FolderRoot.
To support this, a notion of an IMAP root and a local root have been
added from which all remote and local folder paths are now derived,
existing places that assume top-level == root have been fixed, and
unit tests have been added.
po/POTFILES.in | 2 -
.../folder-list/folder-list-account-branch.vala | 2 +-
src/engine/api/geary-account-information.vala | 7 +-
src/engine/api/geary-folder-path.vala | 111 ++++++++-----
src/engine/imap-db/imap-db-account.vala | 180 +++++++++++++--------
.../imap-db/search/imap-db-search-folder-root.vala | 14 --
.../imap-db/search/imap-db-search-folder.vala | 25 ++-
.../gmail/imap-engine-gmail-account.vala | 3 +-
.../gmail/imap-engine-gmail-search-folder.vala | 4 +-
.../imap-engine/imap-engine-generic-account.vala | 46 ++++--
.../yahoo/imap-engine-yahoo-account.vala | 27 +++-
src/engine/imap/api/imap-account-session.vala | 36 ++++-
src/engine/imap/api/imap-folder-root.vala | 71 ++++----
.../imap/message/imap-mailbox-specifier.vala | 91 +++++++----
.../imap/response/imap-mailbox-information.vala | 13 +-
src/engine/imap/transport/imap-client-session.vala | 9 +-
src/engine/meson.build | 2 -
src/engine/outbox/outbox-folder-root.vala | 18 ---
src/engine/outbox/outbox-folder.vala | 22 ++-
test/engine/api/geary-folder-path-mock.vala | 14 --
test/engine/api/geary-folder-path-test.vala | 41 +++++
test/engine/app/app-conversation-monitor-test.vala | 14 +-
test/engine/app/app-conversation-set-test.vala | 20 ++-
test/engine/app/app-conversation-test.vala | 21 ++-
test/engine/imap-db/imap-db-account-test.vala | 24 +--
.../imap/message/imap-mailbox-specifier-test.vala | 85 ++++++----
test/meson.build | 2 +-
test/test-engine.vala | 1 +
28 files changed, 572 insertions(+), 333 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5a5b0e98..ca8fcc94 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -221,7 +221,6 @@ src/engine/imap-db/imap-db-message-addresses.vala
src/engine/imap-db/imap-db-message-row.vala
src/engine/imap-db/search/imap-db-search-email-identifier.vala
src/engine/imap-db/search/imap-db-search-folder-properties.vala
-src/engine/imap-db/search/imap-db-search-folder-root.vala
src/engine/imap-db/search/imap-db-search-folder.vala
src/engine/imap-db/search/imap-db-search-query.vala
src/engine/imap-db/search/imap-db-search-term.vala
@@ -346,7 +345,6 @@ src/engine/nonblocking/nonblocking-variants.vala
src/engine/outbox/outbox-email-identifier.vala
src/engine/outbox/outbox-email-properties.vala
src/engine/outbox/outbox-folder-properties.vala
-src/engine/outbox/outbox-folder-root.vala
src/engine/outbox/outbox-folder.vala
src/engine/rfc822/rfc822-error.vala
src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala
diff --git a/src/client/folder-list/folder-list-account-branch.vala
b/src/client/folder-list/folder-list-account-branch.vala
index ac6a5457..6a87da02 100644
--- a/src/client/folder-list/folder-list-account-branch.vala
+++ b/src/client/folder-list/folder-list-account-branch.vala
@@ -90,7 +90,7 @@ public class FolderList.AccountBranch : Sidebar.Branch {
// Special folders go in the root of the account.
graft_point = get_root();
- } else if (folder.path.get_parent() == null) {
+ } else if (folder.path.is_top_level) {
// Top-level folders get put in our special user folders group.
graft_point = user_folder_group;
diff --git a/src/engine/api/geary-account-information.vala b/src/engine/api/geary-account-information.vala
index 7e9d443c..edc867a1 100644
--- a/src/engine/api/geary-account-information.vala
+++ b/src/engine/api/geary-account-information.vala
@@ -29,9 +29,10 @@ public class Geary.AccountInformation : BaseObject {
if (parts == null || parts.size == 0)
return null;
- Geary.FolderPath path = new Imap.FolderRoot(parts[0]);
- for (int i = 1; i < parts.size; i++)
- path = path.get_child(parts.get(i));
+ Geary.FolderPath path = new Imap.FolderRoot();
+ foreach (string basename in parts) {
+ path = path.get_child(basename);
+ }
return path;
}
diff --git a/src/engine/api/geary-folder-path.vala b/src/engine/api/geary-folder-path.vala
index 5193a638..4d398d99 100644
--- a/src/engine/api/geary-folder-path.vala
+++ b/src/engine/api/geary-folder-path.vala
@@ -15,11 +15,13 @@
public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
Gee.Comparable<Geary.FolderPath> {
+
+
/**
* The name of this folder (without any child or parent names or delimiters).
*/
public string basename { get; private set; }
-
+
/**
* Whether this path is lexiographically case-sensitive.
*
@@ -27,16 +29,34 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
*/
public bool case_sensitive { get; private set; }
+ /**
+ * Determines if this path is a root folder path.
+ */
+ public virtual bool is_root {
+ get { return this.path == null || this.path.size == 0; }
+ }
+
+ /**
+ * Determines if this path is a child of the root folder.
+ */
+ public bool is_top_level {
+ get {
+ FolderPath? parent = get_parent();
+ return parent != null && parent.is_root;
+ }
+ }
+
+
private Gee.List<Geary.FolderPath>? path = null;
private uint stored_hash = uint.MAX;
-
- protected FolderPath(string basename, bool case_sensitive) {
- assert(this is FolderRoot);
-
- this.basename = basename;
- this.case_sensitive = case_sensitive;
+
+
+ /** Constructor only for use by {@link FolderRoot}. */
+ internal FolderPath() {
+ this.basename = "";
+ this.case_sensitive = false;
}
-
+
private FolderPath.child(Gee.List<Geary.FolderPath> path, string basename, bool case_sensitive) {
assert(path[0] is FolderRoot);
@@ -44,19 +64,7 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
this.basename = basename;
this.case_sensitive = case_sensitive;
}
-
- /**
- * Returns true if this {@link FolderPath} is a root folder.
- *
- * This means that the FolderPath ''should'' be castable into {@link FolderRoot}, which is
- * enforced through the constructor and accessor styles of this class. However, this test
- * merely checks if this FolderPath has any children. A GObject "is" operation is the
- * reliable way to cast to FolderRoot.
- */
- public bool is_root() {
- return (path == null || path.size == 0);
- }
-
+
/**
* Returns the {@link FolderRoot} of this path.
*/
@@ -127,24 +135,31 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
return list;
}
-
+
/**
- * Creates a {@link FolderPath} object that is a child of this folder.
+ * Creates a path that is a child of this folder.
*
- * {@link Trillian.TRUE} and {@link Trillian.FALSE} force case-sensitivity.
- * {@link Trillian.UNKNOWN} indicates to use {@link FolderRoot.default_case_sensitivity}.
+ * Specifying {@link Trillian.TRUE} or {@link Trillian.FALSE} for
+ * `is_case_sensitive` forces case-sensitivity either way. If
+ * {@link Trillian.UNKNOWN}, then {@link
+ * FolderRoot.default_case_sensitivity} is used.
*/
- public Geary.FolderPath get_child(string basename, Trillian child_case_sensitive = Trillian.UNKNOWN) {
+ public virtual Geary.FolderPath
+ get_child(string basename,
+ Trillian is_case_sensitive = Trillian.UNKNOWN) {
// Build the child's path, which is this node's path plus this node
Gee.List<FolderPath> child_path = new Gee.ArrayList<FolderPath>();
if (path != null)
child_path.add_all(path);
child_path.add(this);
-
- return new FolderPath.child(child_path, basename,
- child_case_sensitive.to_boolean(get_root().default_case_sensitivity));
+
+ return new FolderPath.child(
+ child_path,
+ basename,
+ is_case_sensitive.to_boolean(get_root().default_case_sensitivity)
+ );
}
-
+
/**
* Returns true if the other {@link FolderPath} has the same parent as this one.
*
@@ -312,28 +327,36 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
}
/**
- * The root of a folder heirarchy.
- *
- * A {@link FolderPath} can only be created by starting with a FolderRoot and adding children
- * via {@link FolderPath.get_child}. Because all FolderPaths hold references to their parents,
- * this element can be retrieved with {@link FolderPath.get_root}.
+ * The root of a folder hierarchy.
*
- * Since each email system may have different requirements for its paths, this is an abstract
- * class.
+ * A {@link FolderPath} can only be created by starting with a
+ * FolderRoot and adding children via {@link FolderPath.get_child}.
+ * Because all FolderPaths hold references to their parents, this
+ * element can be retrieved with {@link FolderPath.get_root}.
*/
-public abstract class Geary.FolderRoot : Geary.FolderPath {
+public class Geary.FolderRoot : Geary.FolderPath {
+
+
+ /** {@inheritDoc} */
+ public override bool is_root {
+ get { return true; }
+ }
+
/**
- * The default case sensitivity of each element in the {@link FolderPath}.
+ * The default case sensitivity of descendant folders.
*
* @see FolderRoot.case_sensitive
* @see FolderPath.get_child
*/
public bool default_case_sensitivity { get; private set; }
-
- protected FolderRoot(string basename, bool case_sensitive, bool default_case_sensitivity) {
- base (basename, case_sensitive);
-
+
+
+ /**
+ * Constructs a new folder root with given default sensitivity.
+ */
+ public FolderRoot(bool default_case_sensitivity) {
+ base();
this.default_case_sensitivity = default_case_sensitivity;
}
-}
+}
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index f6f77f31..2b67edd7 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -1,7 +1,9 @@
-/* Copyright 2016 Software Freedom Conservancy Inc.
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2019 Michael Gratton <mike vee net>.
*
* This software is licensed under the GNU Lesser General Public License
- * (version 2.1 or later). See the COPYING file in this distribution.
+ * (version 2.1 or later). See the COPYING file in this distribution.
*/
private class Geary.ImapDB.Account : BaseObject {
@@ -74,9 +76,22 @@ private class Geary.ImapDB.Account : BaseObject {
public signal void contacts_loaded();
+ /**
+ * The root path for all remote IMAP folders.
+ *
+ * No folder exists for this path locally or on the remote server,
+ * it merely exists to provide a common root for the paths of all
+ * IMAP folders.
+ *
+ * @see list_folders_async
+ */
+ public Imap.FolderRoot imap_folder_root {
+ get; private set; default = new Imap.FolderRoot();
+ }
+
// Only available when the Account is opened
public ImapEngine.ContactStore contact_store { get; private set; }
- public IntervalProgressMonitor search_index_monitor { get; private set;
+ public IntervalProgressMonitor search_index_monitor { get; private set;
default = new IntervalProgressMonitor(ProgressType.SEARCH_INDEX, 0, 0); }
public SimpleProgressMonitor upgrade_monitor { get; private set; default = new SimpleProgressMonitor(
ProgressType.DB_UPGRADE); }
@@ -476,11 +491,19 @@ private class Geary.ImapDB.Account : BaseObject {
contacts_loaded();
}
}
-
- public async Gee.Collection<Geary.ImapDB.Folder> list_folders_async(Geary.FolderPath? parent,
- Cancellable? cancellable = null) throws Error {
+
+ /**
+ * Lists all children of a given folder.
+ *
+ * To list all top-level folders, pass in {@link imap_folder_root}
+ * as the parent.
+ */
+ public async Gee.Collection<Geary.ImapDB.Folder>
+ list_folders_async(Geary.FolderPath parent,
+ GLib.Cancellable? cancellable)
+ throws GLib.Error {
check_open();
-
+
// TODO: A better solution here would be to only pull the FolderProperties if the Folder
// object itself doesn't already exist
Gee.HashMap<Geary.FolderPath, int64?> id_map = new Gee.HashMap<
@@ -489,17 +512,14 @@ private class Geary.ImapDB.Account : BaseObject {
Geary.FolderPath, Geary.Imap.FolderProperties>();
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
int64 parent_id = Db.INVALID_ROWID;
- if (parent != null) {
- if (!do_fetch_folder_id(cx, parent, false, out parent_id, cancellable)) {
- debug("Unable to find folder ID for %s to list folders", parent.to_string());
-
- return Db.TransactionOutcome.ROLLBACK;
- }
-
- if (parent_id == Db.INVALID_ROWID)
- throw new EngineError.NOT_FOUND("Folder %s not found", parent.to_string());
+ if (!parent.is_root &&
+ !do_fetch_folder_id(
+ cx, parent, false, out parent_id, cancellable
+ )) {
+ debug("Unable to find folder ID for \"%s\" to list folders", parent.to_string());
+ return Db.TransactionOutcome.ROLLBACK;
}
-
+
Db.Statement stmt;
if (parent_id != Db.INVALID_ROWID) {
stmt = cx.prepare(
@@ -511,15 +531,11 @@ private class Geary.ImapDB.Account : BaseObject {
"SELECT id, name, last_seen_total, unread_count, last_seen_status_total, "
+ "uid_validity, uid_next, attributes FROM FolderTable WHERE parent_id IS NULL");
}
-
+
Db.Result result = stmt.exec(cancellable);
while (!result.finished) {
string basename = result.string_for("name");
-
- Geary.FolderPath path = (parent != null)
- ? parent.get_child(basename)
- : new Imap.FolderRoot(basename);
-
+ Geary.FolderPath path = parent.get_child(basename);
Geary.Imap.FolderProperties properties = new Geary.Imap.FolderProperties.from_imapdb(
Geary.Imap.MailboxAttributes.deserialize(result.string_for("attributes")),
result.int_for("last_seen_total"),
@@ -544,12 +560,13 @@ private class Geary.ImapDB.Account : BaseObject {
}, cancellable);
assert(id_map.size == prop_map.size);
-
+
if (id_map.size == 0) {
- throw new EngineError.NOT_FOUND("No local folders in %s",
- (parent != null) ? parent.to_string() : "root");
+ throw new EngineError.NOT_FOUND(
+ "No local folders under \"%s\"", parent.to_string()
+ );
}
-
+
Gee.Collection<Geary.ImapDB.Folder> folders = new Gee.ArrayList<Geary.ImapDB.Folder>();
foreach (Geary.FolderPath path in id_map.keys) {
Geary.ImapDB.Folder? folder = get_local_folder(path);
@@ -1555,23 +1572,32 @@ private class Geary.ImapDB.Account : BaseObject {
folder_stmt.exec(cancellable);
}
-
- // If the FolderPath has no parent, returns true and folder_id will be set to Db.INVALID_ROWID.
- // If cannot create path or there is a logical problem traversing it, returns false with folder_id
- // set to Db.INVALID_ROWID.
- internal bool do_fetch_folder_id(Db.Connection cx, Geary.FolderPath path, bool create, out int64
folder_id,
- Cancellable? cancellable) throws Error {
- int length = path.get_path_length();
- if (length < 0)
- throw new EngineError.BAD_PARAMETERS("Invalid path %s", path.to_string());
-
+
+ // If the FolderPath has no parent, returns true and folder_id
+ // will be set to Db.INVALID_ROWID. If cannot create path or
+ // there is a logical problem traversing it, returns false with
+ // folder_id set to Db.INVALID_ROWID.
+ internal bool do_fetch_folder_id(Db.Connection cx,
+ Geary.FolderPath path,
+ bool create,
+ out int64 folder_id,
+ GLib.Cancellable? cancellable)
+ throws GLib.Error {
+ if (path.is_root) {
+ throw new EngineError.BAD_PARAMETERS(
+ "Cannot fetch folder for root path"
+ );
+ }
+
+ // Don't include the root since top-level folders are stored
+ // with no parent.
+ Gee.List<string> parts = path.as_list();
+ parts.remove_at(0);
+
folder_id = Db.INVALID_ROWID;
int64 parent_id = Db.INVALID_ROWID;
-
- // walk the folder tree to the final node (which is at length - 1 - 1)
- for (int ctr = 0; ctr < length; ctr++) {
- string basename = path.get_folder_at(ctr).basename;
-
+
+ foreach (string basename in parts) {
Db.Statement stmt;
if (parent_id != Db.INVALID_ROWID) {
stmt = cx.prepare("SELECT id FROM FolderTable WHERE parent_id=? AND name=?");
@@ -1616,19 +1642,28 @@ private class Geary.ImapDB.Account : BaseObject {
return true;
}
-
- // See do_fetch_folder_id() for return semantics.
- internal bool do_fetch_parent_id(Db.Connection cx, Geary.FolderPath path, bool create, out int64
parent_id,
- Cancellable? cancellable = null) throws Error {
- if (path.is_root()) {
+
+ internal bool do_fetch_parent_id(Db.Connection cx,
+ FolderPath path,
+ bool create,
+ out int64 parent_id,
+ GLib.Cancellable? cancellable = null)
+ throws GLib.Error {
+ // See do_fetch_folder_id() for return semantics
+ bool ret = true;
+
+ // No folder for the root is saved in the database, so
+ // top-levels should not have a parent.
+ if (path.is_top_level) {
parent_id = Db.INVALID_ROWID;
-
- return true;
+ } else {
+ ret = do_fetch_folder_id(
+ cx, path.get_parent(), create, out parent_id, cancellable
+ );
}
-
- return do_fetch_folder_id(cx, path.get_parent(), create, out parent_id, cancellable);
+ return ret;
}
-
+
private bool do_has_children(Db.Connection cx, int64 folder_id, Cancellable? cancellable) throws Error {
Db.Statement stmt = cx.prepare("SELECT 1 FROM FolderTable WHERE parent_id = ?");
stmt.bind_rowid(0, folder_id);
@@ -1700,8 +1735,12 @@ private class Geary.ImapDB.Account : BaseObject {
// For a message row id, return a set of all folders it's in, or null if
// it's not in any folders.
- private static Gee.Set<Geary.FolderPath>? do_find_email_folders(Db.Connection cx, int64 message_id,
- bool include_removed, Cancellable? cancellable) throws Error {
+ private Gee.Set<Geary.FolderPath>?
+ do_find_email_folders(Db.Connection cx,
+ int64 message_id,
+ bool include_removed,
+ GLib.Cancellable? cancellable)
+ throws GLib.Error {
string sql = "SELECT folder_id FROM MessageLocationTable WHERE message_id=?";
if (!include_removed)
sql += " AND remove_marker=0";
@@ -1724,16 +1763,20 @@ private class Geary.ImapDB.Account : BaseObject {
return (folder_paths.size == 0 ? null : folder_paths);
}
-
+
// For a folder row id, return the folder path (constructed with default
// separator and case sensitivity) of that folder, or null in the event
// it's not found.
- private static Geary.FolderPath? do_find_folder_path(Db.Connection cx, int64 folder_id,
- Cancellable? cancellable) throws Error {
- Db.Statement stmt = cx.prepare("SELECT parent_id, name FROM FolderTable WHERE id=?");
+ private Geary.FolderPath? do_find_folder_path(Db.Connection cx,
+ int64 folder_id,
+ GLib.Cancellable? cancellable)
+ throws GLib.Error {
+ Db.Statement stmt = cx.prepare(
+ "SELECT parent_id, name FROM FolderTable WHERE id=?"
+ );
stmt.bind_int64(0, folder_id);
Db.Result result = stmt.exec(cancellable);
-
+
if (result.finished)
return null;
@@ -1746,12 +1789,19 @@ private class Geary.ImapDB.Account : BaseObject {
folder_id.to_string(), parent_id.to_string());
return null;
}
-
- if (parent_id <= 0)
- return new Imap.FolderRoot(name);
-
- Geary.FolderPath? parent_path = do_find_folder_path(cx, parent_id, cancellable);
- return (parent_path == null ? null : parent_path.get_child(name));
+
+ Geary.FolderPath? path = null;
+ if (parent_id <= 0) {
+ path = this.imap_folder_root.get_child(name);
+ } else {
+ Geary.FolderPath? parent_path = do_find_folder_path(
+ cx, parent_id, cancellable
+ );
+ if (parent_path != null) {
+ path = parent_path.get_child(name);
+ }
+ }
+ return path;
}
private void on_unread_updated(ImapDB.Folder source, Gee.Map<ImapDB.EmailIdentifier, bool>
diff --git a/src/engine/imap-db/search/imap-db-search-folder.vala
b/src/engine/imap-db/search/imap-db-search-folder.vala
index 16b957fb..5f590635 100644
--- a/src/engine/imap-db/search/imap-db-search-folder.vala
+++ b/src/engine/imap-db/search/imap-db-search-folder.vala
@@ -5,23 +5,34 @@
*/
private class Geary.ImapDB.SearchFolder : Geary.SearchFolder, Geary.FolderSupport.Remove {
- // Max number of emails that can ever be in the folder.
+
+
+ /** Max number of emails that can ever be in the folder. */
public const int MAX_RESULT_EMAILS = 1000;
-
+
+ /** The canonical name of the search folder. */
+ public const string MAGIC_BASENAME = "$GearySearchFolder$";
+
private const Geary.SpecialFolderType[] exclude_types = {
Geary.SpecialFolderType.SPAM,
Geary.SpecialFolderType.TRASH,
Geary.SpecialFolderType.DRAFTS,
// Orphan emails (without a folder) are also excluded; see ctor.
};
-
+
+
private Gee.HashSet<Geary.FolderPath?> exclude_folders = new Gee.HashSet<Geary.FolderPath?>();
private Gee.TreeSet<ImapDB.SearchEmailIdentifier> search_results;
private Geary.Nonblocking.Mutex result_mutex = new Geary.Nonblocking.Mutex();
-
- public SearchFolder(Geary.Account account) {
- base (account, new SearchFolderProperties(0, 0), new SearchFolderRoot());
-
+
+
+ public SearchFolder(Geary.Account account, FolderRoot root) {
+ base(
+ account,
+ new SearchFolderProperties(0, 0),
+ root.get_child(MAGIC_BASENAME, Trillian.TRUE)
+ );
+
account.folders_available_unavailable.connect(on_folders_available_unavailable);
account.email_locally_complete.connect(on_email_locally_complete);
account.email_removed.connect(on_account_email_removed);
diff --git a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
index 52127abc..f246e5b6 100644
--- a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
+++ b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
@@ -75,6 +75,7 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
}
protected override SearchFolder new_search_folder() {
- return new GmailSearchFolder(this);
+ return new GmailSearchFolder(this, this.local_folder_root);
}
+
}
diff --git a/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala
b/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala
index 9dea5b2a..ef47256d 100644
--- a/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala
+++ b/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala
@@ -12,8 +12,8 @@ private class Geary.ImapEngine.GmailSearchFolder : ImapDB.SearchFolder {
private Geary.App.EmailStore email_store;
- public GmailSearchFolder(Geary.Account account) {
- base (account);
+ public GmailSearchFolder(Geary.Account account, FolderRoot root) {
+ base (account, root);
this.email_store = new Geary.App.EmailStore(account);
}
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 7276ca9f..48e0697c 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -1,6 +1,6 @@
/*
* Copyright 2016 Software Freedom Conservancy Inc.
- * Copyright 2017-2018 Michael Gratton <mike vee net>.
+ * Copyright 2017-2019 Michael Gratton <mike vee net>.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
@@ -34,6 +34,14 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
/** Local database for the account. */
public ImapDB.Account local { get; private set; }
+ /**
+ * The root path for all local folders.
+ *
+ * No folder exists for this path, it merely exists to provide a
+ * common root for the paths of all local folders.
+ */
+ protected FolderRoot local_folder_root = new Geary.FolderRoot(true);
+
private bool open = false;
private Cancellable? open_cancellable = null;
private Nonblocking.Semaphore? remote_ready_lock = null;
@@ -78,7 +86,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
);
this.imap = imap;
- smtp.outbox = new Outbox.Folder(this, local);
+ smtp.outbox = new Outbox.Folder(this, local_folder_root, local);
smtp.email_sent.connect(on_email_sent);
smtp.report_problem.connect(notify_report_problem);
this.smtp = smtp;
@@ -139,10 +147,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
// Create/load local folders
- local_only.set(new Outbox.FolderRoot(), this.smtp.outbox);
+ local_only.set(this.smtp.outbox.path, this.smtp.outbox);
this.search_folder = new_search_folder();
- local_only.set(new ImapDB.SearchFolderRoot(), this.search_folder);
+ local_only.set(this.search_folder.path, this.search_folder);
this.open = true;
notify_opened();
@@ -300,7 +308,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
yield this.remote_ready_lock.wait_async(cancellable);
Imap.ClientSession client =
yield this.imap.claim_authorized_session_async(cancellable);
- return new Imap.AccountSession(this.information.id, client);
+ return new Imap.AccountSession(
+ this.information.id, this.local.imap_folder_root, client
+ );
}
/**
@@ -350,7 +360,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
Imap.ClientSession? client =
yield this.imap.claim_authorized_session_async(cancellable);
Imap.AccountSession account = new Imap.AccountSession(
- this.information.id, client
+ this.information.id, this.local.imap_folder_root, client
);
Imap.Folder? folder = null;
@@ -688,11 +698,15 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
Cancellable? cancellable)
throws Error {
Geary.FolderPath? path = information.get_special_folder_path(special);
- if (path != null) {
- debug("Previously used %s for special folder %s", path.to_string(), special.to_string());
- } else {
- // This is the first time we're turning a non-special folder into a special one.
- // After we do this, we'll record which one we picked in the account info.
+ if (!remote.is_folder_path_valid(path)) {
+ debug("Ignoring bad special folder path '%s' for type %s",
+ path.to_string(),
+ special.to_string());
+ path = null;
+ }
+ if (path == null) {
+ debug("Guessing path for special folder type: %s",
+ special.to_string());
Geary.FolderPath root =
yield remote.get_default_personal_namespace(cancellable);
Gee.List<string> search_names = special_search_names.get(special);
@@ -779,7 +793,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
* override this to return the correct subclass.
*/
protected virtual SearchFolder new_search_folder() {
- return new ImapDB.SearchFolder(this);
+ return new ImapDB.SearchFolder(this, this.local_folder_root);
}
/** {@inheritDoc} */
@@ -1028,7 +1042,9 @@ internal class Geary.ImapEngine.LoadFolders : AccountOperation {
GenericAccount generic = (GenericAccount) this.account;
Gee.List<ImapDB.Folder> folders = new Gee.LinkedList<ImapDB.Folder>();
- yield enumerate_local_folders_async(folders, null, cancellable);
+ yield enumerate_local_folders_async(
+ folders, generic.local.imap_folder_root, cancellable
+ );
generic.add_folders(folders, true);
if (!folders.is_empty) {
// If we have some folders to load, then this isn't the
@@ -1039,7 +1055,7 @@ internal class Geary.ImapEngine.LoadFolders : AccountOperation {
}
private async void enumerate_local_folders_async(Gee.List<ImapDB.Folder> folders,
- Geary.FolderPath? parent,
+ Geary.FolderPath parent,
Cancellable? cancellable)
throws Error {
Gee.Collection<ImapDB.Folder>? children = null;
@@ -1074,7 +1090,7 @@ internal class Geary.ImapEngine.LoadFolders : AccountOperation {
Geary.Folder target = yield generic.fetch_folder_async(path, cancellable);
specials.set(special, target);
} catch (Error err) {
- debug("%s: Previously used special folder %s does not exist: %s",
+ debug("%s: Previously used special folder %s not loaded: %s",
generic.information.id, special.to_string(), err.message);
}
}
diff --git a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
index b64c5afb..cf0d6bc0 100644
--- a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
+++ b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
@@ -1,7 +1,9 @@
-/* Copyright 2016 Software Freedom Conservancy Inc.
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2019 Michael Gratton <mike vee net>
*
* This software is licensed under the GNU Lesser General Public License
- * (version 2.1 or later). See the COPYING file in this distribution.
+ * (version 2.1 or later). See the COPYING file in this distribution.
*/
private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
@@ -36,11 +38,22 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
if (special_map == null) {
special_map = new Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>();
- special_map.set(Imap.MailboxSpecifier.inbox.to_folder_path(null, null),
Geary.SpecialFolderType.INBOX);
- special_map.set(new Imap.FolderRoot("Sent"), Geary.SpecialFolderType.SENT);
- special_map.set(new Imap.FolderRoot("Draft"), Geary.SpecialFolderType.DRAFTS);
- special_map.set(new Imap.FolderRoot("Bulk Mail"), Geary.SpecialFolderType.SPAM);
- special_map.set(new Imap.FolderRoot("Trash"), Geary.SpecialFolderType.TRASH);
+ FolderRoot root = this.local.imap_folder_root;
+ special_map.set(
+ this.local.imap_folder_root.inbox, Geary.SpecialFolderType.INBOX
+ );
+ special_map.set(
+ root.get_child("Sent"), Geary.SpecialFolderType.SENT
+ );
+ special_map.set(
+ root.get_child("Draft"), Geary.SpecialFolderType.DRAFTS
+ );
+ special_map.set(
+ root.get_child("Bulk Mail"), Geary.SpecialFolderType.SPAM
+ );
+ special_map.set(
+ root.get_child("Trash"), Geary.SpecialFolderType.TRASH
+ );
}
}
diff --git a/src/engine/imap/api/imap-account-session.vala b/src/engine/imap/api/imap-account-session.vala
index 99823f6c..99645990 100644
--- a/src/engine/imap/api/imap-account-session.vala
+++ b/src/engine/imap/api/imap-account-session.vala
@@ -23,6 +23,7 @@
*/
internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
+ private FolderRoot root;
private Gee.HashMap<FolderPath,Imap.Folder> folders =
new Gee.HashMap<FolderPath,Imap.Folder>();
@@ -32,8 +33,10 @@ internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
internal AccountSession(string account_id,
+ FolderRoot root,
ClientSession session) {
base("%s:account".printf(account_id), session);
+ this.root = root;
session.list.connect(on_list_data);
session.status.connect(on_status_data);
@@ -56,7 +59,26 @@ internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
prefix = prefix.substring(0, prefix.length - delim.length);
}
- return new FolderRoot(prefix);
+ return Geary.String.is_empty(prefix)
+ ? this.root
+ : this.root.get_child(prefix);
+ }
+
+ /**
+ * Determines if the given folder path appears to a valid mailbox.
+ */
+ public bool is_folder_path_valid(FolderPath? path) throws GLib.Error {
+ bool is_valid = false;
+ if (path != null) {
+ ClientSession session = claim_session();
+ try {
+ session.get_mailbox_for_path(path);
+ is_valid = true;
+ } catch (GLib.Error err) {
+ // still not valid
+ }
+ }
+ return is_valid;
}
/**
@@ -172,7 +194,9 @@ internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
// Mailbox is unselectable, so doesn't need a STATUS,
// so we can create it now if it does not already
// exist
- FolderPath path = session.get_path_for_mailbox(mailbox_info.mailbox);
+ FolderPath path = session.get_path_for_mailbox(
+ this.root, mailbox_info.mailbox
+ );
Folder? child = this.folders.get(path);
if (child == null) {
child = new Imap.Folder(
@@ -223,7 +247,9 @@ internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
}
status_results.remove(status);
- FolderPath child_path = session.get_path_for_mailbox(mailbox_info.mailbox);
+ FolderPath child_path = session.get_path_for_mailbox(
+ this.root, mailbox_info.mailbox
+ );
Imap.Folder? child = this.folders.get(child_path);
if (child != null) {
@@ -314,7 +340,9 @@ internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject {
if (folder != null && list_children) {
Gee.Iterator<MailboxInformation> iter = list_results.iterator();
while (iter.next()) {
- FolderPath list_path = session.get_path_for_mailbox(iter.get().mailbox);
+ FolderPath list_path = session.get_path_for_mailbox(
+ this.root, iter.get().mailbox
+ );
if (list_path.equal_to(folder)) {
debug("Removing parent from LIST results: %s", list_path.to_string());
iter.remove();
diff --git a/src/engine/imap/api/imap-folder-root.vala b/src/engine/imap/api/imap-folder-root.vala
index e19a4a1f..0f8a39ee 100644
--- a/src/engine/imap/api/imap-folder-root.vala
+++ b/src/engine/imap/api/imap-folder-root.vala
@@ -1,40 +1,55 @@
-/* Copyright 2016 Software Freedom Conservancy Inc.
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
- * (version 2.1 or later). See the COPYING file in this distribution.
+ * (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* The root of all IMAP mailbox paths.
*
- * Because IMAP has peculiar requirements about its mailbox paths (in particular, Inbox is
- * guaranteed at the root and is named case-insensitive, and that delimiters are particular to
- * each path), this class ensure certain requirements are held throughout the library.
+ * Because IMAP has peculiar requirements about its mailbox paths (in
+ * particular, Inbox is guaranteed at the root and is named
+ * case-insensitive, and that delimiters are particular to each path),
+ * this class ensure certain requirements are held throughout the
+ * library.
*/
+public class Geary.Imap.FolderRoot : Geary.FolderRoot {
-private class Geary.Imap.FolderRoot : Geary.FolderRoot {
- public bool is_inbox { get; private set; }
-
- public FolderRoot(string basename) {
- bool init_is_inbox;
- string normalized_basename = init(basename, out init_is_inbox);
-
- base (normalized_basename, !init_is_inbox, true);
-
- is_inbox = init_is_inbox;
+
+ /**
+ * The canonical path for the IMAP inbox.
+ *
+ * This specific path object will always be returned when a child
+ * with some case-insensitive version of the IMAP inbox mailbox is
+ * obtained via {@link get_child} from this root folder. However
+ * since multiple folder roots may be constructed, in general
+ * {@link FolderPath.equal_to} or {@link FolderPath.compare_to}
+ * should still be used for testing equality with this path.
+ */
+ public FolderPath inbox { get; private set; }
+
+
+ public FolderRoot() {
+ base(false);
+ this.inbox = base.get_child(
+ MailboxSpecifier.CANONICAL_INBOX_NAME,
+ Trillian.FALSE
+ );
}
-
- // This is the magic that ensures the canonical IMAP Inbox name is used throughout the engine
- private static string init(string basename, out bool is_inbox) {
- if (MailboxSpecifier.is_inbox_name(basename)) {
- is_inbox = true;
-
- return MailboxSpecifier.CANONICAL_INBOX_NAME;
- }
-
- is_inbox = false;
-
- return basename;
+
+ /**
+ * Creates a path that is a child of this folder.
+ *
+ * If the given basename is that of the IMAP inbox, then {@link
+ * inbox} will be returned.
+ */
+ public override
+ FolderPath get_child(string basename,
+ Trillian is_case_sensitive = Trillian.UNKNOWN) {
+ return (MailboxSpecifier.is_inbox_name(basename))
+ ? this.inbox
+ : base.get_child(basename, is_case_sensitive);
}
-}
+}
diff --git a/src/engine/imap/message/imap-mailbox-specifier.vala
b/src/engine/imap/message/imap-mailbox-specifier.vala
index 1984338b..ad6f638e 100644
--- a/src/engine/imap/message/imap-mailbox-specifier.vala
+++ b/src/engine/imap/message/imap-mailbox-specifier.vala
@@ -84,9 +84,9 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
* Returns true if the {@link Geary.FolderPath} points to the IMAP Inbox.
*/
public static bool folder_path_is_inbox(FolderPath path) {
- return path.is_root() && is_inbox_name(path.basename);
+ return path.is_top_level && is_inbox_name(path.basename);
}
-
+
/**
* Returns true if the string is the name of the IMAP Inbox.
*
@@ -115,30 +115,50 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
public static bool is_canonical_inbox_name(string name) {
return Ascii.str_equal(name, CANONICAL_INBOX_NAME);
}
-
+
/**
* Converts a generic {@link FolderPath} into an IMAP mailbox specifier.
*/
- public MailboxSpecifier.from_folder_path(FolderPath path, MailboxSpecifier inbox, string? delim)
- throws ImapError {
+ public MailboxSpecifier.from_folder_path(FolderPath path,
+ MailboxSpecifier inbox,
+ string? delim)
+ throws ImapError {
+ if (path.is_root) {
+ throw new ImapError.INVALID(
+ "Cannot convert root path into a mailbox"
+ );
+ }
+
Gee.List<string> parts = path.as_list();
+ // Don't include the root so that mailboxes do not begin with
+ // the delim.
+ parts.remove_at(0);
+
if (parts.size > 1 && delim == null) {
- // XXX not quite right
- throw new ImapError.INVALID("Path has more than one part but no delimiter given");
+ throw new ImapError.INVALID(
+ "Path has more than one part but no delimiter given"
+ );
}
- // Don't include the root if it is an empty string so that
- // mailboxes do not begin with the delim.
- if (parts.size > 1 && parts[0] == "") {
- parts.remove_at(0);
+ if (String.is_empty_or_whitespace(parts[0])) {
+ throw new ImapError.INVALID(
+ "Path contains empty base part: '%s'", path.to_string()
+ );
}
StringBuilder builder = new StringBuilder(
- is_inbox_name(parts[0]) ? inbox.name : parts[0]);
+ is_inbox_name(parts[0]) ? inbox.name : parts[0]
+ );
for (int i = 1; i < parts.size; i++) {
+ string basename = parts[i];
+ if (String.is_empty_or_whitespace(basename)) {
+ throw new ImapError.INVALID(
+ "Path contains empty part: '%s'", path.to_string()
+ );
+ }
builder.append(delim);
- builder.append(parts[i]);
+ builder.append(basename);
}
init(builder.str);
@@ -156,7 +176,7 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
* the name is returned as a single element.
*/
public Gee.List<string> to_list(string? delim) {
- Gee.List<string> path = new Gee.ArrayList<string>();
+ Gee.List<string> path = new Gee.LinkedList<string>();
if (!String.is_empty(delim)) {
string[] split = name.split(delim);
@@ -171,33 +191,34 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
return path;
}
-
+
/**
- * Converts the {@link MailboxSpecifier} into a {@link FolderPath}.
+ * Converts the mailbox into a folder path.
*
- * If the inbox_specifier is supplied, if the root element matches it, the canonical Inbox
- * name is used in its place. This is useful for XLIST where that command returns a translated
- * name but the standard IMAP name ("INBOX") must be used in addressing its children.
+ * If the inbox_specifier is supplied and the first element
+ * matches it, the canonical Inbox name is used in its place.
+ * This is useful for XLIST where that command returns a
+ * translated name but the standard IMAP name ("INBOX") must be
+ * used in addressing its children.
*/
- public FolderPath to_folder_path(string? delim, MailboxSpecifier? inbox_specifier) {
- // convert path to list of elements
+ public FolderPath to_folder_path(FolderRoot root,
+ string? delim,
+ MailboxSpecifier? inbox_specifier) {
Gee.List<string> list = to_list(delim);
-
- // if root element is same as supplied inbox specifier, use canonical inbox name, otherwise
- // keep
- FolderPath path;
- if (inbox_specifier != null && list[0] == inbox_specifier.name)
- path = new Imap.FolderRoot(CANONICAL_INBOX_NAME);
- else
- path = new Imap.FolderRoot(list[0]);
-
- // walk down rest of elements adding as we go
- for (int ctr = 1; ctr < list.size; ctr++)
- path = path.get_child(list[ctr]);
-
+
+ // If the first element is same as supplied inbox specifier,
+ // use canonical inbox name, otherwise keep
+ FolderPath? path =
+ (inbox_specifier != null && list[0] == inbox_specifier.name)
+ ? root.get_child(CANONICAL_INBOX_NAME)
+ : root.get_child(list[0]);
+
+ foreach (string name in list) {
+ path = path.get_child(name);
+ }
return path;
}
-
+
/**
* The mailbox's name without parent folders.
*
diff --git a/src/engine/imap/response/imap-mailbox-information.vala
b/src/engine/imap/response/imap-mailbox-information.vala
index 6d8c3a0c..0d00324f 100644
--- a/src/engine/imap/response/imap-mailbox-information.vala
+++ b/src/engine/imap/response/imap-mailbox-information.vala
@@ -86,19 +86,8 @@ public class Geary.Imap.MailboxInformation : BaseObject {
);
}
- /**
- * The {@link Geary.FolderPath} for the {@link mailbox}.
- *
- * This is constructed from the supplied {@link mailbox} and {@link delim} returned from the
- * server. If the mailbox is the same as the supplied inbox_specifier, a canonical name for
- * the Inbox is returned.
- */
- public Geary.FolderPath get_path(MailboxSpecifier? inbox_specifier) {
- return mailbox.to_folder_path(delim, inbox_specifier);
- }
-
public string to_string() {
return "%s/%s".printf(mailbox.to_string(), attrs.to_string());
}
-}
+}
diff --git a/src/engine/imap/transport/imap-client-session.vala
b/src/engine/imap/transport/imap-client-session.vala
index 4613a0d6..0e7910ba 100644
--- a/src/engine/imap/transport/imap-client-session.vala
+++ b/src/engine/imap/transport/imap-client-session.vala
@@ -509,7 +509,7 @@ public class Geary.Imap.ClientSession : BaseObject {
* Determines the SELECT-able mailbox name for a specific folder path.
*/
public MailboxSpecifier get_mailbox_for_path(FolderPath path)
- throws ImapError {
+ throws ImapError {
string? delim = get_delimiter_for_path(path);
return new MailboxSpecifier.from_folder_path(path, this.inbox.mailbox, delim);
}
@@ -517,10 +517,11 @@ public class Geary.Imap.ClientSession : BaseObject {
/**
* Determines the folder path for a mailbox name.
*/
- public FolderPath get_path_for_mailbox(MailboxSpecifier mailbox)
- throws ImapError {
+ public FolderPath get_path_for_mailbox(FolderRoot root,
+ MailboxSpecifier mailbox)
+ throws ImapError {
string? delim = get_delimiter_for_mailbox(mailbox);
- return mailbox.to_folder_path(delim, this.inbox.mailbox);
+ return mailbox.to_folder_path(root, delim, this.inbox.mailbox);
}
/**
diff --git a/src/engine/meson.build b/src/engine/meson.build
index 85d256a5..067a400d 100644
--- a/src/engine/meson.build
+++ b/src/engine/meson.build
@@ -181,7 +181,6 @@ geary_engine_vala_sources = files(
'imap-db/search/imap-db-search-email-identifier.vala',
'imap-db/search/imap-db-search-folder.vala',
'imap-db/search/imap-db-search-folder-properties.vala',
- 'imap-db/search/imap-db-search-folder-root.vala',
'imap-db/search/imap-db-search-query.vala',
'imap-db/search/imap-db-search-term.vala',
@@ -264,7 +263,6 @@ geary_engine_vala_sources = files(
'outbox/outbox-email-properties.vala',
'outbox/outbox-folder.vala',
'outbox/outbox-folder-properties.vala',
- 'outbox/outbox-folder-root.vala',
'rfc822/rfc822.vala',
'rfc822/rfc822-error.vala',
diff --git a/src/engine/outbox/outbox-folder.vala b/src/engine/outbox/outbox-folder.vala
index cf8be4a0..6f72194f 100644
--- a/src/engine/outbox/outbox-folder.vala
+++ b/src/engine/outbox/outbox-folder.vala
@@ -16,6 +16,10 @@ private class Geary.Outbox.Folder :
Geary.FolderSupport.Remove {
+ /** The canonical name of the outbox folder. */
+ public const string MAGIC_BASENAME = "$GearyOutbox$";
+
+
private class OutboxRow {
public int64 id;
public int position;
@@ -38,19 +42,32 @@ private class Geary.Outbox.Folder :
}
+ /** {@inheritDoc} */
public override Account account { get { return this._account; } }
+ /** {@inheritDoc} */
public override Geary.FolderProperties properties {
get { return _properties; }
}
- private FolderRoot _path = new FolderRoot();
+ /**
+ * Returns the path to this folder.
+ *
+ * This is always the child of the root given to the constructor,
+ * with the name given by @{link MAGIC_BASENAME}.
+ */
public override FolderPath path {
get {
return _path;
}
}
+ private FolderPath _path;
+ /**
+ * Returns the type of this folder.
+ *
+ * This is always {@link Geary.SpecialFolderType.OUTBOX}
+ */
public override SpecialFolderType special_folder_type {
get {
return Geary.SpecialFolderType.OUTBOX;
@@ -66,8 +83,9 @@ private class Geary.Outbox.Folder :
// Requires the Database from the get-go because it runs a background task that access it
// whether open or not
- public Folder(Account account, ImapDB.Account local) {
+ public Folder(Account account, FolderRoot root, ImapDB.Account local) {
this._account = account;
+ this._path = root.get_child(MAGIC_BASENAME, Trillian.TRUE);
this.local = local;
}
diff --git a/test/engine/api/geary-folder-path-test.vala b/test/engine/api/geary-folder-path-test.vala
new file mode 100644
index 00000000..9cce3dbd
--- /dev/null
+++ b/test/engine/api/geary-folder-path-test.vala
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 Michael Gratton <mike vee net>
+ *
+ * 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.FolderPathTest : TestCase {
+
+ public FolderPathTest() {
+ base("Geary.FolderPathTest");
+ add_test("get_child_from_root", get_child_from_root);
+ add_test("get_child_from_child", get_child_from_child);
+ add_test("root_is_root", root_is_root);
+ add_test("child_is_not_root", root_is_root);
+ }
+
+ public void get_child_from_root() throws GLib.Error {
+ assert_string(
+ ">test",
+ new Geary.FolderRoot(false).get_child("test").to_string()
+ );
+ }
+
+ public void get_child_from_child() throws GLib.Error {
+ assert_string(
+ ">test1>test2",
+ new Geary.FolderRoot(false)
+ .get_child("test1").get_child("test2").to_string()
+ );
+ }
+
+ public void root_is_root() throws GLib.Error {
+ assert_true(new Geary.FolderRoot(false).is_root);
+ }
+
+ public void child_root_is_not_root() throws GLib.Error {
+ assert_false(new Geary.FolderRoot(false).get_child("test").is_root);
+ }
+
+}
diff --git a/test/engine/app/app-conversation-monitor-test.vala
b/test/engine/app/app-conversation-monitor-test.vala
index 8f3c2a7e..f65720ec 100644
--- a/test/engine/app/app-conversation-monitor-test.vala
+++ b/test/engine/app/app-conversation-monitor-test.vala
@@ -11,6 +11,7 @@ class Geary.App.ConversationMonitorTest : TestCase {
AccountInformation? account_info = null;
MockAccount? account = null;
+ FolderRoot? folder_root = null;
MockFolder? base_folder = null;
MockFolder? other_folder = null;
@@ -35,22 +36,31 @@ class Geary.App.ConversationMonitorTest : TestCase {
new RFC822.MailboxAddress(null, "test1 example com")
);
this.account = new MockAccount(this.account_info);
+ this.folder_root = new FolderRoot(false);
this.base_folder = new MockFolder(
this.account,
null,
- new MockFolderRoot("base"),
+ this.folder_root.get_child("base"),
SpecialFolderType.NONE,
null
);
this.other_folder = new MockFolder(
this.account,
null,
- new MockFolderRoot("other"),
+ this.folder_root.get_child("other"),
SpecialFolderType.NONE,
null
);
}
+ public override void tear_down() {
+ this.other_folder = null;
+ this.base_folder = null;
+ this.folder_root = null;
+ this.account_info = null;
+ this.account = null;
+ }
+
public void start_stop_monitoring() throws Error {
ConversationMonitor monitor = new ConversationMonitor(
this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10
diff --git a/test/engine/app/app-conversation-set-test.vala b/test/engine/app/app-conversation-set-test.vala
index 1bc34210..a662e9e3 100644
--- a/test/engine/app/app-conversation-set-test.vala
+++ b/test/engine/app/app-conversation-set-test.vala
@@ -9,6 +9,7 @@ class Geary.App.ConversationSetTest : TestCase {
ConversationSet? test = null;
+ FolderRoot? folder_root = null;
Folder? base_folder = null;
public ConversationSetTest() {
@@ -26,14 +27,21 @@ class Geary.App.ConversationSetTest : TestCase {
}
public override void set_up() {
- this.test = new ConversationSet();
+ this.folder_root = new FolderRoot(false);
this.base_folder = new MockFolder(
null,
null,
- new MockFolderRoot("test"),
+ this.folder_root.get_child("test"),
SpecialFolderType.NONE,
null
);
+ this.test = new ConversationSet();
+ }
+
+ public override void tear_down() {
+ this.test = null;
+ this.folder_root = null;
+ this.base_folder = null;
}
public void add_all_basic() throws Error {
@@ -144,7 +152,7 @@ class Geary.App.ConversationSetTest : TestCase {
Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath> email_paths =
new Gee.HashMultiMap<Geary.EmailIdentifier, Geary.FolderPath>();
email_paths.set(e1.id, this.base_folder.path);
- email_paths.set(e2.id, new MockFolderRoot("other"));
+ email_paths.set(e2.id, this.folder_root.get_child("other"));
Gee.Collection<Conversation>? added = null;
Gee.MultiMap<Conversation,Email>? appended = null;
@@ -310,7 +318,7 @@ class Geary.App.ConversationSetTest : TestCase {
public void add_all_multi_path() throws Error {
Email e1 = setup_email(1);
- MockFolderRoot other_path = new MockFolderRoot("other");
+ FolderPath other_path = this.folder_root.get_child("other");
Gee.LinkedList<Email> emails = new Gee.LinkedList<Email>();
emails.add(e1);
@@ -340,7 +348,7 @@ class Geary.App.ConversationSetTest : TestCase {
Email e1 = setup_email(1);
add_email_to_test_set(e1);
- MockFolderRoot other_path = new MockFolderRoot("other");
+ FolderPath other_path = this.folder_root.get_child("other");
Gee.LinkedList<Email> emails = new Gee.LinkedList<Email>();
emails.add(e1);
@@ -426,7 +434,7 @@ class Geary.App.ConversationSetTest : TestCase {
}
public void remove_all_remove_path() throws Error {
- MockFolderRoot other_path = new MockFolderRoot("other");
+ FolderPath other_path = this.folder_root.get_child("other");
Email e1 = setup_email(1);
add_email_to_test_set(e1, other_path);
diff --git a/test/engine/app/app-conversation-test.vala b/test/engine/app/app-conversation-test.vala
index d3f3d429..709d88ca 100644
--- a/test/engine/app/app-conversation-test.vala
+++ b/test/engine/app/app-conversation-test.vala
@@ -10,6 +10,8 @@ class Geary.App.ConversationTest : TestCase {
Conversation? test = null;
Folder? base_folder = null;
+ FolderRoot? folder_root = null;
+
public ConversationTest() {
base("Geary.App.ConversationTest");
@@ -24,16 +26,23 @@ class Geary.App.ConversationTest : TestCase {
}
public override void set_up() {
+ this.folder_root = new FolderRoot(false);
this.base_folder = new MockFolder(
null,
null,
- new MockFolderRoot("test"),
+ this.folder_root.get_child("test"),
SpecialFolderType.NONE,
null
);
this.test = new Conversation(this.base_folder);
}
+ public override void tear_down() {
+ this.test = null;
+ this.folder_root = null;
+ this.base_folder = null;
+ }
+
public void add_basic() throws Error {
Geary.Email e1 = setup_email(1);
Geary.Email e2 = setup_email(2);
@@ -78,8 +87,8 @@ class Geary.App.ConversationTest : TestCase {
Geary.Email e2 = setup_email(2);
this.test.add(e2, singleton(this.base_folder.path));
- FolderRoot other_path = new MockFolderRoot("other");
- Gee.LinkedList<FolderRoot> other_paths = new Gee.LinkedList<FolderRoot>();
+ FolderPath other_path = this.folder_root.get_child("other");
+ Gee.LinkedList<FolderPath> other_paths = new Gee.LinkedList<FolderPath>();
other_paths.add(other_path);
assert(this.test.add(e1, other_paths) == false);
@@ -145,7 +154,7 @@ class Geary.App.ConversationTest : TestCase {
Geary.Email e1 = setup_email(1);
this.test.add(e1, singleton(this.base_folder.path));
- FolderRoot other_path = new MockFolderRoot("other");
+ FolderPath other_path = this.folder_root.get_child("other");
Geary.Email e2 = setup_email(2);
this.test.add(e2, singleton(other_path));
@@ -158,7 +167,7 @@ class Geary.App.ConversationTest : TestCase {
Geary.Email e1 = setup_email(1);
this.test.add(e1, singleton(this.base_folder.path));
- FolderRoot other_path = new MockFolderRoot("other");
+ FolderPath other_path = this.folder_root.get_child("other");
Geary.Email e2 = setup_email(2);
this.test.add(e2, singleton(other_path));
@@ -193,7 +202,7 @@ class Geary.App.ConversationTest : TestCase {
Geary.Email e1 = setup_email(1);
this.test.add(e1, singleton(this.base_folder.path));
- FolderRoot other_path = new MockFolderRoot("other");
+ FolderPath other_path = this.folder_root.get_child("other");
Geary.Email e2 = setup_email(2);
this.test.add(e2, singleton(other_path));
diff --git a/test/engine/imap-db/imap-db-account-test.vala b/test/engine/imap-db/imap-db-account-test.vala
index 146d7127..27c33de8 100644
--- a/test/engine/imap-db/imap-db-account-test.vala
+++ b/test/engine/imap-db/imap-db-account-test.vala
@@ -12,6 +12,7 @@ class Geary.ImapDB.AccountTest : TestCase {
private GLib.File? tmp_dir = null;
private Geary.AccountInformation? config = null;
private Account? account = null;
+ private FolderRoot? root = null;
public AccountTest() {
@@ -47,9 +48,12 @@ class Geary.ImapDB.AccountTest : TestCase {
(obj, ret) => { async_complete(ret); }
);
this.account.open_async.end(async_result());
+
+ this.root = new FolderRoot(false);
}
public override void tear_down() throws GLib.Error {
+ this.root = null;
this.account.close_async.begin(
null,
(obj, ret) => { async_complete(ret); }
@@ -62,7 +66,7 @@ class Geary.ImapDB.AccountTest : TestCase {
public void create_base_folder() throws GLib.Error {
Imap.Folder folder = new Imap.Folder(
- new Imap.FolderRoot("test"),
+ this.root.get_child("test"),
new Imap.FolderProperties.selectable(
new Imap.MailboxAttributes(
Gee.Collection.empty<Geary.Imap.MailboxAttribute>()
@@ -101,7 +105,7 @@ class Geary.ImapDB.AccountTest : TestCase {
);
Imap.Folder folder = new Imap.Folder(
- new Imap.FolderRoot("test").get_child("child"),
+ this.root.get_child("test").get_child("child"),
new Imap.FolderProperties.selectable(
new Imap.MailboxAttributes(
Gee.Collection.empty<Geary.Imap.MailboxAttribute>()
@@ -144,7 +148,7 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.list_folders_async.begin(
- null,
+ this.account.imap_folder_root,
null,
(obj, ret) => { async_complete(ret); }
);
@@ -187,14 +191,14 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.delete_folder_async.begin(
- new Imap.FolderRoot("test1").get_child("test2"),
+ this.root.get_child("test1").get_child("test2"),
null,
(obj, ret) => { async_complete(ret); }
);
this.account.delete_folder_async.end(async_result());
this.account.delete_folder_async.begin(
- new Imap.FolderRoot("test1"),
+ this.root.get_child("test1"),
null,
(obj, ret) => { async_complete(ret); }
);
@@ -210,7 +214,7 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.delete_folder_async.begin(
- new Imap.FolderRoot("test1"),
+ this.root.get_child("test1"),
null,
(obj, ret) => { async_complete(ret); }
);
@@ -231,7 +235,7 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.delete_folder_async.begin(
- new Imap.FolderRoot("test3"),
+ this.root.get_child("test3"),
null,
(obj, ret) => { async_complete(ret); }
);
@@ -252,7 +256,7 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.fetch_folder_async.begin(
- new Imap.FolderRoot("test1"),
+ this.root.get_child("test1"),
null,
(obj, ret) => { async_complete(ret); }
);
@@ -271,7 +275,7 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.fetch_folder_async.begin(
- new Imap.FolderRoot("test1").get_child("test2"),
+ this.root.get_child("test1").get_child("test2"),
null,
(obj, ret) => { async_complete(ret); }
);
@@ -290,7 +294,7 @@ class Geary.ImapDB.AccountTest : TestCase {
""");
this.account.fetch_folder_async.begin(
- new Imap.FolderRoot("test3"),
+ this.root.get_child("test3"),
null,
(obj, ret) => { async_complete(ret); }
);
diff --git a/test/engine/imap/message/imap-mailbox-specifier-test.vala
b/test/engine/imap/message/imap-mailbox-specifier-test.vala
index 741f279e..6488e5e9 100644
--- a/test/engine/imap/message/imap-mailbox-specifier-test.vala
+++ b/test/engine/imap/message/imap-mailbox-specifier-test.vala
@@ -13,6 +13,7 @@ class Geary.Imap.MailboxSpecifierTest : TestCase {
add_test("to_parameter", to_parameter);
add_test("from_parameter", from_parameter);
add_test("from_folder_path", from_folder_path);
+ add_test("folder_path_is_inbox", folder_path_is_inbox);
}
public void to_parameter() throws Error {
@@ -59,54 +60,82 @@ class Geary.Imap.MailboxSpecifierTest : TestCase {
}
public void from_folder_path() throws Error {
- MockFolderRoot empty_root = new MockFolderRoot("");
- MailboxSpecifier empty_inbox = new MailboxSpecifier("Inbox");
+ FolderRoot root = new FolderRoot();
+ MailboxSpecifier inbox = new MailboxSpecifier("Inbox");
assert_string(
"Foo",
new MailboxSpecifier.from_folder_path(
- empty_root.get_child("Foo"), empty_inbox, "$"
+ root.get_child("Foo"), inbox, "$"
).name
);
assert_string(
"Foo$Bar",
new MailboxSpecifier.from_folder_path(
- empty_root.get_child("Foo").get_child("Bar"), empty_inbox, "$"
+ root.get_child("Foo").get_child("Bar"), inbox, "$"
).name
);
assert_string(
"Inbox",
new MailboxSpecifier.from_folder_path(
- empty_root.get_child(MailboxSpecifier.CANONICAL_INBOX_NAME),
- empty_inbox,
+ root.get_child(MailboxSpecifier.CANONICAL_INBOX_NAME),
+ inbox,
"$"
).name
);
- MockFolderRoot non_empty_root = new MockFolderRoot("Root");
- MailboxSpecifier non_empty_inbox = new MailboxSpecifier("Inbox");
- assert_string(
- "Root$Foo",
+ try {
new MailboxSpecifier.from_folder_path(
- non_empty_root.get_child("Foo"),
- non_empty_inbox,
- "$"
- ).name
- );
- assert_string(
- "Root$Foo$Bar",
+ root.get_child(""), inbox, "$"
+ );
+ assert_not_reached();
+ } catch (GLib.Error err) {
+ // all good
+ }
+
+ try {
new MailboxSpecifier.from_folder_path(
- non_empty_root.get_child("Foo").get_child("Bar"),
- non_empty_inbox,
- "$"
- ).name
+ root.get_child("test").get_child(""), inbox, "$"
+ );
+ assert_not_reached();
+ } catch (GLib.Error err) {
+ // all good
+ }
+
+ try {
+ new MailboxSpecifier.from_folder_path(root, inbox, "$");
+ assert_not_reached();
+ } catch (GLib.Error err) {
+ // all good
+ }
+ }
+
+ public void folder_path_is_inbox() throws GLib.Error {
+ FolderRoot root = new FolderRoot();
+ assert_true(
+ MailboxSpecifier.folder_path_is_inbox(root.get_child("Inbox"))
);
- assert_string(
- "Root$INBOX",
- new MailboxSpecifier.from_folder_path(
- non_empty_root.get_child(MailboxSpecifier.CANONICAL_INBOX_NAME),
- non_empty_inbox,
- "$"
- ).name
+ assert_true(
+ MailboxSpecifier.folder_path_is_inbox(root.get_child("inbox"))
+ );
+ assert_true(
+ MailboxSpecifier.folder_path_is_inbox(root.get_child("INBOX"))
+ );
+
+ assert_false(
+ MailboxSpecifier.folder_path_is_inbox(root)
+ );
+ assert_false(
+ MailboxSpecifier.folder_path_is_inbox(root.get_child("blah"))
+ );
+ assert_false(
+ MailboxSpecifier.folder_path_is_inbox(
+ root.get_child("blah").get_child("Inbox")
+ )
+ );
+ assert_false(
+ MailboxSpecifier.folder_path_is_inbox(
+ root.get_child("Inbox").get_child("Inbox")
+ )
);
}
diff --git a/test/meson.build b/test/meson.build
index c8f45530..26eed436 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -18,11 +18,11 @@ geary_test_engine_sources = [
'engine/api/geary-email-identifier-mock.vala',
'engine/api/geary-email-properties-mock.vala',
'engine/api/geary-folder-mock.vala',
- 'engine/api/geary-folder-path-mock.vala',
'engine/api/geary-account-information-test.vala',
'engine/api/geary-attachment-test.vala',
'engine/api/geary-engine-test.vala',
+ 'engine/api/geary-folder-path-test.vala',
'engine/api/geary-service-information-test.vala',
'engine/app/app-conversation-test.vala',
'engine/app/app-conversation-monitor-test.vala',
diff --git a/test/test-engine.vala b/test/test-engine.vala
index 3c2309d8..a520c379 100644
--- a/test/test-engine.vala
+++ b/test/test-engine.vala
@@ -25,6 +25,7 @@ int main(string[] args) {
engine.add_suite(new Geary.AccountInformationTest().get_suite());
engine.add_suite(new Geary.AttachmentTest().get_suite());
engine.add_suite(new Geary.EngineTest().get_suite());
+ engine.add_suite(new Geary.FolderPathTest().get_suite());
engine.add_suite(new Geary.IdleManagerTest().get_suite());
engine.add_suite(new Geary.TimeoutManagerTest().get_suite());
engine.add_suite(new Geary.TlsNegotiationMethodTest().get_suite());
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]