1 /*
2  *  nautilus-progress-info.h: file operation progress info.
3  *
4  *  Copyright (C) 2007 Red Hat, Inc.
5  *
6  *  This program is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU General Public License as
8  *  published by the Free Software Foundation; either version 2 of the
9  *  License, or (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public
17  *  License along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  *  Author: Alexander Larsson <alexl@redhat.com>
20  */
21 
22 #include <config.h>
23 #include <math.h>
24 #include <glib/gi18n.h>
25 #include <eel/eel-string.h>
26 #include <eel/eel-glib-extensions.h>
27 #include "nautilus-progress-info.h"
28 #include "nautilus-progress-info-manager.h"
29 #include "nautilus-icon-info.h"
30 
31 enum
32 {
33     CHANGED,
34     PROGRESS_CHANGED,
35     STARTED,
36     FINISHED,
37     CANCELLED,
38     LAST_SIGNAL
39 };
40 
41 #define SIGNAL_DELAY_MSEC 100
42 
43 static guint signals[LAST_SIGNAL] = { 0 };
44 
45 struct _NautilusProgressInfo
46 {
47     GObject parent_instance;
48 
49     GCancellable *cancellable;
50     guint cancellable_id;
51 
52     GTimer *progress_timer;
53 
54     char *status;
55     char *details;
56     double progress;
57     gdouble remaining_time;
58     gdouble elapsed_time;
59     gboolean activity_mode;
60     gboolean started;
61     gboolean finished;
62     gboolean paused;
63 
64     GSource *idle_source;
65     gboolean source_is_now;
66 
67     gboolean start_at_idle;
68     gboolean finish_at_idle;
69     gboolean cancel_at_idle;
70     gboolean changed_at_idle;
71     gboolean progress_at_idle;
72 
73     GFile *destination;
74 };
75 
76 G_LOCK_DEFINE_STATIC (progress_info);
77 
78 G_DEFINE_TYPE (NautilusProgressInfo, nautilus_progress_info, G_TYPE_OBJECT)
79 
80 static void set_details (NautilusProgressInfo *info,
81                          const char           *details);
82 static void set_status (NautilusProgressInfo *info,
83                         const char           *status);
84 
85 static void
nautilus_progress_info_finalize(GObject * object)86 nautilus_progress_info_finalize (GObject *object)
87 {
88     NautilusProgressInfo *info;
89 
90     info = NAUTILUS_PROGRESS_INFO (object);
91 
92     g_free (info->status);
93     g_free (info->details);
94     g_clear_pointer (&info->progress_timer, g_timer_destroy);
95     g_cancellable_disconnect (info->cancellable, info->cancellable_id);
96     g_object_unref (info->cancellable);
97     g_clear_object (&info->destination);
98 
99     if (G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize)
100     {
101         (*G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize)(object);
102     }
103 }
104 
105 static void
nautilus_progress_info_dispose(GObject * object)106 nautilus_progress_info_dispose (GObject *object)
107 {
108     NautilusProgressInfo *info;
109 
110     info = NAUTILUS_PROGRESS_INFO (object);
111 
112     G_LOCK (progress_info);
113 
114     /* Destroy source in dispose, because the callback
115      *  could come here before the destroy, which should
116      *  ressurect the object for a while */
117     if (info->idle_source)
118     {
119         g_source_destroy (info->idle_source);
120         g_source_unref (info->idle_source);
121         info->idle_source = NULL;
122     }
123     G_UNLOCK (progress_info);
124 }
125 
126 static void
nautilus_progress_info_class_init(NautilusProgressInfoClass * klass)127 nautilus_progress_info_class_init (NautilusProgressInfoClass *klass)
128 {
129     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
130 
131     gobject_class->finalize = nautilus_progress_info_finalize;
132     gobject_class->dispose = nautilus_progress_info_dispose;
133 
134     signals[CHANGED] =
135         g_signal_new ("changed",
136                       NAUTILUS_TYPE_PROGRESS_INFO,
137                       G_SIGNAL_RUN_LAST,
138                       0,
139                       NULL, NULL,
140                       g_cclosure_marshal_VOID__VOID,
141                       G_TYPE_NONE, 0);
142 
143     signals[PROGRESS_CHANGED] =
144         g_signal_new ("progress-changed",
145                       NAUTILUS_TYPE_PROGRESS_INFO,
146                       G_SIGNAL_RUN_LAST,
147                       0,
148                       NULL, NULL,
149                       g_cclosure_marshal_VOID__VOID,
150                       G_TYPE_NONE, 0);
151 
152     signals[STARTED] =
153         g_signal_new ("started",
154                       NAUTILUS_TYPE_PROGRESS_INFO,
155                       G_SIGNAL_RUN_LAST,
156                       0,
157                       NULL, NULL,
158                       g_cclosure_marshal_VOID__VOID,
159                       G_TYPE_NONE, 0);
160 
161     signals[FINISHED] =
162         g_signal_new ("finished",
163                       NAUTILUS_TYPE_PROGRESS_INFO,
164                       G_SIGNAL_RUN_LAST,
165                       0,
166                       NULL, NULL,
167                       g_cclosure_marshal_VOID__VOID,
168                       G_TYPE_NONE, 0);
169 
170     signals[CANCELLED] =
171         g_signal_new ("cancelled",
172                       NAUTILUS_TYPE_PROGRESS_INFO,
173                       G_SIGNAL_RUN_LAST,
174                       0,
175                       NULL, NULL,
176                       g_cclosure_marshal_VOID__VOID,
177                       G_TYPE_NONE, 0);
178 }
179 
180 static gboolean
idle_callback(gpointer data)181 idle_callback (gpointer data)
182 {
183     NautilusProgressInfo *info = data;
184     gboolean start_at_idle;
185     gboolean finish_at_idle;
186     gboolean changed_at_idle;
187     gboolean progress_at_idle;
188     gboolean cancelled_at_idle;
189     GSource *source;
190 
191     G_LOCK (progress_info);
192 
193     source = g_main_current_source ();
194 
195     /* Protect agains races where the source has
196      *  been destroyed on another thread while it
197      *  was being dispatched.
198      *  Similar to what gdk_threads_add_idle does.
199      */
200     if (g_source_is_destroyed (source))
201     {
202         G_UNLOCK (progress_info);
203         return FALSE;
204     }
205 
206     /* We hadn't destroyed the source, so take a ref.
207      * This might ressurect the object from dispose, but
208      * that should be ok.
209      */
210     g_object_ref (info);
211 
212     g_assert (source == info->idle_source);
213 
214     g_source_unref (source);
215     info->idle_source = NULL;
216 
217     start_at_idle = info->start_at_idle;
218     finish_at_idle = info->finish_at_idle;
219     changed_at_idle = info->changed_at_idle;
220     progress_at_idle = info->progress_at_idle;
221     cancelled_at_idle = info->cancel_at_idle;
222 
223     info->start_at_idle = FALSE;
224     info->finish_at_idle = FALSE;
225     info->changed_at_idle = FALSE;
226     info->progress_at_idle = FALSE;
227     info->cancel_at_idle = FALSE;
228 
229     G_UNLOCK (progress_info);
230 
231     if (start_at_idle)
232     {
233         g_signal_emit (info,
234                        signals[STARTED],
235                        0);
236     }
237 
238     if (changed_at_idle)
239     {
240         g_signal_emit (info,
241                        signals[CHANGED],
242                        0);
243     }
244 
245     if (progress_at_idle)
246     {
247         g_signal_emit (info,
248                        signals[PROGRESS_CHANGED],
249                        0);
250     }
251 
252     if (finish_at_idle)
253     {
254         g_signal_emit (info,
255                        signals[FINISHED],
256                        0);
257     }
258 
259     if (cancelled_at_idle)
260     {
261         g_signal_emit (info,
262                        signals[CANCELLED],
263                        0);
264     }
265 
266     g_object_unref (info);
267 
268     return FALSE;
269 }
270 
271 
272 /* Called with lock held */
273 static void
queue_idle(NautilusProgressInfo * info,gboolean now)274 queue_idle (NautilusProgressInfo *info,
275             gboolean              now)
276 {
277     if (info->idle_source == NULL ||
278         (now && !info->source_is_now))
279     {
280         if (info->idle_source)
281         {
282             g_source_destroy (info->idle_source);
283             g_source_unref (info->idle_source);
284             info->idle_source = NULL;
285         }
286 
287         info->source_is_now = now;
288         if (now)
289         {
290             info->idle_source = g_idle_source_new ();
291         }
292         else
293         {
294             info->idle_source = g_timeout_source_new (SIGNAL_DELAY_MSEC);
295         }
296         g_source_set_callback (info->idle_source, idle_callback, info, NULL);
297         g_source_attach (info->idle_source, NULL);
298     }
299 }
300 
301 static void
on_canceled(GCancellable * cancellable,NautilusProgressInfo * info)302 on_canceled (GCancellable         *cancellable,
303              NautilusProgressInfo *info)
304 {
305     G_LOCK (progress_info);
306     set_details (info, _("Canceled"));
307     info->cancel_at_idle = TRUE;
308     g_timer_stop (info->progress_timer);
309     queue_idle (info, TRUE);
310     G_UNLOCK (progress_info);
311 }
312 
313 static void
nautilus_progress_info_init(NautilusProgressInfo * info)314 nautilus_progress_info_init (NautilusProgressInfo *info)
315 {
316     NautilusProgressInfoManager *manager;
317 
318     info->cancellable = g_cancellable_new ();
319     info->cancellable_id = g_cancellable_connect (info->cancellable,
320                                                   G_CALLBACK (on_canceled),
321                                                   info,
322                                                   NULL);
323 
324     manager = nautilus_progress_info_manager_dup_singleton ();
325     nautilus_progress_info_manager_add_new_info (manager, info);
326     g_object_unref (manager);
327     info->progress_timer = g_timer_new ();
328 }
329 
330 NautilusProgressInfo *
nautilus_progress_info_new(void)331 nautilus_progress_info_new (void)
332 {
333     NautilusProgressInfo *info;
334 
335     info = g_object_new (NAUTILUS_TYPE_PROGRESS_INFO, NULL);
336 
337     return info;
338 }
339 
340 char *
nautilus_progress_info_get_status(NautilusProgressInfo * info)341 nautilus_progress_info_get_status (NautilusProgressInfo *info)
342 {
343     char *res;
344 
345     G_LOCK (progress_info);
346 
347     if (info->status)
348     {
349         res = g_strdup (info->status);
350     }
351     else
352     {
353         res = g_strdup (_("Preparing"));
354     }
355 
356     G_UNLOCK (progress_info);
357 
358     return res;
359 }
360 
361 char *
nautilus_progress_info_get_details(NautilusProgressInfo * info)362 nautilus_progress_info_get_details (NautilusProgressInfo *info)
363 {
364     char *res;
365 
366     G_LOCK (progress_info);
367 
368     if (info->details)
369     {
370         res = g_strdup (info->details);
371     }
372     else
373     {
374         res = g_strdup (_("Preparing"));
375     }
376 
377     G_UNLOCK (progress_info);
378 
379     return res;
380 }
381 
382 double
nautilus_progress_info_get_progress(NautilusProgressInfo * info)383 nautilus_progress_info_get_progress (NautilusProgressInfo *info)
384 {
385     double res;
386 
387     G_LOCK (progress_info);
388 
389     if (info->activity_mode)
390     {
391         res = -1.0;
392     }
393     else
394     {
395         res = info->progress;
396     }
397 
398     G_UNLOCK (progress_info);
399 
400     return res;
401 }
402 
403 void
nautilus_progress_info_cancel(NautilusProgressInfo * info)404 nautilus_progress_info_cancel (NautilusProgressInfo *info)
405 {
406     GCancellable *cancellable;
407 
408     cancellable = nautilus_progress_info_get_cancellable (info);
409     g_cancellable_cancel (cancellable);
410     g_object_unref (cancellable);
411 }
412 
413 GCancellable *
nautilus_progress_info_get_cancellable(NautilusProgressInfo * info)414 nautilus_progress_info_get_cancellable (NautilusProgressInfo *info)
415 {
416     GCancellable *c;
417 
418     G_LOCK (progress_info);
419 
420     c = g_object_ref (info->cancellable);
421 
422     G_UNLOCK (progress_info);
423 
424     return c;
425 }
426 
427 gboolean
nautilus_progress_info_get_is_cancelled(NautilusProgressInfo * info)428 nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info)
429 {
430     gboolean cancelled;
431 
432     G_LOCK (progress_info);
433     cancelled = g_cancellable_is_cancelled (info->cancellable);
434     G_UNLOCK (progress_info);
435 
436     return cancelled;
437 }
438 
439 gboolean
nautilus_progress_info_get_is_started(NautilusProgressInfo * info)440 nautilus_progress_info_get_is_started (NautilusProgressInfo *info)
441 {
442     gboolean res;
443 
444     G_LOCK (progress_info);
445 
446     res = info->started;
447 
448     G_UNLOCK (progress_info);
449 
450     return res;
451 }
452 
453 gboolean
nautilus_progress_info_get_is_finished(NautilusProgressInfo * info)454 nautilus_progress_info_get_is_finished (NautilusProgressInfo *info)
455 {
456     gboolean res;
457 
458     G_LOCK (progress_info);
459 
460     res = info->finished;
461 
462     G_UNLOCK (progress_info);
463 
464     return res;
465 }
466 
467 gboolean
nautilus_progress_info_get_is_paused(NautilusProgressInfo * info)468 nautilus_progress_info_get_is_paused (NautilusProgressInfo *info)
469 {
470     gboolean res;
471 
472     G_LOCK (progress_info);
473 
474     res = info->paused;
475 
476     G_UNLOCK (progress_info);
477 
478     return res;
479 }
480 
481 void
nautilus_progress_info_pause(NautilusProgressInfo * info)482 nautilus_progress_info_pause (NautilusProgressInfo *info)
483 {
484     G_LOCK (progress_info);
485 
486     if (!info->paused)
487     {
488         info->paused = TRUE;
489         g_timer_stop (info->progress_timer);
490     }
491 
492     G_UNLOCK (progress_info);
493 }
494 
495 void
nautilus_progress_info_resume(NautilusProgressInfo * info)496 nautilus_progress_info_resume (NautilusProgressInfo *info)
497 {
498     G_LOCK (progress_info);
499 
500     if (info->paused)
501     {
502         info->paused = FALSE;
503         g_timer_continue (info->progress_timer);
504     }
505 
506     G_UNLOCK (progress_info);
507 }
508 
509 void
nautilus_progress_info_start(NautilusProgressInfo * info)510 nautilus_progress_info_start (NautilusProgressInfo *info)
511 {
512     G_LOCK (progress_info);
513 
514     if (!info->started)
515     {
516         info->started = TRUE;
517         g_timer_start (info->progress_timer);
518 
519         info->start_at_idle = TRUE;
520         queue_idle (info, TRUE);
521     }
522 
523     G_UNLOCK (progress_info);
524 }
525 
526 void
nautilus_progress_info_finish(NautilusProgressInfo * info)527 nautilus_progress_info_finish (NautilusProgressInfo *info)
528 {
529     G_LOCK (progress_info);
530 
531     if (!info->finished)
532     {
533         info->finished = TRUE;
534         g_timer_stop (info->progress_timer);
535 
536         info->finish_at_idle = TRUE;
537         queue_idle (info, TRUE);
538     }
539 
540     G_UNLOCK (progress_info);
541 }
542 
543 static void
set_status(NautilusProgressInfo * info,const char * status)544 set_status (NautilusProgressInfo *info,
545             const char           *status)
546 {
547     g_free (info->status);
548     info->status = g_strdup (status);
549 
550     info->changed_at_idle = TRUE;
551     queue_idle (info, FALSE);
552 }
553 
554 void
nautilus_progress_info_take_status(NautilusProgressInfo * info,char * status)555 nautilus_progress_info_take_status (NautilusProgressInfo *info,
556                                     char                 *status)
557 {
558     G_LOCK (progress_info);
559 
560     if (g_strcmp0 (info->status, status) != 0 &&
561         !g_cancellable_is_cancelled (info->cancellable))
562     {
563         set_status (info, status);
564     }
565 
566     G_UNLOCK (progress_info);
567 
568     g_free (status);
569 }
570 
571 void
nautilus_progress_info_set_status(NautilusProgressInfo * info,const char * status)572 nautilus_progress_info_set_status (NautilusProgressInfo *info,
573                                    const char           *status)
574 {
575     G_LOCK (progress_info);
576 
577     if (g_strcmp0 (info->status, status) != 0 &&
578         !g_cancellable_is_cancelled (info->cancellable))
579     {
580         set_status (info, status);
581     }
582 
583     G_UNLOCK (progress_info);
584 }
585 
586 static void
set_details(NautilusProgressInfo * info,const char * details)587 set_details (NautilusProgressInfo *info,
588              const char           *details)
589 {
590     g_free (info->details);
591     info->details = g_strdup (details);
592 
593     info->changed_at_idle = TRUE;
594     queue_idle (info, FALSE);
595 }
596 
597 void
nautilus_progress_info_take_details(NautilusProgressInfo * info,char * details)598 nautilus_progress_info_take_details (NautilusProgressInfo *info,
599                                      char                 *details)
600 {
601     G_LOCK (progress_info);
602 
603     if (g_strcmp0 (info->details, details) != 0 &&
604         !g_cancellable_is_cancelled (info->cancellable))
605     {
606         set_details (info, details);
607     }
608 
609     G_UNLOCK (progress_info);
610 
611     g_free (details);
612 }
613 
614 void
nautilus_progress_info_set_details(NautilusProgressInfo * info,const char * details)615 nautilus_progress_info_set_details (NautilusProgressInfo *info,
616                                     const char           *details)
617 {
618     G_LOCK (progress_info);
619 
620     if (g_strcmp0 (info->details, details) != 0 &&
621         !g_cancellable_is_cancelled (info->cancellable))
622     {
623         set_details (info, details);
624     }
625 
626     G_UNLOCK (progress_info);
627 }
628 
629 void
nautilus_progress_info_pulse_progress(NautilusProgressInfo * info)630 nautilus_progress_info_pulse_progress (NautilusProgressInfo *info)
631 {
632     G_LOCK (progress_info);
633 
634     info->activity_mode = TRUE;
635     info->progress = 0.0;
636     info->progress_at_idle = TRUE;
637     queue_idle (info, FALSE);
638 
639     G_UNLOCK (progress_info);
640 }
641 
642 void
nautilus_progress_info_set_progress(NautilusProgressInfo * info,double current,double total)643 nautilus_progress_info_set_progress (NautilusProgressInfo *info,
644                                      double                current,
645                                      double                total)
646 {
647     double current_percent;
648 
649     if (total <= 0)
650     {
651         current_percent = 1.0;
652     }
653     else
654     {
655         current_percent = current / total;
656 
657         if (current_percent < 0)
658         {
659             current_percent = 0;
660         }
661 
662         if (current_percent > 1.0)
663         {
664             current_percent = 1.0;
665         }
666     }
667 
668     G_LOCK (progress_info);
669 
670     if ((info->activity_mode ||     /* emit on switch from activity mode */
671          fabs (current_percent - info->progress) > 0.005) &&    /* Emit on change of 0.5 percent */
672         !g_cancellable_is_cancelled (info->cancellable))
673     {
674         info->activity_mode = FALSE;
675         info->progress = current_percent;
676         info->progress_at_idle = TRUE;
677         queue_idle (info, FALSE);
678     }
679 
680     G_UNLOCK (progress_info);
681 }
682 
683 void
nautilus_progress_info_set_remaining_time(NautilusProgressInfo * info,gdouble time)684 nautilus_progress_info_set_remaining_time (NautilusProgressInfo *info,
685                                            gdouble               time)
686 {
687     G_LOCK (progress_info);
688     info->remaining_time = time;
689     G_UNLOCK (progress_info);
690 }
691 
692 gdouble
nautilus_progress_info_get_remaining_time(NautilusProgressInfo * info)693 nautilus_progress_info_get_remaining_time (NautilusProgressInfo *info)
694 {
695     gint remaining_time;
696 
697     G_LOCK (progress_info);
698     remaining_time = info->remaining_time;
699     G_UNLOCK (progress_info);
700 
701     return remaining_time;
702 }
703 
704 void
nautilus_progress_info_set_elapsed_time(NautilusProgressInfo * info,gdouble time)705 nautilus_progress_info_set_elapsed_time (NautilusProgressInfo *info,
706                                          gdouble               time)
707 {
708     G_LOCK (progress_info);
709     info->elapsed_time = time;
710     G_UNLOCK (progress_info);
711 }
712 
713 gdouble
nautilus_progress_info_get_elapsed_time(NautilusProgressInfo * info)714 nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info)
715 {
716     gint elapsed_time;
717 
718     G_LOCK (progress_info);
719     elapsed_time = info->elapsed_time;
720     G_UNLOCK (progress_info);
721 
722     return elapsed_time;
723 }
724 
725 gdouble
nautilus_progress_info_get_total_elapsed_time(NautilusProgressInfo * info)726 nautilus_progress_info_get_total_elapsed_time (NautilusProgressInfo *info)
727 {
728     gdouble elapsed_time;
729 
730     G_LOCK (progress_info);
731     elapsed_time = g_timer_elapsed (info->progress_timer, NULL);
732     G_UNLOCK (progress_info);
733 
734     return elapsed_time;
735 }
736 
737 void
nautilus_progress_info_set_destination(NautilusProgressInfo * info,GFile * file)738 nautilus_progress_info_set_destination (NautilusProgressInfo *info,
739                                         GFile                *file)
740 {
741     G_LOCK (progress_info);
742     g_clear_object (&info->destination);
743     info->destination = g_object_ref (file);
744     G_UNLOCK (progress_info);
745 }
746 
747 GFile *
nautilus_progress_info_get_destination(NautilusProgressInfo * info)748 nautilus_progress_info_get_destination (NautilusProgressInfo *info)
749 {
750     GFile *destination = NULL;
751 
752     G_LOCK (progress_info);
753     if (info->destination)
754     {
755         destination = g_object_ref (info->destination);
756     }
757     G_UNLOCK (progress_info);
758 
759     return destination;
760 }
761