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