~ubuntu-branches/ubuntu/saucy/geary/saucy-updates

« back to all changes in this revision

Viewing changes to src/engine/api/geary-engine.vala

  • Committer: Package Import Robot
  • Author(s): Sebastien Bacher
  • Date: 2013-03-14 13:48:23 UTC
  • mfrom: (1.1.3)
  • Revision ID: package-import@ubuntu.com-20130314134823-gyk5av1g508zyj8a
Tags: 0.3.0~pr1-0ubuntu1
New upstream version (FFE lp: #1154316), supports multiple account as
well as full conversation views with inline replies

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
 */
6
6
 
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; }
10
 
    
11
 
    private static bool inited = false;
12
 
    
13
 
    /**
14
 
     * Geary.Engine.init() should be the first call any application makes prior to calling into
15
 
     * the Geary engine.
16
 
     */
17
 
    public static void init(File _user_data_dir, File _resource_dir) {
18
 
        if (inited)
19
 
            return;
20
 
        
21
 
        user_data_dir = _user_data_dir;
22
 
        resource_dir = _resource_dir;
23
 
        
24
 
        // Initialize GMime
25
 
        GMime.init(0);
26
 
        
27
 
        inited = true;
28
 
    }
29
 
    
30
 
    /**
31
 
     * Returns a list of AccountInformation objects representing accounts setup for use by the Geary
32
 
     * engine.
33
 
     */
34
 
    public static Gee.List<AccountInformation> get_accounts() throws Error {
35
 
        Gee.ArrayList<AccountInformation> list = new Gee.ArrayList<AccountInformation>();
36
 
        
37
 
        if (!inited) {
38
 
            debug("Geary.Engine.get_accounts(): not initialized");
39
 
            
40
 
            return list;
41
 
        }
42
 
        
43
 
        FileEnumerator enumerator = user_data_dir.enumerate_children("standard::*", 
44
 
            FileQueryInfoFlags.NONE);
45
 
        
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())));
50
 
        }
51
 
        
52
 
        return list;
53
 
    }
54
 
    
55
 
    /**
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.
59
 
     *
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.
62
 
     */
63
 
    public static Geary.AccountInformation get_account_for_email(string email) throws Error {
64
 
        return new Geary.AccountInformation(user_data_dir.get_child(email));
 
8
    [Flags]
 
9
    public enum ValidationResult {
 
10
        OK = 0,
 
11
        INVALID_NICKNAME,
 
12
        IMAP_CONNECTION_FAILED,
 
13
        IMAP_CREDENTIALS_INVALID,
 
14
        SMTP_CONNECTION_FAILED,
 
15
        SMTP_CREDENTIALS_INVALID;
 
16
        
 
17
        public inline bool is_all_set(ValidationResult result) {
 
18
            return (result & this) == result;
 
19
        }
 
20
    }
 
21
    
 
22
    private static Engine? _instance = null;
 
23
    public static Engine instance {
 
24
        get {
 
25
            return (_instance != null) ? _instance : (_instance = new Engine());
 
26
        }
 
27
    }
 
28
 
 
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; }
 
32
    
 
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;
 
37
 
 
38
    /**
 
39
     * Fired when the engine is opened.
 
40
     */
 
41
    public signal void opened();
 
42
 
 
43
    /**
 
44
     * Fired when the engine is closed.
 
45
     */
 
46
    public signal void closed();
 
47
 
 
48
    /**
 
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.
 
52
     */
 
53
    public signal void account_available(AccountInformation account);
 
54
 
 
55
    /**
 
56
     * Fired when an account becomes unavailable in the engine.  Closing the
 
57
     * engine makes all accounts unavailable; deleting an account also makes it
 
58
     * unavailable.
 
59
     */
 
60
    public signal void account_unavailable(AccountInformation account);
 
61
 
 
62
    /**
 
63
     * Fired when a new account is created.
 
64
     */
 
65
    public signal void account_added(AccountInformation account);
 
66
 
 
67
    /**
 
68
     * Fired when an account is deleted.
 
69
     */
 
70
    public signal void account_removed(AccountInformation account);
 
71
 
 
72
    private Engine() {
 
73
    }
 
74
    
 
75
    private void check_opened() throws EngineError {
 
76
        if (!is_open)
 
77
            throw new EngineError.OPEN_REQUIRED("Geary.Engine instance not open");
 
78
    }
 
79
    
 
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.
 
82
    //
 
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() {
 
87
        if (is_initialized)
 
88
            return;
 
89
        
 
90
        is_initialized = true;
 
91
        
 
92
        RFC822.init();
 
93
        ImapEngine.init();
 
94
    }
 
95
    
 
96
    /**
 
97
     * Initializes the engine, and makes all existing accounts available.  The
 
98
     * given authentication mediator will be used to retrieve all passwords
 
99
     * when necessary.
 
100
     */
 
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
 
105
        // is closed
 
106
        initialize_library();
 
107
        
 
108
        if (is_open)
 
109
            throw new EngineError.ALREADY_OPEN("Geary.Engine instance already open");
 
110
        
 
111
        this.user_data_dir = user_data_dir;
 
112
        this.resource_dir = resource_dir;
 
113
        this.authentication_mediator = authentication_mediator;
 
114
 
 
115
        accounts = new Gee.HashMap<string, AccountInformation>();
 
116
        account_instances = new Gee.HashMap<string, Account>();
 
117
 
 
118
        is_open = true;
 
119
        opened();
 
120
 
 
121
        yield add_existing_accounts_async(cancellable);
 
122
   }
 
123
 
 
124
    private async void add_existing_accounts_async(Cancellable? cancellable = null) throws Error {
 
125
        try {
 
126
            user_data_dir.make_directory_with_parents(cancellable);
 
127
        } catch (IOError e) {
 
128
            if (!(e is IOError.EXISTS))
 
129
                throw e;
 
130
        }
 
131
 
 
132
        FileEnumerator enumerator
 
133
            = yield user_data_dir.enumerate_children_async("standard::*",
 
134
                FileQueryInfoFlags.NONE, Priority.DEFAULT, cancellable);
 
135
 
 
136
        for (;;) {
 
137
            List<FileInfo> info_list;
 
138
            try {
 
139
                info_list = yield enumerator.next_files_async(1, Priority.DEFAULT, cancellable);
 
140
            } catch (Error e) {
 
141
                debug("Error enumerating existing accounts: %s", e.message);
 
142
                break;
 
143
            }
 
144
 
 
145
            if (info_list.length() == 0)
 
146
                break;
 
147
 
 
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())));
 
152
            }
 
153
        }
 
154
     }
 
155
 
 
156
    /**
 
157
     * Uninitializes the engine, and makes all accounts unavailable.
 
158
     */
 
159
    public async void close_async(Cancellable? cancellable = null) throws Error {
 
160
        if (!is_open)
 
161
            return;
 
162
 
 
163
        foreach(AccountInformation account in accounts.values)
 
164
            account_unavailable(account);
 
165
 
 
166
        user_data_dir = null;
 
167
        resource_dir = null;
 
168
        authentication_mediator = null;
 
169
        accounts = null;
 
170
        account_instances = null;
 
171
 
 
172
        is_open = false;
 
173
        closed();
 
174
    }
 
175
 
 
176
    /**
 
177
     * Returns the current accounts list as a map keyed by email address.
 
178
     */
 
179
    public Gee.Map<string, AccountInformation> get_accounts() throws Error {
 
180
        check_opened();
 
181
 
 
182
        return accounts.read_only_view;
 
183
    }
 
184
 
 
185
    /**
 
186
     * Returns a new account for the given email address not yet stored on disk.
 
187
     */
 
188
    public AccountInformation create_orphan_account(string email) throws Error {
 
189
        check_opened();
 
190
 
 
191
        if (accounts.has_key(email))
 
192
            throw new EngineError.ALREADY_EXISTS("Account %s already exists", email);
 
193
 
 
194
        return new AccountInformation(user_data_dir.get_child(email));
 
195
    }
 
196
    
 
197
    /**
 
198
     * Returns whether the account information "validates", which means here
 
199
     * that we can connect to the endpoints and authenticate using the supplied
 
200
     * credentials.
 
201
     */
 
202
    public async ValidationResult validate_account_information_async(AccountInformation account,
 
203
        Cancellable? cancellable = null) throws Error {
 
204
        check_opened();
 
205
        ValidationResult error_code = ValidationResult.OK;
 
206
        
 
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;
 
211
        }
 
212
        
 
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);
 
215
        try {
 
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;
 
220
        }
 
221
        
 
222
        if (!error_code.is_all_set(ValidationResult.IMAP_CONNECTION_FAILED)) {
 
223
            try {
 
224
                yield imap_session.initiate_session_async(account.imap_credentials, cancellable);
 
225
                
 
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;
 
234
                else
 
235
                    error_code |= ValidationResult.IMAP_CONNECTION_FAILED;
 
236
            }
 
237
        }
 
238
        
 
239
        try {
 
240
            yield imap_session.disconnect_async(cancellable);
 
241
        } catch (Error err) {
 
242
            // ignored
 
243
        } finally {
 
244
            imap_session = null;
 
245
        }
 
246
        
 
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());
 
249
        try {
 
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;
 
255
            else
 
256
                error_code |= ValidationResult.SMTP_CONNECTION_FAILED;
 
257
        }
 
258
        
 
259
        try {
 
260
            yield smtp_session.logout_async(cancellable);
 
261
        } catch (Error err) {
 
262
            // ignored
 
263
        } finally {
 
264
            smtp_session = null;
 
265
        }
 
266
        
 
267
        return error_code;
 
268
    }
 
269
    
 
270
    /**
 
271
     * Creates a Geary.Account from a Geary.AccountInformation (which is what
 
272
     * other methods in this interface deal in).
 
273
     */
 
274
    public Geary.Account get_account_instance(AccountInformation account_information)
 
275
        throws Error {
 
276
        check_opened();
 
277
        
 
278
        if (account_instances.has_key(account_information.email))
 
279
            return account_instances.get(account_information.email);
 
280
        
 
281
        ImapDB.Account local_account = new ImapDB.Account(account_information);
 
282
        Imap.Account remote_account = new Imap.Account(account_information);
 
283
        
 
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);
 
289
            break;
 
290
            
 
291
            case ServiceProvider.YAHOO:
 
292
                account = new ImapEngine.YahooAccount("Yahoo account %s".printf(account_information.email),
 
293
                    account_information, remote_account, local_account);
 
294
            break;
 
295
            
 
296
            case ServiceProvider.OTHER:
 
297
                account = new ImapEngine.OtherAccount("Other account %s".printf(account_information.email),
 
298
                    account_information, remote_account, local_account);
 
299
            break;
 
300
            
 
301
            default:
 
302
                assert_not_reached();
 
303
        }
 
304
        
 
305
        account_instances.set(account_information.email, account);
 
306
        return account;
 
307
    }
 
308
    
 
309
    /**
 
310
     * Adds the account to be tracked by the engine.  Should only be called from
 
311
     * AccountInformation.store_async() and this class.
 
312
     */
 
313
    internal void add_account(AccountInformation account, bool created = false) throws Error {
 
314
        check_opened();
 
315
 
 
316
        bool already_added = accounts.has_key(account.email);
 
317
 
 
318
        accounts.set(account.email, account);
 
319
 
 
320
        if (!already_added) {
 
321
            if (created)
 
322
                account_added(account);
 
323
            account_available(account);
 
324
        }
 
325
    }
 
326
 
 
327
    /**
 
328
     * Deletes the account from disk.
 
329
     */
 
330
    public async void remove_account_async(AccountInformation account,
 
331
                                           Cancellable? cancellable = null) throws Error {
 
332
        check_opened();
 
333
        
 
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",
 
337
                account.email);
 
338
        }
 
339
        
 
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);
 
344
            
 
345
            // 2. Delete the corresponding files.
 
346
            yield account.remove_async(cancellable);
 
347
            
 
348
            // 3. Send the account-removed signal.
 
349
            account_removed(account);
 
350
            
 
351
            // 4. Remove the account data from the engine.
 
352
            account_instances.unset(account.email);
 
353
        }
65
354
    }
66
355
}
67
356