Improved FolderPath and separator handling
This commit fixes a number of issues with traversing, showing, and opening subfolders, especially on systems that don't use the forward slash as a separator.
This commit is contained in:
parent
f246ff4919
commit
b254ad54e4
9 changed files with 220 additions and 80 deletions
|
|
@ -4,8 +4,22 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generic structure for representing and maintaining folder paths.
|
||||
*
|
||||
* A FolderPath may have one parent and one child. A FolderPath without a parent is called a
|
||||
* root folder can be be created with {@link FolderRoot}, which is a FolderPath.
|
||||
*
|
||||
* A FolderPath has a delimiter. This delimiter is specified in the FolderRoot.
|
||||
*
|
||||
* @see FolderRoot
|
||||
*/
|
||||
|
||||
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; }
|
||||
|
||||
private Gee.List<Geary.FolderPath>? path = null;
|
||||
|
|
@ -26,26 +40,53 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
this.basename = basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this {@link FolderPath} is the 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.
|
||||
*/
|
||||
public Geary.FolderRoot get_root() {
|
||||
return (FolderRoot) ((path != null && path.size > 0) ? path[0] : this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent {@link FolderPath} of this folder or null if this is the root.
|
||||
*
|
||||
* @see is_root
|
||||
*/
|
||||
public Geary.FolderPath? get_parent() {
|
||||
return (path != null && path.size > 0) ? path.last() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of folders in this path, not including any children of this object.
|
||||
*/
|
||||
public int get_path_length() {
|
||||
// include self, which is not stored in the path list
|
||||
return (path != null) ? path.size + 1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link FolderPath} object at the index, with this FolderPath object being
|
||||
* the farthest child.
|
||||
*
|
||||
* Root is at index 0 (zero).
|
||||
*
|
||||
* Returns null if index is out of bounds. There is always at least one element in the path,
|
||||
* namely this one.
|
||||
* namely this one, meaning zero is always acceptable and that index[length - 1] will always
|
||||
* return this object.
|
||||
*
|
||||
* @see get_path_length
|
||||
*/
|
||||
public Geary.FolderPath? get_folder_at(int index) {
|
||||
// include self, which is not stored in the path list ... essentially, this logic makes it
|
||||
|
|
@ -63,6 +104,12 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link FolderPath} as a List of {@link basename} strings, this FolderPath's
|
||||
* being the last in the list.
|
||||
*
|
||||
* Thus, the list should have at least one element.
|
||||
*/
|
||||
public Gee.List<string> as_list() {
|
||||
Gee.List<string> list = new Gee.ArrayList<string>();
|
||||
|
||||
|
|
@ -76,6 +123,9 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FolderPath} object that is a child of this folder.
|
||||
*/
|
||||
public Geary.FolderPath get_child(string basename) {
|
||||
// Build the child's path, which is this node's path plus this node
|
||||
Gee.List<FolderPath> child_path = new Gee.ArrayList<FolderPath>();
|
||||
|
|
@ -86,13 +136,54 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
return new FolderPath.child(child_path, basename);
|
||||
}
|
||||
|
||||
public string get_fullpath(string? use_separator = null) {
|
||||
/**
|
||||
* Returns true if this {@link FolderPath} has a default separator.
|
||||
*
|
||||
* It determines this by returning true if its {@link FolderRoot.default_separator} is
|
||||
* non-null and non-empty.
|
||||
*/
|
||||
public bool has_default_separator() {
|
||||
return get_root().default_separator != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the other {@link FolderPath} has the same parent as this one.
|
||||
*
|
||||
* Like {@link equal_to} and {@link compare_to}, this comparison does not account for the
|
||||
* {@link FolderRoot.default_separator}. The comparison is lexiographic, not by reference.
|
||||
*/
|
||||
public bool has_same_parent(FolderPath other) {
|
||||
FolderPath? parent = get_parent();
|
||||
FolderPath? other_parent = other.get_parent();
|
||||
|
||||
if (parent == other_parent)
|
||||
return true;
|
||||
|
||||
if (parent != null && other_parent != null)
|
||||
return parent.equal_to(other_parent);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link FolderPath} as a single string with the supplied separator used as a
|
||||
* delimiter.
|
||||
*
|
||||
* If null is passed in, {@link FolderRoot.default_separator} is used. If the default
|
||||
* separator is null, no fullpath can be produced and this method will return null.
|
||||
*
|
||||
* The separator is not appended to the fullpath.
|
||||
*
|
||||
* @see has_default_separator
|
||||
*/
|
||||
public string? get_fullpath(string? use_separator) {
|
||||
string? separator = use_separator ?? get_root().default_separator;
|
||||
|
||||
// no separator, no hierarchy
|
||||
// no separator, no fullpath
|
||||
if (separator == null)
|
||||
return basename;
|
||||
return null;
|
||||
|
||||
// use cached copy if the stars align
|
||||
if (fullpath != null && fullpath_separator == separator)
|
||||
return fullpath;
|
||||
|
||||
|
|
@ -118,11 +209,19 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Comparisons for Geary.FolderPath is defined as (a) empty paths are less-than non-empty paths
|
||||
* and (b) each element is compared to the corresponding path element of the other FolderPath
|
||||
* following collation rules for casefolded (case-insensitive) compared, and (c) shorter paths
|
||||
* are less-than longer paths, assuming the path elements are equal up to the shorter path's
|
||||
* length.
|
||||
*
|
||||
* Note that the {@ link FolderRoot.default_separator} has no bearing on comparisons, although
|
||||
* {@link FolderRoot.case_sensitive} does.
|
||||
*
|
||||
* Returns -1 if this path is lexiographically before the other, 1 if its after, and 0 if they
|
||||
* are equal.
|
||||
*/
|
||||
public int compare_to(Geary.FolderPath other) {
|
||||
if (this == other)
|
||||
|
|
@ -146,6 +245,12 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
return this_list.size - other_list.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* As with {@link compare_to}, the {@link FolderRoot.default_separator} has no bearing on the
|
||||
* hash, although {@link FolderRoot.case_sensitive} does.
|
||||
*/
|
||||
public uint hash() {
|
||||
if (stored_hash != uint.MAX)
|
||||
return stored_hash;
|
||||
|
|
@ -168,6 +273,9 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
return cs ? (basename == cmp) : (basename.down() == cmp.down());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public bool equal_to(Geary.FolderPath other) {
|
||||
int path_length = get_path_length();
|
||||
if (other.get_path_length() != path_length)
|
||||
|
|
@ -188,21 +296,44 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the fullpath using the default separator. Using only for debugging and logging.
|
||||
* Returns the fullpath using the default separator.
|
||||
*
|
||||
* Use only for debugging and logging.
|
||||
*/
|
||||
public string to_string() {
|
||||
return get_fullpath();
|
||||
// use slash if no default separator available
|
||||
return get_fullpath(has_default_separator() ? null : "/");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
public class Geary.FolderRoot : Geary.FolderPath {
|
||||
/**
|
||||
* The default separator (delimiter) for this path.
|
||||
*
|
||||
* If null, the separator can be supplied later to {@link FolderPath.get_fullpath}.
|
||||
*
|
||||
* This value will never be empty (i.e. zero-length). A zero-length separator passed to the
|
||||
* constructor will result in this property being null.
|
||||
*/
|
||||
public string? default_separator { get; private set; }
|
||||
/**
|
||||
* Whether this path is lexiographically case-sensitive.
|
||||
*
|
||||
* This has implications, as {@link FolderPath} is Comparable and Hashable.
|
||||
*/
|
||||
public bool case_sensitive { get; private set; }
|
||||
|
||||
public FolderRoot(string basename, string? default_separator, bool case_sensitive) {
|
||||
base (basename);
|
||||
|
||||
this.default_separator = default_separator;
|
||||
this.default_separator = !String.is_empty(default_separator) ? default_separator : null;
|
||||
this.case_sensitive;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
|
||||
if (id_map.size == 0) {
|
||||
throw new EngineError.NOT_FOUND("No local folders in %s",
|
||||
(parent != null) ? parent.get_fullpath() : "root");
|
||||
(parent != null) ? parent.to_string() : "root");
|
||||
}
|
||||
|
||||
Gee.Collection<Geary.ImapDB.Folder> folders = new Gee.ArrayList<Geary.ImapDB.Folder>();
|
||||
|
|
@ -431,8 +431,19 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
|
||||
private Geary.ImapDB.Folder? get_local_folder(Geary.FolderPath path) {
|
||||
FolderReference? folder_ref = folder_refs.get(path);
|
||||
if (folder_ref == null)
|
||||
return null;
|
||||
|
||||
return (folder_ref != null) ? (Geary.ImapDB.Folder) folder_ref.get_reference() : null;
|
||||
ImapDB.Folder? folder = (Geary.ImapDB.Folder?) folder_ref.get_reference();
|
||||
if (folder == null)
|
||||
return null;
|
||||
|
||||
// use supplied FolderPath rather than one here; if it came from the server, it has
|
||||
// a usable separator
|
||||
if (path.get_root().default_separator != null)
|
||||
folder.set_path(path);
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
private Geary.ImapDB.Folder create_local_folder(Geary.FolderPath path, int64 folder_id,
|
||||
|
|
@ -684,10 +695,8 @@ private class Geary.ImapDB.Account : BaseObject {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (parent_id <= 0) {
|
||||
return new Geary.FolderRoot(name,
|
||||
Geary.Imap.Account.ASSUMED_SEPARATOR, Geary.Imap.Folder.CASE_SENSITIVE);
|
||||
}
|
||||
if (parent_id <= 0)
|
||||
return new Geary.FolderRoot(name, null, Geary.Imap.Folder.CASE_SENSITIVE);
|
||||
|
||||
Geary.FolderPath? parent_path = do_find_folder_path(cx, parent_id, cancellable);
|
||||
return (parent_path == null ? null : parent_path.get_child(name));
|
||||
|
|
|
|||
|
|
@ -85,6 +85,12 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
return path;
|
||||
}
|
||||
|
||||
// Use with caution; ImapDB.Account uses this to "improve" the path with one from the server,
|
||||
// which has a usable path delimiter.
|
||||
internal void set_path(FolderPath path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Geary.Imap.FolderProperties get_properties() {
|
||||
return properties;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
|
|||
if (path_type_map == null) {
|
||||
path_type_map = new Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>();
|
||||
|
||||
path_type_map.set(new Geary.FolderRoot(Imap.Account.INBOX_NAME, Imap.Account.ASSUMED_SEPARATOR,
|
||||
path_type_map.set(new Geary.FolderRoot(Imap.Account.INBOX_NAME, null,
|
||||
Imap.Folder.CASE_SENSITIVE), SpecialFolderType.INBOX);
|
||||
|
||||
Geary.FolderPath gmail_root = new Geary.FolderRoot(GMAIL_FOLDER,
|
||||
Imap.Account.ASSUMED_SEPARATOR, Imap.Folder.CASE_SENSITIVE);
|
||||
Geary.FolderPath googlemail_root = new Geary.FolderRoot(GOOGLEMAIL_FOLDER,
|
||||
Imap.Account.ASSUMED_SEPARATOR, Imap.Folder.CASE_SENSITIVE);
|
||||
Geary.FolderPath gmail_root = new Geary.FolderRoot(GMAIL_FOLDER, null,
|
||||
Imap.Folder.CASE_SENSITIVE);
|
||||
Geary.FolderPath googlemail_root = new Geary.FolderRoot(GOOGLEMAIL_FOLDER, null,
|
||||
Imap.Folder.CASE_SENSITIVE);
|
||||
|
||||
path_type_map.set(gmail_root.get_child("Drafts"), SpecialFolderType.DRAFTS);
|
||||
path_type_map.set(googlemail_root.get_child("Drafts"), SpecialFolderType.DRAFTS);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
|
||||
private const int REFRESH_FOLDER_LIST_SEC = 10 * 60;
|
||||
|
||||
private static Geary.FolderPath? inbox_path = null;
|
||||
private static Geary.FolderPath? outbox_path = null;
|
||||
|
||||
private Imap.Account remote;
|
||||
|
|
@ -30,14 +29,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
|
|||
this.remote.login_failed.connect(on_login_failed);
|
||||
this.remote.email_sent.connect(on_email_sent);
|
||||
|
||||
if (inbox_path == null) {
|
||||
inbox_path = new Geary.FolderRoot(Imap.Account.INBOX_NAME, Imap.Account.ASSUMED_SEPARATOR,
|
||||
Imap.Folder.CASE_SENSITIVE);
|
||||
}
|
||||
|
||||
if (outbox_path == null) {
|
||||
if (outbox_path == null)
|
||||
outbox_path = new SmtpOutboxFolderRoot();
|
||||
}
|
||||
}
|
||||
|
||||
private void check_open() throws EngineError {
|
||||
|
|
@ -162,12 +155,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
|
|||
return return_folders;
|
||||
}
|
||||
|
||||
public override Gee.Collection<Geary.Folder> list_matching_folders(
|
||||
Geary.FolderPath? parent) throws Error {
|
||||
public override Gee.Collection<Geary.Folder> list_matching_folders(Geary.FolderPath? parent)
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
Gee.ArrayList<Geary.Folder> matches = new Gee.ArrayList<Geary.Folder>();
|
||||
|
||||
|
||||
foreach(FolderPath path in folder_map.keys) {
|
||||
FolderPath? path_parent = path.get_parent();
|
||||
if ((parent == null && path_parent == null) ||
|
||||
|
|
|
|||
|
|
@ -40,16 +40,12 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
|
|||
if (special_map == null) {
|
||||
special_map = new Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>();
|
||||
|
||||
special_map.set(new Geary.FolderRoot(Imap.Account.INBOX_NAME, Imap.Account.ASSUMED_SEPARATOR, false),
|
||||
special_map.set(new Geary.FolderRoot(Imap.Account.INBOX_NAME, null, false),
|
||||
Geary.SpecialFolderType.INBOX);
|
||||
special_map.set(new Geary.FolderRoot("Sent", Imap.Account.ASSUMED_SEPARATOR, false),
|
||||
Geary.SpecialFolderType.SENT);
|
||||
special_map.set(new Geary.FolderRoot("Draft", Imap.Account.ASSUMED_SEPARATOR, false),
|
||||
Geary.SpecialFolderType.DRAFTS);
|
||||
special_map.set(new Geary.FolderRoot("Bulk Mail", Imap.Account.ASSUMED_SEPARATOR, false),
|
||||
Geary.SpecialFolderType.SPAM);
|
||||
special_map.set(new Geary.FolderRoot("Trash", Imap.Account.ASSUMED_SEPARATOR, false),
|
||||
Geary.SpecialFolderType.TRASH);
|
||||
special_map.set(new Geary.FolderRoot("Sent", null, false), Geary.SpecialFolderType.SENT);
|
||||
special_map.set(new Geary.FolderRoot("Draft", null, false), Geary.SpecialFolderType.DRAFTS);
|
||||
special_map.set(new Geary.FolderRoot("Bulk Mail", null, false), Geary.SpecialFolderType.SPAM);
|
||||
special_map.set(new Geary.FolderRoot("Trash", null, false), Geary.SpecialFolderType.TRASH);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ private class Geary.Imap.Account : BaseObject {
|
|||
// all references to Inbox are converted to this string, purely for sanity sake when dealing
|
||||
// with Inbox's case issues
|
||||
public const string INBOX_NAME = "INBOX";
|
||||
public const string ASSUMED_SEPARATOR = "/";
|
||||
|
||||
public bool is_open { get; private set; default = false; }
|
||||
|
||||
|
|
@ -177,7 +176,7 @@ private class Geary.Imap.Account : BaseObject {
|
|||
|
||||
private async MailboxInformation fetch_mailbox_async(FolderPath path, Cancellable? cancellable)
|
||||
throws Error {
|
||||
Geary.FolderPath? processed = process_path(path, null, path.get_root().default_separator);
|
||||
Geary.FolderPath? processed = normalize_inbox(path);
|
||||
if (processed == null)
|
||||
throw new ImapError.INVALID("Invalid path %s", path.to_string());
|
||||
|
||||
|
|
@ -186,7 +185,7 @@ private class Geary.Imap.Account : BaseObject {
|
|||
|
||||
Gee.List<MailboxInformation> list_results = new Gee.ArrayList<MailboxInformation>();
|
||||
StatusResponse response = yield send_command_async(
|
||||
new ListCommand(new MailboxSpecifier.from_folder_path(processed), can_xlist),
|
||||
new ListCommand(new MailboxSpecifier.from_folder_path(processed, null), can_xlist),
|
||||
list_results, null, cancellable);
|
||||
|
||||
throw_fetch_error(response, processed, list_results.size);
|
||||
|
|
@ -198,13 +197,13 @@ private class Geary.Imap.Account : BaseObject {
|
|||
throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.FolderPath? processed = process_path(path, null, path.get_root().default_separator);
|
||||
Geary.FolderPath? processed = normalize_inbox(path);
|
||||
if (processed == null)
|
||||
throw new ImapError.INVALID("Invalid path %s", path.to_string());
|
||||
|
||||
Gee.List<StatusData> status_results = new Gee.ArrayList<StatusData>();
|
||||
StatusResponse response = yield send_command_async(
|
||||
new StatusCommand(new MailboxSpecifier.from_folder_path(processed), StatusDataType.all()),
|
||||
new StatusCommand(new MailboxSpecifier.from_folder_path(processed, null), StatusDataType.all()),
|
||||
null, status_results, cancellable);
|
||||
|
||||
throw_fetch_error(response, processed, status_results.size);
|
||||
|
|
@ -231,8 +230,7 @@ private class Geary.Imap.Account : BaseObject {
|
|||
throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.FolderPath? processed = process_path(parent, null,
|
||||
(parent != null) ? parent.get_root().default_separator : null);
|
||||
Geary.FolderPath? processed = normalize_inbox(parent);
|
||||
|
||||
Gee.List<MailboxInformation>? child_info = yield list_children_async(processed, cancellable);
|
||||
if (child_info == null || child_info.size == 0)
|
||||
|
|
@ -300,8 +298,7 @@ private class Geary.Imap.Account : BaseObject {
|
|||
|
||||
private async Gee.List<MailboxInformation>? list_children_async(FolderPath? parent, Cancellable? cancellable)
|
||||
throws Error {
|
||||
Geary.FolderPath? processed = process_path(parent, null,
|
||||
(parent != null) ? parent.get_root().default_separator : ASSUMED_SEPARATOR);
|
||||
Geary.FolderPath? processed = normalize_inbox(parent);
|
||||
|
||||
ClientSession session = yield claim_session_async(cancellable);
|
||||
bool can_xlist = session.capabilities.has_capability(Capabilities.XLIST);
|
||||
|
|
@ -310,8 +307,12 @@ private class Geary.Imap.Account : BaseObject {
|
|||
if (processed == null) {
|
||||
cmd = new ListCommand.wildcarded("", new MailboxSpecifier("%"), can_xlist);
|
||||
} else {
|
||||
string specifier = processed.get_fullpath();
|
||||
string delim = processed.get_root().default_separator;
|
||||
string? specifier = processed.get_fullpath(null);
|
||||
string? delim = processed.get_root().default_separator;
|
||||
if (specifier == null || delim == null) {
|
||||
throw new ImapError.INVALID("Unable to list children of %s: no delimiter specified",
|
||||
processed.to_string());
|
||||
}
|
||||
|
||||
specifier += specifier.has_suffix(delim) ? "%" : (delim + "%");
|
||||
|
||||
|
|
@ -377,38 +378,28 @@ private class Geary.Imap.Account : BaseObject {
|
|||
(path != null) ? path.to_string() : "root", session_mgr.to_string());
|
||||
}
|
||||
|
||||
// This method ensures that Inbox is dealt with in a consistent fashion throughout the
|
||||
// application.
|
||||
private static Geary.FolderPath? process_path(Geary.FolderPath? parent, string? basename,
|
||||
string? delim) throws ImapError {
|
||||
bool empty_basename = String.is_empty(basename);
|
||||
|
||||
// 1. Both null, done
|
||||
if (parent == null && empty_basename)
|
||||
// This method ensures that INBOX is dealt with in a consistent fashion throughout the
|
||||
// application. In IMAP, INBOX is case-insensitive (although there is no specification on
|
||||
// sensitivity of other folders) and must always be recognized as such. Thus, this method
|
||||
// converts all mention of INBOX (or Inbox, or inbox, or InBoX) into a standard string.
|
||||
private static Geary.FolderPath? normalize_inbox(Geary.FolderPath? path) {
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
// 2. Parent null but basename not, create FolderRoot for Inbox
|
||||
if (parent == null && !empty_basename && basename.up() == INBOX_NAME)
|
||||
return new Geary.FolderRoot(INBOX_NAME, delim, false);
|
||||
FolderRoot root = path.get_root();
|
||||
if (root.basename.up() != INBOX_NAME)
|
||||
return path;
|
||||
|
||||
// 3. Parent and basename supplied, verify parent is not Inbox, as IMAP does not allow it
|
||||
// to have children
|
||||
if (parent != null && !empty_basename && parent.get_root().basename.up() == INBOX_NAME)
|
||||
throw new ImapError.INVALID("Inbox may not have children");
|
||||
// create new FolderPath with normalized INBOX at its root
|
||||
FolderPath new_path = new Geary.FolderRoot(INBOX_NAME, root.default_separator,
|
||||
root.case_sensitive);
|
||||
|
||||
// 4. Parent supplied but basename is not; if parent points to Inbox, normalize it
|
||||
if (parent != null && empty_basename && parent.basename.up() == INBOX_NAME)
|
||||
return new Geary.FolderRoot(INBOX_NAME, delim, false);
|
||||
// copy in children starting at 1 (zero is INBOX)
|
||||
Gee.List<string> basenames = path.as_list();
|
||||
for (int ctr = 1; ctr < basenames.size; ctr++)
|
||||
new_path = new_path.get_child(basenames[ctr]);
|
||||
|
||||
// 5. Default behavior: create child of basename or basename as root, otherwise return parent
|
||||
// unmodified
|
||||
if (parent != null && !empty_basename)
|
||||
return parent.get_child(basename);
|
||||
|
||||
if (!empty_basename)
|
||||
return new Geary.FolderRoot(basename, delim, Folder.CASE_SENSITIVE);
|
||||
|
||||
return parent;
|
||||
return new_path;
|
||||
}
|
||||
|
||||
private void on_login_failed() {
|
||||
|
|
|
|||
|
|
@ -446,7 +446,8 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
CopyCommand cmd = new CopyCommand(msg_set, new MailboxSpecifier.from_folder_path(destination));
|
||||
CopyCommand cmd = new CopyCommand(msg_set,
|
||||
new MailboxSpecifier.from_folder_path(destination, null));
|
||||
Gee.Collection<Command> cmds = new Collection.SingleItem<Command>(cmd);
|
||||
|
||||
yield exec_commands_async(cmds, cancellable);
|
||||
|
|
@ -462,7 +463,7 @@ private class Geary.Imap.Folder : BaseObject {
|
|||
// Don't use copy_email_async followed by remove_email_async; this needs to be one
|
||||
// set of commands executed in order without releasing the cmd_mutex; this is especially
|
||||
// vital if positional addressing is used
|
||||
cmds.add(new CopyCommand(msg_set, new MailboxSpecifier.from_folder_path(destination)));
|
||||
cmds.add(new CopyCommand(msg_set, new MailboxSpecifier.from_folder_path(destination, null)));
|
||||
|
||||
Gee.List<MessageFlag> flags = new Gee.ArrayList<MessageFlag>();
|
||||
flags.add(MessageFlag.DELETED);
|
||||
|
|
|
|||
|
|
@ -48,8 +48,21 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
|
|||
init(param.decode());
|
||||
}
|
||||
|
||||
public MailboxSpecifier.from_folder_path(FolderPath path, string? delim = null) {
|
||||
init(path.get_fullpath(delim));
|
||||
/**
|
||||
* Converts a generic {@link FolderPath} into an IMAP mailbox specifier.
|
||||
*
|
||||
* If the delimiter was supplied from a {@link ListCommand} response, it can be supplied here.
|
||||
* Otherwise, the path's {@link FolderRoot.default_separator} will be used. If neither have a
|
||||
* separator, {@link ImapError.INVALID} is thrown.
|
||||
*/
|
||||
public MailboxSpecifier.from_folder_path(FolderPath path, string? delim) throws ImapError {
|
||||
string? fullpath = path.get_fullpath(delim);
|
||||
if (fullpath == null) {
|
||||
throw new ImapError.INVALID("Unable to convert FolderPath to MailboxSpecifier: no delimiter for %s",
|
||||
path.to_string());
|
||||
}
|
||||
|
||||
init(fullpath);
|
||||
}
|
||||
|
||||
private void init(string decoded) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue