1/* Copyright 2016 Software Freedom Conservancy Inc.
2 *
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later).  See the COPYING file in this distribution.
5 */
6
7// A branch that holds all the folders for a particular account.
8public class FolderList.AccountBranch : Sidebar.Branch {
9
10    // Defines the ordering that special-use folders appear in the
11    // folder list
12    private const Geary.Folder.SpecialUse[] SPECIAL_USE_ORDERING = {
13        INBOX,
14        SEARCH,
15        FLAGGED,
16        IMPORTANT,
17        DRAFTS,
18        CUSTOM,
19        NONE,
20        OUTBOX,
21        SENT,
22        ARCHIVE,
23        ALL_MAIL,
24        TRASH,
25        JUNK
26    };
27
28
29    public Geary.Account account { get; private set; }
30    public SpecialGrouping user_folder_group { get; private set; }
31    public Gee.HashMap<Geary.FolderPath, FolderEntry> folder_entries { get; private set; }
32
33    private string display_name = "";
34
35    public AccountBranch(Geary.Account account) {
36        base(new Sidebar.Header(account.information.display_name),
37             STARTUP_OPEN_GROUPING | STARTUP_EXPAND_TO_FIRST_CHILD,
38             normal_folder_comparator,
39             special_folder_comparator
40        );
41
42        this.account = account;
43        // Translators: The name of the folder group containing
44        // folders created by people (as opposed to special-use
45        // folders)
46        user_folder_group = new SpecialGrouping(2, _("Labels"), "tag-symbolic");
47        folder_entries = new Gee.HashMap<Geary.FolderPath, FolderEntry>();
48
49        this.display_name = account.information.display_name;
50        account.information.changed.connect(on_information_changed);
51
52        entry_removed.connect(on_entry_removed);
53        entry_moved.connect(check_user_folders);
54    }
55
56    ~AccountBranch() {
57        account.information.changed.disconnect(on_information_changed);
58        entry_removed.disconnect(on_entry_removed);
59        entry_moved.disconnect(check_user_folders);
60    }
61
62    private void on_information_changed() {
63        if (this.display_name != this.account.information.display_name) {
64            this.display_name = account.information.display_name;
65            ((Sidebar.Grouping) get_root()).rename(this.display_name);
66        }
67    }
68
69    private static int special_grouping_comparator(Sidebar.Entry a, Sidebar.Entry b) {
70        SpecialGrouping? grouping_a = a as SpecialGrouping;
71        SpecialGrouping? grouping_b = b as SpecialGrouping;
72
73        assert(grouping_a != null || grouping_b != null);
74
75        int position_a = (grouping_a != null ? grouping_a.position : 0);
76        int position_b = (grouping_b != null ? grouping_b.position : 0);
77
78        return position_a - position_b;
79    }
80
81    private static int special_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) {
82        if (a is Sidebar.Grouping || b is Sidebar.Grouping)
83            return special_grouping_comparator(a, b);
84
85        FolderEntry entry_a = (FolderEntry) a;
86        FolderEntry entry_b = (FolderEntry) b;
87        Geary.Folder.SpecialUse type_a = entry_a.folder.used_as;
88        Geary.Folder.SpecialUse type_b = entry_b.folder.used_as;
89
90        if (type_a == type_b) return 0;
91        if (type_a == INBOX) return -1;
92        if (type_b == INBOX) return 1;
93
94        int ordering_a = 0;
95        for (; ordering_a < SPECIAL_USE_ORDERING.length; ordering_a++) {
96            if (type_a == SPECIAL_USE_ORDERING[ordering_a]) {
97                break;
98            }
99        }
100        int ordering_b = 0;
101        for (; ordering_b < SPECIAL_USE_ORDERING.length; ordering_b++) {
102            if (type_b == SPECIAL_USE_ORDERING[ordering_b]) {
103                break;
104            }
105        }
106
107        if (ordering_a == ordering_b) return normal_folder_comparator(a, b);
108        return ordering_a - ordering_b;
109    }
110
111    private static int normal_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) {
112        // Non-special folders are compared based on name.
113        return a.get_sidebar_name().collate(b.get_sidebar_name());
114    }
115
116    public FolderEntry? get_entry_for_path(Geary.FolderPath folder_path) {
117        return folder_entries.get(folder_path);
118    }
119
120    public void add_folder(Application.FolderContext context) {
121        Sidebar.Entry? graft_point = null;
122        FolderEntry folder_entry = new FolderEntry(context);
123        Geary.Folder.SpecialUse used_as = context.folder.used_as;
124        if (used_as != NONE) {
125            if (used_as == SEARCH)
126                return; // Don't show search folder under the account.
127
128            // Special folders go in the root of the account.
129            graft_point = get_root();
130        } else if (context.folder.path.is_top_level) {
131            // Top-level folders get put in our special user folders group.
132            graft_point = user_folder_group;
133
134            if (!has_entry(user_folder_group)) {
135                graft(get_root(), user_folder_group);
136            }
137        } else {
138            var entry = folder_entries.get(context.folder.path.parent);
139            if (entry != null)
140                graft_point = entry;
141        }
142
143        // Due to how we enumerate folders on the server, it's unfortunately
144        // possible now to have two folders that we'd put in the same place in
145        // our tree.  In that case, we just ignore the second folder for now.
146        // See #6616.
147        if (graft_point != null) {
148            Sidebar.Entry? twin = find_first_child(graft_point, (e) => {
149                return e.get_sidebar_name() == folder_entry.get_sidebar_name();
150            });
151            if (twin != null)
152                graft_point = null;
153        }
154
155        if (graft_point != null) {
156            graft(graft_point, folder_entry);
157            folder_entries.set(context.folder.path, folder_entry);
158        } else {
159            debug(
160                "Could not add folder %s of type %s to folder list",
161                context.folder.to_string(),
162                used_as.to_string()
163            );
164        }
165    }
166
167    public void remove_folder(Geary.FolderPath path) {
168        Sidebar.Entry? entry = this.folder_entries.get(path);
169        if (entry == null) {
170            debug("Could not remove folder %s", path.to_string());
171            return;
172        }
173
174        prune(entry);
175        this.folder_entries.unset(path);
176    }
177
178    private void on_entry_removed(Sidebar.Entry entry) {
179        FolderEntry? folder_entry = entry as FolderEntry;
180        if (folder_entry != null && folder_entries.has_key(folder_entry.folder.path))
181            folder_entries.unset(folder_entry.folder.path);
182
183        check_user_folders(entry);
184    }
185
186    private void check_user_folders(Sidebar.Entry entry) {
187        if (entry != user_folder_group) {
188            // remove "Labels" entry if there are no more user entries
189            if (has_entry(user_folder_group) && (get_child_count(user_folder_group) == 0)) {
190                prune(user_folder_group);
191            }
192        }
193    }
194}
195