geary/src/engine/imap/api/imap-account.vala
Jim Nelson 0533bc9700 Further work on detecting message removal when folder first selected: #3805
Needed to rethink storage strategies as I researched this and realized that a true scarce database -- where the database is sparsely populated both in columns and rows -- is not feasible due to IMAP's UID rules.  The strategy now means that the database rows are contiguous from the highest (newest) message to the oldest *requested by the user*.  This is a better situation than having to download the UID for the entire folder.
2011-07-15 13:39:02 -07:00

165 lines
7 KiB
Vala

/* Copyright 2011 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.Imap.Account : Geary.AbstractAccount, Geary.RemoteAccount {
// 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 = "/";
private ClientSessionManager session_mgr;
private Gee.HashMap<string, string?> delims = new Gee.HashMap<string, string?>();
public Account(Credentials cred, uint default_port) {
base ("IMAP Account for %s".printf(cred.to_string()));
session_mgr = new ClientSessionManager(cred, default_port);
}
public override Geary.Email.Field get_required_fields_for_writing() {
return Geary.Email.Field.HEADER | Geary.Email.Field.BODY;
}
public async string? get_folder_delimiter_async(string toplevel,
Cancellable? cancellable = null) throws Error {
if (delims.has_key(toplevel))
return delims.get(toplevel);
MailboxInformation? mbox = yield session_mgr.fetch_async(toplevel, cancellable);
if (mbox == null) {
throw new EngineError.NOT_FOUND("Toplevel folder %s not found on %s", toplevel,
session_mgr.to_string());
}
delims.set(toplevel, mbox.delim);
return mbox.delim;
}
public override async Gee.Collection<Geary.Folder> list_folders_async(Geary.FolderPath? parent,
Cancellable? cancellable = null) throws Error {
Geary.FolderPath? processed = process_path(parent, null,
(parent != null) ? parent.get_root().default_separator : ASSUMED_SEPARATOR);
Gee.Collection<MailboxInformation> mboxes;
try {
mboxes = (processed == null)
? yield session_mgr.list_roots(cancellable)
: yield session_mgr.list(processed.get_fullpath(), processed.get_root().default_separator,
cancellable);
} catch (Error err) {
if (err is ImapError.SERVER_ERROR)
throw_not_found(parent);
else
throw err;
}
Gee.Collection<Geary.Folder> folders = new Gee.ArrayList<Geary.Folder>();
foreach (MailboxInformation mbox in mboxes) {
Geary.FolderPath path = process_path(processed, mbox.name, mbox.delim);
// only add to delims map if root-level folder (all sub-folders respect its delimiter)
// also use the processed name, not the one reported off the wire
if (processed == null)
delims.set(path.get_root().basename, mbox.delim);
StatusResults? status = null;
if (!mbox.attrs.contains(MailboxAttribute.NO_SELECT)) {
try {
status = yield session_mgr.status_async(path.get_fullpath(),
StatusDataType.all(), cancellable);
} catch (Error status_err) {
message("Unable to fetch status for %s: %s", path.to_string(), status_err.message);
}
}
folders.add(new Geary.Imap.Folder(session_mgr, path, status, mbox));
}
return folders;
}
public override async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null)
throws Error {
Geary.FolderPath? processed = process_path(path, null, path.get_root().default_separator);
if (processed == null)
throw new ImapError.INVALID_PATH("Invalid path %s", path.to_string());
return yield session_mgr.folder_exists_async(processed.get_fullpath(), cancellable);
}
public override async Geary.Folder fetch_folder_async(Geary.FolderPath path,
Cancellable? cancellable = null) throws Error {
Geary.FolderPath? processed = process_path(path, null, path.get_root().default_separator);
if (processed == null)
throw new ImapError.INVALID_PATH("Invalid path %s", path.to_string());
try {
MailboxInformation? mbox = yield session_mgr.fetch_async(processed.get_fullpath(),
cancellable);
if (mbox == null)
throw_not_found(path);
StatusResults? status = null;
if (!mbox.attrs.contains(MailboxAttribute.NO_SELECT)) {
try {
status = yield session_mgr.status_async(processed.get_fullpath(),
StatusDataType.all(), cancellable);
} catch (Error status_err) {
debug("Unable to get status for %s: %s", processed.to_string(), status_err.message);
}
}
return new Geary.Imap.Folder(session_mgr, processed, status, mbox);
} catch (ImapError err) {
if (err is ImapError.SERVER_ERROR)
throw_not_found(path);
else
throw err;
}
}
[NoReturn]
private void throw_not_found(Geary.FolderPath? path) throws EngineError {
throw new EngineError.NOT_FOUND("Folder %s not found on %s",
(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)
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);
// 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_PATH("Inbox may not have children");
// 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);
// 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;
}
}