Internationalization: Use XLIST when possible: Closes #3704

This major patch switches Geary to using XLIST whenever possible
to identify special folders.  Because it's now possible for special
folders to be identified by MailboxAttributes and/or hard-coded
paths, needed to find a flexible system for the various flavors to
identify them as such.

Courtesy Timo Kluck.
This commit is contained in:
Timo Kluck 2012-06-22 14:42:51 -07:00 committed by Jim Nelson
parent 331cc1d983
commit c98a6b07b2
25 changed files with 425 additions and 380 deletions

1
THANKS
View file

@ -2,6 +2,7 @@ The Geary team would like to thank the following contributors:
Robert Ancell <robert.ancell@canonical.com>
Christian Dywan <christian@twotoasts.de>
Timo Kluck <tkluck@infty.nl>
Charles Lindsay <chaz@yorba.org>
Matthew Pirocchi <matthew@yorba.org>

View file

@ -20,17 +20,17 @@ public class GearyController {
}
}
private class FetchSpecialFolderOperation : Geary.NonblockingBatchOperation {
private class FetchFolderOperation : Geary.NonblockingBatchOperation {
public Geary.Account account;
public Geary.SpecialFolder special_folder;
public Geary.FolderPath folder_path;
public FetchSpecialFolderOperation(Geary.Account account, Geary.SpecialFolder special_folder) {
public FetchFolderOperation(Geary.Account account, Geary.FolderPath folder_path) {
this.account = account;
this.special_folder = special_folder;
this.folder_path = folder_path;
}
public override async Object? execute_async(Cancellable? cancellable) throws Error {
return yield account.fetch_folder_async(special_folder.path);
return yield account.fetch_folder_async(folder_path);
}
}
@ -311,7 +311,7 @@ public class GearyController {
if (account.get_account_information().service_provider == Geary.ServiceProvider.YAHOO)
main_window.title = GearyApplication.NAME + "!";
main_window.folder_list.set_user_folders_root_name(account.get_user_folders_label());
main_window.folder_list.set_user_folders_root_name(_("Labels"));
load_folders.begin(cancellable_folder);
}
}
@ -327,48 +327,6 @@ public class GearyController {
private async void load_folders(Cancellable? cancellable) {
try {
// add all the special folders, which are assumed to always exist
Geary.SpecialFolderMap? special_folders = account.get_special_folder_map();
if (special_folders != null) {
Geary.NonblockingBatch batch = new Geary.NonblockingBatch();
foreach (Geary.SpecialFolder special_folder in special_folders.get_all())
batch.add(new FetchSpecialFolderOperation(account, special_folder));
debug("Listing special folders");
yield batch.execute_all_async(cancellable);
debug("Completed list of special folders");
foreach (int id in batch.get_ids()) {
FetchSpecialFolderOperation op = (FetchSpecialFolderOperation)
batch.get_operation(id);
try {
Geary.Folder folder = (Geary.Folder) batch.get_result(id);
main_window.folder_list.add_special_folder(op.special_folder, folder);
} catch (Error inner_error) {
message("Unable to fetch special folder %s: %s",
op.special_folder.path.to_string(), inner_error.message);
}
}
if (cancellable.is_cancelled())
return;
// If inbox is available (should be!), monitor it and select it for the user
Geary.SpecialFolder? inbox = special_folders.get_folder(Geary.SpecialFolderType.INBOX);
if (inbox != null) {
// create and leave open the Inbox, which is constantly monitored for notifications
inbox_folder = yield account.fetch_folder_async(inbox.path, cancellable_inbox);
assert(inbox_folder != null);
yield inbox_folder.open_async(false, cancellable_inbox);
inbox_folder.email_locally_appended.connect(on_inbox_new_email);
// select the inbox and get the show started
main_window.folder_list.select_path(inbox.path);
}
}
// pull down the root-level user folders and recursively add to sidebar
Gee.Collection<Geary.Folder> folders = yield account.list_folders_async(null);
if (folders != null)
@ -730,31 +688,35 @@ public class GearyController {
set_busy(false);
}
private void on_special_folder_type_changed(Geary.Folder folder, Geary.SpecialFolderType old_type,
Geary.SpecialFolderType new_type) {
main_window.folder_list.remove_folder(folder);
main_window.folder_list.add_folder(folder);
}
private void on_folders_added_removed(Gee.Collection<Geary.Folder>? added,
Gee.Collection<Geary.Folder>? removed) {
if (added != null && added.size > 0) {
Gee.Set<Geary.FolderPath>? ignored_paths = account.get_ignored_paths();
Gee.ArrayList<Geary.Folder> skipped = new Gee.ArrayList<Geary.Folder>();
foreach (Geary.Folder folder in added) {
if (ignored_paths != null && ignored_paths.contains(folder.get_path()))
skipped.add(folder);
else {
main_window.folder_list.add_folder(folder);
main_window.main_toolbar.copy_folder_menu.add_folder(folder);
main_window.main_toolbar.move_folder_menu.add_folder(folder);
main_window.folder_list.add_folder(folder);
main_window.main_toolbar.copy_folder_menu.add_folder(folder);
main_window.main_toolbar.move_folder_menu.add_folder(folder);
// monitor the Inbox for notifications
if (folder.get_special_folder_type() == Geary.SpecialFolderType.INBOX && inbox_folder == null) {
inbox_folder = folder;
inbox_folder.email_locally_appended.connect(on_inbox_new_email);
// select the inbox and get the show started
main_window.folder_list.select_path(folder.get_path());
inbox_folder.open_async.begin(false, cancellable_inbox);
}
folder.special_folder_type_changed.connect(on_special_folder_type_changed);
}
Gee.Collection<Geary.Folder> remaining = added;
if (skipped.size > 0) {
remaining = new Gee.ArrayList<Geary.Folder>();
remaining.add_all(added);
remaining.remove_all(skipped);
}
search_folders_for_children.begin(remaining);
search_folders_for_children.begin(added);
}
}

View file

@ -11,8 +11,10 @@ public class FolderList : Sidebar.Tree {
};
private class SpecialFolderBranch : Sidebar.RootOnlyBranch {
public SpecialFolderBranch(Geary.SpecialFolder special, Geary.Folder folder) {
base(new SpecialFolderEntry(special, folder));
public SpecialFolderBranch(Geary.Folder folder) {
base(new FolderEntry(folder));
assert(folder.get_special_folder_type() != Geary.SpecialFolderType.NONE);
}
}
@ -25,51 +27,18 @@ public class FolderList : Sidebar.Tree {
}
public virtual string get_sidebar_name() {
return folder.get_path().basename;
return folder.get_display_name();
}
public string? get_sidebar_tooltip() {
return null;
}
public virtual Icon? get_sidebar_icon() {
return IconFactory.instance.label_icon;
}
public virtual string to_string() {
return "FolderEntry: " + get_sidebar_name();
}
public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) {
// Copy or move?
Gdk.ModifierType mask;
double[] axes = new double[2];
context.get_device().get_state(context.get_dest_window(), axes, out mask);
MainWindow main_window = GearyApplication.instance.get_main_window() as MainWindow;
if ((mask & Gdk.ModifierType.CONTROL_MASK) != 0) {
main_window.folder_list.copy_conversation(folder);
} else {
main_window.folder_list.move_conversation(folder);
}
return true;
}
}
private class SpecialFolderEntry : FolderEntry {
public Geary.SpecialFolder special { get; private set; }
public SpecialFolderEntry(Geary.SpecialFolder special, Geary.Folder folder) {
base (folder);
this.special = special;
}
public override string get_sidebar_name() {
return special.name;
}
public override Icon? get_sidebar_icon() {
switch (special.folder_type) {
public Icon? get_sidebar_icon() {
switch (folder.get_special_folder_type()) {
case Geary.SpecialFolderType.NONE:
return IconFactory.instance.label_icon;
case Geary.SpecialFolderType.INBOX:
return new ThemedIcon("mail-inbox");
@ -99,8 +68,23 @@ public class FolderList : Sidebar.Tree {
}
}
public override string to_string() {
return "SpecialFolderEntry: " + get_sidebar_name();
public virtual string to_string() {
return "FolderEntry: " + get_sidebar_name();
}
public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) {
// Copy or move?
Gdk.ModifierType mask;
double[] axes = new double[2];
context.get_device().get_state(context.get_dest_window(), axes, out mask);
MainWindow main_window = GearyApplication.instance.get_main_window() as MainWindow;
if ((mask & Gdk.ModifierType.CONTROL_MASK) != 0) {
main_window.folder_list.copy_conversation(folder);
} else {
main_window.folder_list.move_conversation(folder);
}
return true;
}
}
@ -112,6 +96,8 @@ public class FolderList : Sidebar.Tree {
private Sidebar.Branch user_folder_branch;
internal Gee.HashMap<Geary.FolderPath, Sidebar.Entry> entries = new Gee.HashMap<
Geary.FolderPath, Sidebar.Entry>(Geary.Hashable.hash_func, Geary.Equalable.equal_func);
internal Gee.HashMap<Geary.FolderPath, Sidebar.Branch> branches = new Gee.HashMap<
Geary.FolderPath, Sidebar.Branch>(Geary.Hashable.hash_func, Geary.Equalable.equal_func);
public FolderList() {
base(new Gtk.TargetEntry[0], Gdk.DragAction.ASK, drop_handler);
@ -136,9 +122,7 @@ public class FolderList : Sidebar.Tree {
}
private void on_entry_selected(Sidebar.SelectableEntry selectable) {
if (selectable is SpecialFolderEntry) {
folder_selected(((SpecialFolderEntry) selectable).folder);
} else if (selectable is FolderEntry) {
if (selectable is FolderEntry) {
folder_selected(((FolderEntry) selectable).folder);
}
}
@ -154,31 +138,51 @@ public class FolderList : Sidebar.Tree {
}
public void add_folder(Geary.Folder folder) {
FolderEntry folder_entry = new FolderEntry(folder);
bool added = false;
if (folder.get_path().get_parent() == null) {
Geary.SpecialFolderType special_folder_type = folder.get_special_folder_type();
if (special_folder_type != Geary.SpecialFolderType.NONE) {
SpecialFolderBranch branch = new SpecialFolderBranch(folder);
graft(branch, (int) special_folder_type);
entries.set(folder.get_path(), branch.get_root());
branches.set(folder.get_path(), branch);
added = true;
} else if (folder.get_path().get_parent() == null) {
// Top-level folder.
FolderEntry folder_entry = new FolderEntry(folder);
user_folder_branch.graft(user_folder_group, folder_entry);
entries.set(folder.get_path(), folder_entry);
branches.set(folder.get_path(), user_folder_branch);
added = true;
} else {
FolderEntry folder_entry = new FolderEntry(folder);
Sidebar.Entry? entry = get_entry_for_folder_path(folder.get_path().get_parent());
if (entry != null) {
user_folder_branch.graft(entry, folder_entry);
entries.set(folder.get_path(), folder_entry);
branches.set(folder.get_path(), user_folder_branch);
added = true;
}
}
if (added)
entries.set(folder.get_path(), folder_entry);
else
debug("Could not add folder to folder list: %s", folder.to_string());
if (!added) {
debug("Could not add folder %s of type %s to folder list", folder.to_string(),
special_folder_type.to_string());
}
}
public void add_special_folder(Geary.SpecialFolder special, Geary.Folder folder) {
SpecialFolderBranch branch = new SpecialFolderBranch(special, folder);
graft(branch, (int) special.folder_type);
entries.set(folder.get_path(), branch.get_root());
public void remove_folder(Geary.Folder folder) {
Sidebar.Entry? entry = get_entry_for_folder_path(folder.get_path());
Sidebar.Branch? branch = get_branch_for_folder_path(folder.get_path());
if(entry != null && branch != null) {
if (branch is SpecialFolderBranch) {
this.prune(branch);
} else {
branch.prune(entry);
}
} else {
debug(@"Could not remove folder $(folder.get_path())");
}
}
public void remove_all_branches() {
@ -197,6 +201,10 @@ public class FolderList : Sidebar.Tree {
return entries.get(path);
}
private Sidebar.Branch? get_branch_for_folder_path(Geary.FolderPath path) {
return branches.get(path);
}
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
// Run the base version first.
bool ret = base.drag_motion(context, x, y, time);

View file

@ -150,11 +150,8 @@ class ImapConsole : Gtk.Window {
break;
case "list":
list(cmd, args);
break;
case "xlist":
xlist(cmd, args);
list(cmd, args);
break;
case "examine":
@ -362,7 +359,7 @@ class ImapConsole : Gtk.Window {
check_connected(cmd, args, 2, "<reference> <mailbox>");
status("Listing...");
cx.send_async.begin(new Geary.Imap.ListCommand.wildcarded(args[0], args[1]),
cx.send_async.begin(new Geary.Imap.ListCommand.wildcarded(args[0], args[1], (cmd.down() == "xlist")),
null, on_list);
}
@ -375,13 +372,6 @@ class ImapConsole : Gtk.Window {
}
}
private void xlist(string cmd, string[] args) throws Error {
check_connected(cmd, args, 2, "<reference> <mailbox>");
status("Xlisting...");
cx.send_async.begin(new Geary.Imap.XListCommand.wildcarded(args[0], args[1]), null, on_list);
}
private void examine(string cmd, string[] args) throws Error {
check_connected(cmd, args, 1, "<mailbox>");

View file

@ -44,10 +44,19 @@ public class Geary.DBus.Controller {
account.report_problem.connect(on_report_problem);
// Open the Inbox folder.
Geary.SpecialFolderMap? special_folders = account.get_special_folder_map();
Geary.Folder folder = yield account.fetch_folder_async(special_folders.get_folder(
Geary.SpecialFolderType.INBOX).path);
Geary.Folder? folder = null;
Gee.Collection<Geary.Folder> folders = yield account.list_folders_async(null, null);
foreach(Geary.Folder folder_to_check in folders) {
if(folder_to_check.get_special_folder_type() == Geary.SpecialFolderType.INBOX) {
folder = folder_to_check;
break;
}
}
if (folder == null) {
warning("No inbox folder found");
return;
}
yield folder.open_async(false, null);
conversations = new Geary.DBus.Conversations(folder);

View file

@ -18,12 +18,6 @@ public abstract class Geary.EngineAccount : Geary.AbstractAccount, Geary.Persona
return account_information;
}
public abstract string get_user_folders_label();
public abstract Geary.SpecialFolderMap? get_special_folder_map();
public abstract Gee.Set<Geary.FolderPath>? get_ignored_paths();
public abstract bool delete_is_archive();
public abstract async void send_email_async(Geary.ComposedEmail composed, Cancellable? cancellable = null)

View file

@ -150,7 +150,6 @@ public class Geary.FolderPath : Object, Hashable, Equalable {
if (other.get_path_length() != path_length)
return false;
bool cs = get_root().case_sensitive;
if (other.get_root().case_sensitive != cs) {
message("Comparing %s and %s with different case sensitivities", to_string(),

View file

@ -154,6 +154,15 @@ public interface Geary.Folder : Object {
* mark_email_async() method as well as changes occur remotely.
*/
public signal void email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map);
/**
* "special-folder-type-changed" is fired when the special folder type has changed.
*
* This will usually happen when the local account object has been updated with data
* from the remote account.
*/
public signal void special_folder_type_changed(Geary.SpecialFolderType old_type,
Geary.SpecialFolderType new_type);
protected abstract void notify_opened(OpenState state, int count);
@ -172,15 +181,22 @@ public interface Geary.Folder : Object {
protected abstract void notify_email_flags_changed(Gee.Map<Geary.EmailIdentifier,
Geary.EmailFlags> flag_map);
protected abstract void notify_special_folder_type_changed(Geary.SpecialFolderType old_type,
Geary.SpecialFolderType new_type);
public abstract Geary.FolderPath get_path();
public abstract Geary.Trillian has_children();
/**
* Returns the special folder type of the folder. If the the folder is not a special one then
* null is returned.
* Returns the special folder type of the folder.
*/
public abstract Geary.SpecialFolderType? get_special_folder_type();
public abstract Geary.SpecialFolderType get_special_folder_type();
/**
* Returns a name suitable for displaying to the user.
*/
public abstract string get_display_name();
/**
* Returns the state of the Folder's connections to the local and remote stores.

View file

@ -5,12 +5,6 @@
*/
public interface Geary.Personality : Object {
public abstract string get_user_folders_label();
public abstract Geary.SpecialFolderMap? get_special_folder_map();
public abstract Gee.Set<Geary.FolderPath>? get_ignored_paths();
public abstract bool delete_is_archive();
}

View file

@ -5,6 +5,7 @@
*/
public enum Geary.SpecialFolderType {
NONE,
INBOX,
DRAFTS,
SENT,
@ -12,53 +13,38 @@ public enum Geary.SpecialFolderType {
ALL_MAIL,
SPAM,
TRASH,
OUTBOX
}
public class Geary.SpecialFolder : Object {
public SpecialFolderType folder_type { get; private set; }
public string name { get; private set; }
public Geary.FolderPath path { get; private set; }
public int ordering { get; private set; }
OUTBOX;
public SpecialFolder(SpecialFolderType folder_type, string name, FolderPath path, int ordering) {
this.folder_type = folder_type;
this.name = name;
this.path = path;
this.ordering = ordering;
}
}
public class Geary.SpecialFolderMap : Object {
private Gee.HashMap<SpecialFolderType, SpecialFolder> map = new Gee.HashMap<SpecialFolderType,
SpecialFolder>();
public SpecialFolderMap() {
}
public void set_folder(SpecialFolder special_folder) {
map.set(special_folder.folder_type, special_folder);
}
public SpecialFolder? get_folder(SpecialFolderType folder_type) {
return map.get(folder_type);
}
public SpecialFolder? get_folder_by_path(FolderPath path) {
foreach (SpecialFolder folder in map.values) {
if (folder.path == path) {
return folder;
}
public unowned string get_display_name() {
switch (this) {
case INBOX:
return _("Inbox");
case DRAFTS:
return _("Drafts");
case SENT:
return _("Sent Mail");
case FLAGGED:
return _("Starred");
case ALL_MAIL:
return _("All Mail");
case SPAM:
return _("Spam");
case TRASH:
return _("Trash");
case OUTBOX:
return _("Outbox");
case NONE:
default:
return _("None");
}
return null;
}
public Gee.Set<SpecialFolderType> get_supported_types() {
return map.keys.read_only_view;
}
public Gee.Collection<SpecialFolder> get_all() {
return map.values.read_only_view;
}
}

View file

@ -42,25 +42,14 @@ public class Geary.Imap.LogoutCommand : Command {
public class Geary.Imap.ListCommand : Command {
public const string NAME = "list";
public const string XLIST_NAME = "xlist";
public ListCommand(string mailbox) {
base (NAME, { "", mailbox });
public ListCommand(string mailbox, bool use_xlist) {
base (use_xlist ? XLIST_NAME : NAME, { "", mailbox });
}
public ListCommand.wildcarded(string reference, string mailbox) {
base (NAME, { reference, mailbox });
}
}
public class Geary.Imap.XListCommand : Command {
public const string NAME = "xlist";
public XListCommand(string mailbox) {
base (NAME, { "", mailbox });
}
public XListCommand.wildcarded(string reference, string mailbox) {
base (NAME, { reference, mailbox });
public ListCommand.wildcarded(string reference, string mailbox, bool use_xlist) {
base (use_xlist ? XLIST_NAME : NAME, { reference, mailbox });
}
}

View file

@ -78,7 +78,7 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults {
StringParameter? delim = data.get_as_nullable_string(3);
StringParameter mailbox = data.get_as_string(4);
if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(XListCommand.NAME)) {
if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(ListCommand.XLIST_NAME)) {
debug("Bad list response \"%s\": Not marked as list or xlist response",
data.to_string());
@ -98,9 +98,17 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults {
attrlist.add(new MailboxAttribute(stringp.value));
}
MailboxInformation info = new MailboxInformation(mailbox.value, delim.nullable_value,
new MailboxAttributes(attrlist));
// Set \Inbox to standard path
MailboxInformation info;
MailboxAttributes attributes = new MailboxAttributes(attrlist);
if (Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes) {
info = new MailboxInformation(Geary.Imap.Account.INBOX_NAME, delim.nullable_value,
attributes);
} else {
info = new MailboxInformation(mailbox.value, delim.nullable_value,
attributes);
}
map.set(mailbox.value, info);
list.add(info);
} catch (ImapError ierr) {

View file

@ -178,6 +178,62 @@ public class Geary.Imap.MailboxAttribute : Geary.Imap.Flag {
return _allows_new;
} }
private static MailboxAttribute? _xlist_inbox = null;
public static MailboxAttribute SPECIAL_FOLDER_INBOX { get {
if (_xlist_inbox == null)
_xlist_inbox = new MailboxAttribute("\\Inbox");
return _xlist_inbox;
} }
private static MailboxAttribute? _xlist_all_mail = null;
public static MailboxAttribute SPECIAL_FOLDER_ALL_MAIL { get {
if (_xlist_all_mail == null)
_xlist_all_mail = new MailboxAttribute("\\AllMail");
return _xlist_all_mail;
} }
private static MailboxAttribute? _xlist_trash = null;
public static MailboxAttribute SPECIAL_FOLDER_TRASH { get {
if (_xlist_trash == null)
_xlist_trash = new MailboxAttribute("\\Trash");
return _xlist_trash;
} }
private static MailboxAttribute? _xlist_drafts = null;
public static MailboxAttribute SPECIAL_FOLDER_DRAFTS { get {
if (_xlist_drafts == null)
_xlist_drafts = new MailboxAttribute("\\Drafts");
return _xlist_drafts;
} }
private static MailboxAttribute? _xlist_sent = null;
public static MailboxAttribute SPECIAL_FOLDER_SENT { get {
if (_xlist_sent == null)
_xlist_sent = new MailboxAttribute("\\Sent");
return _xlist_sent;
} }
private static MailboxAttribute? _xlist_spam = null;
public static MailboxAttribute SPECIAL_FOLDER_SPAM { get {
if (_xlist_spam == null)
_xlist_spam = new MailboxAttribute("\\Spam");
return _xlist_spam;
} }
private static MailboxAttribute? _xlist_starred = null;
public static MailboxAttribute SPECIAL_FOLDER_STARRED { get {
if (_xlist_starred == null)
_xlist_starred = new MailboxAttribute("\\Starred");
return _xlist_starred;
} }
public MailboxAttribute(string value) {
base (value);
}

View file

@ -183,6 +183,31 @@ public class Geary.Imap.MailboxAttributes : Geary.Imap.Flags {
return new MailboxAttributes(attrs);
}
public Geary.SpecialFolderType get_special_folder_type() {
if (contains(MailboxAttribute.SPECIAL_FOLDER_INBOX))
return Geary.SpecialFolderType.INBOX;
if (contains(MailboxAttribute.SPECIAL_FOLDER_ALL_MAIL))
return Geary.SpecialFolderType.ALL_MAIL;
if (contains(MailboxAttribute.SPECIAL_FOLDER_TRASH))
return Geary.SpecialFolderType.TRASH;
if (contains(MailboxAttribute.SPECIAL_FOLDER_DRAFTS))
return Geary.SpecialFolderType.DRAFTS;
if (contains(MailboxAttribute.SPECIAL_FOLDER_SENT))
return Geary.SpecialFolderType.SENT;
if (contains(MailboxAttribute.SPECIAL_FOLDER_SPAM))
return Geary.SpecialFolderType.SPAM;
if (contains(MailboxAttribute.SPECIAL_FOLDER_STARRED))
return Geary.SpecialFolderType.FLAGGED;
return Geary.SpecialFolderType.NONE;
}
}
public class Geary.Imap.InternalDate : Geary.RFC822.Date, Geary.Imap.MessageData {

View file

@ -97,7 +97,8 @@ public class Geary.Imap.ClientSessionManager {
ClientSession session = yield get_authorized_session_async(cancellable);
ListResults results = ListResults.decode(yield session.send_command_async(
new ListCommand.wildcarded("", "%"), cancellable));
new ListCommand.wildcarded("", "%", session.get_capabilities().has_capability("XLIST")),
cancellable));
if (results.status_response.status != Status.OK)
throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string());
@ -114,7 +115,8 @@ public class Geary.Imap.ClientSessionManager {
ClientSession session = yield get_authorized_session_async(cancellable);
ListResults results = ListResults.decode(yield session.send_command_async(
new ListCommand(specifier), cancellable));
new ListCommand(specifier, session.get_capabilities().has_capability("XLIST")),
cancellable));
if (results.status_response.status != Status.OK)
throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string());
@ -126,7 +128,8 @@ public class Geary.Imap.ClientSessionManager {
ClientSession session = yield get_authorized_session_async(cancellable);
ListResults results = ListResults.decode(yield session.send_command_async(
new ListCommand(path), cancellable));
new ListCommand(path, session.get_capabilities().has_capability("XLIST")),
cancellable));
return (results.status_response.status == Status.OK) && (results.get_count() == 1);
}
@ -136,7 +139,8 @@ public class Geary.Imap.ClientSessionManager {
ClientSession session = yield get_authorized_session_async(cancellable);
ListResults results = ListResults.decode(yield session.send_command_async(
new ListCommand(path), cancellable));
new ListCommand(path, session.get_capabilities().has_capability("XLIST")),
cancellable));
if (results.status_response.status != Status.OK)
throw new ImapError.SERVER_ERROR("Server error: %s", results.to_string());

View file

@ -43,11 +43,26 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
email_flags_changed(flag_map);
}
internal virtual void notify_special_folder_type_changed(Geary.SpecialFolderType old_type,
Geary.SpecialFolderType new_type) {
special_folder_type_changed(old_type, new_type);
}
public abstract Geary.FolderPath get_path();
public abstract Geary.Trillian has_children();
public abstract Geary.SpecialFolderType? get_special_folder_type();
public abstract Geary.SpecialFolderType get_special_folder_type();
/**
* Default is to display the basename of the Folder's path.
*/
public virtual string get_display_name() {
Geary.SpecialFolderType special_folder_type = get_special_folder_type();
return (special_folder_type == Geary.SpecialFolderType.NONE)
? get_path().basename : special_folder_type.get_display_name();
}
public abstract Geary.Folder.OpenState get_open_state();

View file

@ -5,6 +5,9 @@
*/
private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
private static Geary.FolderPath? inbox_path = null;
private static Geary.FolderPath? outbox_path = null;
private Imap.Account remote;
private Sqlite.Account local;
private Gee.HashMap<FolderPath, Imap.FolderProperties> properties_map = new Gee.HashMap<
@ -12,6 +15,8 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
private SmtpOutboxFolder? outbox = null;
private Gee.HashMap<FolderPath, GenericImapFolder> existing_folders = new Gee.HashMap<
FolderPath, GenericImapFolder>(Hashable.hash_func, Equalable.equal_func);
private Gee.HashSet<FolderPath> local_only = new Gee.HashSet<FolderPath>(
Hashable.hash_func, Equalable.equal_func);
public GenericImapAccount(string name, string username, AccountInformation? account_info,
File user_data_dir, Imap.Account remote, Sqlite.Account local) {
@ -21,6 +26,16 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
this.local = local;
this.remote.login_failed.connect(on_login_failed);
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) {
outbox_path = new SmtpOutboxFolderRoot();
local_only.add(outbox_path);
}
}
internal Imap.FolderProperties? get_properties_for_folder(FolderPath path) {
@ -75,13 +90,33 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
throw remote_err;
}
// Subclasses should implement this for hardcoded paths that correspond to special folders ...
// if the server supports XLIST, this doesn't have to be implemented.
//
// This won't be called for INBOX or the Outbox.
protected virtual Geary.SpecialFolderType get_special_folder_type_for_path(Geary.FolderPath path) {
return Geary.SpecialFolderType.NONE;
}
private Geary.SpecialFolderType internal_get_special_folder_type_for_path(Geary.FolderPath path) {
if (path.equals(inbox_path))
return Geary.SpecialFolderType.INBOX;
if (path.equals(outbox_path))
return Geary.SpecialFolderType.OUTBOX;
return get_special_folder_type_for_path(path);
}
private GenericImapFolder build_folder(Sqlite.Folder local_folder) {
GenericImapFolder? folder = existing_folders.get(local_folder.get_path());
if (folder != null)
return folder;
folder = new GenericImapFolder(this, remote, local, local_folder,
get_special_folder(local_folder.get_path()));
folder = new GenericImapFolder(this, remote, local, local_folder);
if (folder.get_special_folder_type() == Geary.SpecialFolderType.NONE)
folder.set_special_folder_type(internal_get_special_folder_type_for_path(local_folder.get_path()));
existing_folders.set(folder.get_path(), folder);
return folder;
@ -104,8 +139,11 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
engine_list.add(build_folder(local_folder));
}
// Add Outbox to root
if (parent == null)
engine_list.add(outbox);
background_update_folders.begin(parent, engine_list, cancellable);
engine_list.add(outbox);
return engine_list;
}
@ -163,27 +201,49 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
return;
}
Gee.Set<string> local_names = new Gee.HashSet<string>();
foreach (Geary.Folder folder in engine_folders)
local_names.add(folder.get_path().basename);
Gee.Set<string> remote_names = new Gee.HashSet<string>();
foreach (Geary.Imap.Folder folder in remote_folders) {
remote_names.add(folder.get_path().basename);
// use this iteration to add discovered properties to map
properties_map.set(folder.get_path(), folder.get_properties());
// update all remote folders properties in the local store and active in the system
foreach (Imap.Folder remote_folder in remote_folders) {
try {
yield local.update_folder_async(remote_folder, cancellable);
} catch (Error update_error) {
debug("Unable to update local folder %s with remote properties: %s",
remote_folder.to_string(), update_error.message);
}
}
// Get local paths of all engine (local) folders
Gee.Set<Geary.FolderPath> local_paths = new Gee.HashSet<Geary.FolderPath>(
Geary.Hashable.hash_func, Geary.Equalable.equal_func);
foreach (Geary.Folder local_folder in engine_folders)
local_paths.add(local_folder.get_path());
// Get remote paths of all remote folders
Gee.Set<Geary.FolderPath> remote_paths = new Gee.HashSet<Geary.FolderPath>(
Geary.Hashable.hash_func, Geary.Equalable.equal_func);
foreach (Geary.Imap.Folder remote_folder in remote_folders) {
remote_paths.add(remote_folder.get_path());
// use this iteration to add discovered properties to map
properties_map.set(remote_folder.get_path(), remote_folder.get_properties());
// also use this iteration to set the local folder's special type
GenericImapFolder? local_folder = existing_folders.get(remote_folder.get_path());
if (local_folder != null)
local_folder.set_special_folder_type(remote_folder.get_properties().attrs.get_special_folder_type());
}
// If path in remote but not local, need to add it
Gee.List<Geary.Imap.Folder> to_add = new Gee.ArrayList<Geary.Imap.Folder>();
foreach (Geary.Imap.Folder folder in remote_folders) {
if (!local_names.contains(folder.get_path().basename))
if (!local_paths.contains(folder.get_path()))
to_add.add(folder);
}
// If path in local but not remote (and isn't local-only, i.e. the Outbox), need to remove
// it
Gee.List<Geary.Folder>? to_remove = new Gee.ArrayList<Geary.Imap.Folder>();
foreach (Geary.Folder folder in engine_folders) {
if (!remote_names.contains(folder.get_path().basename))
if (!remote_paths.contains(folder.get_path()) && !local_only.contains(folder.get_path()))
to_remove.add(folder);
}
@ -193,6 +253,7 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
if (to_remove.size == 0)
to_remove = null;
// For folders to add, clone them and their properties locally
if (to_add != null) {
foreach (Geary.Imap.Folder folder in to_add) {
try {
@ -204,6 +265,7 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
}
}
// Create Geary.Folder objects for all added folders
Gee.Collection<Geary.Folder> engine_added = null;
if (to_add != null) {
engine_added = new Gee.ArrayList<Geary.Folder>();
@ -217,18 +279,17 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
}
}
// TODO: Remove local folders no longer available remotely.
if (to_remove != null) {
foreach (Geary.Folder folder in to_remove) {
debug(@"Need to remove folder $folder");
}
}
if (engine_added != null)
notify_folders_added_removed(engine_added, null);
}
public override string get_user_folders_label() {
return _("Folders");
}
public override Gee.Set<Geary.FolderPath>? get_ignored_paths() {
return null;
}
public override bool delete_is_archive() {
return false;
}
@ -242,12 +303,5 @@ private abstract class Geary.GenericImapAccount : Geary.EngineAccount {
private void on_login_failed(Geary.Credentials? credentials) {
notify_report_problem(Geary.Account.Problem.LOGIN_FAILED, credentials, null);
}
private SpecialFolder? get_special_folder(FolderPath path) {
if (get_special_folder_map() != null) {
return get_special_folder_map().get_folder_by_path(path);
}
return null;
}
}

View file

@ -12,7 +12,6 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder {
internal Sqlite.Folder local_folder { get; protected set; }
internal Imap.Folder? remote_folder { get; protected set; default = null; }
internal SpecialFolder? special_folder { get; protected set; default = null; }
internal int remote_count { get; private set; default = -1; }
private weak GenericImapAccount account;
@ -20,18 +19,19 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder {
private Sqlite.Account local;
private EmailFlagWatcher email_flag_watcher;
private EmailPrefetcher email_prefetcher;
private SpecialFolderType special_folder_type;
private bool opened = false;
private NonblockingSemaphore remote_semaphore;
private ReplayQueue? replay_queue = null;
private NonblockingMutex normalize_email_positions_mutex = new NonblockingMutex();
public GenericImapFolder(GenericImapAccount account, Imap.Account remote, Sqlite.Account local,
Sqlite.Folder local_folder, SpecialFolder? special_folder) {
Sqlite.Folder local_folder) {
this.account = account;
this.remote = remote;
this.local = local;
this.local_folder = local_folder;
this.special_folder = special_folder;
this.special_folder_type = local_folder.get_properties().attrs.get_special_folder_type();
email_flag_watcher = new EmailFlagWatcher(this);
email_flag_watcher.email_flags_changed.connect(on_email_flags_changed);
@ -48,12 +48,18 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder {
return local_folder.get_path();
}
public override Geary.SpecialFolderType? get_special_folder_type() {
if (special_folder == null) {
return null;
} else {
return special_folder.folder_type;
}
public override Geary.SpecialFolderType get_special_folder_type() {
return special_folder_type;
}
public void set_special_folder_type(SpecialFolderType new_type) {
if (special_folder_type == new_type)
return;
Geary.SpecialFolderType old_type = special_folder_type;
special_folder_type = new_type;
notify_special_folder_type_changed(old_type, new_type);
}
private Imap.FolderProperties? get_folder_properties() {

View file

@ -6,6 +6,7 @@
private class Geary.GmailAccount : Geary.GenericImapAccount {
private const string GMAIL_FOLDER = "[Gmail]";
private const string GOOGLEMAIL_FOLDER = "[Google Mail]";
private static Geary.Endpoint? _imap_endpoint = null;
public static Geary.Endpoint IMAP_ENDPOINT { get {
@ -33,58 +34,9 @@ private class Geary.GmailAccount : Geary.GenericImapAccount {
return _smtp_endpoint;
} }
private static SpecialFolderMap? special_folder_map = null;
private static Gee.Set<Geary.FolderPath>? ignored_paths = null;
public GmailAccount(string name, string username, AccountInformation account_info,
File user_data_dir, Imap.Account remote, Sqlite.Account local) {
base (name, username, account_info, user_data_dir, remote, local);
if (special_folder_map == null || ignored_paths == null)
initialize_personality();
}
private static void initialize_personality() {
Geary.FolderPath gmail_root = new Geary.FolderRoot(GMAIL_FOLDER, Imap.Account.ASSUMED_SEPARATOR,
true);
Geary.FolderRoot inbox_folder = new Geary.FolderRoot(Imap.Account.INBOX_NAME,
Imap.Account.ASSUMED_SEPARATOR, false);
Geary.FolderRoot outbox_folder = new SmtpOutboxFolderRoot();
special_folder_map = new SpecialFolderMap();
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.INBOX, _("Inbox"),
inbox_folder, 0));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.DRAFTS, _("Drafts"),
gmail_root.get_child("Drafts"), 1));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SENT, _("Sent Mail"),
gmail_root.get_child("Sent Mail"), 2));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.FLAGGED, _("Starred"),
gmail_root.get_child("Starred"), 3));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.ALL_MAIL, _("All Mail"),
gmail_root.get_child("All Mail"), 4));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SPAM, _("Spam"),
gmail_root.get_child("Spam"), 5));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.OUTBOX,
_("Outbox"), outbox_folder, 6));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.TRASH, _("Trash"),
gmail_root.get_child("Trash"), 7));
ignored_paths = new Gee.HashSet<Geary.FolderPath>(Hashable.hash_func, Equalable.equal_func);
ignored_paths.add(gmail_root);
ignored_paths.add(inbox_folder);
ignored_paths.add(outbox_folder);
}
public override string get_user_folders_label() {
return _("Labels");
}
public override Geary.SpecialFolderMap? get_special_folder_map() {
return special_folder_map;
}
public override Gee.Set<Geary.FolderPath>? get_ignored_paths() {
return ignored_paths.read_only_view;
}
public override bool delete_is_archive() {

View file

@ -10,16 +10,8 @@ private class Geary.OtherAccount : Geary.GenericImapAccount {
base (name, username, account_info, user_data_dir, remote, local);
}
public override string get_user_folders_label() {
return _("Folders");
}
public override Geary.SpecialFolderMap? get_special_folder_map() {
return null;
}
public override Gee.Set<Geary.FolderPath>? get_ignored_paths() {
return null;
protected override Geary.SpecialFolderType get_special_folder_type_for_path(Geary.FolderPath path) {
return Geary.SpecialFolderType.NONE;
}
public override bool delete_is_archive() {

View file

@ -31,62 +31,28 @@ private class Geary.YahooAccount : Geary.GenericImapAccount {
return _smtp_endpoint;
} }
private static SpecialFolderMap? special_folder_map = null;
private static Gee.Set<Geary.FolderPath>? ignored_paths = null;
private Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType> special_map = new Gee.HashMap<
Geary.FolderPath, Geary.SpecialFolderType>(Hashable.hash_func, Equalable.equal_func);
public YahooAccount(string name, string username, AccountInformation account_info,
File user_data_dir, Imap.Account remote, Sqlite.Account local) {
base (name, username, account_info, user_data_dir, remote, local);
if (special_folder_map == null || ignored_paths == null)
initialize_personality();
}
private static void initialize_personality() {
special_folder_map = new SpecialFolderMap();
FolderPath sent = new Geary.FolderRoot("Sent", Imap.Account.ASSUMED_SEPARATOR, false);
special_map.set(sent, Geary.SpecialFolderType.SENT);
FolderRoot inbox_folder = new FolderRoot(Imap.Account.INBOX_NAME,
Imap.Account.ASSUMED_SEPARATOR, false);
FolderRoot sent_folder = new Geary.FolderRoot("Sent", Imap.Account.ASSUMED_SEPARATOR, false);
FolderRoot drafts_folder = new Geary.FolderRoot("Draft", Imap.Account.ASSUMED_SEPARATOR,
false);
FolderRoot spam_folder = new Geary.FolderRoot("Bulk Mail", Imap.Account.ASSUMED_SEPARATOR,
false);
FolderRoot trash_folder = new Geary.FolderRoot("Trash", Imap.Account.ASSUMED_SEPARATOR, false);
FolderRoot outbox_folder = new SmtpOutboxFolderRoot();
FolderPath drafts = new Geary.FolderRoot("Draft", Imap.Account.ASSUMED_SEPARATOR, false);
special_map.set(drafts, Geary.SpecialFolderType.DRAFTS);
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.INBOX, _("Inbox"),
inbox_folder, 0));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.DRAFTS, _("Drafts"),
drafts_folder, 1));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SENT, _("Sent Mail"),
sent_folder, 2));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.SPAM, _("Spam"),
spam_folder, 3));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.OUTBOX,
_("Outbox"), outbox_folder, 4));
special_folder_map.set_folder(new SpecialFolder(Geary.SpecialFolderType.TRASH, _("Trash"),
trash_folder, 5));
FolderPath bulk = new Geary.FolderRoot("Bulk Mail", Imap.Account.ASSUMED_SEPARATOR, false);
special_map.set(bulk, Geary.SpecialFolderType.SPAM);
ignored_paths = new Gee.HashSet<Geary.FolderPath>(Hashable.hash_func, Equalable.equal_func);
ignored_paths.add(inbox_folder);
ignored_paths.add(drafts_folder);
ignored_paths.add(sent_folder);
ignored_paths.add(spam_folder);
ignored_paths.add(outbox_folder);
ignored_paths.add(trash_folder);
FolderPath trash = new Geary.FolderRoot("Trash", Imap.Account.ASSUMED_SEPARATOR, false);
special_map.set(trash, Geary.SpecialFolderType.TRASH);
}
public override string get_user_folders_label() {
return _("Folders");
}
public override Geary.SpecialFolderMap? get_special_folder_map() {
return special_folder_map;
}
public override Gee.Set<Geary.FolderPath>? get_ignored_paths() {
return ignored_paths;
protected override Geary.SpecialFolderType get_special_folder_type_for_path(Geary.FolderPath path) {
return special_map.has_key(path) ? special_map.get(path) : Geary.SpecialFolderType.NONE;
}
public override bool delete_is_archive() {

View file

@ -117,7 +117,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder {
return Geary.Trillian.FALSE;
}
public override Geary.SpecialFolderType? get_special_folder_type() {
public override Geary.SpecialFolderType get_special_folder_type() {
return Geary.SpecialFolderType.OUTBOX;
}

View file

@ -50,7 +50,7 @@ public abstract class Geary.Sqlite.Database {
return t;
}
public void upgrade() throws Error {
public int upgrade() throws Error {
// Get the SQLite database version.
SQLHeavy.QueryResult result = db.execute("PRAGMA user_version;");
int db_version = result.fetch_int();
@ -74,6 +74,8 @@ public abstract class Geary.Sqlite.Database {
post_upgrade(db_version);
}
return db.execute("PRAGMA user_version;").fetch_int();
}
private File get_upgrade_script(int version) {

View file

@ -43,10 +43,8 @@ private class Geary.Sqlite.Account : Object {
db.pre_upgrade.connect(on_pre_upgrade);
db.post_upgrade.connect(on_post_upgrade);
db.upgrade();
// Need to clear duplicate folders (due to ticket #nnnn)
clear_duplicate_folders();
// upgrade and do any processing that should be done on this version of the database
process_database(db.upgrade());
} catch (Error err) {
warning("Unable to open database: %s", err.message);
@ -296,6 +294,25 @@ private class Geary.Sqlite.Account : Object {
// TODO Add per-version data massaging.
}
// Called every run after executing db.upgrade(); this gives a chance to perform work that
// cannot be easily expressed in an upgrade script and should happen whether an upgrade to that
// version has happened or not
private void process_database(int version) {
switch (version) {
case 3:
try {
clear_duplicate_folders();
} catch (SQLHeavy.Error err) {
debug("Unable to clear duplicate folders in version %d: %s", version, err.message);
}
break;
default:
// nothing to do
break;
}
}
private void clear_duplicate_folders() throws SQLHeavy.Error {
int count = 0;
@ -320,7 +337,7 @@ private class Geary.Sqlite.Account : Object {
SQLHeavy.QueryResult message_result = message_query.execute();
if (child_result.finished && message_result.finished) {
// no children, delete it
// no children and no messages, delete it
SQLHeavy.Query child_delete = db.db.prepare(
"DELETE FROM FolderTable WHERE id=?");
child_delete.bind_int64(0, id);

View file

@ -32,14 +32,14 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
private ImapDatabase db;
private FolderRow folder_row;
private Geary.Imap.FolderProperties? properties;
private Geary.Imap.FolderProperties properties;
private MessageTable message_table;
private MessageLocationTable location_table;
private MessageAttachmentTable attachment_table;
private ImapMessagePropertiesTable imap_message_properties_table;
private Geary.FolderPath path;
internal Folder(ImapDatabase db, FolderRow folder_row, Geary.Imap.FolderProperties? properties,
internal Folder(ImapDatabase db, FolderRow folder_row, Geary.Imap.FolderProperties properties,
Geary.FolderPath path) throws Error {
this.db = db;
this.folder_row = folder_row;
@ -61,12 +61,12 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
return path;
}
public Geary.Imap.FolderProperties? get_properties() {
// TODO: TBD: alteration/updated signals for folders
public Geary.Imap.FolderProperties get_properties() {
return properties;
}
internal void update_properties(Geary.Imap.FolderProperties? properties) {
internal void update_properties(Geary.Imap.FolderProperties properties) {
// TODO: TBD: alteration/updated signals for folders
this.properties = properties;
}