1 /*
2 * e-shell-backend.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
18 *
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 * Copyright (C) 2009 Intel Corporation
21 */
22
23 /**
24 * SECTION: e-shell-backend
25 * @short_description: dynamically loaded capabilities
26 * @include: shell/e-shell-backend.h
27 **/
28
29 #include "evolution-config.h"
30
31 #include "e-shell-backend.h"
32
33 #include <errno.h>
34 #include <glib/gi18n.h>
35
36 #include "e-util/e-util.h"
37
38 #include "e-shell.h"
39 #include "e-shell-view.h"
40
41 #define E_SHELL_BACKEND_GET_PRIVATE(obj) \
42 (G_TYPE_INSTANCE_GET_PRIVATE \
43 ((obj), E_TYPE_SHELL_BACKEND, EShellBackendPrivate))
44
45 struct _EShellBackendPrivate {
46
47 /* We keep a reference to corresponding EShellView subclass
48 * since it keeps a reference back to us. This ensures the
49 * subclass is not finalized before we are, otherwise it
50 * would leak its EShellBackend reference. */
51 EShellViewClass *shell_view_class;
52
53 /* This tracks what the backend is busy doing. */
54 GQueue *activities;
55
56 gchar *config_dir;
57 gchar *data_dir;
58 gchar *prefer_new_item;
59
60 /* This is set to delay shutdown. */
61 gulong notify_busy_handler_id;
62
63 guint started : 1;
64 };
65
66 enum {
67 PROP_0,
68 PROP_BUSY,
69 PROP_PREFER_NEW_ITEM
70 };
71
72 enum {
73 ACTIVITY_ADDED,
74 LAST_SIGNAL
75 };
76
77 static guint signals[LAST_SIGNAL];
78
G_DEFINE_ABSTRACT_TYPE(EShellBackend,e_shell_backend,E_TYPE_EXTENSION)79 G_DEFINE_ABSTRACT_TYPE (EShellBackend, e_shell_backend, E_TYPE_EXTENSION)
80
81 static void
82 shell_backend_debug_list_activities (EShellBackend *shell_backend)
83 {
84 EShellBackendClass *class;
85 GList *head, *link;
86 guint n_activities;
87
88 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
89 g_return_if_fail (class != NULL);
90
91 n_activities = g_queue_get_length (shell_backend->priv->activities);
92
93 if (n_activities == 0)
94 return;
95
96 g_debug (
97 "%u active '%s' %s:",
98 n_activities, class->name,
99 (n_activities == 1) ? "activity" : "activities");
100
101 head = g_queue_peek_head_link (shell_backend->priv->activities);
102
103 for (link = head; link != NULL; link = g_list_next (link)) {
104 EActivity *activity = E_ACTIVITY (link->data);
105 gchar *description;
106 const gchar *was;
107
108 description = e_activity_describe (activity);
109 was = e_activity_get_last_known_text (activity);
110
111 if (description != NULL)
112 g_debug ("* %s", description);
113 else if (was != NULL)
114 g_debug ("* (was \"%s\")", was);
115 else
116 g_debug ("* (no description)");
117
118 g_free (description);
119 }
120 }
121
122 static void
shell_backend_activity_finalized_cb(EShellBackend * shell_backend,EActivity * finalized_activity)123 shell_backend_activity_finalized_cb (EShellBackend *shell_backend,
124 EActivity *finalized_activity)
125 {
126 g_queue_remove (shell_backend->priv->activities, finalized_activity);
127
128 /* Only emit "notify::busy" when switching from busy to idle. */
129 if (g_queue_is_empty (shell_backend->priv->activities))
130 g_object_notify (G_OBJECT (shell_backend), "busy");
131
132 g_object_unref (shell_backend);
133 }
134
135 static void
shell_backend_notify_busy_cb(EShellBackend * shell_backend,GParamSpec * pspec,EActivity * activity)136 shell_backend_notify_busy_cb (EShellBackend *shell_backend,
137 GParamSpec *pspec,
138 EActivity *activity)
139 {
140 shell_backend_debug_list_activities (shell_backend);
141
142 if (!e_shell_backend_is_busy (shell_backend)) {
143 /* Disconnecting this signal handler will unreference the
144 * EActivity and allow the shell to proceed with shutdown. */
145 g_signal_handler_disconnect (
146 shell_backend,
147 shell_backend->priv->notify_busy_handler_id);
148 shell_backend->priv->notify_busy_handler_id = 0;
149 }
150 }
151
152 static void
shell_backend_prepare_for_quit_cb(EShell * shell,EActivity * activity,EShellBackend * shell_backend)153 shell_backend_prepare_for_quit_cb (EShell *shell,
154 EActivity *activity,
155 EShellBackend *shell_backend)
156 {
157 shell_backend_debug_list_activities (shell_backend);
158
159 if (e_shell_backend_is_busy (shell_backend)) {
160 gulong handler_id;
161
162 /* Referencing the EActivity delays shutdown; the
163 * reference count acts like a counting semaphore. */
164 handler_id = g_signal_connect_data (
165 shell_backend, "notify::busy",
166 G_CALLBACK (shell_backend_notify_busy_cb),
167 g_object_ref (activity),
168 (GClosureNotify) g_object_unref, 0);
169 shell_backend->priv->notify_busy_handler_id = handler_id;
170 }
171 }
172
173 static GObject *
shell_backend_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)174 shell_backend_constructor (GType type,
175 guint n_construct_properties,
176 GObjectConstructParam *construct_properties)
177 {
178 EShellBackend *shell_backend;
179 EShellBackendClass *class;
180 EShellViewClass *shell_view_class;
181 EShell *shell;
182 GObject *object;
183
184 /* Chain up to parent's construct() method. */
185 object = G_OBJECT_CLASS (e_shell_backend_parent_class)->constructor (
186 type, n_construct_properties, construct_properties);
187
188 shell_backend = E_SHELL_BACKEND (object);
189 shell = e_shell_backend_get_shell (shell_backend);
190
191 /* Install a reference to ourselves in the
192 * corresponding EShellViewClass structure. */
193 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
194 g_return_val_if_fail (class != NULL, object);
195
196 shell_view_class = g_type_class_ref (class->shell_view_type);
197 shell_view_class->shell_backend = g_object_ref (shell_backend);
198 shell_backend->priv->shell_view_class = shell_view_class;
199
200 g_signal_connect (
201 shell, "prepare-for-quit",
202 G_CALLBACK (shell_backend_prepare_for_quit_cb),
203 shell_backend);
204
205 return object;
206 }
207
208 static void
shell_backend_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)209 shell_backend_get_property (GObject *object,
210 guint property_id,
211 GValue *value,
212 GParamSpec *pspec)
213 {
214 switch (property_id) {
215 case PROP_BUSY:
216 g_value_set_boolean (
217 value, e_shell_backend_is_busy (
218 E_SHELL_BACKEND (object)));
219 return;
220
221 case PROP_PREFER_NEW_ITEM:
222 g_value_set_string (
223 value,
224 e_shell_backend_get_prefer_new_item (
225 E_SHELL_BACKEND (object)));
226 return;
227 }
228
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
230 }
231
232 static void
shell_backend_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)233 shell_backend_set_property (GObject *object,
234 guint property_id,
235 const GValue *value,
236 GParamSpec *pspec)
237 {
238 switch (property_id) {
239 case PROP_PREFER_NEW_ITEM:
240 e_shell_backend_set_prefer_new_item (
241 E_SHELL_BACKEND (object),
242 g_value_get_string (value));
243 return;
244 }
245
246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
247 }
248
249 static void
shell_backend_dispose(GObject * object)250 shell_backend_dispose (GObject *object)
251 {
252 EShellBackendPrivate *priv;
253
254 priv = E_SHELL_BACKEND_GET_PRIVATE (object);
255 g_clear_pointer (&priv->shell_view_class, g_type_class_unref);
256
257 if (priv->notify_busy_handler_id > 0) {
258 g_signal_handler_disconnect (
259 object, priv->notify_busy_handler_id);
260 priv->notify_busy_handler_id = 0;
261 }
262
263 /* Chain up to parent's dispose() method. */
264 G_OBJECT_CLASS (e_shell_backend_parent_class)->dispose (object);
265 }
266
267 static void
shell_backend_finalize(GObject * object)268 shell_backend_finalize (GObject *object)
269 {
270 EShellBackendPrivate *priv;
271
272 priv = E_SHELL_BACKEND_GET_PRIVATE (object);
273
274 g_warn_if_fail (g_queue_is_empty (priv->activities));
275 g_queue_free (priv->activities);
276
277 g_free (priv->config_dir);
278 g_free (priv->data_dir);
279 g_free (priv->prefer_new_item);
280
281 /* Chain up to parent's finalize() method. */
282 G_OBJECT_CLASS (e_shell_backend_parent_class)->finalize (object);
283 }
284
285 static const gchar *
shell_backend_get_config_dir(EShellBackend * shell_backend)286 shell_backend_get_config_dir (EShellBackend *shell_backend)
287 {
288 EShellBackendClass *class;
289
290 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
291 g_return_val_if_fail (class != NULL, NULL);
292
293 /* Determine the user config directory for this backend. */
294 if (G_UNLIKELY (shell_backend->priv->config_dir == NULL)) {
295 const gchar *user_config_dir;
296
297 user_config_dir = e_get_user_config_dir ();
298 shell_backend->priv->config_dir =
299 g_build_filename (user_config_dir, class->name, NULL);
300 g_mkdir_with_parents (shell_backend->priv->config_dir, 0700);
301 }
302
303 return shell_backend->priv->config_dir;
304 }
305
306 static const gchar *
shell_backend_get_data_dir(EShellBackend * shell_backend)307 shell_backend_get_data_dir (EShellBackend *shell_backend)
308 {
309 EShellBackendClass *class;
310
311 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
312 g_return_val_if_fail (class != NULL, NULL);
313
314 /* Determine the user data directory for this backend. */
315 if (G_UNLIKELY (shell_backend->priv->data_dir == NULL)) {
316 const gchar *user_data_dir;
317
318 user_data_dir = e_get_user_data_dir ();
319 shell_backend->priv->data_dir =
320 g_build_filename (user_data_dir, class->name, NULL);
321 g_mkdir_with_parents (shell_backend->priv->data_dir, 0700);
322 }
323
324 return shell_backend->priv->data_dir;
325 }
326
327 static void
e_shell_backend_class_init(EShellBackendClass * class)328 e_shell_backend_class_init (EShellBackendClass *class)
329 {
330 GObjectClass *object_class;
331 EExtensionClass *extension_class;
332
333 g_type_class_add_private (class, sizeof (EShellBackendPrivate));
334
335 object_class = G_OBJECT_CLASS (class);
336 object_class->constructor = shell_backend_constructor;
337 object_class->get_property = shell_backend_get_property;
338 object_class->set_property = shell_backend_set_property;
339 object_class->dispose = shell_backend_dispose;
340 object_class->finalize = shell_backend_finalize;
341
342 extension_class = E_EXTENSION_CLASS (class);
343 extension_class->extensible_type = E_TYPE_SHELL;
344
345 class->get_config_dir = shell_backend_get_config_dir;
346 class->get_data_dir = shell_backend_get_data_dir;
347
348 /**
349 * EShellBackend:busy
350 *
351 * Whether any activities are still in progress.
352 **/
353 g_object_class_install_property (
354 object_class,
355 PROP_BUSY,
356 g_param_spec_boolean (
357 "busy",
358 "Busy",
359 "Whether any activities are still in progress",
360 FALSE,
361 G_PARAM_READABLE |
362 G_PARAM_STATIC_STRINGS));
363
364 /**
365 * EShellBackend:prefer-new-item
366 *
367 * Name of an item to prefer in New toolbar button; can be NULL
368 **/
369 g_object_class_install_property (
370 object_class,
371 PROP_PREFER_NEW_ITEM,
372 g_param_spec_string (
373 "prefer-new-item",
374 "Prefer New Item",
375 "Name of an item to prefer in New toolbar button",
376 NULL,
377 G_PARAM_READWRITE |
378 G_PARAM_STATIC_STRINGS));
379
380 /**
381 * EShellBackend::activity-added
382 * @shell_backend: the #EShellBackend that emitted the signal
383 * @activity: an #EActivity
384 *
385 * Broadcasts a newly added activity.
386 **/
387 signals[ACTIVITY_ADDED] = g_signal_new (
388 "activity-added",
389 G_OBJECT_CLASS_TYPE (object_class),
390 G_SIGNAL_RUN_LAST,
391 0, NULL, NULL,
392 g_cclosure_marshal_VOID__OBJECT,
393 G_TYPE_NONE, 1,
394 E_TYPE_ACTIVITY);
395 }
396
397 static void
e_shell_backend_init(EShellBackend * shell_backend)398 e_shell_backend_init (EShellBackend *shell_backend)
399 {
400 shell_backend->priv = E_SHELL_BACKEND_GET_PRIVATE (shell_backend);
401 shell_backend->priv->activities = g_queue_new ();
402 }
403
404 /**
405 * e_shell_backend_compare:
406 * @shell_backend_a: an #EShellBackend
407 * @shell_backend_b: an #EShellBackend
408 *
409 * Using the <structfield>sort_order</structfield> field from both backends'
410 * #EShellBackendClass, compares @shell_backend_a with @shell_mobule_b and
411 * returns -1, 0 or +1 if @shell_backend_a is found to be less than, equal
412 * to or greater than @shell_backend_b, respectively.
413 *
414 * Returns: -1, 0 or +1, for a less than, equal to or greater than result
415 **/
416 gint
e_shell_backend_compare(EShellBackend * shell_backend_a,EShellBackend * shell_backend_b)417 e_shell_backend_compare (EShellBackend *shell_backend_a,
418 EShellBackend *shell_backend_b)
419 {
420 EShellBackendClass *a_klass, *b_klass;
421 gint aa, bb;
422
423 a_klass = E_SHELL_BACKEND_GET_CLASS (shell_backend_a);
424 b_klass = E_SHELL_BACKEND_GET_CLASS (shell_backend_b);
425
426 g_return_val_if_fail (a_klass != NULL, 0);
427 g_return_val_if_fail (b_klass != NULL, 0);
428
429 aa = a_klass->sort_order;
430 bb = b_klass->sort_order;
431
432 return (aa < bb) ? -1 : (aa > bb) ? 1 : 0;
433 }
434
435 /**
436 * e_shell_backend_get_config_dir:
437 * @shell_backend: an #EShellBackend
438 *
439 * Returns the absolute path to the configuration directory for
440 * @shell_backend. The string is owned by @shell_backend and should
441 * not be modified or freed.
442 *
443 * Returns: the backend's configuration directory
444 **/
445 const gchar *
e_shell_backend_get_config_dir(EShellBackend * shell_backend)446 e_shell_backend_get_config_dir (EShellBackend *shell_backend)
447 {
448 EShellBackendClass *class;
449
450 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);
451
452 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
453 g_return_val_if_fail (class != NULL, NULL);
454 g_return_val_if_fail (class->get_config_dir != NULL, NULL);
455
456 return class->get_config_dir (shell_backend);
457 }
458
459 /**
460 * e_shell_backend_get_data_dir:
461 * @shell_backend: an #EShellBackend
462 *
463 * Returns the absolute path to the data directory for @shell_backend.
464 * The string is owned by @shell_backend and should not be modified or
465 * freed.
466 *
467 * Returns: the backend's data directory
468 **/
469 const gchar *
e_shell_backend_get_data_dir(EShellBackend * shell_backend)470 e_shell_backend_get_data_dir (EShellBackend *shell_backend)
471 {
472 EShellBackendClass *class;
473
474 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);
475
476 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
477 g_return_val_if_fail (class != NULL, NULL);
478 g_return_val_if_fail (class->get_data_dir != NULL, NULL);
479
480 return class->get_data_dir (shell_backend);
481 }
482
483 /**
484 * e_shell_backend_get_shell:
485 * @shell_backend: an #EShellBackend
486 *
487 * Returns the #EShell singleton.
488 *
489 * Returns: the #EShell
490 **/
491 EShell *
e_shell_backend_get_shell(EShellBackend * shell_backend)492 e_shell_backend_get_shell (EShellBackend *shell_backend)
493 {
494 EExtensible *extensible;
495
496 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);
497
498 extensible = e_extension_get_extensible (E_EXTENSION (shell_backend));
499
500 return E_SHELL (extensible);
501 }
502
503 /**
504 * e_shell_backend_add_activity:
505 * @shell_backend: an #EShellBackend
506 * @activity: an #EActivity
507 *
508 * Emits an #EShellBackend::activity-added signal and tracks the @activity
509 * until it is finalized.
510 **/
511 void
e_shell_backend_add_activity(EShellBackend * shell_backend,EActivity * activity)512 e_shell_backend_add_activity (EShellBackend *shell_backend,
513 EActivity *activity)
514 {
515 EActivityState state;
516
517 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
518 g_return_if_fail (E_IS_ACTIVITY (activity));
519
520 state = e_activity_get_state (activity);
521
522 /* Disregard cancelled or completed activities. */
523
524 if (state == E_ACTIVITY_CANCELLED)
525 return;
526
527 if (state == E_ACTIVITY_COMPLETED)
528 return;
529
530 g_queue_push_tail (shell_backend->priv->activities, activity);
531
532 /* Emit the "activity-added" signal before adding a weak reference
533 * to the EActivity because EShellTaskbar's signal handler also adds
534 * a weak reference to the EActivity, and we want its GWeakNotify
535 * to run before ours, since ours may destroy the EShellTaskbar
536 * during application shutdown. */
537 g_signal_emit (shell_backend, signals[ACTIVITY_ADDED], 0, activity);
538
539 /* We reference the backend on every activity to
540 * guarantee the backend outlives the activity. */
541 g_object_weak_ref (
542 G_OBJECT (activity), (GWeakNotify)
543 shell_backend_activity_finalized_cb,
544 g_object_ref (shell_backend));
545
546 /* Only emit "notify::busy" when switching from idle to busy. */
547 if (g_queue_get_length (shell_backend->priv->activities) == 1)
548 g_object_notify (G_OBJECT (shell_backend), "busy");
549 }
550
551 /**
552 * e_shell_backend_is_busy:
553 * @shell_backend: an #EShellBackend
554 *
555 * Returns %TRUE if any activities passed to e_shell_backend_add_activity()
556 * are still in progress, %FALSE if the @shell_backend is currently idle.
557 *
558 * Returns: %TRUE if activities are still in progress
559 **/
560 gboolean
e_shell_backend_is_busy(EShellBackend * shell_backend)561 e_shell_backend_is_busy (EShellBackend *shell_backend)
562 {
563 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE);
564
565 return !g_queue_is_empty (shell_backend->priv->activities);
566 }
567
568 /**
569 * e_shell_backend_get_prefer_new_item:
570 * @shell_backend: an #EShellBackend
571 *
572 * Returns: Name of a preferred item in New toolbar button, %NULL or
573 * an empty string for no preference.
574 *
575 * Since: 3.4
576 **/
577 const gchar *
e_shell_backend_get_prefer_new_item(EShellBackend * shell_backend)578 e_shell_backend_get_prefer_new_item (EShellBackend *shell_backend)
579 {
580 g_return_val_if_fail (shell_backend != NULL, NULL);
581 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);
582
583 return shell_backend->priv->prefer_new_item;
584 }
585
586 /**
587 * e_shell_backend_set_prefer_new_item:
588 * @shell_backend: an #EShellBackend
589 * @prefer_new_item: name of an item
590 *
591 * Sets name of a preferred item in New toolbar button. Use %NULL or
592 * an empty string for no preference.
593 *
594 * Since: 3.4
595 **/
596 void
e_shell_backend_set_prefer_new_item(EShellBackend * shell_backend,const gchar * prefer_new_item)597 e_shell_backend_set_prefer_new_item (EShellBackend *shell_backend,
598 const gchar *prefer_new_item)
599 {
600 g_return_if_fail (shell_backend != NULL);
601 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
602
603 if (g_strcmp0 (shell_backend->priv->prefer_new_item, prefer_new_item) == 0)
604 return;
605
606 g_free (shell_backend->priv->prefer_new_item);
607 shell_backend->priv->prefer_new_item = g_strdup (prefer_new_item);
608
609 g_object_notify (G_OBJECT (shell_backend), "prefer-new-item");
610 }
611
612 /**
613 * e_shell_backend_cancel_all:
614 * @shell_backend: an #EShellBackend
615 *
616 * Cancels all activities passed to e_shell_backend_add_activity() that
617 * have not already been finalized. Note that an #EActivity can only be
618 * cancelled if it was given a #GCancellable object.
619 *
620 * Also, assuming all activities are cancellable, there may still be a
621 * delay before e_shell_backend_is_busy() returns %FALSE, because some
622 * activities may not be able to respond to the cancellation request
623 * immediately. Connect to the "notify::busy" signal if you need
624 * notification of @shell_backend becoming idle.
625 **/
626 void
e_shell_backend_cancel_all(EShellBackend * shell_backend)627 e_shell_backend_cancel_all (EShellBackend *shell_backend)
628 {
629 GList *list, *iter;
630
631 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
632
633 list = g_queue_peek_head_link (shell_backend->priv->activities);
634
635 for (iter = list; iter != NULL; iter = g_list_next (iter))
636 e_activity_cancel (E_ACTIVITY (iter->data));
637 }
638
639 /**
640 * e_shell_backend_start:
641 * @shell_backend: an #EShellBackend
642 *
643 * Tells the @shell_backend to begin loading data or running background
644 * tasks which may consume significant resources. This gets called in
645 * reponse to the user switching to the corresponding #EShellView for
646 * the first time. The function is idempotent for each @shell_backend.
647 **/
648 void
e_shell_backend_start(EShellBackend * shell_backend)649 e_shell_backend_start (EShellBackend *shell_backend)
650 {
651 EShellBackendClass *class;
652
653 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
654
655 if (shell_backend->priv->started)
656 return;
657
658 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
659 g_return_if_fail (class != NULL);
660
661 if (class->start != NULL)
662 class->start (shell_backend);
663
664 shell_backend->priv->started = TRUE;
665 }
666
667 /**
668 * e_shell_backend_is_started:
669 * @shell_backend: an #EShellBackend
670 *
671 * Returns whether e_shell_backend_start() was called for @shell_backend.
672 *
673 * Returns: whether @shell_backend is started
674 **/
675 gboolean
e_shell_backend_is_started(EShellBackend * shell_backend)676 e_shell_backend_is_started (EShellBackend *shell_backend)
677 {
678 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE);
679
680 return shell_backend->priv->started;
681 }
682
683 /**
684 * e_shell_backend_migrate:
685 * @shell_backend: an #EShellBackend
686 * @major: major part of version to migrate from
687 * @minor: minor part of version to migrate from
688 * @micro: micro part of version to migrate from
689 * @error: return location for a #GError, or %NULL
690 *
691 * Attempts to migrate data and settings from version %major.%minor.%micro.
692 * Returns %TRUE if the migration was successful or if no action was
693 * necessary. Returns %FALSE and sets %error if the migration failed.
694 *
695 * Returns: %TRUE if successful, %FALSE otherwise
696 **/
697 gboolean
e_shell_backend_migrate(EShellBackend * shell_backend,gint major,gint minor,gint micro,GError ** error)698 e_shell_backend_migrate (EShellBackend *shell_backend,
699 gint major,
700 gint minor,
701 gint micro,
702 GError **error)
703 {
704 EShellBackendClass *class;
705
706 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), TRUE);
707
708 class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
709 g_return_val_if_fail (class != NULL, TRUE);
710
711 if (class->migrate == NULL)
712 return TRUE;
713
714 return class->migrate (shell_backend, major, minor, micro, error);
715 }
716