1 /*
2  * e-activity.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  */
17 
18 /**
19  * SECTION: e-activity
20  * @include: e-util/e-util.h
21  * @short_description: Describe activities in progress
22  *
23  * #EActivity is used to track and describe application activities in
24  * progress.  An #EActivity usually manifests in a user interface as a
25  * status bar message (see #EActivityProxy) or information bar message
26  * (see #EActivityBar), with optional progress indication and a cancel
27  * button which is linked to a #GCancellable.
28  **/
29 
30 #include "evolution-config.h"
31 
32 #include "e-activity.h"
33 
34 #include <stdarg.h>
35 #include <glib/gi18n.h>
36 #include <camel/camel.h>
37 #include <libedataserver/libedataserver.h>
38 
39 #include "e-util-enumtypes.h"
40 
41 #define E_ACTIVITY_GET_PRIVATE(obj) \
42 	(G_TYPE_INSTANCE_GET_PRIVATE \
43 	((obj), E_TYPE_ACTIVITY, EActivityPrivate))
44 
45 struct _EActivityPrivate {
46 	GCancellable *cancellable;
47 	EAlertSink *alert_sink;
48 	EActivityState state;
49 
50 	gchar *icon_name;
51 	gchar *text;
52 	gchar *last_known_text;
53 	gdouble percent;
54 
55 	/* Whether to emit a runtime warning if we
56 	 * have to suppress a bogus percent value. */
57 	gboolean warn_bogus_percent;
58 };
59 
60 enum {
61 	PROP_0,
62 	PROP_ALERT_SINK,
63 	PROP_CANCELLABLE,
64 	PROP_ICON_NAME,
65 	PROP_PERCENT,
66 	PROP_STATE,
67 	PROP_TEXT
68 };
69 
G_DEFINE_TYPE(EActivity,e_activity,G_TYPE_OBJECT)70 G_DEFINE_TYPE (
71 	EActivity,
72 	e_activity,
73 	G_TYPE_OBJECT)
74 
75 static void
76 activity_camel_status_cb (EActivity *activity,
77                           const gchar *description,
78                           gint percent)
79 {
80 	/* CamelOperation::status signals are always emitted from idle
81 	 * callbacks, so we don't have to screw around with locking. */
82 
83 	g_object_set (
84 		activity, "percent", (gdouble) percent,
85 		"text", description, NULL);
86 }
87 
88 static void
activity_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)89 activity_set_property (GObject *object,
90                        guint property_id,
91                        const GValue *value,
92                        GParamSpec *pspec)
93 {
94 	switch (property_id) {
95 		case PROP_ALERT_SINK:
96 			e_activity_set_alert_sink (
97 				E_ACTIVITY (object),
98 				g_value_get_object (value));
99 			return;
100 
101 		case PROP_CANCELLABLE:
102 			e_activity_set_cancellable (
103 				E_ACTIVITY (object),
104 				g_value_get_object (value));
105 			return;
106 
107 		case PROP_ICON_NAME:
108 			e_activity_set_icon_name (
109 				E_ACTIVITY (object),
110 				g_value_get_string (value));
111 			return;
112 
113 		case PROP_PERCENT:
114 			e_activity_set_percent (
115 				E_ACTIVITY (object),
116 				g_value_get_double (value));
117 			return;
118 
119 		case PROP_STATE:
120 			e_activity_set_state (
121 				E_ACTIVITY (object),
122 				g_value_get_enum (value));
123 			return;
124 
125 		case PROP_TEXT:
126 			e_activity_set_text (
127 				E_ACTIVITY (object),
128 				g_value_get_string (value));
129 			return;
130 	}
131 
132 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
133 }
134 
135 static void
activity_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)136 activity_get_property (GObject *object,
137                        guint property_id,
138                        GValue *value,
139                        GParamSpec *pspec)
140 {
141 	switch (property_id) {
142 		case PROP_ALERT_SINK:
143 			g_value_set_object (
144 				value, e_activity_get_alert_sink (
145 				E_ACTIVITY (object)));
146 			return;
147 
148 		case PROP_CANCELLABLE:
149 			g_value_set_object (
150 				value, e_activity_get_cancellable (
151 				E_ACTIVITY (object)));
152 			return;
153 
154 		case PROP_ICON_NAME:
155 			g_value_set_string (
156 				value, e_activity_get_icon_name (
157 				E_ACTIVITY (object)));
158 			return;
159 
160 		case PROP_PERCENT:
161 			g_value_set_double (
162 				value, e_activity_get_percent (
163 				E_ACTIVITY (object)));
164 			return;
165 
166 		case PROP_STATE:
167 			g_value_set_enum (
168 				value, e_activity_get_state (
169 				E_ACTIVITY (object)));
170 			return;
171 
172 		case PROP_TEXT:
173 			g_value_set_string (
174 				value, e_activity_get_text (
175 				E_ACTIVITY (object)));
176 			return;
177 	}
178 
179 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
180 }
181 
182 static void
activity_dispose(GObject * object)183 activity_dispose (GObject *object)
184 {
185 	EActivityPrivate *priv;
186 
187 	priv = E_ACTIVITY_GET_PRIVATE (object);
188 	g_clear_object (&priv->alert_sink);
189 
190 	if (priv->cancellable != NULL) {
191 		g_signal_handlers_disconnect_matched (
192 			priv->cancellable,
193 			G_SIGNAL_MATCH_DATA,
194 			0, 0, NULL, NULL, object);
195 		g_object_unref (priv->cancellable);
196 		priv->cancellable = NULL;
197 	}
198 
199 	/* Chain up to parent's dispose() method. */
200 	G_OBJECT_CLASS (e_activity_parent_class)->dispose (object);
201 }
202 
203 static void
activity_finalize(GObject * object)204 activity_finalize (GObject *object)
205 {
206 	EActivityPrivate *priv;
207 
208 	priv = E_ACTIVITY_GET_PRIVATE (object);
209 
210 	g_free (priv->icon_name);
211 	g_free (priv->text);
212 	g_free (priv->last_known_text);
213 
214 	/* Chain up to parent's finalize() method. */
215 	G_OBJECT_CLASS (e_activity_parent_class)->finalize (object);
216 }
217 
218 static gchar *
activity_describe(EActivity * activity)219 activity_describe (EActivity *activity)
220 {
221 	GString *string;
222 	GCancellable *cancellable;
223 	EActivityState state;
224 	const gchar *text;
225 	gdouble percent;
226 
227 	text = e_activity_get_text (activity);
228 
229 	if (text == NULL)
230 		return NULL;
231 
232 	string = g_string_sized_new (256);
233 	cancellable = e_activity_get_cancellable (activity);
234 	percent = e_activity_get_percent (activity);
235 	state = e_activity_get_state (activity);
236 
237 	/* Sanity check the percentage. */
238 	if (percent > 100.0) {
239 		if (activity->priv->warn_bogus_percent) {
240 			g_warning (
241 				"Nonsensical (%d%% complete) reported on "
242 				"activity \"%s\"", (gint) (percent), text);
243 			activity->priv->warn_bogus_percent = FALSE;
244 		}
245 		percent = -1.0;  /* suppress it */
246 	} else {
247 		activity->priv->warn_bogus_percent = TRUE;
248 	}
249 
250 	if (state == E_ACTIVITY_CANCELLED) {
251 		/* Translators: This is a cancelled activity. */
252 		g_string_printf (string, _("%s (cancelled)"), text);
253 	} else if (state == E_ACTIVITY_COMPLETED) {
254 		/* Translators: This is a completed activity. */
255 		g_string_printf (string, _("%s (completed)"), text);
256 	} else if (state == E_ACTIVITY_WAITING) {
257 		/* Translators: This is an activity waiting to run. */
258 		g_string_printf (string, _("%s (waiting)"), text);
259 	} else if (g_cancellable_is_cancelled (cancellable)) {
260 		/* Translators: This is a running activity which
261 		 *              the user has requested to cancel. */
262 		g_string_printf (string, _("%s (cancelling)"), text);
263 	} else if (percent <= 0.0) {
264 		g_string_printf (string, _("%s"), text);
265 	} else {
266 		g_string_printf (
267 			/* Translators: This is a running activity whose
268 			 *              percent complete is known. */
269 			string, _("%s (%d%% complete)"),
270 			text, (gint) (percent));
271 	}
272 
273 	return g_string_free (string, FALSE);
274 }
275 
276 static void
e_activity_class_init(EActivityClass * class)277 e_activity_class_init (EActivityClass *class)
278 {
279 	GObjectClass *object_class;
280 
281 	g_type_class_add_private (class, sizeof (EActivityPrivate));
282 
283 	object_class = G_OBJECT_CLASS (class);
284 	object_class->set_property = activity_set_property;
285 	object_class->get_property = activity_get_property;
286 	object_class->dispose = activity_dispose;
287 	object_class->finalize = activity_finalize;
288 
289 	class->describe = activity_describe;
290 
291 	g_object_class_install_property (
292 		object_class,
293 		PROP_ALERT_SINK,
294 		g_param_spec_object (
295 			"alert-sink",
296 			NULL,
297 			NULL,
298 			E_TYPE_ALERT_SINK,
299 			G_PARAM_READWRITE |
300 			G_PARAM_CONSTRUCT));
301 
302 	g_object_class_install_property (
303 		object_class,
304 		PROP_CANCELLABLE,
305 		g_param_spec_object (
306 			"cancellable",
307 			NULL,
308 			NULL,
309 			G_TYPE_CANCELLABLE,
310 			G_PARAM_READWRITE |
311 			G_PARAM_CONSTRUCT));
312 
313 	g_object_class_install_property (
314 		object_class,
315 		PROP_ICON_NAME,
316 		g_param_spec_string (
317 			"icon-name",
318 			NULL,
319 			NULL,
320 			NULL,
321 			G_PARAM_READWRITE |
322 			G_PARAM_CONSTRUCT));
323 
324 	g_object_class_install_property (
325 		object_class,
326 		PROP_PERCENT,
327 		g_param_spec_double (
328 			"percent",
329 			NULL,
330 			NULL,
331 			-G_MAXDOUBLE,
332 			G_MAXDOUBLE,
333 			-1.0,
334 			G_PARAM_READWRITE |
335 			G_PARAM_CONSTRUCT));
336 
337 	g_object_class_install_property (
338 		object_class,
339 		PROP_STATE,
340 		g_param_spec_enum (
341 			"state",
342 			NULL,
343 			NULL,
344 			E_TYPE_ACTIVITY_STATE,
345 			E_ACTIVITY_RUNNING,
346 			G_PARAM_READWRITE |
347 			G_PARAM_CONSTRUCT));
348 
349 	g_object_class_install_property (
350 		object_class,
351 		PROP_TEXT,
352 		g_param_spec_string (
353 			"text",
354 			NULL,
355 			NULL,
356 			NULL,
357 			G_PARAM_READWRITE |
358 			G_PARAM_CONSTRUCT));
359 }
360 
361 static void
e_activity_init(EActivity * activity)362 e_activity_init (EActivity *activity)
363 {
364 	activity->priv = E_ACTIVITY_GET_PRIVATE (activity);
365 	activity->priv->warn_bogus_percent = TRUE;
366 }
367 
368 /**
369  * e_activity_new:
370  *
371  * Creates a new #EActivity.
372  *
373  * Returns: an #EActivity
374  **/
375 EActivity *
e_activity_new(void)376 e_activity_new (void)
377 {
378 	return g_object_new (E_TYPE_ACTIVITY, NULL);
379 }
380 
381 /**
382  * e_activity_cancel:
383  * @activity: an #EActivity
384  *
385  * Convenience function cancels @activity's #EActivity:cancellable.
386  *
387  * <para>
388  *   <note>
389  *     This function will not set @activity's #EActivity:state to
390  *     @E_ACTIVITY_CANCELLED.  It merely requests that the associated
391  *     operation be cancelled.  Only after the operation finishes with
392  *     a @G_IO_ERROR_CANCELLED should the @activity's #EActivity:state
393  *     be changed (see e_activity_handle_cancellation()).  During this
394  *     interim period e_activity_describe() will indicate the activity
395  *     is "cancelling".
396  *   </note>
397  * </para>
398  **/
399 void
e_activity_cancel(EActivity * activity)400 e_activity_cancel (EActivity *activity)
401 {
402 	g_return_if_fail (E_IS_ACTIVITY (activity));
403 
404 	/* This function handles NULL gracefully. */
405 	g_cancellable_cancel (activity->priv->cancellable);
406 }
407 
408 /**
409  * e_activity_describe:
410  * @activity: an #EActivity
411  *
412  * Returns a description of the current state of the @activity based on
413  * the #EActivity:text, #EActivity:percent and #EActivity:state properties.
414  * Suitable for displaying in a status bar or similar widget.
415  *
416  * Free the returned string with g_free() when finished with it.
417  *
418  * Returns: a description of @activity
419  **/
420 gchar *
e_activity_describe(EActivity * activity)421 e_activity_describe (EActivity *activity)
422 {
423 	EActivityClass *class;
424 
425 	g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
426 
427 	class = E_ACTIVITY_GET_CLASS (activity);
428 	g_return_val_if_fail (class != NULL, NULL);
429 	g_return_val_if_fail (class->describe != NULL, NULL);
430 
431 	return class->describe (activity);
432 }
433 
434 /**
435  * e_activity_get_alert_sink:
436  * @activity: an #EActivity
437  *
438  * Returns the #EAlertSink for @activity, if one was provided.
439  *
440  * The #EActivity:alert-sink property is convenient for when the user
441  * should be alerted about a failed asynchronous operation.  Generally
442  * an #EActivity:alert-sink is set prior to dispatching the operation,
443  * and retrieved by a callback function when the operation completes.
444  *
445  * Returns: an #EAlertSink, or %NULL
446  **/
447 EAlertSink *
e_activity_get_alert_sink(EActivity * activity)448 e_activity_get_alert_sink (EActivity *activity)
449 {
450 	g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
451 
452 	return activity->priv->alert_sink;
453 }
454 
455 /**
456  * e_activity_set_alert_sink:
457  * @activity: an #EActivity
458  * @alert_sink: an #EAlertSink, or %NULL
459  *
460  * Sets (or clears) the #EAlertSink for @activity.
461  *
462  * The #EActivity:alert-sink property is convenient for when the user
463  * should be alerted about a failed asynchronous operation.  Generally
464  * an #EActivity:alert-sink is set prior to dispatching the operation,
465  * and retrieved by a callback function when the operation completes.
466  **/
467 void
e_activity_set_alert_sink(EActivity * activity,EAlertSink * alert_sink)468 e_activity_set_alert_sink (EActivity *activity,
469                            EAlertSink *alert_sink)
470 {
471 	g_return_if_fail (E_IS_ACTIVITY (activity));
472 
473 	if (activity->priv->alert_sink == alert_sink)
474 		return;
475 
476 	if (alert_sink != NULL) {
477 		g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
478 		g_object_ref (alert_sink);
479 	}
480 
481 	if (activity->priv->alert_sink != NULL)
482 		g_object_unref (activity->priv->alert_sink);
483 
484 	activity->priv->alert_sink = alert_sink;
485 
486 	g_object_notify (G_OBJECT (activity), "alert-sink");
487 }
488 
489 /**
490  * e_activity_get_cancellable:
491  * @activity: an #EActivity
492  *
493  * Returns the #GCancellable for @activity, if one was provided.
494  *
495  * Generally the @activity's #EActivity:cancellable property holds the same
496  * #GCancellable instance passed to a cancellable function, so widgets like
497  * #EActivityBar can bind the #GCancellable to a cancel button.
498  *
499  * Returns: a #GCancellable, or %NULL
500  **/
501 GCancellable *
e_activity_get_cancellable(EActivity * activity)502 e_activity_get_cancellable (EActivity *activity)
503 {
504 	g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
505 
506 	return activity->priv->cancellable;
507 }
508 
509 /**
510  * e_activity_set_cancellable:
511  * @activity: an #EActivity
512  * @cancellable: a #GCancellable, or %NULL
513  *
514  * Sets (or clears) the #GCancellable for @activity.
515  *
516  * Generally the @activity's #EActivity:cancellable property holds the same
517  * #GCancellable instance passed to a cancellable function, so widgets like
518  * #EActivityBar can bind the #GCancellable to a cancel button.
519  **/
520 void
e_activity_set_cancellable(EActivity * activity,GCancellable * cancellable)521 e_activity_set_cancellable (EActivity *activity,
522                             GCancellable *cancellable)
523 {
524 	g_return_if_fail (E_IS_ACTIVITY (activity));
525 
526 	if (activity->priv->cancellable == cancellable)
527 		return;
528 
529 	if (cancellable != NULL) {
530 		g_return_if_fail (G_IS_CANCELLABLE (cancellable));
531 		g_object_ref (cancellable);
532 	}
533 
534 	if (activity->priv->cancellable != NULL) {
535 		g_signal_handlers_disconnect_matched (
536 			activity->priv->cancellable,
537 			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, activity);
538 		g_object_unref (activity->priv->cancellable);
539 	}
540 
541 	activity->priv->cancellable = cancellable;
542 
543 	/* If this is a CamelOperation, listen for status updates
544 	 * from it and propagate them to our own status properties. */
545 	if (CAMEL_IS_OPERATION (cancellable))
546 		g_signal_connect_swapped (
547 			cancellable, "status",
548 			G_CALLBACK (activity_camel_status_cb), activity);
549 
550 	g_object_notify (G_OBJECT (activity), "cancellable");
551 }
552 
553 /**
554  * e_activity_get_icon_name:
555  * @activity: an #EActivity
556  *
557  * Returns the themed icon name for @activity, if one was provided.
558  *
559  * Generally widgets like #EActivityBar will honor the #EActivity:icon-name
560  * property while the @activity's #EActivity:state is @E_ACTIVITY_RUNNING or
561  * @E_ACTIVITY_WAITING, but will override the icon for @E_ACTIVITY_CANCELLED
562  * and @E_ACTIVITY_COMPLETED.
563  *
564  * Returns: a themed icon name, or %NULL
565  **/
566 const gchar *
e_activity_get_icon_name(EActivity * activity)567 e_activity_get_icon_name (EActivity *activity)
568 {
569 	g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
570 
571 	return activity->priv->icon_name;
572 }
573 
574 /**
575  * e_activity_set_icon_name:
576  * @activity: an #EActivity
577  * @icon_name: a themed icon name, or %NULL
578  *
579  * Sets (or clears) the themed icon name for @activity.
580  *
581  * Generally widgets like #EActivityBar will honor the #EActivity:icon-name
582  * property while the @activity's #EActivity:state is @E_ACTIVITY_RUNNING or
583  * @E_ACTIVITY_WAITING, but will override the icon for @E_ACTIVITY_CANCELLED
584  * and @E_ACTIVITY_COMPLETED.
585  **/
586 void
e_activity_set_icon_name(EActivity * activity,const gchar * icon_name)587 e_activity_set_icon_name (EActivity *activity,
588                           const gchar *icon_name)
589 {
590 	g_return_if_fail (E_IS_ACTIVITY (activity));
591 
592 	if (g_strcmp0 (activity->priv->icon_name, icon_name) == 0)
593 		return;
594 
595 	g_free (activity->priv->icon_name);
596 	activity->priv->icon_name = g_strdup (icon_name);
597 
598 	g_object_notify (G_OBJECT (activity), "icon-name");
599 }
600 
601 /**
602  * e_activity_get_percent:
603  * @activity: an #EActivity
604  *
605  * Returns the percent complete for @activity as a value between 0 and 100,
606  * or a negative value if the percent complete is unknown.
607  *
608  * Generally widgets like #EActivityBar will display the percent complete by
609  * way of e_activity_describe(), but only if the value is between 0 and 100.
610  *
611  * Returns: the percent complete, or a negative value if unknown
612  **/
613 gdouble
e_activity_get_percent(EActivity * activity)614 e_activity_get_percent (EActivity *activity)
615 {
616 	g_return_val_if_fail (E_IS_ACTIVITY (activity), -1.0);
617 
618 	return activity->priv->percent;
619 }
620 
621 /**
622  * e_activity_set_percent:
623  * @activity: an #EActivity
624  * @percent: the percent complete, or a negative value if unknown
625  *
626  * Sets the percent complete for @activity.  The value should be between 0
627  * and 100, or negative if the percent complete is unknown.
628  *
629  * Generally widgets like #EActivityBar will display the percent complete by
630  * way of e_activity_describe(), but only if the value is between 0 and 100.
631  **/
632 void
e_activity_set_percent(EActivity * activity,gdouble percent)633 e_activity_set_percent (EActivity *activity,
634                         gdouble percent)
635 {
636 	g_return_if_fail (E_IS_ACTIVITY (activity));
637 
638 	if (activity->priv->percent == percent)
639 		return;
640 
641 	activity->priv->percent = percent;
642 
643 	g_object_notify (G_OBJECT (activity), "percent");
644 }
645 
646 /**
647  * e_activity_get_state:
648  * @activity: an #EActivity
649  *
650  * Returns the state of @activity.
651  *
652  * Generally widgets like #EActivityBar will display the activity state by
653  * way of e_activity_describe() and possibly an icon.  The activity state is
654  * @E_ACTIVITY_RUNNING by default, and is usually only changed once when the
655  * associated operation is finished.
656  *
657  * Returns: an #EActivityState
658  **/
659 EActivityState
e_activity_get_state(EActivity * activity)660 e_activity_get_state (EActivity *activity)
661 {
662 	g_return_val_if_fail (E_IS_ACTIVITY (activity), 0);
663 
664 	return activity->priv->state;
665 }
666 
667 /**
668  * e_activity_set_state:
669  * @activity: an #EActivity
670  * @state: an #EActivityState
671  *
672  * Sets the state of @activity.
673  *
674  * Generally widgets like #EActivityBar will display the activity state by
675  * way of e_activity_describe() and possibly an icon.  The activity state is
676  * @E_ACTIVITY_RUNNING by default, and is usually only changed once when the
677  * associated operation is finished.
678  **/
679 void
e_activity_set_state(EActivity * activity,EActivityState state)680 e_activity_set_state (EActivity *activity,
681                       EActivityState state)
682 {
683 	g_return_if_fail (E_IS_ACTIVITY (activity));
684 
685 	if (activity->priv->state == state)
686 		return;
687 
688 	activity->priv->state = state;
689 
690 	g_object_notify (G_OBJECT (activity), "state");
691 }
692 
693 /**
694  * e_activity_get_text:
695  * @activity: an #EActivity
696  *
697  * Returns a message describing what @activity is doing.
698  *
699  * Generally widgets like #EActivityBar will display the message by way of
700  * e_activity_describe().
701  *
702  * Returns: a descriptive message
703  **/
704 const gchar *
e_activity_get_text(EActivity * activity)705 e_activity_get_text (EActivity *activity)
706 {
707 	g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
708 
709 	return activity->priv->text;
710 }
711 
712 /**
713  * e_activity_set_text:
714  * @activity: an #EActivity
715  * @text: a descriptive message, or %NULL
716  *
717  * Sets (or clears) a message describing what @activity is doing.
718  *
719  * Generally widgets like #EActivityBar will display the message by way of
720  * e_activity_describe().
721  **/
722 void
e_activity_set_text(EActivity * activity,const gchar * text)723 e_activity_set_text (EActivity *activity,
724                      const gchar *text)
725 {
726 	gchar *last_known_text = NULL;
727 
728 	g_return_if_fail (E_IS_ACTIVITY (activity));
729 
730 	if (g_strcmp0 (activity->priv->text, text) == 0)
731 		return;
732 
733 	g_free (activity->priv->text);
734 	activity->priv->text = g_strdup (text);
735 
736 	/* See e_activity_get_last_known_text(). */
737 	last_known_text = e_util_strdup_strip (text);
738 	if (last_known_text != NULL) {
739 		g_free (activity->priv->last_known_text);
740 		activity->priv->last_known_text = last_known_text;
741 	}
742 
743 	g_object_notify (G_OBJECT (activity), "text");
744 }
745 
746 /**
747  * e_activity_get_last_known_text:
748  * @activity: an #EActivity
749  *
750  * Returns the last non-empty #EActivity:text value, so it's possible to
751  * identify what the @activity <emphasis>was</emphasis> doing even if it
752  * currently has no description.
753  *
754  * Mostly useful for debugging.
755  *
756  * Returns: a descriptive message, or %NULL
757  **/
758 const gchar *
e_activity_get_last_known_text(EActivity * activity)759 e_activity_get_last_known_text (EActivity *activity)
760 {
761 	g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
762 
763 	return activity->priv->last_known_text;
764 }
765 
766 /**
767  * e_activity_handle_cancellation:
768  * @activity: an #EActivity
769  * @error: a #GError, or %NULL
770  *
771  * Convenience function sets @activity's #EActivity:state to
772  * @E_ACTIVITY_CANCELLED if @error is @G_IO_ERROR_CANCELLED.
773  *
774  * Returns: %TRUE if @activity was set to @E_ACTIVITY_CANCELLED
775  **/
776 gboolean
e_activity_handle_cancellation(EActivity * activity,const GError * error)777 e_activity_handle_cancellation (EActivity *activity,
778                                 const GError *error)
779 {
780 	gboolean handled = FALSE;
781 
782 	g_return_val_if_fail (E_IS_ACTIVITY (activity), FALSE);
783 
784 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
785 		e_activity_set_state (activity, E_ACTIVITY_CANCELLED);
786 		handled = TRUE;
787 	}
788 
789 	return handled;
790 }
791