evolution-3.6.4/shell/e-shell-backend.c

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 }