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.
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
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/>.
17 * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 * Copyright (C) 2009 Intel Corporation
24 * SECTION: e-shell-backend
25 * @short_description: dynamically loaded capabilities
26 * @include: shell/e-shell-backend.h
29 #include "evolution-config.h"
31 #include "e-shell-backend.h"
34 #include <glib/gi18n.h>
36 #include "e-util/e-util.h"
39 #include "e-shell-view.h"
41 #define E_SHELL_BACKEND_GET_PRIVATE(obj) \
42 (G_TYPE_INSTANCE_GET_PRIVATE \
43 ((obj), E_TYPE_SHELL_BACKEND, EShellBackendPrivate))
45 struct _EShellBackendPrivate
{
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
;
53 /* This tracks what the backend is busy doing. */
58 gchar
*prefer_new_item
;
60 /* This is set to delay shutdown. */
61 gulong notify_busy_handler_id
;
77 static guint signals
[LAST_SIGNAL
];
79 G_DEFINE_ABSTRACT_TYPE (EShellBackend
, e_shell_backend
, E_TYPE_EXTENSION
)
82 shell_backend_debug_list_activities (EShellBackend
*shell_backend
)
84 EShellBackendClass
*class;
88 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
89 g_return_if_fail (class != NULL
);
91 n_activities
= g_queue_get_length (shell_backend
->priv
->activities
);
93 if (n_activities
== 0)
98 n_activities
, class->name
,
99 (n_activities
== 1) ? "activity" : "activities");
101 head
= g_queue_peek_head_link (shell_backend
->priv
->activities
);
103 for (link
= head
; link
!= NULL
; link
= g_list_next (link
)) {
104 EActivity
*activity
= E_ACTIVITY (link
->data
);
108 description
= e_activity_describe (activity
);
109 was
= e_activity_get_last_known_text (activity
);
111 if (description
!= NULL
)
112 g_debug ("* %s", description
);
113 else if (was
!= NULL
)
114 g_debug ("* (was \"%s\")", was
);
116 g_debug ("* (no description)");
118 g_free (description
);
123 shell_backend_activity_finalized_cb (EShellBackend
*shell_backend
,
124 EActivity
*finalized_activity
)
126 g_queue_remove (shell_backend
->priv
->activities
, finalized_activity
);
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");
132 g_object_unref (shell_backend
);
136 shell_backend_notify_busy_cb (EShellBackend
*shell_backend
,
140 shell_backend_debug_list_activities (shell_backend
);
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 (
147 shell_backend
->priv
->notify_busy_handler_id
);
148 shell_backend
->priv
->notify_busy_handler_id
= 0;
153 shell_backend_prepare_for_quit_cb (EShell
*shell
,
155 EShellBackend
*shell_backend
)
157 shell_backend_debug_list_activities (shell_backend
);
159 if (e_shell_backend_is_busy (shell_backend
)) {
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
;
174 shell_backend_constructor (GType type
,
175 guint n_construct_properties
,
176 GObjectConstructParam
*construct_properties
)
178 EShellBackend
*shell_backend
;
179 EShellBackendClass
*class;
180 EShellViewClass
*shell_view_class
;
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
);
188 shell_backend
= E_SHELL_BACKEND (object
);
189 shell
= e_shell_backend_get_shell (shell_backend
);
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
);
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
;
201 shell
, "prepare-for-quit",
202 G_CALLBACK (shell_backend_prepare_for_quit_cb
),
209 shell_backend_get_property (GObject
*object
,
214 switch (property_id
) {
216 g_value_set_boolean (
217 value
, e_shell_backend_is_busy (
218 E_SHELL_BACKEND (object
)));
221 case PROP_PREFER_NEW_ITEM
:
224 e_shell_backend_get_prefer_new_item (
225 E_SHELL_BACKEND (object
)));
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
233 shell_backend_set_property (GObject
*object
,
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
));
246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
250 shell_backend_dispose (GObject
*object
)
252 EShellBackendPrivate
*priv
;
254 priv
= E_SHELL_BACKEND_GET_PRIVATE (object
);
256 if (priv
->shell_view_class
!= NULL
) {
257 g_type_class_unref (priv
->shell_view_class
);
258 priv
->shell_view_class
= NULL
;
261 if (priv
->notify_busy_handler_id
> 0) {
262 g_signal_handler_disconnect (
263 object
, priv
->notify_busy_handler_id
);
264 priv
->notify_busy_handler_id
= 0;
267 /* Chain up to parent's dispose() method. */
268 G_OBJECT_CLASS (e_shell_backend_parent_class
)->dispose (object
);
272 shell_backend_finalize (GObject
*object
)
274 EShellBackendPrivate
*priv
;
276 priv
= E_SHELL_BACKEND_GET_PRIVATE (object
);
278 g_warn_if_fail (g_queue_is_empty (priv
->activities
));
279 g_queue_free (priv
->activities
);
281 g_free (priv
->config_dir
);
282 g_free (priv
->data_dir
);
283 g_free (priv
->prefer_new_item
);
285 /* Chain up to parent's finalize() method. */
286 G_OBJECT_CLASS (e_shell_backend_parent_class
)->finalize (object
);
290 shell_backend_get_config_dir (EShellBackend
*shell_backend
)
292 EShellBackendClass
*class;
294 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
295 g_return_val_if_fail (class != NULL
, NULL
);
297 /* Determine the user config directory for this backend. */
298 if (G_UNLIKELY (shell_backend
->priv
->config_dir
== NULL
)) {
299 const gchar
*user_config_dir
;
301 user_config_dir
= e_get_user_config_dir ();
302 shell_backend
->priv
->config_dir
=
303 g_build_filename (user_config_dir
, class->name
, NULL
);
304 g_mkdir_with_parents (shell_backend
->priv
->config_dir
, 0700);
307 return shell_backend
->priv
->config_dir
;
311 shell_backend_get_data_dir (EShellBackend
*shell_backend
)
313 EShellBackendClass
*class;
315 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
316 g_return_val_if_fail (class != NULL
, NULL
);
318 /* Determine the user data directory for this backend. */
319 if (G_UNLIKELY (shell_backend
->priv
->data_dir
== NULL
)) {
320 const gchar
*user_data_dir
;
322 user_data_dir
= e_get_user_data_dir ();
323 shell_backend
->priv
->data_dir
=
324 g_build_filename (user_data_dir
, class->name
, NULL
);
325 g_mkdir_with_parents (shell_backend
->priv
->data_dir
, 0700);
328 return shell_backend
->priv
->data_dir
;
332 e_shell_backend_class_init (EShellBackendClass
*class)
334 GObjectClass
*object_class
;
335 EExtensionClass
*extension_class
;
337 g_type_class_add_private (class, sizeof (EShellBackendPrivate
));
339 object_class
= G_OBJECT_CLASS (class);
340 object_class
->constructor
= shell_backend_constructor
;
341 object_class
->get_property
= shell_backend_get_property
;
342 object_class
->set_property
= shell_backend_set_property
;
343 object_class
->dispose
= shell_backend_dispose
;
344 object_class
->finalize
= shell_backend_finalize
;
346 extension_class
= E_EXTENSION_CLASS (class);
347 extension_class
->extensible_type
= E_TYPE_SHELL
;
349 class->get_config_dir
= shell_backend_get_config_dir
;
350 class->get_data_dir
= shell_backend_get_data_dir
;
355 * Whether any activities are still in progress.
357 g_object_class_install_property (
360 g_param_spec_boolean (
363 "Whether any activities are still in progress",
366 G_PARAM_STATIC_STRINGS
));
369 * EShellBackend:prefer-new-item
371 * Name of an item to prefer in New toolbar button; can be NULL
373 g_object_class_install_property (
375 PROP_PREFER_NEW_ITEM
,
376 g_param_spec_string (
379 "Name of an item to prefer in New toolbar button",
382 G_PARAM_STATIC_STRINGS
));
385 * EShellBackend::activity-added
386 * @shell_backend: the #EShellBackend that emitted the signal
387 * @activity: an #EActivity
389 * Broadcasts a newly added activity.
391 signals
[ACTIVITY_ADDED
] = g_signal_new (
393 G_OBJECT_CLASS_TYPE (object_class
),
396 g_cclosure_marshal_VOID__OBJECT
,
402 e_shell_backend_init (EShellBackend
*shell_backend
)
404 shell_backend
->priv
= E_SHELL_BACKEND_GET_PRIVATE (shell_backend
);
405 shell_backend
->priv
->activities
= g_queue_new ();
409 * e_shell_backend_compare:
410 * @shell_backend_a: an #EShellBackend
411 * @shell_backend_b: an #EShellBackend
413 * Using the <structfield>sort_order</structfield> field from both backends'
414 * #EShellBackendClass, compares @shell_backend_a with @shell_mobule_b and
415 * returns -1, 0 or +1 if @shell_backend_a is found to be less than, equal
416 * to or greater than @shell_backend_b, respectively.
418 * Returns: -1, 0 or +1, for a less than, equal to or greater than result
421 e_shell_backend_compare (EShellBackend
*shell_backend_a
,
422 EShellBackend
*shell_backend_b
)
424 EShellBackendClass
*a_klass
, *b_klass
;
427 a_klass
= E_SHELL_BACKEND_GET_CLASS (shell_backend_a
);
428 b_klass
= E_SHELL_BACKEND_GET_CLASS (shell_backend_b
);
430 g_return_val_if_fail (a_klass
!= NULL
, 0);
431 g_return_val_if_fail (b_klass
!= NULL
, 0);
433 aa
= a_klass
->sort_order
;
434 bb
= b_klass
->sort_order
;
436 return (aa
< bb
) ? -1 : (aa
> bb
) ? 1 : 0;
440 * e_shell_backend_get_config_dir:
441 * @shell_backend: an #EShellBackend
443 * Returns the absolute path to the configuration directory for
444 * @shell_backend. The string is owned by @shell_backend and should
445 * not be modified or freed.
447 * Returns: the backend's configuration directory
450 e_shell_backend_get_config_dir (EShellBackend
*shell_backend
)
452 EShellBackendClass
*class;
454 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), NULL
);
456 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
457 g_return_val_if_fail (class != NULL
, NULL
);
458 g_return_val_if_fail (class->get_config_dir
!= NULL
, NULL
);
460 return class->get_config_dir (shell_backend
);
464 * e_shell_backend_get_data_dir:
465 * @shell_backend: an #EShellBackend
467 * Returns the absolute path to the data directory for @shell_backend.
468 * The string is owned by @shell_backend and should not be modified or
471 * Returns: the backend's data directory
474 e_shell_backend_get_data_dir (EShellBackend
*shell_backend
)
476 EShellBackendClass
*class;
478 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), NULL
);
480 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
481 g_return_val_if_fail (class != NULL
, NULL
);
482 g_return_val_if_fail (class->get_data_dir
!= NULL
, NULL
);
484 return class->get_data_dir (shell_backend
);
488 * e_shell_backend_get_shell:
489 * @shell_backend: an #EShellBackend
491 * Returns the #EShell singleton.
493 * Returns: the #EShell
496 e_shell_backend_get_shell (EShellBackend
*shell_backend
)
498 EExtensible
*extensible
;
500 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), NULL
);
502 extensible
= e_extension_get_extensible (E_EXTENSION (shell_backend
));
504 return E_SHELL (extensible
);
508 * e_shell_backend_add_activity:
509 * @shell_backend: an #EShellBackend
510 * @activity: an #EActivity
512 * Emits an #EShellBackend::activity-added signal and tracks the @activity
513 * until it is finalized.
516 e_shell_backend_add_activity (EShellBackend
*shell_backend
,
519 EActivityState state
;
521 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend
));
522 g_return_if_fail (E_IS_ACTIVITY (activity
));
524 state
= e_activity_get_state (activity
);
526 /* Disregard cancelled or completed activities. */
528 if (state
== E_ACTIVITY_CANCELLED
)
531 if (state
== E_ACTIVITY_COMPLETED
)
534 g_queue_push_tail (shell_backend
->priv
->activities
, activity
);
536 /* Emit the "activity-added" signal before adding a weak reference
537 * to the EActivity because EShellTaskbar's signal handler also adds
538 * a weak reference to the EActivity, and we want its GWeakNotify
539 * to run before ours, since ours may destroy the EShellTaskbar
540 * during application shutdown. */
541 g_signal_emit (shell_backend
, signals
[ACTIVITY_ADDED
], 0, activity
);
543 /* We reference the backend on every activity to
544 * guarantee the backend outlives the activity. */
546 G_OBJECT (activity
), (GWeakNotify
)
547 shell_backend_activity_finalized_cb
,
548 g_object_ref (shell_backend
));
550 /* Only emit "notify::busy" when switching from idle to busy. */
551 if (g_queue_get_length (shell_backend
->priv
->activities
) == 1)
552 g_object_notify (G_OBJECT (shell_backend
), "busy");
556 * e_shell_backend_is_busy:
557 * @shell_backend: an #EShellBackend
559 * Returns %TRUE if any activities passed to e_shell_backend_add_activity()
560 * are still in progress, %FALSE if the @shell_backend is currently idle.
562 * Returns: %TRUE if activities are still in progress
565 e_shell_backend_is_busy (EShellBackend
*shell_backend
)
567 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), FALSE
);
569 return !g_queue_is_empty (shell_backend
->priv
->activities
);
573 * e_shell_backend_get_prefer_new_item:
574 * @shell_backend: an #EShellBackend
576 * Returns: Name of a preferred item in New toolbar button, %NULL or
577 * an empty string for no preference.
582 e_shell_backend_get_prefer_new_item (EShellBackend
*shell_backend
)
584 g_return_val_if_fail (shell_backend
!= NULL
, NULL
);
585 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), NULL
);
587 return shell_backend
->priv
->prefer_new_item
;
591 * e_shell_backend_set_prefer_new_item:
592 * @shell_backend: an #EShellBackend
593 * @prefer_new_item: name of an item
595 * Sets name of a preferred item in New toolbar button. Use %NULL or
596 * an empty string for no preference.
601 e_shell_backend_set_prefer_new_item (EShellBackend
*shell_backend
,
602 const gchar
*prefer_new_item
)
604 g_return_if_fail (shell_backend
!= NULL
);
605 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend
));
607 if (g_strcmp0 (shell_backend
->priv
->prefer_new_item
, prefer_new_item
) == 0)
610 g_free (shell_backend
->priv
->prefer_new_item
);
611 shell_backend
->priv
->prefer_new_item
= g_strdup (prefer_new_item
);
613 g_object_notify (G_OBJECT (shell_backend
), "prefer-new-item");
617 * e_shell_backend_cancel_all:
618 * @shell_backend: an #EShellBackend
620 * Cancels all activities passed to e_shell_backend_add_activity() that
621 * have not already been finalized. Note that an #EActivity can only be
622 * cancelled if it was given a #GCancellable object.
624 * Also, assuming all activities are cancellable, there may still be a
625 * delay before e_shell_backend_is_busy() returns %FALSE, because some
626 * activities may not be able to respond to the cancellation request
627 * immediately. Connect to the "notify::busy" signal if you need
628 * notification of @shell_backend becoming idle.
631 e_shell_backend_cancel_all (EShellBackend
*shell_backend
)
635 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend
));
637 list
= g_queue_peek_head_link (shell_backend
->priv
->activities
);
639 for (iter
= list
; iter
!= NULL
; iter
= g_list_next (iter
))
640 e_activity_cancel (E_ACTIVITY (iter
->data
));
644 * e_shell_backend_start:
645 * @shell_backend: an #EShellBackend
647 * Tells the @shell_backend to begin loading data or running background
648 * tasks which may consume significant resources. This gets called in
649 * reponse to the user switching to the corresponding #EShellView for
650 * the first time. The function is idempotent for each @shell_backend.
653 e_shell_backend_start (EShellBackend
*shell_backend
)
655 EShellBackendClass
*class;
657 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend
));
659 if (shell_backend
->priv
->started
)
662 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
663 g_return_if_fail (class != NULL
);
665 if (class->start
!= NULL
)
666 class->start (shell_backend
);
668 shell_backend
->priv
->started
= TRUE
;
672 * e_shell_backend_is_started:
673 * @shell_backend: an #EShellBackend
675 * Returns whether e_shell_backend_start() was called for @shell_backend.
677 * Returns: whether @shell_backend is started
680 e_shell_backend_is_started (EShellBackend
*shell_backend
)
682 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), FALSE
);
684 return shell_backend
->priv
->started
;
688 * e_shell_backend_migrate:
689 * @shell_backend: an #EShellBackend
690 * @major: major part of version to migrate from
691 * @minor: minor part of version to migrate from
692 * @micro: micro part of version to migrate from
693 * @error: return location for a #GError, or %NULL
695 * Attempts to migrate data and settings from version %major.%minor.%micro.
696 * Returns %TRUE if the migration was successful or if no action was
697 * necessary. Returns %FALSE and sets %error if the migration failed.
699 * Returns: %TRUE if successful, %FALSE otherwise
702 e_shell_backend_migrate (EShellBackend
*shell_backend
,
708 EShellBackendClass
*class;
710 g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend
), TRUE
);
712 class = E_SHELL_BACKEND_GET_CLASS (shell_backend
);
713 g_return_val_if_fail (class != NULL
, TRUE
);
715 if (class->migrate
== NULL
)
718 return class->migrate (shell_backend
, major
, minor
, micro
, error
);