1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3    nemo-progress-info.h: file operation progress info.
4 
5    Copyright (C) 2007 Red Hat, Inc.
6 
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU General Public
18    License along with this program; if not, write to the
19    Free Software Foundation, Inc., 51 Franklin Street - Suite 500,
20    Boston, MA 02110-1335, USA.
21 
22    Author: Alexander Larsson <alexl@redhat.com>
23 */
24 
25 #include <config.h>
26 #include <math.h>
27 #include <glib/gi18n.h>
28 #include <eel/eel-string.h>
29 #include <eel/eel-glib-extensions.h>
30 #include "nemo-progress-info.h"
31 #include "nemo-progress-info-manager.h"
32 
33 enum {
34   CHANGED,
35   QUEUED,
36   PROGRESS_CHANGED,
37   STARTED,
38   FINISHED,
39   LAST_SIGNAL
40 };
41 
42 #define SIGNAL_DELAY_MSEC 100
43 
44 static guint signals[LAST_SIGNAL] = { 0 };
45 
46 struct _NemoProgressInfo
47 {
48 	GObject parent_instance;
49 
50 	GCancellable *cancellable;
51 
52 	char *status;
53 	char *details;
54     char *initial_details;
55 	double progress;
56 	gboolean activity_mode;
57 	gboolean started;
58 	gboolean finished;
59 	gboolean paused;
60     gboolean queued;
61 
62 	GSource *idle_source;
63 	gboolean source_is_now;
64 
65 	gboolean start_at_idle;
66 	gboolean finish_at_idle;
67 	gboolean changed_at_idle;
68 	gboolean progress_at_idle;
69     gboolean queue_at_idle;
70 };
71 
72 struct _NemoProgressInfoClass
73 {
74 	GObjectClass parent_class;
75 };
76 
77 G_LOCK_DEFINE_STATIC(progress_info);
78 
G_DEFINE_TYPE(NemoProgressInfo,nemo_progress_info,G_TYPE_OBJECT)79 G_DEFINE_TYPE (NemoProgressInfo, nemo_progress_info, G_TYPE_OBJECT)
80 
81 static void
82 nemo_progress_info_finalize (GObject *object)
83 {
84 	NemoProgressInfo *info;
85 
86 	info = NEMO_PROGRESS_INFO (object);
87 
88 	g_free (info->status);
89 	g_free (info->details);
90     g_free (info->initial_details);
91 	g_object_unref (info->cancellable);
92 
93 	if (G_OBJECT_CLASS (nemo_progress_info_parent_class)->finalize) {
94 		(*G_OBJECT_CLASS (nemo_progress_info_parent_class)->finalize) (object);
95 	}
96 }
97 
98 static void
nemo_progress_info_dispose(GObject * object)99 nemo_progress_info_dispose (GObject *object)
100 {
101 	NemoProgressInfo *info;
102 
103 	info = NEMO_PROGRESS_INFO (object);
104 
105 	G_LOCK (progress_info);
106 
107 	/* Destroy source in dispose, because the callback
108 	   could come here before the destroy, which should
109 	   ressurect the object for a while */
110 	if (info->idle_source) {
111 		g_source_destroy (info->idle_source);
112 		g_source_unref (info->idle_source);
113 		info->idle_source = NULL;
114 	}
115 	G_UNLOCK (progress_info);
116 }
117 
118 static void
nemo_progress_info_class_init(NemoProgressInfoClass * klass)119 nemo_progress_info_class_init (NemoProgressInfoClass *klass)
120 {
121 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
122 
123 	gobject_class->finalize = nemo_progress_info_finalize;
124 	gobject_class->dispose = nemo_progress_info_dispose;
125 
126 	signals[CHANGED] =
127 		g_signal_new ("changed",
128 			      NEMO_TYPE_PROGRESS_INFO,
129 			      G_SIGNAL_RUN_LAST,
130 			      0,
131 			      NULL, NULL,
132 			      g_cclosure_marshal_VOID__VOID,
133 			      G_TYPE_NONE, 0);
134 
135     signals[QUEUED] =
136         g_signal_new ("queued",
137                   NEMO_TYPE_PROGRESS_INFO,
138                   G_SIGNAL_RUN_LAST,
139                   0,
140                   NULL, NULL,
141                   g_cclosure_marshal_VOID__VOID,
142                   G_TYPE_NONE, 0);
143 
144 	signals[PROGRESS_CHANGED] =
145 		g_signal_new ("progress-changed",
146 			      NEMO_TYPE_PROGRESS_INFO,
147 			      G_SIGNAL_RUN_LAST,
148 			      0,
149 			      NULL, NULL,
150 			      g_cclosure_marshal_VOID__VOID,
151 			      G_TYPE_NONE, 0);
152 
153 	signals[STARTED] =
154 		g_signal_new ("started",
155 			      NEMO_TYPE_PROGRESS_INFO,
156 			      G_SIGNAL_RUN_LAST,
157 			      0,
158 			      NULL, NULL,
159 			      g_cclosure_marshal_VOID__VOID,
160 			      G_TYPE_NONE, 0);
161 
162 	signals[FINISHED] =
163 		g_signal_new ("finished",
164 			      NEMO_TYPE_PROGRESS_INFO,
165 			      G_SIGNAL_RUN_LAST,
166 			      0,
167 			      NULL, NULL,
168 			      g_cclosure_marshal_VOID__VOID,
169 			      G_TYPE_NONE, 0);
170 
171 }
172 
173 static void
nemo_progress_info_init(NemoProgressInfo * info)174 nemo_progress_info_init (NemoProgressInfo *info)
175 {
176 	NemoProgressInfoManager *manager;
177 
178 	info->cancellable = g_cancellable_new ();
179 
180 	manager = nemo_progress_info_manager_new ();
181 	nemo_progress_info_manager_add_new_info (manager, info);
182 	g_object_unref (manager);
183 }
184 
185 NemoProgressInfo *
nemo_progress_info_new(void)186 nemo_progress_info_new (void)
187 {
188 	NemoProgressInfo *info;
189 
190 	info = g_object_new (NEMO_TYPE_PROGRESS_INFO, NULL);
191 
192 	return info;
193 }
194 
195 char *
nemo_progress_info_get_status(NemoProgressInfo * info)196 nemo_progress_info_get_status (NemoProgressInfo *info)
197 {
198 	char *res;
199 
200 	G_LOCK (progress_info);
201 
202 	if (info->status) {
203 		res = g_strdup (info->status);
204 	} else {
205 		res = g_strdup (_("Preparing"));
206 	}
207 
208 	G_UNLOCK (progress_info);
209 
210 	return res;
211 }
212 
213 char *
nemo_progress_info_get_details(NemoProgressInfo * info)214 nemo_progress_info_get_details (NemoProgressInfo *info)
215 {
216 	char *res;
217 
218 	G_LOCK (progress_info);
219 
220 	if (info->details) {
221 		res = g_strdup (info->details);
222 	} else {
223 		res = g_strdup (_("Preparing"));
224 	}
225 
226 	G_UNLOCK (progress_info);
227 
228 	return res;
229 }
230 
231 char *
nemo_progress_info_get_initial_details(NemoProgressInfo * info)232 nemo_progress_info_get_initial_details (NemoProgressInfo *info)
233 {
234     char *res;
235 
236     G_LOCK (progress_info);
237 
238     if (info->initial_details) {
239         res = g_strdup (info->initial_details);
240     } else {
241         res = g_strdup (_("Preparing"));
242     }
243 
244     G_UNLOCK (progress_info);
245 
246     return res;
247 }
248 
249 double
nemo_progress_info_get_progress(NemoProgressInfo * info)250 nemo_progress_info_get_progress (NemoProgressInfo *info)
251 {
252 	double res;
253 
254 	G_LOCK (progress_info);
255 
256 	if (info->activity_mode) {
257 		res = -1.0;
258 	} else {
259 		res = info->progress;
260 	}
261 
262 	G_UNLOCK (progress_info);
263 
264 	return res;
265 }
266 
267 void
nemo_progress_info_cancel(NemoProgressInfo * info)268 nemo_progress_info_cancel (NemoProgressInfo *info)
269 {
270 	G_LOCK (progress_info);
271 
272 	g_cancellable_cancel (info->cancellable);
273 
274 	G_UNLOCK (progress_info);
275 }
276 
277 GCancellable *
nemo_progress_info_get_cancellable(NemoProgressInfo * info)278 nemo_progress_info_get_cancellable (NemoProgressInfo *info)
279 {
280 	GCancellable *c;
281 
282 	G_LOCK (progress_info);
283 
284 	c = g_object_ref (info->cancellable);
285 
286 	G_UNLOCK (progress_info);
287 
288 	return c;
289 }
290 
291 gboolean
nemo_progress_info_get_is_started(NemoProgressInfo * info)292 nemo_progress_info_get_is_started (NemoProgressInfo *info)
293 {
294 	gboolean res;
295 
296 	G_LOCK (progress_info);
297 
298 	res = info->started;
299 
300 	G_UNLOCK (progress_info);
301 
302 	return res;
303 }
304 
305 gboolean
nemo_progress_info_get_is_finished(NemoProgressInfo * info)306 nemo_progress_info_get_is_finished (NemoProgressInfo *info)
307 {
308 	gboolean res;
309 
310 	G_LOCK (progress_info);
311 
312 	res = info->finished;
313 
314 	G_UNLOCK (progress_info);
315 
316 	return res;
317 }
318 
319 gboolean
nemo_progress_info_get_is_paused(NemoProgressInfo * info)320 nemo_progress_info_get_is_paused (NemoProgressInfo *info)
321 {
322 	gboolean res;
323 
324 	G_LOCK (progress_info);
325 
326 	res = info->paused;
327 
328 	G_UNLOCK (progress_info);
329 
330 	return res;
331 }
332 
333 static gboolean
idle_callback(gpointer data)334 idle_callback (gpointer data)
335 {
336 	NemoProgressInfo *info = data;
337 	gboolean start_at_idle;
338 	gboolean finish_at_idle;
339 	gboolean changed_at_idle;
340 	gboolean progress_at_idle;
341     gboolean queue_at_idle;
342 	GSource *source;
343 
344 	source = g_main_current_source ();
345 
346 	G_LOCK (progress_info);
347 
348 	/* Protect agains races where the source has
349 	   been destroyed on another thread while it
350 	   was being dispatched.
351 	   Similar to what gdk_threads_add_idle does.
352 	*/
353 	if (g_source_is_destroyed (source)) {
354 		G_UNLOCK (progress_info);
355 		return FALSE;
356 	}
357 
358 	/* We hadn't destroyed the source, so take a ref.
359 	 * This might ressurect the object from dispose, but
360 	 * that should be ok.
361 	 */
362 	g_object_ref (info);
363 
364 	g_assert (source == info->idle_source);
365 
366 	g_source_unref (source);
367 	info->idle_source = NULL;
368 
369 	start_at_idle = info->start_at_idle;
370 	finish_at_idle = info->finish_at_idle;
371 	changed_at_idle = info->changed_at_idle;
372 	progress_at_idle = info->progress_at_idle;
373     queue_at_idle = info->queue_at_idle;
374 
375 	info->start_at_idle = FALSE;
376 	info->finish_at_idle = FALSE;
377 	info->changed_at_idle = FALSE;
378 	info->progress_at_idle = FALSE;
379     info->queue_at_idle = FALSE;
380 
381 	G_UNLOCK (progress_info);
382 
383     if (queue_at_idle) {
384         g_signal_emit (info,
385                    signals[QUEUED],
386                    0);
387     }
388 
389 	if (start_at_idle) {
390 		g_signal_emit (info,
391 			       signals[STARTED],
392 			       0);
393 	}
394 
395 	if (changed_at_idle) {
396 		g_signal_emit (info,
397 			       signals[CHANGED],
398 			       0);
399 	}
400 
401 	if (progress_at_idle) {
402 		g_signal_emit (info,
403 			       signals[PROGRESS_CHANGED],
404 			       0);
405 	}
406 
407 	if (finish_at_idle) {
408 		g_signal_emit (info,
409 			       signals[FINISHED],
410 			       0);
411 	}
412 
413 	g_object_unref (info);
414 
415 	return FALSE;
416 }
417 
418 /* Called with lock held */
419 static void
queue_idle(NemoProgressInfo * info,gboolean now)420 queue_idle (NemoProgressInfo *info, gboolean now)
421 {
422 	if (info->idle_source == NULL ||
423 	    (now && !info->source_is_now)) {
424 		if (info->idle_source) {
425 			g_source_destroy (info->idle_source);
426 			g_source_unref (info->idle_source);
427 			info->idle_source = NULL;
428 		}
429 
430 		info->source_is_now = now;
431 		if (now) {
432 			info->idle_source = g_idle_source_new ();
433 		} else {
434 			info->idle_source = g_timeout_source_new (SIGNAL_DELAY_MSEC);
435 		}
436 		g_source_set_callback (info->idle_source, idle_callback, info, NULL);
437 		g_source_attach (info->idle_source, NULL);
438 	}
439 }
440 
441 void
nemo_progress_info_queue(NemoProgressInfo * info)442 nemo_progress_info_queue (NemoProgressInfo *info)
443 {
444     G_LOCK (progress_info);
445 
446     if (!info->queued) {
447         info->queued = TRUE;
448 
449         info->queue_at_idle = TRUE;
450         queue_idle (info, TRUE);
451     }
452 
453     G_UNLOCK (progress_info);
454 }
455 
456 void
nemo_progress_info_pause(NemoProgressInfo * info)457 nemo_progress_info_pause (NemoProgressInfo *info)
458 {
459 	G_LOCK (progress_info);
460 
461 	if (!info->paused) {
462 		info->paused = TRUE;
463 	}
464 
465 	G_UNLOCK (progress_info);
466 }
467 
468 void
nemo_progress_info_resume(NemoProgressInfo * info)469 nemo_progress_info_resume (NemoProgressInfo *info)
470 {
471 	G_LOCK (progress_info);
472 
473 	if (info->paused) {
474 		info->paused = FALSE;
475 	}
476 
477 	G_UNLOCK (progress_info);
478 }
479 
480 void
nemo_progress_info_start(NemoProgressInfo * info)481 nemo_progress_info_start (NemoProgressInfo *info)
482 {
483 	G_LOCK (progress_info);
484 
485 	if (!info->started) {
486 		info->started = TRUE;
487 
488 		info->start_at_idle = TRUE;
489 		queue_idle (info, TRUE);
490 	}
491 
492 	G_UNLOCK (progress_info);
493 }
494 
495 void
nemo_progress_info_finish(NemoProgressInfo * info)496 nemo_progress_info_finish (NemoProgressInfo *info)
497 {
498 	G_LOCK (progress_info);
499 
500 	if (!info->finished) {
501 		info->finished = TRUE;
502 
503 		info->finish_at_idle = TRUE;
504 		queue_idle (info, TRUE);
505 	}
506 
507 	G_UNLOCK (progress_info);
508 }
509 
510 void
nemo_progress_info_take_status(NemoProgressInfo * info,char * status)511 nemo_progress_info_take_status (NemoProgressInfo *info,
512 				    char *status)
513 {
514 	G_LOCK (progress_info);
515 
516 	if (g_strcmp0 (info->status, status) != 0) {
517 		g_free (info->status);
518 		info->status = status;
519 
520 		info->changed_at_idle = TRUE;
521 		queue_idle (info, FALSE);
522 	} else {
523 		g_free (status);
524 	}
525 
526 	G_UNLOCK (progress_info);
527 }
528 
529 void
nemo_progress_info_set_status(NemoProgressInfo * info,const char * status)530 nemo_progress_info_set_status (NemoProgressInfo *info,
531 				   const char *status)
532 {
533 	G_LOCK (progress_info);
534 
535 	if (g_strcmp0 (info->status, status) != 0) {
536 		g_free (info->status);
537 		info->status = g_strdup (status);
538 
539 		info->changed_at_idle = TRUE;
540 		queue_idle (info, FALSE);
541 	}
542 
543 	G_UNLOCK (progress_info);
544 }
545 
546 
547 void
nemo_progress_info_take_details(NemoProgressInfo * info,char * details)548 nemo_progress_info_take_details (NemoProgressInfo *info,
549 				     char           *details)
550 {
551 	G_LOCK (progress_info);
552 
553 	if (g_strcmp0 (info->details, details) != 0) {
554 		g_free (info->details);
555 		info->details = details;
556 
557 		info->changed_at_idle = TRUE;
558 		queue_idle (info, FALSE);
559 	} else {
560 		g_free (details);
561 	}
562 
563 	G_UNLOCK (progress_info);
564 }
565 
566 void
nemo_progress_info_set_details(NemoProgressInfo * info,const char * details)567 nemo_progress_info_set_details (NemoProgressInfo *info,
568 				    const char           *details)
569 {
570 	G_LOCK (progress_info);
571 
572 	if (g_strcmp0 (info->details, details) != 0) {
573 		g_free (info->details);
574 		info->details = g_strdup (details);
575 
576 		info->changed_at_idle = TRUE;
577 		queue_idle (info, FALSE);
578 	}
579 
580 	G_UNLOCK (progress_info);
581 }
582 
583 void
nemo_progress_info_take_initial_details(NemoProgressInfo * info,char * initial_details)584 nemo_progress_info_take_initial_details (NemoProgressInfo *info,
585                                                      char *initial_details)
586 {
587     G_LOCK (progress_info);
588 
589     if (g_strcmp0 (info->initial_details, initial_details) != 0) {
590         g_free (info->initial_details);
591         info->initial_details = initial_details;
592 
593         info->changed_at_idle = TRUE;
594         queue_idle (info, FALSE);
595     } else {
596         g_free (initial_details);
597     }
598 
599     G_UNLOCK (progress_info);
600 }
601 
602 void
nemo_progress_info_pulse_progress(NemoProgressInfo * info)603 nemo_progress_info_pulse_progress (NemoProgressInfo *info)
604 {
605 	G_LOCK (progress_info);
606 
607 	info->activity_mode = TRUE;
608 	info->progress = 0.0;
609 	info->progress_at_idle = TRUE;
610 	queue_idle (info, FALSE);
611 
612 	G_UNLOCK (progress_info);
613 }
614 
615 void
nemo_progress_info_set_progress(NemoProgressInfo * info,double current,double total)616 nemo_progress_info_set_progress (NemoProgressInfo *info,
617 				     double                current,
618 				     double                total)
619 {
620 	double current_percent;
621 
622 	if (total <= 0) {
623 		current_percent = 1.0;
624 	} else {
625 		current_percent = current / total;
626 
627 		if (current_percent < 0) {
628 			current_percent	= 0;
629 		}
630 
631 		if (current_percent > 1.0) {
632 			current_percent	= 1.0;
633 		}
634 	}
635 
636 	G_LOCK (progress_info);
637 
638 	if (info->activity_mode || /* emit on switch from activity mode */
639 	    fabs (current_percent - info->progress) > 0.005 /* Emit on change of 0.5 percent */
640 	    ) {
641 		info->activity_mode = FALSE;
642 		info->progress = current_percent;
643 		info->progress_at_idle = TRUE;
644 		queue_idle (info, FALSE);
645 	}
646 
647 	G_UNLOCK (progress_info);
648 }
649