Added folder list to sidebar.
geary now has an (unsorted) list of folders in its sidebar. When one is clicked on the messages in that folder are displayed. This patch also fixes an issue in the Serializer that wasn't dealing with quoted strings properly.
This commit is contained in:
parent
65eb370e96
commit
aa094cb813
19 changed files with 574 additions and 93 deletions
17
Makefile
17
Makefile
|
|
@ -1,3 +1,5 @@
|
|||
# Only geary is built by default. Use "make all" to build command-line tools.
|
||||
|
||||
PROGRAM = geary
|
||||
BUILD_ROOT = 1
|
||||
|
||||
|
|
@ -16,6 +18,7 @@ ENGINE_SRC := \
|
|||
src/engine/common/MessageData.vala \
|
||||
src/engine/imap/ClientConnection.vala \
|
||||
src/engine/imap/ClientSession.vala \
|
||||
src/engine/imap/DataFormat.vala \
|
||||
src/engine/imap/Mailbox.vala \
|
||||
src/engine/imap/Parameter.vala \
|
||||
src/engine/imap/Tag.vala \
|
||||
|
|
@ -34,9 +37,11 @@ ENGINE_SRC := \
|
|||
src/engine/imap/Serializer.vala \
|
||||
src/engine/imap/Deserializer.vala \
|
||||
src/engine/imap/Error.vala \
|
||||
src/engine/imap/Flag.vala \
|
||||
src/engine/imap/decoders/FetchDataDecoder.vala \
|
||||
src/engine/imap/decoders/FetchResults.vala \
|
||||
src/engine/imap/decoders/NoopResults.vala \
|
||||
src/engine/imap/decoders/ListResults.vala \
|
||||
src/engine/rfc822/MailboxAddress.vala \
|
||||
src/engine/rfc822/MessageData.vala \
|
||||
src/engine/util/String.vala \
|
||||
|
|
@ -49,6 +54,8 @@ CLIENT_SRC := \
|
|||
src/client/ui/MainWindow.vala \
|
||||
src/client/ui/MessageListView.vala \
|
||||
src/client/ui/MessageListStore.vala \
|
||||
src/client/ui/FolderListView.vala \
|
||||
src/client/ui/FolderListStore.vala \
|
||||
src/client/util/Intl.vala \
|
||||
src/client/util/Date.vala
|
||||
|
||||
|
|
@ -80,6 +87,11 @@ EXTERNAL_PKGS := \
|
|||
VAPI_FILES := \
|
||||
vapi/gmime-2.4.vapi
|
||||
|
||||
geary: $(ENGINE_SRC) $(CLIENT_SRC) Makefile $(VAPI_FILES)
|
||||
$(VALAC) $(VALAFLAGS) $(foreach pkg,$(EXTERNAL_PKGS),--pkg=$(pkg)) \
|
||||
$(ENGINE_SRC) $(CLIENT_SRC) \
|
||||
-o $@
|
||||
|
||||
.PHONY: all
|
||||
all: $(APPS)
|
||||
|
||||
|
|
@ -88,11 +100,6 @@ clean:
|
|||
rm -f $(ALL_SRC:.vala=.c)
|
||||
rm -f $(APPS)
|
||||
|
||||
geary: $(ENGINE_SRC) $(CLIENT_SRC) Makefile $(VAPI_FILES)
|
||||
$(VALAC) $(VALAFLAGS) $(foreach pkg,$(EXTERNAL_PKGS),--pkg=$(pkg)) \
|
||||
$(ENGINE_SRC) $(CLIENT_SRC) \
|
||||
-o $@
|
||||
|
||||
console: $(ENGINE_SRC) $(CONSOLE_SRC) Makefile
|
||||
$(VALAC) $(VALAFLAGS) $(foreach pkg,$(EXTERNAL_PKGS),--pkg=$(pkg)) \
|
||||
$(ENGINE_SRC) $(CONSOLE_SRC) \
|
||||
|
|
|
|||
55
src/client/ui/FolderListStore.vala
Normal file
55
src/client/ui/FolderListStore.vala
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* 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 FolderListStore : Gtk.TreeStore {
|
||||
public enum Column {
|
||||
NAME,
|
||||
N_COLUMNS;
|
||||
|
||||
public static Column[] all() {
|
||||
return { NAME };
|
||||
}
|
||||
|
||||
public static Type[] get_types() {
|
||||
return {
|
||||
typeof (string)
|
||||
};
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
switch (this) {
|
||||
case NAME:
|
||||
return _("Name");
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FolderListStore() {
|
||||
set_column_types(Column.get_types());
|
||||
}
|
||||
|
||||
public void add_folder(string folder) {
|
||||
Gtk.TreeIter iter;
|
||||
append(out iter, null);
|
||||
|
||||
set(iter, Column.NAME, folder);
|
||||
}
|
||||
|
||||
public string? get_folder_at(Gtk.TreePath path) {
|
||||
Gtk.TreeIter iter;
|
||||
if (!get_iter(out iter, path))
|
||||
return null;
|
||||
|
||||
string folder;
|
||||
get(iter, 0, out folder);
|
||||
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
34
src/client/ui/FolderListView.vala
Normal file
34
src/client/ui/FolderListView.vala
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* 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 FolderListView : Gtk.TreeView {
|
||||
public signal void folder_selected(string folder);
|
||||
|
||||
public FolderListView(FolderListStore store) {
|
||||
set_model(store);
|
||||
|
||||
Gtk.CellRendererText name_renderer = new Gtk.CellRendererText();
|
||||
Gtk.TreeViewColumn name_column = new Gtk.TreeViewColumn.with_attributes(
|
||||
FolderListStore.Column.NAME.to_string(), name_renderer, "text", FolderListStore.Column.NAME);
|
||||
append_column(name_column);
|
||||
|
||||
get_selection().changed.connect(on_selection_changed);
|
||||
}
|
||||
|
||||
private FolderListStore get_store() {
|
||||
return (FolderListStore) get_model();
|
||||
}
|
||||
|
||||
private void on_selection_changed() {
|
||||
Gtk.TreeModel model;
|
||||
Gtk.TreePath path = get_selection().get_selected_rows(out model).nth_data(0);
|
||||
|
||||
string? folder = get_store().get_folder_at(path);
|
||||
|
||||
folder_selected(folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -21,8 +21,11 @@ public class MainWindow : Gtk.Window {
|
|||
|
||||
private MessageListStore message_list_store = new MessageListStore();
|
||||
private MessageListView message_list_view;
|
||||
private FolderListStore folder_list_store = new FolderListStore();
|
||||
private FolderListView folder_list_view;
|
||||
private Gtk.UIManager ui = new Gtk.UIManager();
|
||||
private Geary.Engine? engine = null;
|
||||
private Geary.Account? account = null;
|
||||
|
||||
public MainWindow() {
|
||||
title = GearyApplication.PROGRAM_NAME;
|
||||
|
|
@ -42,6 +45,9 @@ public class MainWindow : Gtk.Window {
|
|||
|
||||
message_list_view = new MessageListView(message_list_store);
|
||||
|
||||
folder_list_view = new FolderListView(folder_list_store);
|
||||
folder_list_view.folder_selected.connect(on_folder_selected);
|
||||
|
||||
create_layout();
|
||||
}
|
||||
|
||||
|
|
@ -53,20 +59,17 @@ public class MainWindow : Gtk.Window {
|
|||
|
||||
private async void do_login(string user, string pass) {
|
||||
try {
|
||||
Geary.Account? account = yield engine.login("imap.gmail.com", user, pass);
|
||||
account = yield engine.login("imap.gmail.com", user, pass);
|
||||
if (account == null)
|
||||
error("Unable to login");
|
||||
|
||||
Geary.Folder folder = yield account.open("inbox");
|
||||
|
||||
Geary.MessageStream? msg_stream = folder.read(1, 100);
|
||||
if (msg_stream == null)
|
||||
error("Unable to read from folder");
|
||||
|
||||
Gee.List<Geary.Message>? msgs = yield msg_stream.read();
|
||||
if (msgs != null && msgs.size > 0) {
|
||||
foreach (Geary.Message msg in msgs)
|
||||
message_list_store.append_message(msg);
|
||||
Gee.Collection<string>? folders = yield account.list("/");
|
||||
if (folders != null) {
|
||||
debug("%d folders found", folders.size);
|
||||
foreach (string folder in folders)
|
||||
folder_list_store.add_folder(folder);
|
||||
} else {
|
||||
debug("no folders");
|
||||
}
|
||||
} catch (Error err) {
|
||||
error("%s", err.message);
|
||||
|
|
@ -115,11 +118,23 @@ public class MainWindow : Gtk.Window {
|
|||
// main menu
|
||||
main_layout.pack_start(ui.get_widget("/MenuBar"), false, false, 0);
|
||||
|
||||
// three-pane display: folder list on left, message list on right, separated with grippable
|
||||
// pane
|
||||
Gtk.HPaned paned = new Gtk.HPaned();
|
||||
|
||||
// folder list
|
||||
Gtk.ScrolledWindow folder_list_scrolled = new Gtk.ScrolledWindow(null, null);
|
||||
folder_list_scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
folder_list_scrolled.add_with_viewport(folder_list_view);
|
||||
paned.pack1(folder_list_scrolled, false, false);
|
||||
|
||||
// message list
|
||||
Gtk.ScrolledWindow message_list_scrolled = new Gtk.ScrolledWindow(null, null);
|
||||
message_list_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
|
||||
message_list_scrolled.add_with_viewport(message_list_view);
|
||||
main_layout.pack_end(message_list_scrolled, true, true, 0);
|
||||
paned.pack2(message_list_scrolled, true, false);
|
||||
|
||||
main_layout.pack_end(paned, true, true, 0);
|
||||
|
||||
add(main_layout);
|
||||
}
|
||||
|
|
@ -139,5 +154,35 @@ public class MainWindow : Gtk.Window {
|
|||
"website-label", GearyApplication.WEBSITE_LABEL
|
||||
);
|
||||
}
|
||||
|
||||
private void on_folder_selected(string folder) {
|
||||
do_select_folder.begin(folder, on_select_folder_completed);
|
||||
}
|
||||
|
||||
private async void do_select_folder(string folder_name) throws Error {
|
||||
message_list_store.clear();
|
||||
|
||||
Geary.Folder folder = yield account.open(folder_name);
|
||||
|
||||
Geary.MessageStream? msg_stream = folder.read(1, 100);
|
||||
if (msg_stream == null)
|
||||
error("Unable to read from folder");
|
||||
|
||||
Gee.List<Geary.Message>? msgs = yield msg_stream.read();
|
||||
if (msgs != null && msgs.size > 0) {
|
||||
foreach (Geary.Message msg in msgs)
|
||||
message_list_store.append_message(msg);
|
||||
}
|
||||
|
||||
yield folder.close();
|
||||
}
|
||||
|
||||
private void on_select_folder_completed(Object? source, AsyncResult result) {
|
||||
try {
|
||||
do_select_folder.end(result);
|
||||
} catch (Error err) {
|
||||
debug("Unable to select folder: %s", err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ errordomain CommandException {
|
|||
}
|
||||
|
||||
class ImapConsole : Gtk.Window {
|
||||
private static const int KEEPALIVE_SEC = 60 * 10;
|
||||
|
||||
private Gtk.TextView console = new Gtk.TextView();
|
||||
private Gtk.Entry cmdline = new Gtk.Entry();
|
||||
private Gtk.Statusbar statusbar = new Gtk.Statusbar();
|
||||
|
|
@ -82,12 +84,14 @@ class ImapConsole : Gtk.Window {
|
|||
"logout",
|
||||
"bye",
|
||||
"list",
|
||||
"xlist",
|
||||
"examine",
|
||||
"fetch",
|
||||
"help",
|
||||
"exit",
|
||||
"quit",
|
||||
"gmail"
|
||||
"gmail",
|
||||
"keepalive"
|
||||
};
|
||||
|
||||
private void exec(string input) {
|
||||
|
|
@ -144,6 +148,10 @@ class ImapConsole : Gtk.Window {
|
|||
list(cmd, args);
|
||||
break;
|
||||
|
||||
case "xlist":
|
||||
xlist(cmd, args);
|
||||
break;
|
||||
|
||||
case "examine":
|
||||
examine(cmd, args);
|
||||
break;
|
||||
|
|
@ -168,6 +176,10 @@ class ImapConsole : Gtk.Window {
|
|||
connect_cmd("connect", fake_args);
|
||||
break;
|
||||
|
||||
case "keepalive":
|
||||
keepalive(cmd, args);
|
||||
break;
|
||||
|
||||
default:
|
||||
status("Unknown command \"%s\"".printf(cmd));
|
||||
break;
|
||||
|
|
@ -333,6 +345,14 @@ class ImapConsole : Gtk.Window {
|
|||
}
|
||||
}
|
||||
|
||||
private void xlist(string cmd, string[] args) throws Error {
|
||||
check_connected(cmd, args, 2, "<reference> <mailbox>");
|
||||
|
||||
status("Xlisting...");
|
||||
cx.post(new Geary.Imap.XListCommand.wildcarded(cx.generate_tag(), args[0], args[1]),
|
||||
on_list);
|
||||
}
|
||||
|
||||
private void examine(string cmd, string[] args) throws Error {
|
||||
check_connected(cmd, args, 1, "<mailbox>");
|
||||
|
||||
|
|
@ -374,6 +394,35 @@ class ImapConsole : Gtk.Window {
|
|||
Gtk.main_quit();
|
||||
}
|
||||
|
||||
private bool keepalive_on = false;
|
||||
|
||||
private void keepalive(string cmd, string[] args) throws Error {
|
||||
if (keepalive_on) {
|
||||
status("Keepalive already active.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
check_connected(cmd, args, 0, null);
|
||||
|
||||
keepalive_on = true;
|
||||
Timeout.add_seconds(KEEPALIVE_SEC, on_keepalive);
|
||||
|
||||
status("Keepalive on.");
|
||||
}
|
||||
|
||||
private bool on_keepalive() {
|
||||
try {
|
||||
noop("noop", new string[0]);
|
||||
} catch (Error err) {
|
||||
status("Keepalive failed, halted: %s".printf(err.message));
|
||||
|
||||
keepalive_on = false;
|
||||
}
|
||||
|
||||
return keepalive_on;
|
||||
}
|
||||
|
||||
private void print_console_line(string text) {
|
||||
append_to_console("[C] ");
|
||||
append_to_console(text);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,16 @@
|
|||
*/
|
||||
|
||||
public interface Geary.Account : Object {
|
||||
public abstract async Folder open(string name, Cancellable? cancellable = null) throws Error;
|
||||
public abstract async Gee.Collection<string> list(string parent, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public abstract async Folder open(string folder, Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
||||
public interface Geary.Folder : Object {
|
||||
public abstract MessageStream? read(int low, int count);
|
||||
|
||||
public abstract async void close(Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
||||
public interface Geary.MessageStream : Object {
|
||||
|
|
|
|||
|
|
@ -644,8 +644,6 @@ public class Geary.Imap.ClientSession : Object, Geary.Account {
|
|||
|
||||
if (params.err != null)
|
||||
throw params.err;
|
||||
|
||||
debug("Closed mailbox");
|
||||
}
|
||||
|
||||
private uint on_close_mailbox(uint state, uint event, void *user, Object? object) {
|
||||
|
|
@ -666,6 +664,8 @@ public class Geary.Imap.ClientSession : Object, Geary.Account {
|
|||
}
|
||||
|
||||
private uint on_closed_mailbox(uint state, uint event) {
|
||||
current_mailbox = null;
|
||||
|
||||
return State.AUTHORIZED;
|
||||
}
|
||||
|
||||
|
|
@ -864,7 +864,7 @@ public class Geary.Imap.ClientSession : Object, Geary.Account {
|
|||
return new AsyncCommandResponse(cmd_response, user, null);
|
||||
}
|
||||
|
||||
private void generic_issue_command_completed(AsyncResult result, Event ok_event, Event error_event) {
|
||||
private bool generic_issue_command_completed(AsyncResult result, Event ok_event, Event error_event) {
|
||||
AsyncCommandResponse async_response = issue_command_async.end(result);
|
||||
|
||||
assert(async_response.user != null);
|
||||
|
|
@ -873,14 +873,33 @@ public class Geary.Imap.ClientSession : Object, Geary.Account {
|
|||
params.cmd_response = async_response.cmd_response;
|
||||
params.err = async_response.err;
|
||||
|
||||
bool success;
|
||||
if (async_response.err != null) {
|
||||
fsm.issue(Event.SEND_ERROR, null, null, async_response.err);
|
||||
success = false;
|
||||
} else {
|
||||
issue_status(async_response.cmd_response.status_response.status, ok_event, error_event,
|
||||
params);
|
||||
success = true;
|
||||
}
|
||||
|
||||
Idle.add(params.cb);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
//
|
||||
// Geary.Account
|
||||
//
|
||||
|
||||
public async Gee.Collection<string> list(string parent, Cancellable? cancellable = null) throws Error {
|
||||
string specifier = String.is_empty(parent) ? "/" : parent;
|
||||
specifier += (specifier.has_suffix("/")) ? "%" : "/%";
|
||||
|
||||
ListResults results = ListResults.decode(yield send_command_async(
|
||||
new ListCommand(generate_tag(), specifier), cancellable));
|
||||
|
||||
return results.get_names();
|
||||
}
|
||||
|
||||
public async Geary.Folder open(string mailbox, Cancellable? cancellable = null) throws Error {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,18 @@ public class Geary.Imap.ListCommand : Command {
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.XListCommand : Command {
|
||||
public const string NAME = "xlist";
|
||||
|
||||
public XListCommand(Tag tag, string mailbox) {
|
||||
base (tag, NAME, { "", mailbox });
|
||||
}
|
||||
|
||||
public XListCommand.wildcarded(Tag tag, string reference, string mailbox) {
|
||||
base (tag, NAME, { reference, mailbox });
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.ExamineCommand : Command {
|
||||
public const string NAME = "examine";
|
||||
|
||||
|
|
|
|||
129
src/engine/imap/DataFormat.vala
Normal file
129
src/engine/imap/DataFormat.vala
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
namespace Geary.Imap.DataFormat {
|
||||
|
||||
private const unichar[] ATOM_SPECIALS = {
|
||||
'(', ')', '{', ' ', '%', '*', '\"'
|
||||
};
|
||||
|
||||
private const unichar[] TAG_SPECIALS = {
|
||||
'(', ')', '{', '%', '\"', '\\', '+'
|
||||
};
|
||||
|
||||
public enum Quoting {
|
||||
REQUIRED,
|
||||
OPTIONAL,
|
||||
UNALLOWED
|
||||
}
|
||||
|
||||
private bool is_special_char(unichar ch, unichar[] ar, string? exceptions) {
|
||||
if (ch > 0x7F || ch.iscntrl())
|
||||
return true;
|
||||
|
||||
if (ch in ar)
|
||||
return (exceptions != null) ? exceptions.index_of_char(ch) < 0 : true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the character is considered an atom special. Note that while documentation
|
||||
* indicates that the backslash cannot be used in an atom, they *are* used for message flags and
|
||||
* thus must be special cased by the caller.
|
||||
*/
|
||||
public inline bool is_atom_special(unichar ch, string? exceptions = null) {
|
||||
return is_special_char(ch, ATOM_SPECIALS, exceptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag specials are like atom specials but include the continuation character ('+'). Also, the
|
||||
* star character is allowed, although technically only correct in the context of a status response;
|
||||
* it's the responsibility of the caller to catch this.
|
||||
*/
|
||||
public bool is_tag_special(unichar ch, string? exceptions = null) {
|
||||
return is_special_char(ch, TAG_SPECIALS, exceptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Quoting to indicate if the string must be quoted before sent on the wire, of if it
|
||||
* must be sent as a literal.
|
||||
*/
|
||||
public Quoting is_quoting_required(string str) {
|
||||
if (String.is_empty(str))
|
||||
return Quoting.REQUIRED;
|
||||
|
||||
int index = 0;
|
||||
unichar ch;
|
||||
while (str.get_next_char(ref index, out ch)) {
|
||||
if (ch > 0x7F)
|
||||
return Quoting.UNALLOWED;
|
||||
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\0':
|
||||
return Quoting.UNALLOWED;
|
||||
|
||||
case '"':
|
||||
case '\\':
|
||||
return Quoting.REQUIRED;
|
||||
|
||||
default:
|
||||
if (is_atom_special(ch))
|
||||
return Quoting.REQUIRED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Quoting.OPTIONAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied string to a quoted string and returns whether or not the quoted format
|
||||
* is required on the wire. If Quoting.UNALLOWED is returned, the only way to represent the string
|
||||
* is with a literal.
|
||||
*/
|
||||
public Quoting convert_to_quoted(string str, out string quoted) {
|
||||
Quoting requirement = String.is_empty(str) ? Quoting.REQUIRED : Quoting.OPTIONAL;
|
||||
quoted = "";
|
||||
|
||||
StringBuilder builder = new StringBuilder("\"");
|
||||
int index = 0;
|
||||
unichar ch;
|
||||
while (str.get_next_char(ref index, out ch)) {
|
||||
if (ch > 0x7F)
|
||||
return Quoting.UNALLOWED;
|
||||
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\0':
|
||||
return Quoting.UNALLOWED;
|
||||
|
||||
case '"':
|
||||
case '\\':
|
||||
requirement = Quoting.REQUIRED;
|
||||
builder.append_c('\\');
|
||||
builder.append_unichar(ch);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_atom_special(ch))
|
||||
requirement = Quoting.REQUIRED;
|
||||
|
||||
builder.append_unichar(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
quoted = builder.append_c('"').str;
|
||||
|
||||
return requirement;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -53,22 +53,6 @@ public class Geary.Imap.Deserializer {
|
|||
return ((Event) event).to_string();
|
||||
}
|
||||
|
||||
// Atom specials includes space and close-parens, but those are handled in particular ways while
|
||||
// in the ATOM state, so they're not included here. Also note that while documentation
|
||||
// indicates that the backslash cannot be used in an atom, they *are* used for message flags
|
||||
// and thus must be special-cased in the code.
|
||||
private static unichar[] atom_specials = {
|
||||
'(', '{', '%', '*', '\"'
|
||||
};
|
||||
|
||||
// Tag specials are like atom specials but include the continuation character ('+'). Like atom
|
||||
// specials, the space is treated in a particular way, but unlike atom, the close-parens
|
||||
// character is not. Also, the star character is allowed, although technically only correct
|
||||
// in the context of a status response; it's the responsibility of higher layers to catch this.
|
||||
private static unichar[] tag_specials = {
|
||||
'(', ')', '{', '%', '\"', '\\', '+'
|
||||
};
|
||||
|
||||
private static Geary.State.MachineDescriptor machine_desc = new Geary.State.MachineDescriptor(
|
||||
"Geary.Imap.Deserializer", State.TAG, State.COUNT, Event.COUNT,
|
||||
state_to_string, event_to_string);
|
||||
|
|
@ -385,14 +369,12 @@ public class Geary.Imap.Deserializer {
|
|||
|
||||
unichar ch = *((unichar *) user);
|
||||
|
||||
// drop everything above 0x7F and control characters
|
||||
if (ch > 0x7F || ch.iscntrl())
|
||||
// Atom specials includes space and close-parens, but those are handled in particular ways
|
||||
// while in the ATOM state, so they're excluded here. Like atom specials, the space is
|
||||
// treated in a particular way for tags, but unlike atom, the close-parens character is not.
|
||||
if (state == State.TAG && DataFormat.is_tag_special(ch, " "))
|
||||
return state;
|
||||
|
||||
// tags and atoms have different special characters
|
||||
if (state == State.TAG && (ch in tag_specials))
|
||||
return state;
|
||||
else if (state == State.ATOM && (ch in atom_specials))
|
||||
else if (state == State.ATOM && DataFormat.is_atom_special(ch, " )"))
|
||||
return state;
|
||||
|
||||
// message flag indicator is only legal at start of atom
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public enum Geary.Imap.FetchDataType {
|
|||
return new UIDDecoder();
|
||||
|
||||
case FLAGS:
|
||||
return new FlagsDecoder();
|
||||
return new MessageFlagsDecoder();
|
||||
|
||||
case ENVELOPE:
|
||||
return new EnvelopeDecoder();
|
||||
|
|
|
|||
63
src/engine/imap/Flag.vala
Normal file
63
src/engine/imap/Flag.vala
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* 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 abstract class Geary.Imap.Flag {
|
||||
public string value { get; private set; }
|
||||
|
||||
public Flag(string value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool is_system() {
|
||||
return value[0] == '\\';
|
||||
}
|
||||
|
||||
public bool equals_string(string value) {
|
||||
return this.value.down() == value.down();
|
||||
}
|
||||
|
||||
public bool equals(Flag flag) {
|
||||
return (flag == this) ? true : flag.equals_string(value);
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static uint hash_func(void *flag) {
|
||||
return str_hash(((Flag *) flag)->value);
|
||||
}
|
||||
|
||||
public static bool equal_func(void *a, void *b) {
|
||||
return ((Flag *) a)->equals((Flag *) b);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.MessageFlag : Geary.Imap.Flag {
|
||||
public static MessageFlag ANSWERED = new MessageFlag("\\answered");
|
||||
public static MessageFlag DELETED = new MessageFlag("\\deleted");
|
||||
public static MessageFlag DRAFT = new MessageFlag("\\draft");
|
||||
public static MessageFlag FLAGGED = new MessageFlag("\\flagged");
|
||||
public static MessageFlag RECENT = new MessageFlag("\\recent");
|
||||
public static MessageFlag SEEN = new MessageFlag("\\seen");
|
||||
|
||||
public MessageFlag(string value) {
|
||||
base (value);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.MailboxAttribute : Geary.Imap.Flag {
|
||||
public static MailboxAttribute NO_INFERIORS = new MailboxAttribute("\\noinferiors");
|
||||
public static MailboxAttribute NO_SELECT = new MailboxAttribute("\\noselect");
|
||||
public static MailboxAttribute MARKED = new MailboxAttribute("\\marked");
|
||||
public static MailboxAttribute UNMARKED = new MailboxAttribute("\\unmarked");
|
||||
public static MailboxAttribute HAS_NO_CHILDREN = new MailboxAttribute("\\hasnochildren");
|
||||
|
||||
public MailboxAttribute(string value) {
|
||||
base (value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8,15 +8,25 @@ public class Geary.Imap.Mailbox : Object, Geary.Folder {
|
|||
public string name { get; private set; }
|
||||
|
||||
private ClientSession sess;
|
||||
private bool is_closed = false;
|
||||
|
||||
internal Mailbox(string name, ClientSession sess) {
|
||||
this.name = name;
|
||||
this.sess = sess;
|
||||
}
|
||||
|
||||
~Mailbox() {
|
||||
assert(is_closed);
|
||||
}
|
||||
|
||||
public MessageStream? read(int low, int count) {
|
||||
return new MessageStreamImpl(sess, low, count);
|
||||
}
|
||||
|
||||
public async void close(Cancellable? cancellable = null) throws Error {
|
||||
yield sess.close_mailbox_async(cancellable);
|
||||
is_closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class Geary.Imap.MessageStreamImpl : Object, Geary.MessageStream {
|
||||
|
|
|
|||
|
|
@ -32,46 +32,7 @@ public class Geary.Imap.MessageNumber : Geary.Common.IntMessageData, Geary.Imap.
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.Flag {
|
||||
public static Flag ANSWERED = new Flag("\\answered");
|
||||
public static Flag DELETED = new Flag("\\deleted");
|
||||
public static Flag DRAFT = new Flag("\\draft");
|
||||
public static Flag FLAGGED = new Flag("\\flagged");
|
||||
public static Flag RECENT = new Flag("\\recent");
|
||||
public static Flag SEEN = new Flag("\\seen");
|
||||
|
||||
public string value { get; private set; }
|
||||
|
||||
public Flag(string value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool is_system() {
|
||||
return value[0] == '\\';
|
||||
}
|
||||
|
||||
public bool has_value(string value) {
|
||||
return this.value.down() == value.down();
|
||||
}
|
||||
|
||||
public bool equals(Flag flag) {
|
||||
return (flag == this) ? true : flag.has_value(value);
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static uint hash_func(void *flag) {
|
||||
return str_hash(((Flag *) flag)->value);
|
||||
}
|
||||
|
||||
public static bool equal_func(void *a, void *b) {
|
||||
return ((Flag *) a)->equals((Flag *) b);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.MessageData {
|
||||
public abstract class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.MessageData {
|
||||
private Gee.Set<Flag> list;
|
||||
|
||||
public Flags(Gee.Collection<Flag> flags) {
|
||||
|
|
@ -100,6 +61,18 @@ public class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.MessageData
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.MessageFlags : Geary.Imap.Flags {
|
||||
public MessageFlags(Gee.Collection<MessageFlag> flags) {
|
||||
base (flags);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.MailboxAttributes : Geary.Imap.Flags {
|
||||
public MailboxAttributes(Gee.Collection<MailboxAttribute> attrs) {
|
||||
base (attrs);
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.InternalDate : Geary.RFC822.Date, Geary.Imap.MessageData {
|
||||
public InternalDate(string iso8601) throws ImapError {
|
||||
base (iso8601);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class Geary.Imap.StringParameter : Geary.Imap.Parameter {
|
|||
}
|
||||
}
|
||||
|
||||
public StringParameter(string value) requires (!String.is_empty(value)) {
|
||||
public StringParameter(string value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,26 @@ public class Geary.Imap.Serializer {
|
|||
}
|
||||
|
||||
public void push_string(string str) throws Error {
|
||||
douts.put_string(str, null);
|
||||
// see if need to convert to quoted string, only emitting it if required
|
||||
switch (DataFormat.is_quoting_required(str)) {
|
||||
case DataFormat.Quoting.OPTIONAL:
|
||||
douts.put_string(str);
|
||||
break;
|
||||
|
||||
case DataFormat.Quoting.REQUIRED:
|
||||
string quoted;
|
||||
DataFormat.Quoting requirement = DataFormat.convert_to_quoted(str, out quoted);
|
||||
debug("str=%s quoted=%s", str, quoted);
|
||||
assert(requirement == DataFormat.Quoting.REQUIRED);
|
||||
|
||||
douts.put_string(quoted);
|
||||
break;
|
||||
|
||||
case DataFormat.Quoting.UNALLOWED:
|
||||
default:
|
||||
// TODO: Not handled currently
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public void push_space() throws Error {
|
||||
|
|
|
|||
|
|
@ -66,17 +66,17 @@ public class Geary.Imap.UIDDecoder : Geary.Imap.FetchDataDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.FlagsDecoder : Geary.Imap.FetchDataDecoder {
|
||||
public FlagsDecoder() {
|
||||
public class Geary.Imap.MessageFlagsDecoder : Geary.Imap.FetchDataDecoder {
|
||||
public MessageFlagsDecoder() {
|
||||
base (FetchDataType.FLAGS);
|
||||
}
|
||||
|
||||
protected override MessageData decode_list(ListParameter listp) throws ImapError {
|
||||
Gee.List<Flag> flags = new Gee.ArrayList<Flag>();
|
||||
for (int ctr = 0; ctr < listp.get_count(); ctr++)
|
||||
flags.add(new Flag(listp.get_as_string(ctr).value));
|
||||
flags.add(new MessageFlag(listp.get_as_string(ctr).value));
|
||||
|
||||
return new Flags(flags);
|
||||
return new MessageFlags(flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public class Geary.Imap.FetchResults {
|
|||
ListParameter list = data.get_as_list(3);
|
||||
|
||||
// verify this is a FETCH response
|
||||
if (!cmd.equals_ci("fetch")) {
|
||||
if (!cmd.equals_ci(FetchCommand.NAME)) {
|
||||
throw new ImapError.TYPE_ERROR("Unable to decode fetch response \"%s\": Not marked as fetch response",
|
||||
data.to_string());
|
||||
}
|
||||
|
|
|
|||
79
src/engine/imap/decoders/ListResults.vala
Normal file
79
src/engine/imap/decoders/ListResults.vala
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/* 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.FolderDetail {
|
||||
public string name { get; private set; }
|
||||
public string delim { get; private set; }
|
||||
public MailboxAttributes attrs { get; private set; }
|
||||
|
||||
public FolderDetail(string name, string delim, MailboxAttributes attrs) {
|
||||
this.name = name;
|
||||
this.delim = delim;
|
||||
this.attrs = attrs;
|
||||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.ListResults {
|
||||
private Gee.HashMap<string, FolderDetail> map = new Gee.HashMap<string, FolderDetail>();
|
||||
|
||||
private ListResults(Gee.Collection<FolderDetail> details) {
|
||||
foreach (FolderDetail detail in details)
|
||||
map.set(detail.name, detail);
|
||||
}
|
||||
|
||||
public static ListResults decode(CommandResponse response) {
|
||||
assert(response.is_sealed());
|
||||
|
||||
Gee.List<FolderDetail> details = new Gee.ArrayList<FolderDetail>();
|
||||
foreach (ServerData data in response.server_data) {
|
||||
try {
|
||||
StringParameter cmd = data.get_as_string(1);
|
||||
ListParameter attrs = data.get_as_list(2);
|
||||
StringParameter delim = data.get_as_string(3);
|
||||
StringParameter mailbox = data.get_as_string(4);
|
||||
|
||||
if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(XListCommand.NAME)) {
|
||||
debug("Bad list response \"%s\": Not marked as list or xlist response",
|
||||
data.to_string());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Gee.Collection<MailboxAttribute> list = new Gee.ArrayList<MailboxAttribute>();
|
||||
foreach (Parameter attr in attrs.get_all()) {
|
||||
StringParameter? stringp = attr as StringParameter;
|
||||
if (stringp == null) {
|
||||
debug("Bad list attribute \"%s\": Attribute not a string value",
|
||||
data.to_string());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
list.add(new MailboxAttribute(stringp.value));
|
||||
}
|
||||
|
||||
details.add(new FolderDetail(mailbox.value, delim.value, new MailboxAttributes(list)));
|
||||
} catch (ImapError ierr) {
|
||||
debug("Unable to decode \"%s\": %s", data.to_string(), ierr.message);
|
||||
}
|
||||
}
|
||||
|
||||
return new ListResults(details);
|
||||
}
|
||||
|
||||
public Gee.Collection<string> get_names() {
|
||||
return map.keys;
|
||||
}
|
||||
|
||||
public Gee.Collection<FolderDetail> get_all() {
|
||||
return map.values;
|
||||
}
|
||||
|
||||
public FolderDetail? get_detail(string name) {
|
||||
return map.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue