2
* Copyright 2016 Software Freedom Conservancy Inc.
3
* Copyright 2018-2019 Michael Gratton <mike@vee.net>.
5
* This software is licensed under the GNU Lesser General Public License
6
* (version 2.1 or later). See the COPYING file in this distribution.
10
* Account is the basic interface to the user's email account, {@link Folder}s, and various signals,
11
* monitors, and notifications of account activity.
13
* In addition to providing an interface to the various Folders, Account offers aggregation signals
14
* indicating changes to all Folders as they're discovered. Because some mail interfaces don't
15
* provide account-wide notification, these signals may only be fired when the Folder is open,
16
* which sometimes happens in the background as background synchronization occurs.
18
* Accounts must be opened and closed with {@link open_async} and {@link close_async}. Most
19
* methods only work if the Account is opened (if not, that will be mentioned in their
22
* A list of all Accounts may be retrieved from the {@link Engine} singleton.
25
public abstract class Geary.Account : BaseObject, Loggable {
28
/** Number of times to attempt re-authentication. */
29
internal const uint AUTH_ATTEMPTS_MAX = 3;
33
* Denotes the account's current status.
35
* @see Account.current_status
36
* @see ClientService.current_status
42
* The account is currently online and operating normally.
44
* This flags will be set when the account's {@link incoming}
45
* service's {@link ClientService.current_status} is {@link
46
* ClientService.Status.CONNECTED}.
51
* One or of the account's services is degraded.
53
* This flag will be set when one or both of its services has
54
* encountered a problem. Consult the {@link
55
* ClientService.current_status} to determine which and the
61
/** Determines if the {@link ONLINE} flag is set. */
62
public bool is_online() {
63
return (this & ONLINE) == ONLINE;
66
/** Determines if the {@link SERVICE_PROBLEM} flag is set. */
67
public bool has_service_problem() {
68
return (this & SERVICE_PROBLEM) == SERVICE_PROBLEM;
74
* A utility method to sort a Gee.Collection of {@link Folder}s by
75
* their {@link FolderPath}s to ensure they comport with {@link
76
* folders_available_unavailable}, {@link folders_created}, {@link
77
* folders_deleted} signals' contracts.
79
public static Gee.BidirSortedSet<Folder>
80
sort_by_path(Gee.Collection<Folder> folders) {
81
Gee.TreeSet<Folder> sorted =
82
new Gee.TreeSet<Folder>(Account.folder_path_comparator);
83
sorted.add_all(folders);
88
* Comparator used to sort folders.
92
public static int folder_path_comparator(Geary.Folder a, Geary.Folder b) {
93
return a.path.compare_to(b.path);
98
* The account's current configuration.
100
public AccountInformation information { get; protected set; }
103
* The account's current status.
105
* This property's value is set based on the {@link
106
* ClientService.current_status} of the account's {@link incoming}
107
* and {@link outgoing} services. See {@link Status} for more
110
* The initial value for this property is {@link Status.ONLINE},
111
* which may or may not be incorrect. However the once the account
112
* has been opened, its services will begin checking connectivity
113
* and the value will be updated to match in due course.
115
* @see ClientService.current_status
117
public Status current_status { get; protected set; default = ONLINE; }
120
* The service manager for the incoming email service.
122
public ClientService incoming { get; private set; }
125
* The service manager for the outgoing email service.
127
public ClientService outgoing { get; private set; }
130
* The contact information store for this account.
132
public Geary.ContactStore contact_store { get; protected set; }
134
public Geary.ProgressMonitor search_upgrade_monitor { get; protected set; }
135
public Geary.ProgressMonitor db_upgrade_monitor { get; protected set; }
136
public Geary.ProgressMonitor db_vacuum_monitor { get; protected set; }
137
public Geary.ProgressMonitor opening_monitor { get; protected set; }
138
public Geary.ProgressMonitor sending_monitor { get; protected set; }
141
public signal void opened();
143
public signal void closed();
145
public signal void email_sent(Geary.RFC822.Message rfc822);
148
* Emitted to notify the client that some problem has occurred.
150
* The engine uses this signal to report internal errors and other
151
* issues that the client should notify the user about. The {@link
152
* ProblemReport} class provides context about the nature of the
155
public signal void report_problem(Geary.ProblemReport problem);
158
* Fired when folders become available or unavailable in the account.
160
* Folders become available when the account is first opened or when
161
* they're created later; they become unavailable when the account is
162
* closed or they're deleted later.
164
* Folders are ordered for the convenience of the caller from the
165
* top of the hierarchy to lower in the hierarchy. In other
166
* words, parents are listed before children, assuming the
167
* collections are traversed in natural order.
172
folders_available_unavailable(Gee.BidirSortedSet<Folder>? available,
173
Gee.BidirSortedSet<Folder>? unavailable);
176
* Fired when new folders have been created.
178
* This is fired in response to new folders appearing, for example
179
* the user created a new folder. It will be fired after {@link
180
* folders_available_unavailable} has been fired to mark the
181
* folders as having been made available.
183
* Folders are ordered for the convenience of the caller from the
184
* top of the hierarchy to lower in the hierarchy. In other
185
* words, parents are listed before children, assuming the
186
* collection is traversed in natural order.
188
public signal void folders_created(Gee.BidirSortedSet<Geary.Folder> created);
191
* Fired when existing folders are deleted.
193
* This is fired in response to existing folders being removed,
194
* for example if the user deleted a folder. it will be fired
195
* after {@link folders_available_unavailable} has been fired to
196
* mark the folders as having been made unavailable.
198
* Folders are ordered for the convenience of the caller from the
199
* top of the hierarchy to lower in the hierarchy. In other
200
* words, parents are listed before children, assuming the
201
* collection is traversed in natural order.
203
public signal void folders_deleted(Gee.BidirSortedSet<Geary.Folder> deleted);
206
* Fired when a Folder's contents is detected having changed.
208
public signal void folders_contents_altered(Gee.Collection<Geary.Folder> altered);
211
* Fired when a Folder's type is detected having changed.
213
public signal void folders_special_type(Gee.Collection<Geary.Folder> altered);
216
* Fired when emails are appended to a folder in this account.
218
public signal void email_appended(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids);
221
* Fired when emails are inserted to a folder in this account.
223
* @see Folder.email_inserted
225
public signal void email_inserted(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids);
228
* Fired when emails are removed from a folder in this account.
230
public signal void email_removed(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids);
233
* Fired when one or more emails have been locally saved to a folder with
234
* the full set of Fields.
236
public signal void email_locally_complete(Geary.Folder folder,
237
Gee.Collection<Geary.EmailIdentifier> ids);
240
* Fired when one or more emails have been discovered (added) to the Folder, but not necessarily
241
* appended (i.e. old email pulled down due to user request or background fetching).
243
public signal void email_discovered(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids);
246
* Fired when the supplied email flags have changed from any folder.
248
public signal void email_flags_changed(Geary.Folder folder,
249
Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map);
252
public Logging.Flag loggable_flags {
253
get; protected set; default = Logging.Flag.ALL;
257
public Loggable? loggable_parent { get { return null; } }
260
protected Account(AccountInformation information,
261
ClientService incoming,
262
ClientService outgoing) {
263
this.information = information;
264
this.incoming = incoming;
265
this.outgoing = outgoing;
267
incoming.notify["current-status"].connect(
268
on_service_status_notify
270
outgoing.notify["current-status"].connect(
271
on_service_status_notify
276
* Opens the {@link Account} and makes it and its {@link Folder}s available for use.
278
* @throws EngineError.CORRUPT if the local store is corrupt or unusable
279
* @throws EngineError.PERMISSIONS if the local store is inaccessible
280
* @throws EngineError.VERSION if the local store was created or updated for a different
283
public abstract async void open_async(Cancellable? cancellable = null) throws Error;
286
* Closes the {@link Account}, which makes most its operations unavailable.
288
* This does not delete the Account, merely closes database and network channels.
290
* Returns without error if the Account is already closed.
292
public abstract async void close_async(Cancellable? cancellable = null) throws Error;
295
* Returns true if this account is open, else false.
297
public abstract bool is_open();
300
* Rebuild the local data stores for this {@link Account}.
302
* This should only be used if {@link open_async} throws {@link EngineError.CORRUPT},
303
* indicating that the local data store is corrupted and cannot be used.
305
* ''rebuild_async() will delete all local data''.
307
* If the Account is backed by a synchronized copy on the network, it will rebuild its local
308
* mail store. If not, the data is forever deleted. Hence, it's best to query the user before
309
* calling this method.
311
* Unlike most methods in Account, this should only be called when the Account is closed.
313
public abstract async void rebuild_async(Cancellable? cancellable = null) throws Error;
316
* Returns an email identifier from its serialised form.
318
* This is useful for converting a string representation of a
319
* email id back into an actual instance of an id. This does not
320
* guarantee that the email represented by the id will exist.
322
* @see EmailIdentifier.to_variant
323
* @throws EngineError.BAD_PARAMETERS when the variant is not the
324
* have the correct type.
326
public abstract EmailIdentifier to_email_identifier(GLib.Variant serialised)
327
throws EngineError.BAD_PARAMETERS;
330
* Returns the folder path from its serialised form.
332
* This is useful for converting a string representation of a
333
* folder path back into an actual instance of a path. This does
334
* not guarantee that the folder represented by the path will
337
* @see FolderPath.to_variant
338
* @throws EngineError.BAD_PARAMETERS when the variant is not the
339
* have the correct type or if no folder root with an appropriate
342
public abstract FolderPath to_folder_path(GLib.Variant serialised)
343
throws EngineError.BAD_PARAMETERS;
346
* Determines if a folder is known to the engine.
348
* This method only considers currently known folders, it does not
349
* check the remote to see if a previously folder exists.
351
public virtual bool has_folder(FolderPath path) {
355
} catch (EngineError.NOT_FOUND err) {
361
* Returns the folder represented by a specific path.
363
* This method only considers currently known folders, it does not
364
* check the remote to see if a previously folder exists.
366
* @throws EngineError.NOT_FOUND if the folder does not exist.
368
public abstract Folder get_folder(FolderPath path)
369
throws EngineError.NOT_FOUND;
372
* Lists all the currently-available folders found under the parent path
373
* unless it's null, in which case it lists all the root folders. If the
374
* parent path cannot be found, EngineError.NOT_FOUND is thrown. If no
375
* folders exist in the root, EngineError.NOT_FOUND may be thrown as well.
376
* However, the caller should be prepared to deal with an empty list being
379
* The same Geary.Folder objects (instances) will be returned if the same path is submitted
380
* multiple times. This means that multiple callers may be holding references to the same
381
* Folders. This is important when thinking of opening and closing folders and signal
384
public abstract Gee.Collection<Geary.Folder> list_matching_folders(Geary.FolderPath? parent)
388
* Lists all currently-available folders. See caveats under
389
* list_matching_folders().
391
public abstract Gee.Collection<Geary.Folder> list_folders() throws Error;
394
* Returns the folder representing the given special folder type. If no such folder exists,
397
public virtual Geary.Folder? get_special_folder(Geary.SpecialFolderType special) throws Error {
398
return traverse<Folder>(list_folders())
399
.first_matching(f => f.special_folder_type == special);
403
* Returns the Folder object with the given special folder type. The folder will be
404
* created on the server if it doesn't already exist. An error will be thrown if the
405
* folder doesn't exist and can't be created. The only valid special folder types that
406
* can be required are: DRAFTS, SENT, SPAM, and TRASH.
408
public abstract async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special,
409
Cancellable? cancellable = null) throws Error;
412
* Submits a ComposedEmail for delivery. Messages may be scheduled for later delivery or immediately
413
* sent. Subscribe to the "email-sent" signal to be notified of delivery. Note that that signal
414
* does not return the ComposedEmail object but an RFC822-formatted object. Allowing for the
415
* subscriber to attach some kind of token for later comparison is being considered.
417
public abstract async void send_email_async(Geary.ComposedEmail composed, Cancellable? cancellable = null)
421
* Search the local account for emails referencing a Message-ID value
422
* (which can appear in the Message-ID header itself, as well as the
423
* In-Reply-To header, and maybe more places). Fetch the requested fields,
424
* optionally ignoring emails that don't have the requested fields set.
425
* Don't include emails that appear in any of the blacklisted folders in
426
* the result. If null is included in the blacklist, omit emails appearing
427
* in no folders. Return a map of Email object to a list of FolderPaths
428
* it's in, which can be null if it's in no folders.
430
public abstract async Gee.MultiMap<Geary.Email, Geary.FolderPath?>? local_search_message_id_async(
431
Geary.RFC822.MessageID message_id, Geary.Email.Field requested_fields, bool partial_ok,
432
Gee.Collection<Geary.FolderPath?>? folder_blacklist, Geary.EmailFlags? flag_blacklist,
433
Cancellable? cancellable = null) throws Error;
436
* Return a single email fulfilling the required fields. The email to pull
437
* is identified by an EmailIdentifier from a previous call to
438
* local_search_message_id_async() or local_search_async(). Throw
439
* EngineError.NOT_FOUND if the email isn't found and
440
* EngineError.INCOMPLETE_MESSAGE if the fields aren't available.
442
public abstract async Geary.Email local_fetch_email_async(Geary.EmailIdentifier email_id,
443
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error;
446
* Create a new {@link SearchQuery} for this {@link Account}.
448
* See {@link Geary.SearchQuery.Strategy} for more information about how its interpreted by the
449
* Engine. In particular, note that it's an advisory parameter only and may have no effect,
450
* especially on server searches. However, it may also have a dramatic effect on what search
451
* results are returned and so should be used with some caution. Whether this parameter is
452
* user-configurable, available through GSettings or another configuration mechanism, or simply
453
* baked into the caller's code is up to the caller. CONSERVATIVE is designed to be a good
456
* The SearchQuery object can only be used with calls into this Account.
458
* Dropping the last reference to the SearchQuery will close it.
460
public abstract async Geary.SearchQuery open_search(string query,
461
SearchQuery.Strategy strategy,
462
GLib.Cancellable? cancellable)
466
* Performs a search with the given query. Optionally, a list of folders not to search
467
* can be passed as well as a list of email identifiers to restrict the search to only those messages.
468
* Returns a list of EmailIdentifiers, or null if there are no results.
469
* The list is limited to a maximum number of results and starting offset,
470
* so you can walk the table. limit can be negative to mean "no limit" but
471
* offset must not be negative.
473
public abstract async Gee.Collection<Geary.EmailIdentifier>? local_search_async(Geary.SearchQuery query,
474
int limit = 100, int offset = 0, Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
475
Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws Error;
478
* Given a list of mail IDs, returns a set of casefolded words that match for the query.
480
public abstract async Gee.Set<string>? get_search_matches_async(Geary.SearchQuery query,
481
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
484
* Return a map of each passed-in email identifier to the set of folders
485
* that contain it. If an email id doesn't appear in the resulting map,
486
* it isn't contained in any folders. Return null if the resulting map
487
* would be empty. Only throw database errors et al., not errors due to
488
* the email id not being found.
490
public abstract async Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath>? get_containing_folders_async(
491
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable) throws Error;
494
public virtual string to_string() {
495
return "%s(%s)".printf(
496
this.get_type().name(),
501
/** Fires a {@link opened} signal. */
502
protected virtual void notify_opened() {
506
/** Fires a {@link closed} signal. */
507
protected virtual void notify_closed() {
511
/** Fires a {@link folders_available_unavailable} signal. */
512
protected virtual void
513
notify_folders_available_unavailable(Gee.BidirSortedSet<Folder>? available,
514
Gee.BidirSortedSet<Folder>? unavailable) {
515
folders_available_unavailable(available, unavailable);
518
/** Fires a {@link folders_created} signal. */
519
protected virtual void notify_folders_created(Gee.BidirSortedSet<Geary.Folder> created) {
520
folders_created(created);
523
/** Fires a {@link folders_deleted} signal. */
524
protected virtual void notify_folders_deleted(Gee.BidirSortedSet<Geary.Folder> deleted) {
525
folders_deleted(deleted);
528
/** Fires a {@link folders_contents_altered} signal. */
529
protected virtual void notify_folders_contents_altered(Gee.Collection<Geary.Folder> altered) {
530
folders_contents_altered(altered);
533
/** Fires a {@link email_appended} signal. */
534
protected virtual void notify_email_appended(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids) {
535
email_appended(folder, ids);
538
/** Fires a {@link email_inserted} signal. */
539
protected virtual void notify_email_inserted(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids) {
540
email_inserted(folder, ids);
543
/** Fires a {@link email_removed} signal. */
544
protected virtual void notify_email_removed(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids) {
545
email_removed(folder, ids);
548
/** Fires a {@link email_locally_complete} signal. */
549
protected virtual void notify_email_locally_complete(Geary.Folder folder,
550
Gee.Collection<Geary.EmailIdentifier> ids) {
551
email_locally_complete(folder, ids);
554
/** Fires a {@link email_discovered} signal. */
555
protected virtual void notify_email_discovered(Geary.Folder folder,
556
Gee.Collection<Geary.EmailIdentifier> ids) {
557
email_discovered(folder, ids);
560
/** Fires a {@link email_flags_changed} signal. */
561
protected virtual void notify_email_flags_changed(Geary.Folder folder,
562
Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> flag_map) {
563
email_flags_changed(folder, flag_map);
566
protected virtual void notify_email_sent(RFC822.Message message) {
570
/** Fires a {@link report_problem} signal for this account. */
571
protected virtual void notify_report_problem(ProblemReport report) {
572
report_problem(report);
576
* Fires a {@link report_problem} signal for this account.
578
protected virtual void notify_account_problem(Error? err) {
579
report_problem(new AccountProblemReport(this.information, err));
582
/** Fires a {@link report_problem} signal for a service for this account. */
583
protected virtual void notify_service_problem(ServiceInformation service,
586
new ServiceProblemReport(this.information, service, err)
590
private void on_service_status_notify() {
591
Status new_status = 0;
592
// Don't consider service status UNKNOWN to indicate being
593
// offline, since clients will indicate offline status, but
594
// not indicate online status. So when at startup, or when
595
// restarting services, we don't want to cause them to
596
// spuriously indicate being offline.
597
if (incoming.current_status != UNREACHABLE) {
598
new_status |= ONLINE;
600
if (incoming.current_status.is_error() ||
601
outgoing.current_status.is_error()) {
602
new_status |= SERVICE_PROBLEM;
604
this.current_status = new_status;