First stab at implementing persistent storage of IMAP data: #3695.
This large diff represents a growth of the architecture to persist IMAP data as its downloaded. When listing folders, a local database is consulted first to immediately feed to the caller. In the background, network calls fetch the "real" list. The two are collated for differences which are reported to the caller via signals, who are then responsible for updating the user interface appropriately. No other synchronization work is represented in this diff. Note that this breaks functionality: when a folder is selected, no messages appear in the message list. Fixing this requires more work, and this patch was already large enough. It's ticketed here: #3741
This commit is contained in:
parent
38d9cb6790
commit
4b8ac5689f
38 changed files with 1014 additions and 425 deletions
77
Makefile
77
Makefile
|
|
@ -6,12 +6,23 @@ BUILD_ROOT = 1
|
|||
VALAC := valac
|
||||
VALAFLAGS := -g --enable-checking --fatal-warnings --vapidir=vapi
|
||||
|
||||
APPS := geary console syntax lsmbox readmail watchmbox
|
||||
APPS := geary console watchmbox
|
||||
|
||||
ENGINE_SRC := \
|
||||
src/engine/Engine.vala \
|
||||
src/engine/Interfaces.vala \
|
||||
src/engine/Email.vala \
|
||||
src/engine/api/Account.vala \
|
||||
src/engine/api/Email.vala \
|
||||
src/engine/api/Folder.vala \
|
||||
src/engine/api/Credentials.vala \
|
||||
src/engine/api/EngineError.vala \
|
||||
src/engine/sqlite/Database.vala \
|
||||
src/engine/sqlite/Table.vala \
|
||||
src/engine/sqlite/Row.vala \
|
||||
src/engine/sqlite/MailDatabase.vala \
|
||||
src/engine/sqlite/FolderTable.vala \
|
||||
src/engine/sqlite/FolderRow.vala \
|
||||
src/engine/sqlite/api/Account.vala \
|
||||
src/engine/sqlite/api/Folder.vala \
|
||||
src/engine/state/Machine.vala \
|
||||
src/engine/state/MachineDescriptor.vala \
|
||||
src/engine/state/Mapping.vala \
|
||||
|
|
@ -50,15 +61,22 @@ ENGINE_SRC := \
|
|||
src/engine/imap/decoders/ListResults.vala \
|
||||
src/engine/imap/decoders/SelectExamineResults.vala \
|
||||
src/engine/imap/decoders/StatusResults.vala \
|
||||
src/engine/imap/api/Account.vala \
|
||||
src/engine/imap/api/Folder.vala \
|
||||
src/engine/rfc822/MailboxAddress.vala \
|
||||
src/engine/rfc822/MessageData.vala \
|
||||
src/engine/util/String.vala \
|
||||
src/engine/util/Memory.vala \
|
||||
src/engine/util/ReferenceSemantics.vala
|
||||
src/engine/util/ReferenceSemantics.vala \
|
||||
src/engine/util/Trillian.vala
|
||||
|
||||
COMMON_SRC := \
|
||||
src/common/String.vala \
|
||||
src/common/Interfaces.vala \
|
||||
src/common/YorbaApplication.vala \
|
||||
src/common/Date.vala
|
||||
|
||||
CLIENT_SRC := \
|
||||
src/client/main.vala \
|
||||
src/client/YorbaApplication.vala \
|
||||
src/client/GearyApplication.vala \
|
||||
src/client/ui/MainWindow.vala \
|
||||
src/client/ui/MessageListView.vala \
|
||||
|
|
@ -67,32 +85,23 @@ CLIENT_SRC := \
|
|||
src/client/ui/FolderListStore.vala \
|
||||
src/client/ui/MessageViewer.vala \
|
||||
src/client/ui/MessageBuffer.vala \
|
||||
src/client/util/Intl.vala \
|
||||
src/client/util/Date.vala
|
||||
src/client/util/Intl.vala
|
||||
|
||||
CONSOLE_SRC := \
|
||||
src/console/main.vala
|
||||
|
||||
SYNTAX_SRC := \
|
||||
src/tests/syntax.vala
|
||||
|
||||
LSMBOX_SRC := \
|
||||
src/tests/lsmbox.vala
|
||||
|
||||
READMAIL_SRC := \
|
||||
src/tests/readmail.vala
|
||||
|
||||
WATCHMBOX_SRC := \
|
||||
src/tests/watchmbox.vala
|
||||
|
||||
ALL_SRC := $(ENGINE_SRC) $(CLIENT_SRC) $(CONSOLE_SRC) $(SYNTAX_SRC) $(LSMBOX_SRC) $(READMAIL_SRC) $(WATCHMBOX_SRC)
|
||||
ALL_SRC := $(ENGINE_SRC) $(COMMON_SRC) $(CLIENT_SRC) $(CONSOLE_SRC) $(WATCHMBOX_SRC)
|
||||
|
||||
EXTERNAL_PKGS := \
|
||||
gio-2.0 >= 2.26.1 \
|
||||
gee-1.0 >= 0.6.1 \
|
||||
gtk+-2.0 >= 2.22.1 \
|
||||
unique-1.0 >= 1.0.0 \
|
||||
gmime-2.4 >= 2.4.14
|
||||
gmime-2.4 >= 2.4.14 \
|
||||
sqlheavy-0.1 >= 0.0.1
|
||||
|
||||
EXTERNAL_BINDINGS := \
|
||||
gio-2.0 \
|
||||
|
|
@ -100,15 +109,16 @@ EXTERNAL_BINDINGS := \
|
|||
gtk+-2.0 \
|
||||
unique-1.0 \
|
||||
posix \
|
||||
gmime-2.4
|
||||
gmime-2.4 \
|
||||
sqlheavy-0.1
|
||||
|
||||
VAPI_FILES := \
|
||||
vapi/gmime-2.4.vapi
|
||||
|
||||
geary: $(ENGINE_SRC) $(CLIENT_SRC) Makefile $(VAPI_FILES)
|
||||
geary: $(ENGINE_SRC) $(COMMON_SRC) $(CLIENT_SRC) Makefile $(VAPI_FILES)
|
||||
pkg-config --exists --print-errors '$(EXTERNAL_PKGS)'
|
||||
$(VALAC) $(VALAFLAGS) $(foreach binding,$(EXTERNAL_BINDINGS),--pkg=$(binding)) \
|
||||
$(ENGINE_SRC) $(CLIENT_SRC) \
|
||||
$(ENGINE_SRC) $(COMMON_SRC) $(CLIENT_SRC) \
|
||||
-o $@
|
||||
|
||||
.PHONY: all
|
||||
|
|
@ -120,28 +130,13 @@ clean:
|
|||
rm -f $(ALL_SRC:.vala=.vala.c)
|
||||
rm -f $(APPS)
|
||||
|
||||
console: $(ENGINE_SRC) $(CONSOLE_SRC) Makefile
|
||||
console: $(ENGINE_SRC) $(COMMON_SRC) $(CONSOLE_SRC) Makefile
|
||||
$(VALAC) $(VALAFLAGS) $(foreach binding,$(EXTERNAL_BINDINGS),--pkg=$(binding)) \
|
||||
$(ENGINE_SRC) $(CONSOLE_SRC) \
|
||||
$(ENGINE_SRC) $(COMMON_SRC) $(CONSOLE_SRC) \
|
||||
-o $@
|
||||
|
||||
syntax: $(ENGINE_SRC) $(SYNTAX_SRC) Makefile
|
||||
watchmbox: $(ENGINE_SRC) $(COMMON_SRC) $(WATCHMBOX_SRC) Makefile
|
||||
$(VALAC) $(VALAFLAGS) $(foreach binding,$(EXTERNAL_BINDINGS),--pkg=$(binding)) \
|
||||
$(ENGINE_SRC) $(SYNTAX_SRC) \
|
||||
-o $@
|
||||
|
||||
lsmbox: $(ENGINE_SRC) $(LSMBOX_SRC) Makefile
|
||||
$(VALAC) $(VALAFLAGS) $(foreach binding,$(EXTERNAL_BINDINGS),--pkg=$(binding)) \
|
||||
$(ENGINE_SRC) $(LSMBOX_SRC) \
|
||||
-o $@
|
||||
|
||||
readmail: $(ENGINE_SRC) $(READMAIL_SRC) Makefile
|
||||
$(VALAC) $(VALAFLAGS) $(foreach binding,$(EXTERNAL_BINDINGS),--pkg=$(binding)) \
|
||||
$(ENGINE_SRC) $(READMAIL_SRC) \
|
||||
-o $@
|
||||
|
||||
watchmbox: $(ENGINE_SRC) $(WATCHMBOX_SRC) Makefile
|
||||
$(VALAC) $(VALAFLAGS) $(foreach binding,$(EXTERNAL_BINDINGS),--pkg=$(binding)) \
|
||||
$(ENGINE_SRC) $(WATCHMBOX_SRC) \
|
||||
$(ENGINE_SRC) $(COMMON_SRC) $(WATCHMBOX_SRC) \
|
||||
-o $@
|
||||
|
||||
|
|
|
|||
12
sql/Create.sql
Normal file
12
sql/Create.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
CREATE TABLE FolderTable (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
supports_children INTEGER,
|
||||
is_openable INTEGER,
|
||||
parent_id INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX FolderTableNameIndex ON FolderTable (name);
|
||||
CREATE INDEX FolderTableParentIndex ON FolderTable (parent_id);
|
||||
|
||||
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
public class GearyApplication : YorbaApplication {
|
||||
// TODO: replace static strings with const strings when gettext is integrated properly
|
||||
public const string PROGRAM_NAME = "Geary";
|
||||
public static string PROGRAM_DESCRIPTION = _("Email Client");
|
||||
public const string VERSION = "0.0.1";
|
||||
public const string NAME = "Geary";
|
||||
public static string DESCRIPTION = _("Email Client");
|
||||
public const string VERSION = "0.0.0+trunk";
|
||||
public const string COPYRIGHT = "Copyright 2011 Yorba Foundation";
|
||||
public const string WEBSITE = "http://www.yorba.org";
|
||||
public static string WEBSITE_LABEL = _("Visit the Yorba web site");
|
||||
|
|
@ -33,7 +33,7 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
|
|||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
""";
|
||||
|
||||
public static GearyApplication instance {
|
||||
public new static GearyApplication instance {
|
||||
get {
|
||||
if (_instance == null)
|
||||
_instance = new GearyApplication();
|
||||
|
|
@ -45,15 +45,23 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
|
|||
private static GearyApplication? _instance = null;
|
||||
|
||||
private MainWindow main_window = new MainWindow();
|
||||
private Geary.Engine engine = new Geary.Engine();
|
||||
private Geary.Account? account = null;
|
||||
|
||||
private GearyApplication() {
|
||||
base ("org.yorba.geary");
|
||||
base (NAME, "geary", "org.yorba.geary");
|
||||
}
|
||||
|
||||
public override void startup() {
|
||||
Geary.Credentials cred = new Geary.Credentials("imap.gmail.com", args[1], args[2]);
|
||||
|
||||
try {
|
||||
account = Geary.Engine.open(cred);
|
||||
} catch (Error err) {
|
||||
error("Unable to open mail database for %s: %s", cred.user, err.message);
|
||||
}
|
||||
|
||||
main_window.show_all();
|
||||
main_window.login(engine, args[1], args[2]);
|
||||
main_window.start(account);
|
||||
}
|
||||
|
||||
public override void activate() {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,20 @@
|
|||
public class FolderListStore : Gtk.TreeStore {
|
||||
public enum Column {
|
||||
NAME,
|
||||
FOLDER_OBJECT,
|
||||
N_COLUMNS;
|
||||
|
||||
public static Column[] all() {
|
||||
return { NAME };
|
||||
return {
|
||||
NAME,
|
||||
FOLDER_OBJECT
|
||||
};
|
||||
}
|
||||
|
||||
public static Type[] get_types() {
|
||||
return {
|
||||
typeof (string)
|
||||
typeof (string),
|
||||
typeof (Geary.Folder)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +29,9 @@ public class FolderListStore : Gtk.TreeStore {
|
|||
case NAME:
|
||||
return _("Name");
|
||||
|
||||
case FOLDER_OBJECT:
|
||||
return "(hidden)";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
|
@ -34,25 +42,28 @@ public class FolderListStore : Gtk.TreeStore {
|
|||
set_column_types(Column.get_types());
|
||||
}
|
||||
|
||||
public void add_folder(Geary.FolderDetail folder) {
|
||||
public void add_folder(Geary.Folder folder) {
|
||||
Gtk.TreeIter iter;
|
||||
append(out iter, null);
|
||||
|
||||
set(iter, Column.NAME, folder.name);
|
||||
set(iter,
|
||||
Column.NAME, folder.name,
|
||||
Column.FOLDER_OBJECT, folder
|
||||
);
|
||||
}
|
||||
|
||||
public void add_folders(Gee.Collection<Geary.FolderDetail> folders) {
|
||||
foreach (Geary.FolderDetail folder in folders)
|
||||
public void add_folders(Gee.Collection<Geary.Folder> folders) {
|
||||
foreach (Geary.Folder folder in folders)
|
||||
add_folder(folder);
|
||||
}
|
||||
|
||||
public string? get_folder_at(Gtk.TreePath path) {
|
||||
public Geary.Folder? get_folder_at(Gtk.TreePath path) {
|
||||
Gtk.TreeIter iter;
|
||||
if (!get_iter(out iter, path))
|
||||
return null;
|
||||
|
||||
string folder;
|
||||
get(iter, Column.NAME, out folder);
|
||||
Geary.Folder folder;
|
||||
get(iter, Column.FOLDER_OBJECT, out folder);
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
public class FolderListView : Gtk.TreeView {
|
||||
public signal void folder_selected(string? folder);
|
||||
public signal void folder_selected(Geary.Folder? folder);
|
||||
|
||||
public FolderListView(FolderListStore store) {
|
||||
set_model(store);
|
||||
|
|
@ -31,7 +31,7 @@ public class FolderListView : Gtk.TreeView {
|
|||
return;
|
||||
}
|
||||
|
||||
string? folder = get_store().get_folder_at(path);
|
||||
Geary.Folder? folder = get_store().get_folder_at(path);
|
||||
if (folder != null)
|
||||
folder_selected(folder);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,11 @@ public class MainWindow : Gtk.Window {
|
|||
private MessageViewer message_viewer = new MessageViewer();
|
||||
private MessageBuffer message_buffer = new MessageBuffer();
|
||||
private Gtk.UIManager ui = new Gtk.UIManager();
|
||||
private Geary.Engine? engine = null;
|
||||
private Geary.Account? account = null;
|
||||
private Geary.Folder? current_folder = null;
|
||||
|
||||
public MainWindow() {
|
||||
title = GearyApplication.PROGRAM_NAME;
|
||||
title = GearyApplication.NAME;
|
||||
set_default_size(800, 600);
|
||||
|
||||
try {
|
||||
|
|
@ -57,26 +56,34 @@ public class MainWindow : Gtk.Window {
|
|||
create_layout();
|
||||
}
|
||||
|
||||
public void login(Geary.Engine engine, string user, string pass) {
|
||||
this.engine = engine;
|
||||
|
||||
do_login.begin(user, pass);
|
||||
~MainWindow() {
|
||||
if (account != null)
|
||||
account.folders_added_removed.disconnect(on_folders_added_removed);
|
||||
}
|
||||
|
||||
private async void do_login(string user, string pass) {
|
||||
public void start(Geary.Account account) {
|
||||
this.account = account;
|
||||
account.folders_added_removed.connect(on_folders_added_removed);
|
||||
|
||||
do_start.begin();
|
||||
}
|
||||
|
||||
private void on_folders_added_removed(Gee.Collection<Geary.Folder>? added,
|
||||
Gee.Collection<Geary.Folder>? removed) {
|
||||
if (added != null) {
|
||||
folder_list_store.add_folders(added);
|
||||
debug("%d folders added", added.size);
|
||||
}
|
||||
}
|
||||
|
||||
private async void do_start() {
|
||||
try {
|
||||
account = yield engine.login("imap.gmail.com", user, pass);
|
||||
if (account == null)
|
||||
error("Unable to login");
|
||||
|
||||
// pull down the root-level folders
|
||||
Gee.Collection<Geary.FolderDetail> folders = yield account.list(null);
|
||||
if (folders != null) {
|
||||
debug("%d folders found", folders.size);
|
||||
folder_list_store.add_folders(folders);
|
||||
} else {
|
||||
Gee.Collection<Geary.Folder> folders = yield account.list_async(null);
|
||||
if (folders != null)
|
||||
on_folders_added_removed(folders, null);
|
||||
else
|
||||
debug("no folders");
|
||||
}
|
||||
} catch (Error err) {
|
||||
error("%s", err.message);
|
||||
}
|
||||
|
|
@ -162,7 +169,8 @@ public class MainWindow : Gtk.Window {
|
|||
|
||||
private void on_about() {
|
||||
Gtk.show_about_dialog(this,
|
||||
"program-name", GearyApplication.PROGRAM_NAME,
|
||||
"program-name", GearyApplication.NAME,
|
||||
"comments", GearyApplication.DESCRIPTION,
|
||||
"authors", GearyApplication.AUTHORS,
|
||||
"copyright", GearyApplication.COPYRIGHT,
|
||||
"license", GearyApplication.LICENSE,
|
||||
|
|
@ -172,22 +180,30 @@ public class MainWindow : Gtk.Window {
|
|||
);
|
||||
}
|
||||
|
||||
private void on_folder_selected(string? folder) {
|
||||
private void on_folder_selected(Geary.Folder? folder) {
|
||||
if (folder == null) {
|
||||
debug("no folder selected");
|
||||
message_list_store.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Folder %s selected", folder.name);
|
||||
|
||||
do_select_folder.begin(folder, on_select_folder_completed);
|
||||
}
|
||||
|
||||
private async void do_select_folder(string folder_name) throws Error {
|
||||
private async void do_select_folder(Geary.Folder folder) throws Error {
|
||||
message_list_store.clear();
|
||||
|
||||
current_folder = yield account.open(folder_name);
|
||||
if (current_folder != null)
|
||||
yield current_folder.close_async();
|
||||
|
||||
Gee.List<Geary.EmailHeader>? headers = yield current_folder.read(1, 100);
|
||||
current_folder = folder;
|
||||
|
||||
yield current_folder.open_async(true);
|
||||
|
||||
Gee.List<Geary.EmailHeader>? headers = yield current_folder.read_async(1, 100);
|
||||
if (headers != null && headers.size > 0) {
|
||||
foreach (Geary.EmailHeader header in headers)
|
||||
message_list_store.append_header(header);
|
||||
|
|
@ -219,8 +235,8 @@ public class MainWindow : Gtk.Window {
|
|||
return;
|
||||
}
|
||||
|
||||
Geary.EmailBody body = yield current_folder.fetch_body(header);
|
||||
message_buffer.set_text(body.full);
|
||||
Geary.Email email = yield current_folder.fetch_async(header);
|
||||
message_buffer.set_text(email.full);
|
||||
}
|
||||
|
||||
private void on_select_message_completed(Object? source, AsyncResult result) {
|
||||
|
|
|
|||
22
src/common/Interfaces.vala
Normal file
22
src/common/Interfaces.vala
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* 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 interface Geary.Comparable {
|
||||
public abstract bool equals(Comparable other);
|
||||
|
||||
public static bool equal_func(void *a, void *b) {
|
||||
return ((Comparable *) a)->equals((Comparable *) b);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Geary.Hashable {
|
||||
public abstract uint get_hash();
|
||||
|
||||
public static uint hash_func(void *ptr) {
|
||||
return ((Hashable *) ptr)->get_hash();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,16 +4,18 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
//
|
||||
// YorbaApplication is a poor man's lookalike of GNOME 3's GApplication, with a couple of additions.
|
||||
// It's only here to give some of GApplication's functionality in a GTK+ 2 environment. The idea
|
||||
// is to ease a future migration to GTK 3.
|
||||
//
|
||||
// YorbaApplication specifically expects to be run in a GTK environment, and Gtk.init() *must* be
|
||||
// called prior to invoking YorbaApplication.
|
||||
//
|
||||
/**
|
||||
* YorbaApplication is a poor man's lookalike of GNOME 3's GApplication, with a couple of additions.
|
||||
* It's only here to give some of GApplication's functionality in a GTK+ 2 environment. The idea
|
||||
* is to ease a future migration to GTK 3.
|
||||
*
|
||||
* YorbaApplication specifically expects to be run in a GTK environment, and Gtk.init() *must* be
|
||||
* called prior to invoking YorbaApplication.
|
||||
*/
|
||||
|
||||
public abstract class YorbaApplication {
|
||||
public static YorbaApplication? instance { get; private set; default = null; }
|
||||
|
||||
public bool registered { get; private set; }
|
||||
public string[]? args { get; private set; }
|
||||
|
||||
|
|
@ -23,6 +25,12 @@ public abstract class YorbaApplication {
|
|||
private int exitcode = 0;
|
||||
private Unique.App? unique_app = null;
|
||||
|
||||
/**
|
||||
* This signal is fired only when the application is starting the first time, not on
|
||||
* subsequent activations (i.e. the application is launched while running by the user).
|
||||
*
|
||||
* The args[] array will be available when this signal is fired.
|
||||
*/
|
||||
public virtual signal void startup() {
|
||||
}
|
||||
|
||||
|
|
@ -32,8 +40,20 @@ public abstract class YorbaApplication {
|
|||
public virtual signal void exiting(bool panicked) {
|
||||
}
|
||||
|
||||
protected YorbaApplication(string app_id) {
|
||||
/**
|
||||
* application_title is a localized name of the application. program_name is non-localized
|
||||
* and used by the system. app_id is a CORBA-esque program identifier.
|
||||
*
|
||||
* Only one YorbaApplication instance may be created in an program.
|
||||
*/
|
||||
protected YorbaApplication(string application_title, string program_name, string app_id) {
|
||||
this.app_id = app_id;
|
||||
|
||||
Environment.set_application_name(application_title);
|
||||
Environment.set_prgname(program_name);
|
||||
|
||||
assert(instance == null);
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public bool register(Cancellable? cancellable = null) throws Error {
|
||||
|
|
@ -110,5 +130,22 @@ public abstract class YorbaApplication {
|
|||
|
||||
Posix.exit(1);
|
||||
}
|
||||
|
||||
public File get_user_data_directory() {
|
||||
return File.new_for_path(Environment.get_user_data_dir()).get_child(Environment.get_prgname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base directory that the application's various resource files are stored. If the
|
||||
* application is running from its installed directory, this will point to
|
||||
* $(BASEDIR)/share/<program name>. If it's running from the build directory, this points to
|
||||
* that.
|
||||
*
|
||||
* TODO: Implement. This is placeholder code for build environments and assumes you're running
|
||||
* the program in the build directory.
|
||||
*/
|
||||
public File get_resource_directory() {
|
||||
return File.new_for_path(Environment.get_current_dir());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,10 +4,99 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Engine : Object {
|
||||
public static async Account? login(string server, string user, string pass) throws Error {
|
||||
return new Imap.ClientSessionManager(server, Imap.ClientConnection.DEFAULT_PORT_TLS, user,
|
||||
pass);
|
||||
public class Geary.Engine : Object, Geary.Account {
|
||||
private NetworkAccount net;
|
||||
private LocalAccount local;
|
||||
|
||||
private Engine(NetworkAccount net, LocalAccount local) {
|
||||
this.net = net;
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
public static Account open(Geary.Credentials cred) throws Error {
|
||||
return new Engine(
|
||||
new Geary.Imap.Account(cred, Imap.ClientConnection.DEFAULT_PORT_TLS),
|
||||
new Geary.Sqlite.Account(cred));
|
||||
}
|
||||
|
||||
public async Gee.Collection<Geary.Folder> list_async(string? parent_folder,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
Gee.Collection<Geary.Folder> list = yield local.list_async(parent_folder, cancellable);
|
||||
|
||||
background_update_folders.begin(parent_folder, list);
|
||||
|
||||
debug("Reporting %d folders", list.size);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private Gee.Set<string> get_folder_names(Gee.Collection<Geary.Folder> folders) {
|
||||
Gee.Set<string> names = new Gee.HashSet<string>();
|
||||
foreach (Geary.Folder folder in folders)
|
||||
names.add(folder.name);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private Gee.List<Geary.Folder> get_excluded_folders(Gee.Collection<Geary.Folder> folders,
|
||||
Gee.Set<string> names) {
|
||||
Gee.List<Geary.Folder> excluded = new Gee.ArrayList<Geary.Folder>();
|
||||
foreach (Geary.Folder folder in folders) {
|
||||
if (!names.contains(folder.name))
|
||||
excluded.add(folder);
|
||||
}
|
||||
|
||||
return excluded;
|
||||
}
|
||||
|
||||
private async void background_update_folders(string? parent_folder,
|
||||
Gee.Collection<Geary.Folder> local_folders) {
|
||||
Gee.Collection<Geary.Folder> net_folders;
|
||||
try {
|
||||
net_folders = yield net.list_async(parent_folder);
|
||||
} catch (Error neterror) {
|
||||
error("Unable to retrieve folder list from server: %s", neterror.message);
|
||||
}
|
||||
|
||||
Gee.Set<string> local_names = get_folder_names(local_folders);
|
||||
Gee.Set<string> net_names = get_folder_names(net_folders);
|
||||
|
||||
debug("%d local names, %d net names", local_names.size, net_names.size);
|
||||
|
||||
Gee.List<Geary.Folder>? to_add = get_excluded_folders(net_folders, local_names);
|
||||
Gee.List<Geary.Folder>? to_remove = get_excluded_folders(local_folders, net_names);
|
||||
|
||||
debug("Adding %d, removing %d to/from local store", to_add.size, to_remove.size);
|
||||
|
||||
if (to_add.size == 0)
|
||||
to_add = null;
|
||||
|
||||
if (to_remove.size == 0)
|
||||
to_remove = null;
|
||||
|
||||
if (to_add != null || to_remove != null)
|
||||
notify_folders_added_removed(to_add, null);
|
||||
|
||||
try {
|
||||
if (to_add != null)
|
||||
yield local.create_many_async(to_add);
|
||||
} catch (Error err) {
|
||||
error("Unable to add/remove folders: %s", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error {
|
||||
}
|
||||
|
||||
public async void create_many_async(Gee.Collection<Geary.Folder> folders,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
}
|
||||
|
||||
public async void remove_async(string folder, Cancellable? cancellable = null) throws Error {
|
||||
}
|
||||
|
||||
public async void remove_many_async(Gee.Set<string> folders, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
/* 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 interface Geary.FolderDetail : Object {
|
||||
public abstract string name { get; protected set; }
|
||||
}
|
||||
|
||||
public interface Geary.Account : Object {
|
||||
public abstract async Gee.Collection<FolderDetail> list(FolderDetail? parent,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async Folder open(string folder, Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
||||
public interface Geary.Folder : Object {
|
||||
public enum CloseReason {
|
||||
LOCAL_CLOSE,
|
||||
REMOTE_CLOSE,
|
||||
FOLDER_CLOSED
|
||||
}
|
||||
|
||||
public abstract string name { get; protected set; }
|
||||
|
||||
public abstract int count { get; protected set; }
|
||||
|
||||
public abstract bool is_readonly { get; protected set; }
|
||||
|
||||
public signal void closed(CloseReason reason);
|
||||
|
||||
public abstract async Gee.List<EmailHeader>? read(int low, int count, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public abstract async EmailBody fetch_body(EmailHeader header, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
}
|
||||
|
||||
40
src/engine/api/Account.vala
Normal file
40
src/engine/api/Account.vala
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* 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 interface Geary.Account : Object {
|
||||
public signal void folders_added_removed(Gee.Collection<Geary.Folder>? added,
|
||||
Gee.Collection<Geary.Folder>? removed);
|
||||
|
||||
protected virtual void notify_folders_added_removed(Gee.Collection<Geary.Folder>? added,
|
||||
Gee.Collection<Geary.Folder>? removed) {
|
||||
folders_added_removed(added, removed);
|
||||
}
|
||||
|
||||
public abstract async Gee.Collection<Geary.Folder> list_async(string? parent_folder,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async void create_async(Geary.Folder folder, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public abstract async void create_many_async(Gee.Collection<Geary.Folder> folders,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async void remove_async(string folder, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
|
||||
public abstract async void remove_many_async(Gee.Set<string> folders, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
}
|
||||
|
||||
public interface Geary.NetworkAccount : Object, Geary.Account {
|
||||
public signal void connectivity_changed(bool online);
|
||||
|
||||
public abstract bool is_online();
|
||||
}
|
||||
|
||||
public interface Geary.LocalAccount : Object, Geary.Account {
|
||||
}
|
||||
|
||||
18
src/engine/api/Credentials.vala
Normal file
18
src/engine/api/Credentials.vala
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* 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.Credentials {
|
||||
public string server { get; private set; }
|
||||
public string user { get; private set; }
|
||||
public string pass { get; private set; }
|
||||
|
||||
public Credentials(string server, string user, string pass) {
|
||||
this.server = server;
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ public class Geary.EmailHeader : Object {
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.EmailBody : Object {
|
||||
public class Geary.Email : Object {
|
||||
public EmailHeader header { get; private set; }
|
||||
public string full { get; private set; }
|
||||
|
||||
public EmailBody(EmailHeader header, string full) {
|
||||
public Email(EmailHeader header, string full) {
|
||||
this.header = header;
|
||||
this.full = full;
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public class Geary.EmailBody : Object {
|
|||
* This does not return the full body or any portion of it. It's intended only for debugging.
|
||||
*/
|
||||
public string to_string() {
|
||||
return "email body (%d bytes)".printf(full.data.length);
|
||||
return "%s (%d bytes)".printf(header.to_string(), full.data.length);
|
||||
}
|
||||
}
|
||||
|
||||
11
src/engine/api/EngineError.vala
Normal file
11
src/engine/api/EngineError.vala
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/* 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 errordomain Geary.EngineError {
|
||||
OPEN_REQUIRED,
|
||||
ALREADY_OPEN
|
||||
}
|
||||
|
||||
38
src/engine/api/Folder.vala
Normal file
38
src/engine/api/Folder.vala
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* 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 interface Geary.Folder : Object {
|
||||
public enum CloseReason {
|
||||
LOCAL_CLOSE,
|
||||
REMOTE_CLOSE,
|
||||
FOLDER_CLOSED
|
||||
}
|
||||
|
||||
public abstract string name { get; protected set; }
|
||||
public abstract Trillian is_readonly { get; protected set; }
|
||||
public abstract Trillian supports_children { get; protected set; }
|
||||
public abstract Trillian has_children { get; protected set; }
|
||||
public abstract Trillian is_openable { get; protected set; }
|
||||
|
||||
public signal void opened();
|
||||
|
||||
public signal void closed(CloseReason reason);
|
||||
|
||||
public signal void updated();
|
||||
|
||||
public abstract async void open_async(bool readonly, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async void close_async(Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract int get_message_count() throws Error;
|
||||
|
||||
public abstract async Gee.List<Geary.EmailHeader>? read_async(int low, int count,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async Geary.Email fetch_async(Geary.EmailHeader header,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
public class Geary.Imap.ClientSession {
|
||||
// 30 min keepalive required to maintain session; back off by 30 sec for breathing room
|
||||
public const int MIN_KEEPALIVE_SEC = (30 * 60) - 30;
|
||||
public const int DEFAULT_KEEPALIVE_SEC = 60;
|
||||
// 30 min keepalive required to maintain session; back off by 5 min for breathing room
|
||||
public const int MIN_KEEPALIVE_SEC = 25 * 60;
|
||||
public const int DEFAULT_KEEPALIVE_SEC = 3 * 60;
|
||||
|
||||
public enum Context {
|
||||
UNCONNECTED,
|
||||
|
|
|
|||
|
|
@ -4,21 +4,17 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Imap.ClientSessionManager : Object, Geary.Account {
|
||||
private string server;
|
||||
public class Geary.Imap.ClientSessionManager {
|
||||
private Credentials cred;
|
||||
private uint default_port;
|
||||
private string user;
|
||||
private string pass;
|
||||
private Gee.HashSet<ClientSession> sessions = new Gee.HashSet<ClientSession>();
|
||||
private Gee.HashSet<MailboxContext> examined_contexts = new Gee.HashSet<MailboxContext>();
|
||||
private Gee.HashSet<MailboxContext> selected_contexts = new Gee.HashSet<MailboxContext>();
|
||||
private Gee.HashSet<SelectedContext> examined_contexts = new Gee.HashSet<SelectedContext>();
|
||||
private Gee.HashSet<SelectedContext> selected_contexts = new Gee.HashSet<SelectedContext>();
|
||||
private int keepalive_sec = ClientSession.DEFAULT_KEEPALIVE_SEC;
|
||||
|
||||
public ClientSessionManager(string server, uint default_port, string user, string pass) {
|
||||
this.server = server;
|
||||
public ClientSessionManager(Credentials cred, uint default_port) {
|
||||
this.cred = cred;
|
||||
this.default_port = default_port;
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -33,9 +29,10 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account {
|
|||
session.enable_keepalives(keepalive_sec);
|
||||
}
|
||||
|
||||
public async Gee.Collection<Geary.FolderDetail> list(Geary.FolderDetail? parent,
|
||||
public async Gee.Collection<Geary.Imap.MailboxInformation> list(string? parent_name,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
string specifier = (parent != null) ? parent.name : "/";
|
||||
// build a proper IMAP specifier
|
||||
string specifier = parent_name ?? "/";
|
||||
specifier += (specifier.has_suffix("/")) ? "%" : "/%";
|
||||
|
||||
ClientSession session = yield get_authorized_session(cancellable);
|
||||
|
|
@ -54,31 +51,31 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account {
|
|||
return yield select_examine_mailbox(path, false, cancellable);
|
||||
}
|
||||
|
||||
private async Mailbox select_examine_mailbox(string path, bool is_select,
|
||||
public async Mailbox select_examine_mailbox(string path, bool is_select,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
Gee.HashSet<MailboxContext> contexts = is_select ? selected_contexts : examined_contexts;
|
||||
Gee.HashSet<SelectedContext> contexts = is_select ? selected_contexts : examined_contexts;
|
||||
|
||||
foreach (MailboxContext mailbox_context in contexts) {
|
||||
if (mailbox_context.name == path)
|
||||
return new Mailbox(mailbox_context);
|
||||
foreach (SelectedContext context in contexts) {
|
||||
if (context.name == path)
|
||||
return new Mailbox(context);
|
||||
}
|
||||
|
||||
SelectExamineResults results;
|
||||
ClientSession session = yield select_examine_async(path, is_select, out results, cancellable);
|
||||
|
||||
MailboxContext new_mailbox_context = new MailboxContext(session, results);
|
||||
SelectedContext new_context = new SelectedContext(session, results);
|
||||
|
||||
// Can't use the ternary operator due to this bug:
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=599349
|
||||
if (is_select)
|
||||
new_mailbox_context.freed.connect(on_selected_context_freed);
|
||||
new_context.freed.connect(on_selected_context_freed);
|
||||
else
|
||||
new_mailbox_context.freed.connect(on_examined_context_freed);
|
||||
new_context.freed.connect(on_examined_context_freed);
|
||||
|
||||
bool added = contexts.add(new_mailbox_context);
|
||||
bool added = contexts.add(new_context);
|
||||
assert(added);
|
||||
|
||||
return new Mailbox(new_mailbox_context);
|
||||
return new Mailbox(new_context);
|
||||
}
|
||||
|
||||
private void on_selected_context_freed(Geary.ReferenceSemantics semantics) {
|
||||
|
|
@ -90,22 +87,18 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account {
|
|||
}
|
||||
|
||||
private void on_context_freed(Geary.ReferenceSemantics semantics,
|
||||
Gee.HashSet<MailboxContext> contexts) {
|
||||
MailboxContext mailbox_context = (MailboxContext) semantics;
|
||||
Gee.HashSet<SelectedContext> contexts) {
|
||||
SelectedContext context = (SelectedContext) semantics;
|
||||
|
||||
debug("Mailbox %s freed, closing select/examine", mailbox_context.name);
|
||||
debug("Mailbox %s freed, closing select/examine", context.name);
|
||||
|
||||
// last reference to the Mailbox has been dropped, so drop the mailbox and move the
|
||||
// ClientSession back to the authorized state
|
||||
bool removed = contexts.remove(mailbox_context);
|
||||
bool removed = contexts.remove(context);
|
||||
assert(removed);
|
||||
|
||||
if (mailbox_context.session != null)
|
||||
mailbox_context.session.close_mailbox_async.begin();
|
||||
}
|
||||
|
||||
public async Geary.Folder open(string folder, Cancellable? cancellable = null) throws Error {
|
||||
return yield examine_mailbox(folder, cancellable);
|
||||
if (context.session != null)
|
||||
context.session.close_mailbox_async.begin();
|
||||
}
|
||||
|
||||
private async ClientSession get_authorized_session(Cancellable? cancellable = null) throws Error {
|
||||
|
|
@ -115,13 +108,13 @@ public class Geary.Imap.ClientSessionManager : Object, Geary.Account {
|
|||
return session;
|
||||
}
|
||||
|
||||
debug("Creating new session to %s", server);
|
||||
debug("Creating new session to %s", cred.server);
|
||||
|
||||
ClientSession new_session = new ClientSession(server, default_port);
|
||||
ClientSession new_session = new ClientSession(cred.server, default_port);
|
||||
new_session.disconnected.connect(on_disconnected);
|
||||
|
||||
yield new_session.connect_async(cancellable);
|
||||
yield new_session.login_async(user, pass, cancellable);
|
||||
yield new_session.login_async(cred.user, cred.pass, cancellable);
|
||||
|
||||
// do this after logging in
|
||||
new_session.enable_keepalives(keepalive_sec);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ public class Geary.Imap.EmailHeader : Geary.EmailHeader {
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.EmailBody : Geary.EmailBody {
|
||||
public EmailBody(EmailHeader header, string full) {
|
||||
public class Geary.Imap.Email : Geary.Email {
|
||||
public Email(EmailHeader header, string full) {
|
||||
base (header, full);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public abstract class Geary.Imap.Flag {
|
||||
public abstract class Geary.Imap.Flag : Comparable, Hashable {
|
||||
public string value { get; private set; }
|
||||
|
||||
public Flag(string value) {
|
||||
|
|
@ -19,21 +19,21 @@ public abstract class Geary.Imap.Flag {
|
|||
return this.value.down() == value.down();
|
||||
}
|
||||
|
||||
public bool equals(Flag flag) {
|
||||
public bool equals(Comparable b) {
|
||||
Flag? flag = b as Flag;
|
||||
if (flag == null)
|
||||
return false;
|
||||
|
||||
return (flag == this) ? true : flag.equals_string(value);
|
||||
}
|
||||
|
||||
public uint get_hash() {
|
||||
return str_hash(value.down());
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -4,33 +4,37 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Imap.Mailbox : Geary.SmartReference, Geary.Folder {
|
||||
public class Geary.Imap.Mailbox : Geary.SmartReference {
|
||||
public string name { get; private set; }
|
||||
public int count { get; private set; }
|
||||
public bool is_readonly { get; private set; }
|
||||
|
||||
private MailboxContext mailbox;
|
||||
private SelectedContext context;
|
||||
|
||||
internal Mailbox(MailboxContext mailbox) {
|
||||
base (mailbox);
|
||||
public signal void closed();
|
||||
|
||||
public signal void disconnected(bool local);
|
||||
|
||||
internal Mailbox(SelectedContext context) {
|
||||
base (context);
|
||||
|
||||
this.mailbox = mailbox;
|
||||
mailbox.exists_changed.connect(on_exists_changed);
|
||||
mailbox.closed.connect(on_closed);
|
||||
mailbox.disconnected.connect(on_disconnected);
|
||||
this.context = context;
|
||||
context.exists_changed.connect(on_exists_changed);
|
||||
context.closed.connect(on_closed);
|
||||
context.disconnected.connect(on_disconnected);
|
||||
|
||||
name = mailbox.name;
|
||||
count = mailbox.exists;
|
||||
is_readonly = mailbox.is_readonly;
|
||||
name = context.name;
|
||||
count = context.exists;
|
||||
is_readonly = context.is_readonly;
|
||||
}
|
||||
|
||||
public async Gee.List<EmailHeader>? read(int low, int count, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
if (mailbox.is_closed())
|
||||
throw new ImapError.NOT_SELECTED("Mailbox %s closed", mailbox.to_string());
|
||||
if (context.is_closed())
|
||||
throw new ImapError.NOT_SELECTED("Mailbox %s closed", name);
|
||||
|
||||
CommandResponse resp = yield mailbox.session.send_command_async(
|
||||
new FetchCommand(mailbox.session.generate_tag(), new MessageSet.range(low, count),
|
||||
CommandResponse resp = yield context.session.send_command_async(
|
||||
new FetchCommand(context.session.generate_tag(), new MessageSet.range(low, count),
|
||||
{ FetchDataType.ENVELOPE }), cancellable);
|
||||
|
||||
if (resp.status_response.status != Status.OK)
|
||||
|
|
@ -47,16 +51,16 @@ public class Geary.Imap.Mailbox : Geary.SmartReference, Geary.Folder {
|
|||
return msgs;
|
||||
}
|
||||
|
||||
public async Geary.EmailBody fetch_body(Geary.EmailHeader hdr, Cancellable? cancellable = null)
|
||||
public async Geary.Email fetch(Geary.EmailHeader hdr, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
Geary.Imap.EmailHeader? header = hdr as Geary.Imap.EmailHeader;
|
||||
assert(header != null);
|
||||
|
||||
if (mailbox.is_closed())
|
||||
throw new ImapError.NOT_SELECTED("Folder closed");
|
||||
if (context.is_closed())
|
||||
throw new ImapError.NOT_SELECTED("Mailbox %s closed", name);
|
||||
|
||||
CommandResponse resp = yield mailbox.session.send_command_async(
|
||||
new FetchCommand(mailbox.session.generate_tag(), new MessageSet(hdr.msg_num),
|
||||
CommandResponse resp = yield context.session.send_command_async(
|
||||
new FetchCommand(context.session.generate_tag(), new MessageSet(hdr.msg_num),
|
||||
{ FetchDataType.RFC822_TEXT }), cancellable);
|
||||
|
||||
if (resp.status_response.status != Status.OK)
|
||||
|
|
@ -68,7 +72,7 @@ public class Geary.Imap.Mailbox : Geary.SmartReference, Geary.Folder {
|
|||
|
||||
Geary.RFC822.Text text = (Geary.RFC822.Text) results[0].get_data(FetchDataType.RFC822_TEXT);
|
||||
|
||||
return new EmailBody(header, text.buffer.to_ascii_string());
|
||||
return new Email(header, text.buffer.to_ascii_string());
|
||||
}
|
||||
|
||||
private void on_exists_changed(int exists) {
|
||||
|
|
@ -76,15 +80,15 @@ public class Geary.Imap.Mailbox : Geary.SmartReference, Geary.Folder {
|
|||
}
|
||||
|
||||
private void on_closed() {
|
||||
closed(CloseReason.FOLDER_CLOSED);
|
||||
closed();
|
||||
}
|
||||
|
||||
private void on_disconnected(bool local) {
|
||||
closed(local ? CloseReason.LOCAL_CLOSE : CloseReason.REMOTE_CLOSE);
|
||||
disconnected(local);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Geary.Imap.MailboxContext : Object, Geary.ReferenceSemantics {
|
||||
internal class Geary.Imap.SelectedContext : Object, Geary.ReferenceSemantics {
|
||||
public ClientSession? session { get; private set; }
|
||||
|
||||
protected int manual_ref_count { get; protected set; }
|
||||
|
|
@ -102,7 +106,7 @@ internal class Geary.Imap.MailboxContext : Object, Geary.ReferenceSemantics {
|
|||
|
||||
public signal void disconnected(bool local);
|
||||
|
||||
internal MailboxContext(ClientSession session, SelectExamineResults results) {
|
||||
internal SelectedContext(ClientSession session, SelectExamineResults results) {
|
||||
this.session = session;
|
||||
|
||||
name = session.get_current_mailbox();
|
||||
|
|
@ -117,7 +121,7 @@ internal class Geary.Imap.MailboxContext : Object, Geary.ReferenceSemantics {
|
|||
session.disconnected.connect(on_session_disconnected);
|
||||
}
|
||||
|
||||
~MailboxSession() {
|
||||
~SelectedContext() {
|
||||
if (session != null) {
|
||||
session.current_mailbox_changed.disconnect(on_session_mailbox_changed);
|
||||
session.unsolicited_exists.disconnect(on_unsolicited_exists);
|
||||
|
|
@ -169,9 +173,5 @@ internal class Geary.Imap.MailboxContext : Object, Geary.ReferenceSemantics {
|
|||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return "Mailbox %s".printf(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public abstract class Geary.Imap.Flags : Geary.Common.MessageData, Geary.Imap.Me
|
|||
private Gee.Set<Flag> list;
|
||||
|
||||
public Flags(Gee.Collection<Flag> flags) {
|
||||
list = new Gee.HashSet<Flag>(Flag.hash_func, Flag.equal_func);
|
||||
list = new Gee.HashSet<Flag>(Hashable.hash_func, Comparable.equal_func);
|
||||
list.add_all(flags);
|
||||
}
|
||||
|
||||
|
|
|
|||
47
src/engine/imap/api/Account.vala
Normal file
47
src/engine/imap/api/Account.vala
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/* 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 : Object, Geary.Account, Geary.NetworkAccount {
|
||||
private ClientSessionManager session_mgr;
|
||||
|
||||
public Account(Credentials cred, uint default_port) {
|
||||
session_mgr = new ClientSessionManager(cred, default_port);
|
||||
}
|
||||
|
||||
public bool is_online() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Gee.Collection<Geary.Folder> list_async(string? parent_folder,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
Gee.Collection<MailboxInformation> mboxes = yield session_mgr.list(parent_folder, cancellable);
|
||||
|
||||
Gee.Collection<Geary.Folder> folders = new Gee.ArrayList<Geary.Folder>();
|
||||
foreach (MailboxInformation mbox in mboxes)
|
||||
folders.add(new Geary.Imap.Folder(session_mgr, mbox));
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
public async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public async void create_many_async(Gee.Collection<Geary.Folder> folders,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public async void remove_async(string folder, Cancellable? cancellable = null) throws Error {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public async void remove_many_async(Gee.Set<string> folders, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
74
src/engine/imap/api/Folder.vala
Normal file
74
src/engine/imap/api/Folder.vala
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/* 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.Folder : Object, Geary.Folder {
|
||||
public string name { get; protected set; }
|
||||
// This is only for when a context has been selected
|
||||
public Trillian is_readonly { get; protected set; }
|
||||
public Trillian supports_children { get; protected set; }
|
||||
public Trillian has_children { get; protected set; }
|
||||
public Trillian is_openable { get; protected set; }
|
||||
|
||||
private ClientSessionManager session_mgr;
|
||||
private MailboxInformation info;
|
||||
private Mailbox? mailbox = null;
|
||||
|
||||
internal Folder(ClientSessionManager session_mgr, MailboxInformation info) {
|
||||
this.session_mgr = session_mgr;
|
||||
this.info = info;
|
||||
|
||||
name = info.name;
|
||||
is_readonly = Trillian.UNKNOWN;
|
||||
supports_children = Trillian.from_boolean(!info.attrs.contains(MailboxAttribute.NO_INFERIORS));
|
||||
// \HasNoChildren is an optional attribute and lack of presence doesn't indiciate anything
|
||||
has_children = info.attrs.contains(MailboxAttribute.HAS_NO_CHILDREN) ? Trillian.TRUE
|
||||
: Trillian.UNKNOWN;
|
||||
is_openable = Trillian.from_boolean(!info.attrs.contains(MailboxAttribute.NO_SELECT));
|
||||
}
|
||||
|
||||
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
|
||||
if (mailbox != null)
|
||||
throw new EngineError.ALREADY_OPEN("%s already open", to_string());
|
||||
|
||||
mailbox = yield session_mgr.select_examine_mailbox(name, !readonly, cancellable);
|
||||
// hook up signals
|
||||
|
||||
this.is_readonly = Trillian.from_boolean(readonly);
|
||||
}
|
||||
|
||||
public async void close_async(Cancellable? cancellable = null) throws Error {
|
||||
mailbox = null;
|
||||
is_readonly = Trillian.UNKNOWN;
|
||||
}
|
||||
|
||||
public int get_message_count() throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
||||
return mailbox.count;
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.EmailHeader>? read_async(int low, int count,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
||||
return yield mailbox.read(low, count, cancellable);
|
||||
}
|
||||
|
||||
public async Geary.Email fetch_async(Geary.EmailHeader header,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
||||
return yield mailbox.fetch(header, cancellable);
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,12 +4,12 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.Imap.FolderDetail : Object, Geary.FolderDetail {
|
||||
public string name { get; protected set; }
|
||||
public class Geary.Imap.MailboxInformation {
|
||||
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) {
|
||||
public MailboxInformation(string name, string delim, MailboxAttributes attrs) {
|
||||
this.name = name;
|
||||
this.delim = delim;
|
||||
this.attrs = attrs;
|
||||
|
|
@ -17,19 +17,18 @@ public class Geary.Imap.FolderDetail : Object, Geary.FolderDetail {
|
|||
}
|
||||
|
||||
public class Geary.Imap.ListResults : Geary.Imap.CommandResults {
|
||||
private Gee.HashMap<string, FolderDetail> map = new Gee.HashMap<string, FolderDetail>();
|
||||
private Gee.Map<string, MailboxInformation> map;
|
||||
|
||||
public ListResults(StatusResponse status_response, Gee.Collection<FolderDetail> details) {
|
||||
public ListResults(StatusResponse status_response, Gee.Map<string, MailboxInformation> map) {
|
||||
base (status_response);
|
||||
|
||||
foreach (FolderDetail detail in details)
|
||||
map.set(detail.name, detail);
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public static ListResults decode(CommandResponse response) {
|
||||
assert(response.is_sealed());
|
||||
|
||||
Gee.List<FolderDetail> details = new Gee.ArrayList<FolderDetail>();
|
||||
Gee.Map<string, MailboxInformation> map = new Gee.HashMap<string, MailboxInformation>();
|
||||
foreach (ServerData data in response.server_data) {
|
||||
try {
|
||||
StringParameter cmd = data.get_as_string(1);
|
||||
|
|
@ -57,24 +56,25 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults {
|
|||
list.add(new MailboxAttribute(stringp.value));
|
||||
}
|
||||
|
||||
details.add(new FolderDetail(mailbox.value, delim.value, new MailboxAttributes(list)));
|
||||
map.set(mailbox.value,
|
||||
new MailboxInformation(mailbox.value, delim.value, new MailboxAttributes(list)));
|
||||
} catch (ImapError ierr) {
|
||||
debug("Unable to decode \"%s\": %s", data.to_string(), ierr.message);
|
||||
}
|
||||
}
|
||||
|
||||
return new ListResults(response.status_response, details);
|
||||
return new ListResults(response.status_response, map);
|
||||
}
|
||||
|
||||
public Gee.Collection<string> get_names() {
|
||||
return map.keys;
|
||||
}
|
||||
|
||||
public Gee.Collection<FolderDetail> get_all() {
|
||||
public Gee.Collection<MailboxInformation> get_all() {
|
||||
return map.values;
|
||||
}
|
||||
|
||||
public FolderDetail? get_detail(string name) {
|
||||
public MailboxInformation? get_info(string name) {
|
||||
return map.get(name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
src/engine/sqlite/Database.vala
Normal file
34
src/engine/sqlite/Database.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 abstract class Geary.Sqlite.Database {
|
||||
internal SQLHeavy.VersionedDatabase db;
|
||||
|
||||
private Gee.HashMap<SQLHeavy.Table, Geary.Sqlite.Table> table_map = new Gee.HashMap<
|
||||
SQLHeavy.Table, Geary.Sqlite.Table>();
|
||||
|
||||
public Database(File db_file, File schema_dir) throws Error {
|
||||
if (!db_file.get_parent().query_exists())
|
||||
db_file.get_parent().make_directory_with_parents();
|
||||
|
||||
db = new SQLHeavy.VersionedDatabase(db_file.get_path(), schema_dir.get_path());
|
||||
}
|
||||
|
||||
protected Geary.Sqlite.Table? get_table(string name, out SQLHeavy.Table heavy_table) {
|
||||
try {
|
||||
heavy_table = db.get_table(name);
|
||||
} catch (SQLHeavy.Error err) {
|
||||
error("Unable to load %s: %s", name, err.message);
|
||||
}
|
||||
|
||||
return table_map.get(heavy_table);
|
||||
}
|
||||
|
||||
protected void add_table(Geary.Sqlite.Table table) {
|
||||
table_map.set(table.table, table);
|
||||
}
|
||||
}
|
||||
|
||||
33
src/engine/sqlite/FolderRow.vala
Normal file
33
src/engine/sqlite/FolderRow.vala
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* 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.Sqlite.FolderRow : Geary.Sqlite.Row {
|
||||
public int64 id { get; private set; }
|
||||
public string name { get; private set; }
|
||||
public Trillian supports_children { get; private set; }
|
||||
public Trillian is_openable { get; private set; }
|
||||
public int64 parent_id { get; private set; }
|
||||
|
||||
public FolderRow(string name, Trillian supports_children, Trillian is_openable,
|
||||
int64 parent_id = INVALID_ID) {
|
||||
this.id = -1;
|
||||
this.name = name;
|
||||
this.supports_children = supports_children;
|
||||
this.is_openable = is_openable;
|
||||
this.parent_id = parent_id;
|
||||
}
|
||||
|
||||
public FolderRow.from_query_result(SQLHeavy.QueryResult result) throws Error {
|
||||
id = fetch_int64_for(result, FolderTable.Column.ID.colname());
|
||||
name = fetch_string_for(result, FolderTable.Column.NAME.colname());
|
||||
supports_children = Trillian.from_int(fetch_int_for(result,
|
||||
FolderTable.Column.SUPPORTS_CHILDREN.colname()));
|
||||
is_openable = Trillian.from_int(fetch_int_for(result,
|
||||
FolderTable.Column.IS_OPENABLE.colname()));
|
||||
parent_id = fetch_int64_for(result, FolderTable.Column.PARENT_ID.colname());
|
||||
}
|
||||
}
|
||||
|
||||
97
src/engine/sqlite/FolderTable.vala
Normal file
97
src/engine/sqlite/FolderTable.vala
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* 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.Sqlite.FolderTable : Geary.Sqlite.Table {
|
||||
// This *must* match the column order in the database
|
||||
public enum Column {
|
||||
ID,
|
||||
NAME,
|
||||
SUPPORTS_CHILDREN,
|
||||
IS_OPENABLE,
|
||||
PARENT_ID;
|
||||
|
||||
public string colname() {
|
||||
switch (this) {
|
||||
case ID:
|
||||
return "id";
|
||||
|
||||
case NAME:
|
||||
return "name";
|
||||
|
||||
case SUPPORTS_CHILDREN:
|
||||
return "supports_children";
|
||||
|
||||
case IS_OPENABLE:
|
||||
return "is_openable";
|
||||
|
||||
case PARENT_ID:
|
||||
return "parent_id";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal FolderTable(Geary.Sqlite.Database gdb, SQLHeavy.Table table) {
|
||||
base (gdb, table);
|
||||
}
|
||||
|
||||
public async Gee.List<FolderRow> list_async(int64 parent_id, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
SQLHeavy.Query query = db.prepare("SELECT * FROM FolderTable WHERE parent_id=?");
|
||||
query.bind_int64(0, parent_id);
|
||||
|
||||
SQLHeavy.QueryResult result = yield query.execute_async(cancellable);
|
||||
|
||||
Gee.List<FolderRow> rows = new Gee.ArrayList<FolderRow>();
|
||||
while (!result.finished) {
|
||||
rows.add(new FolderRow.from_query_result(result));
|
||||
|
||||
yield result.next_async(cancellable);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private SQLHeavy.Query create_query(SQLHeavy.Queryable? queryable = null) throws SQLHeavy.Error {
|
||||
SQLHeavy.Queryable q = queryable ?? db;
|
||||
SQLHeavy.Query query = q.prepare(
|
||||
"INSERT INTO FolderTable (name, supports_children, is_openable, parent_id) VALUES (?, ?, ?, ?)");
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private void create_binding(SQLHeavy.Query query, FolderRow row) throws SQLHeavy.Error {
|
||||
query.clear();
|
||||
query.bind_string(0, row.name);
|
||||
query.bind_int(1, row.supports_children.to_int());
|
||||
query.bind_int(2, row.is_openable.to_int());
|
||||
query.bind_int64(3, row.parent_id);
|
||||
}
|
||||
|
||||
public async void create_async(FolderRow row, Cancellable? cancellable = null) throws Error {
|
||||
SQLHeavy.Query query = create_query();
|
||||
create_binding(query, row);
|
||||
|
||||
yield query.execute_insert_async(cancellable);
|
||||
}
|
||||
|
||||
public async void create_many_async(Gee.Collection<FolderRow> rows, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
SQLHeavy.Transaction transaction = db.begin_transaction();
|
||||
|
||||
SQLHeavy.Query query = create_query(transaction);
|
||||
foreach (FolderRow row in rows) {
|
||||
create_binding(query, row);
|
||||
query.execute_insert();
|
||||
}
|
||||
|
||||
// TODO: Need an async transaction commit
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
27
src/engine/sqlite/MailDatabase.vala
Normal file
27
src/engine/sqlite/MailDatabase.vala
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/* 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.Sqlite.MailDatabase : Geary.Sqlite.Database {
|
||||
public const string FILENAME = "geary.db";
|
||||
|
||||
public MailDatabase(string user) throws Error {
|
||||
base (YorbaApplication.instance.get_user_data_directory().get_child(user).get_child(FILENAME),
|
||||
YorbaApplication.instance.get_resource_directory().get_child("sql"));
|
||||
}
|
||||
|
||||
public Geary.Sqlite.FolderTable get_folder_table() {
|
||||
SQLHeavy.Table heavy_table;
|
||||
FolderTable? folder_table = get_table("FolderTable", out heavy_table) as FolderTable;
|
||||
if (folder_table != null)
|
||||
return folder_table;
|
||||
|
||||
folder_table = new FolderTable(this, heavy_table);
|
||||
add_table(folder_table);
|
||||
|
||||
return folder_table;
|
||||
}
|
||||
}
|
||||
|
||||
25
src/engine/sqlite/Row.vala
Normal file
25
src/engine/sqlite/Row.vala
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* 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.Sqlite.Row {
|
||||
public const int64 INVALID_ID = -1;
|
||||
|
||||
public static int fetch_int_for(SQLHeavy.QueryResult result, string name)
|
||||
throws SQLHeavy.Error {
|
||||
return result.fetch_int(result.field_index(name));
|
||||
}
|
||||
|
||||
public static int64 fetch_int64_for(SQLHeavy.QueryResult result, string name)
|
||||
throws SQLHeavy.Error {
|
||||
return result.fetch_int64(result.field_index(name));
|
||||
}
|
||||
|
||||
public static string fetch_string_for(SQLHeavy.QueryResult result, string name)
|
||||
throws SQLHeavy.Error {
|
||||
return result.fetch_string(result.field_index(name));
|
||||
}
|
||||
}
|
||||
|
||||
22
src/engine/sqlite/Table.vala
Normal file
22
src/engine/sqlite/Table.vala
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* 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.Sqlite.Table {
|
||||
internal SQLHeavy.Database db {
|
||||
get {
|
||||
return gdb.db;
|
||||
}
|
||||
}
|
||||
|
||||
internal weak Geary.Sqlite.Database gdb;
|
||||
internal SQLHeavy.Table table;
|
||||
|
||||
internal Table(Geary.Sqlite.Database gdb, SQLHeavy.Table table) {
|
||||
this.gdb = gdb;
|
||||
this.table = table;
|
||||
}
|
||||
}
|
||||
|
||||
56
src/engine/sqlite/api/Account.vala
Normal file
56
src/engine/sqlite/api/Account.vala
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* 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.Sqlite.Account : Object, Geary.Account, Geary.LocalAccount {
|
||||
private MailDatabase db;
|
||||
private FolderTable folder_table;
|
||||
|
||||
public Account(Geary.Credentials cred) {
|
||||
try {
|
||||
db = new MailDatabase(cred.user);
|
||||
} catch (Error err) {
|
||||
error("Unable to open database: %s", err.message);
|
||||
}
|
||||
|
||||
folder_table = db.get_folder_table();
|
||||
}
|
||||
|
||||
public async Gee.Collection<Geary.Folder> list_async(string? parent_folder,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
Gee.List<FolderRow> rows = yield folder_table.list_async(Row.INVALID_ID, cancellable);
|
||||
|
||||
Gee.Collection<Geary.Folder> folders = new Gee.ArrayList<Geary.Sqlite.Folder>();
|
||||
foreach (FolderRow row in rows)
|
||||
folders.add(new Geary.Sqlite.Folder(row));
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
public async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error {
|
||||
yield folder_table.create_async(
|
||||
new FolderRow(folder.name, folder.supports_children, folder.is_openable),
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public async void create_many_async(Gee.Collection<Geary.Folder> folders,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
Gee.List<FolderRow> rows = new Gee.ArrayList<FolderRow>();
|
||||
foreach (Geary.Folder folder in folders)
|
||||
rows.add(new FolderRow(folder.name, folder.supports_children, folder.is_openable));
|
||||
|
||||
yield folder_table.create_many_async(rows, cancellable);
|
||||
}
|
||||
|
||||
public async void remove_async(string folder, Cancellable? cancellable = null) throws Error {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public async void remove_many_async(Gee.Set<string> folders, Cancellable? cancellable = null)
|
||||
throws Error {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
48
src/engine/sqlite/api/Folder.vala
Normal file
48
src/engine/sqlite/api/Folder.vala
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* 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.Sqlite.Folder : Object, Geary.Folder {
|
||||
private FolderRow row;
|
||||
|
||||
public string name { get; protected set; }
|
||||
public Trillian is_readonly { get; protected set; }
|
||||
public Trillian supports_children { get; protected set; }
|
||||
public Trillian has_children { get; protected set; }
|
||||
public Trillian is_openable { get; protected set; }
|
||||
|
||||
internal Folder(FolderRow row) throws Error {
|
||||
this.row = row;
|
||||
|
||||
name = row.name;
|
||||
is_readonly = Trillian.UNKNOWN;
|
||||
supports_children = row.supports_children;
|
||||
has_children = Trillian.UNKNOWN;
|
||||
is_openable = row.is_openable;
|
||||
}
|
||||
|
||||
public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error {
|
||||
is_readonly = Trillian.TRUE;
|
||||
}
|
||||
|
||||
public async void close_async(Cancellable? cancellable = null) throws Error {
|
||||
is_readonly = Trillian.UNKNOWN;
|
||||
}
|
||||
|
||||
public int get_message_count() throws Error {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public async Gee.List<Geary.EmailHeader>? read_async(int low, int count,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Geary.Email fetch_async(Geary.EmailHeader header,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
throw new EngineError.OPEN_REQUIRED("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
69
src/engine/util/Trillian.vala
Normal file
69
src/engine/util/Trillian.vala
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A trillian is a three-state boolean, used when the value is potentially unknown.
|
||||
*/
|
||||
|
||||
public enum Geary.Trillian {
|
||||
UNKNOWN = -1,
|
||||
FALSE = 0,
|
||||
TRUE = 1;
|
||||
|
||||
public bool to_boolean(bool if_unknown) {
|
||||
switch (this) {
|
||||
case UNKNOWN:
|
||||
return if_unknown;
|
||||
|
||||
case FALSE:
|
||||
return false;
|
||||
|
||||
case TRUE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
public inline static Trillian from_boolean(bool b) {
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
public int to_int() {
|
||||
return (int) this;
|
||||
}
|
||||
|
||||
public inline static Trillian from_int(int i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
return FALSE;
|
||||
|
||||
case 1:
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
switch (this) {
|
||||
case UNKNOWN:
|
||||
return "unknown";
|
||||
|
||||
case FALSE:
|
||||
return "false";
|
||||
|
||||
case TRUE:
|
||||
return "true";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
MainLoop? main_loop = null;
|
||||
Geary.Imap.ClientSessionManager? sess = null;
|
||||
string? mailbox = null;
|
||||
int start = 0;
|
||||
int count = 0;
|
||||
|
||||
async void async_start() {
|
||||
try {
|
||||
Geary.Folder folder = yield sess.open(mailbox);
|
||||
|
||||
bool ok = false;
|
||||
Gee.List<Geary.EmailHeader>? msgs = yield folder.read(start, count);
|
||||
if (msgs != null && msgs.size > 0) {
|
||||
foreach (Geary.EmailHeader msg in msgs)
|
||||
stdout.printf("%s\n", msg.to_string());
|
||||
|
||||
ok = true;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
debug("Unable to examine mailbox %s", mailbox);
|
||||
} catch (Error err) {
|
||||
debug("Error: %s", err.message);
|
||||
}
|
||||
|
||||
main_loop.quit();
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
if (args.length < 6) {
|
||||
stderr.printf("usage: lsmbox <user> <pass> <mailbox> <start #> <count>\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
main_loop = new MainLoop();
|
||||
|
||||
string user = args[1];
|
||||
string pass = args[2];
|
||||
mailbox = args[3];
|
||||
start = int.parse(args[4]);
|
||||
count = int.parse(args[5]);
|
||||
|
||||
sess = new Geary.Imap.ClientSessionManager("imap.gmail.com", 993, user, pass);
|
||||
async_start.begin();
|
||||
|
||||
main_loop.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
MainLoop? main_loop = null;
|
||||
Geary.Imap.ClientSession? sess = null;
|
||||
string? user = null;
|
||||
string? pass = null;
|
||||
string? mailbox = null;
|
||||
int msg_num = 0;
|
||||
|
||||
async void async_start() {
|
||||
try {
|
||||
yield sess.connect_async();
|
||||
yield sess.login_async(user, pass);
|
||||
yield sess.examine_async(mailbox);
|
||||
|
||||
Geary.Imap.FetchCommand fetch = new Geary.Imap.FetchCommand(sess.generate_tag(),
|
||||
new Geary.Imap.MessageSet(msg_num), { Geary.Imap.FetchDataType.RFC822 });
|
||||
Geary.Imap.CommandResponse resp = yield sess.send_command_async(fetch);
|
||||
Geary.Imap.FetchResults[] results = Geary.Imap.FetchResults.decode(resp);
|
||||
|
||||
assert(results.length == 1);
|
||||
Geary.RFC822.Full? full =
|
||||
results[0].get_data(Geary.Imap.FetchDataType.RFC822) as Geary.RFC822.Full;
|
||||
assert(full != null);
|
||||
|
||||
DataInputStream dins = new DataInputStream(full.buffer.get_input_stream());
|
||||
dins.set_newline_type(DataStreamNewlineType.CR_LF);
|
||||
for (;;) {
|
||||
string? line = dins.read_line(null);
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
stdout.printf("%s\n", line);
|
||||
}
|
||||
|
||||
yield sess.close_mailbox_async();
|
||||
|
||||
yield sess.logout_async();
|
||||
yield sess.disconnect_async();
|
||||
} catch (Error err) {
|
||||
debug("Error: %s", err.message);
|
||||
}
|
||||
|
||||
main_loop.quit();
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
if (args.length < 5) {
|
||||
stderr.printf("usage: readmail <user> <pass> <mailbox> <msg #>\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
main_loop = new MainLoop();
|
||||
|
||||
user = args[1];
|
||||
pass = args[2];
|
||||
mailbox = args[3];
|
||||
msg_num = int.parse(args[4]);
|
||||
|
||||
sess = new Geary.Imap.ClientSession("imap.gmail.com", 993);
|
||||
async_start.begin();
|
||||
|
||||
main_loop.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
MainLoop? main_loop = null;
|
||||
|
||||
void print(int depth, Gee.List<Geary.Imap.Parameter> params) {
|
||||
string pad = string.nfill(depth * 4, ' ');
|
||||
|
||||
int index = 0;
|
||||
foreach (Geary.Imap.Parameter param in params) {
|
||||
Geary.Imap.ListParameter? list = param as Geary.Imap.ListParameter;
|
||||
if (list == null) {
|
||||
stdout.printf("%s#%02d >%s<\n", pad, index++, param.to_string());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
print(depth + 1, list.get_all());
|
||||
}
|
||||
}
|
||||
|
||||
void on_params_ready(Geary.Imap.RootParameters root) {
|
||||
print(0, root.get_all());
|
||||
}
|
||||
|
||||
void on_eos() {
|
||||
main_loop.quit();
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
if (args.length < 2) {
|
||||
stderr.printf("usage: syntax <imap command>\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
main_loop = new MainLoop();
|
||||
|
||||
// turn argument into single line for deserializer
|
||||
string line = "";
|
||||
for (int ctr = 1; ctr < args.length; ctr++) {
|
||||
line += args[ctr];
|
||||
if (ctr < (args.length - 1))
|
||||
line += " ";
|
||||
}
|
||||
line += "\r\n";
|
||||
|
||||
MemoryInputStream mins = new MemoryInputStream();
|
||||
mins.add_data(line.data, null);
|
||||
|
||||
Geary.Imap.Deserializer des = new Geary.Imap.Deserializer(mins);
|
||||
des.parameters_ready.connect(on_params_ready);
|
||||
des.eos.connect(on_eos);
|
||||
|
||||
stdout.printf("INPUT: >%s<\n", line);
|
||||
des.xon();
|
||||
|
||||
main_loop.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue