7
7
public class Geary.Engine {
8
public static File? user_data_dir { get; private set; default = null; }
9
public static File? resource_dir { get; private set; default = null; }
11
private static bool inited = false;
14
* Geary.Engine.init() should be the first call any application makes prior to calling into
17
public static void init(File _user_data_dir, File _resource_dir) {
21
user_data_dir = _user_data_dir;
22
resource_dir = _resource_dir;
31
* Returns a list of AccountInformation objects representing accounts setup for use by the Geary
34
public static Gee.List<AccountInformation> get_accounts() throws Error {
35
Gee.ArrayList<AccountInformation> list = new Gee.ArrayList<AccountInformation>();
38
debug("Geary.Engine.get_accounts(): not initialized");
43
FileEnumerator enumerator = user_data_dir.enumerate_children("standard::*",
44
FileQueryInfoFlags.NONE);
46
FileInfo? info = null;
47
while ((info = enumerator.next_file()) != null) {
48
if (info.get_file_type() == FileType.DIRECTORY)
49
list.add(new AccountInformation(user_data_dir.get_child(info.get_name())));
56
* Returns a Geary.AccountInformation for the specified email address. If the account
57
* has not been set up previously, an object is returned, although it's merely backed by memory
58
* and filled with defaults. Otherwise, the account information for that address is loaded.
60
* "email" in this case means the Internet mailbox for the account, i.e. username@domain.com.
61
* Use the "address" field of RFC822.MailboxAddress for this parameter.
63
public static Geary.AccountInformation get_account_for_email(string email) throws Error {
64
return new Geary.AccountInformation(user_data_dir.get_child(email));
9
public enum ValidationResult {
12
IMAP_CONNECTION_FAILED,
13
IMAP_CREDENTIALS_INVALID,
14
SMTP_CONNECTION_FAILED,
15
SMTP_CREDENTIALS_INVALID;
17
public inline bool is_all_set(ValidationResult result) {
18
return (result & this) == result;
22
private static Engine? _instance = null;
23
public static Engine instance {
25
return (_instance != null) ? _instance : (_instance = new Engine());
29
public File? user_data_dir { get; private set; default = null; }
30
public File? resource_dir { get; private set; default = null; }
31
public Geary.CredentialsMediator? authentication_mediator { get; private set; default = null; }
33
private bool is_initialized = false;
34
private bool is_open = false;
35
private Gee.HashMap<string, AccountInformation>? accounts = null;
36
private Gee.HashMap<string, Account>? account_instances = null;
39
* Fired when the engine is opened.
41
public signal void opened();
44
* Fired when the engine is closed.
46
public signal void closed();
49
* Fired when an account becomes available in the engine. Opening the
50
* engine makes all existing accounts available; newly created accounts are
51
* also made available as soon as they're stored.
53
public signal void account_available(AccountInformation account);
56
* Fired when an account becomes unavailable in the engine. Closing the
57
* engine makes all accounts unavailable; deleting an account also makes it
60
public signal void account_unavailable(AccountInformation account);
63
* Fired when a new account is created.
65
public signal void account_added(AccountInformation account);
68
* Fired when an account is deleted.
70
public signal void account_removed(AccountInformation account);
75
private void check_opened() throws EngineError {
77
throw new EngineError.OPEN_REQUIRED("Geary.Engine instance not open");
80
// This can't be called from within the ctor, as initialization code may want to access the
81
// Engine instance to make their own calls and, in particular, subscribe to signals.
83
// TODO: It would make sense to have a terminate_library() call, but it technically should not
84
// be called until the application is exiting, not merely if the Engine is closed, as termination
85
// means shutting down resources for good
86
private void initialize_library() {
90
is_initialized = true;
97
* Initializes the engine, and makes all existing accounts available. The
98
* given authentication mediator will be used to retrieve all passwords
101
public async void open_async(File user_data_dir, File resource_dir,
102
Geary.CredentialsMediator? authentication_mediator,
103
Cancellable? cancellable = null) throws Error {
104
// initialize *before* opening the Engine ... all initialize code should assume the Engine
106
initialize_library();
109
throw new EngineError.ALREADY_OPEN("Geary.Engine instance already open");
111
this.user_data_dir = user_data_dir;
112
this.resource_dir = resource_dir;
113
this.authentication_mediator = authentication_mediator;
115
accounts = new Gee.HashMap<string, AccountInformation>();
116
account_instances = new Gee.HashMap<string, Account>();
121
yield add_existing_accounts_async(cancellable);
124
private async void add_existing_accounts_async(Cancellable? cancellable = null) throws Error {
126
user_data_dir.make_directory_with_parents(cancellable);
127
} catch (IOError e) {
128
if (!(e is IOError.EXISTS))
132
FileEnumerator enumerator
133
= yield user_data_dir.enumerate_children_async("standard::*",
134
FileQueryInfoFlags.NONE, Priority.DEFAULT, cancellable);
137
List<FileInfo> info_list;
139
info_list = yield enumerator.next_files_async(1, Priority.DEFAULT, cancellable);
141
debug("Error enumerating existing accounts: %s", e.message);
145
if (info_list.length() == 0)
148
FileInfo info = info_list.nth_data(0);
149
if (info.get_file_type() == FileType.DIRECTORY) {
150
// TODO: check for geary.ini
151
add_account(new AccountInformation(user_data_dir.get_child(info.get_name())));
157
* Uninitializes the engine, and makes all accounts unavailable.
159
public async void close_async(Cancellable? cancellable = null) throws Error {
163
foreach(AccountInformation account in accounts.values)
164
account_unavailable(account);
166
user_data_dir = null;
168
authentication_mediator = null;
170
account_instances = null;
177
* Returns the current accounts list as a map keyed by email address.
179
public Gee.Map<string, AccountInformation> get_accounts() throws Error {
182
return accounts.read_only_view;
186
* Returns a new account for the given email address not yet stored on disk.
188
public AccountInformation create_orphan_account(string email) throws Error {
191
if (accounts.has_key(email))
192
throw new EngineError.ALREADY_EXISTS("Account %s already exists", email);
194
return new AccountInformation(user_data_dir.get_child(email));
198
* Returns whether the account information "validates", which means here
199
* that we can connect to the endpoints and authenticate using the supplied
202
public async ValidationResult validate_account_information_async(AccountInformation account,
203
Cancellable? cancellable = null) throws Error {
205
ValidationResult error_code = ValidationResult.OK;
207
// Make sure the account nickname is not in use.
208
foreach (AccountInformation a in get_accounts().values) {
209
if (account != a && Geary.String.equals_ci(account.nickname, a.nickname))
210
error_code |= ValidationResult.INVALID_NICKNAME;
213
// validate IMAP, which requires logging in and establishing an AUTHORIZED cx state
214
Geary.Imap.ClientSession? imap_session = new Imap.ClientSession(account.get_imap_endpoint(), true);
216
yield imap_session.connect_async(cancellable);
217
} catch (Error err) {
218
debug("Error connecting to IMAP server: %s", err.message);
219
error_code |= ValidationResult.IMAP_CONNECTION_FAILED;
222
if (!error_code.is_all_set(ValidationResult.IMAP_CONNECTION_FAILED)) {
224
yield imap_session.initiate_session_async(account.imap_credentials, cancellable);
226
// Connected and initiated, still need to be sure connection authorized
227
string current_mailbox;
228
if (imap_session.get_context(out current_mailbox) != Imap.ClientSession.Context.AUTHORIZED)
229
error_code |= ValidationResult.IMAP_CREDENTIALS_INVALID;
230
} catch (Error err) {
231
debug("Error validating IMAP account info: %s", err.message);
232
if (err is ImapError.UNAUTHENTICATED)
233
error_code |= ValidationResult.IMAP_CREDENTIALS_INVALID;
235
error_code |= ValidationResult.IMAP_CONNECTION_FAILED;
240
yield imap_session.disconnect_async(cancellable);
241
} catch (Error err) {
247
// SMTP is simpler, merely see if login works and done (throws an SmtpError if not)
248
Geary.Smtp.ClientSession? smtp_session = new Geary.Smtp.ClientSession(account.get_smtp_endpoint());
250
yield smtp_session.login_async(account.smtp_credentials, cancellable);
251
} catch (Error err) {
252
debug("Error validating SMTP account info: %s", err.message);
253
if (err is SmtpError.AUTHENTICATION_FAILED)
254
error_code |= ValidationResult.SMTP_CREDENTIALS_INVALID;
256
error_code |= ValidationResult.SMTP_CONNECTION_FAILED;
260
yield smtp_session.logout_async(cancellable);
261
} catch (Error err) {
271
* Creates a Geary.Account from a Geary.AccountInformation (which is what
272
* other methods in this interface deal in).
274
public Geary.Account get_account_instance(AccountInformation account_information)
278
if (account_instances.has_key(account_information.email))
279
return account_instances.get(account_information.email);
281
ImapDB.Account local_account = new ImapDB.Account(account_information);
282
Imap.Account remote_account = new Imap.Account(account_information);
284
Geary.Account account;
285
switch (account_information.service_provider) {
286
case ServiceProvider.GMAIL:
287
account = new ImapEngine.GmailAccount("Gmail account %s".printf(account_information.email),
288
account_information, remote_account, local_account);
291
case ServiceProvider.YAHOO:
292
account = new ImapEngine.YahooAccount("Yahoo account %s".printf(account_information.email),
293
account_information, remote_account, local_account);
296
case ServiceProvider.OTHER:
297
account = new ImapEngine.OtherAccount("Other account %s".printf(account_information.email),
298
account_information, remote_account, local_account);
302
assert_not_reached();
305
account_instances.set(account_information.email, account);
310
* Adds the account to be tracked by the engine. Should only be called from
311
* AccountInformation.store_async() and this class.
313
internal void add_account(AccountInformation account, bool created = false) throws Error {
316
bool already_added = accounts.has_key(account.email);
318
accounts.set(account.email, account);
320
if (!already_added) {
322
account_added(account);
323
account_available(account);
328
* Deletes the account from disk.
330
public async void remove_account_async(AccountInformation account,
331
Cancellable? cancellable = null) throws Error {
334
// Ensure account is closed.
335
if (account_instances.has_key(account.email) && account_instances.get(account.email).is_open()) {
336
throw new EngineError.CLOSE_REQUIRED("Account %s must be closed before removal",
340
if (accounts.unset(account.email)) {
341
// Removal *MUST* be done in the following order:
342
// 1. Send the account-unavailable signal.
343
account_unavailable(account);
345
// 2. Delete the corresponding files.
346
yield account.remove_async(cancellable);
348
// 3. Send the account-removed signal.
349
account_removed(account);
351
// 4. Remove the account data from the engine.
352
account_instances.unset(account.email);