1 /* ide-transfer-manager.c
2  *
3  * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-transfer-manager"
22 
23 #include "config.h"
24 
25 #include "ide-context.h"
26 #include "ide-debug.h"
27 #include "ide-macros.h"
28 
29 #include "ide-transfer.h"
30 #include "ide-transfer-manager.h"
31 #include "ide-transfer-manager-private.h"
32 
33 struct _IdeTransferManager
34 {
35   GObject             parent_instance;
36   GPtrArray          *transfers;
37   GSimpleActionGroup *actions;
38 };
39 
40 static void list_model_iface_init (GListModelInterface *iface);
41 
42 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeTransferManager, ide_transfer_manager, G_TYPE_OBJECT,
43                          G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
44 
45 enum {
46   PROP_0,
47   PROP_HAS_ACTIVE,
48   PROP_PROGRESS,
49   N_PROPS
50 };
51 
52 enum {
53   TRANSFER_COMPLETED,
54   TRANSFER_FAILED,
55   ALL_TRANSFERS_COMPLETED,
56   N_SIGNALS
57 };
58 
59 static GParamSpec *properties [N_PROPS];
60 static guint signals [N_SIGNALS];
61 
62 /**
63  * ide_transfer_manager_get_has_active:
64  *
65  * Gets if there are active transfers.
66  *
67  * Returns: %TRUE if there are active transfers.
68  *
69  * Since: 3.32
70  */
71 gboolean
ide_transfer_manager_get_has_active(IdeTransferManager * self)72 ide_transfer_manager_get_has_active (IdeTransferManager *self)
73 {
74   g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), FALSE);
75 
76   for (guint i = 0; i < self->transfers->len; i++)
77     {
78       IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
79 
80       if (ide_transfer_get_active (transfer))
81         return TRUE;
82     }
83 
84   return FALSE;
85 }
86 
87 static void
ide_transfer_manager_finalize(GObject * object)88 ide_transfer_manager_finalize (GObject *object)
89 {
90   IdeTransferManager *self = (IdeTransferManager *)object;
91 
92   g_clear_pointer (&self->transfers, g_ptr_array_unref);
93   g_clear_object (&self->actions);
94 
95   G_OBJECT_CLASS (ide_transfer_manager_parent_class)->finalize (object);
96 }
97 
98 static void
ide_transfer_manager_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)99 ide_transfer_manager_get_property (GObject    *object,
100                                    guint       prop_id,
101                                    GValue     *value,
102                                    GParamSpec *pspec)
103 {
104   IdeTransferManager *self = IDE_TRANSFER_MANAGER (object);
105 
106   switch (prop_id)
107     {
108     case PROP_HAS_ACTIVE:
109       g_value_set_boolean (value, ide_transfer_manager_get_has_active (self));
110       break;
111 
112     case PROP_PROGRESS:
113       g_value_set_double (value, ide_transfer_manager_get_progress (self));
114       break;
115 
116     default:
117       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
118     }
119 }
120 
121 static void
ide_transfer_manager_class_init(IdeTransferManagerClass * klass)122 ide_transfer_manager_class_init (IdeTransferManagerClass *klass)
123 {
124   GObjectClass *object_class = G_OBJECT_CLASS (klass);
125 
126   object_class->finalize = ide_transfer_manager_finalize;
127   object_class->get_property = ide_transfer_manager_get_property;
128 
129   /**
130    * IdeTransferManager:has-active:
131    *
132    * If there are transfers active, this will be set.
133    *
134    * Since: 3.32
135    */
136   properties [PROP_HAS_ACTIVE] =
137     g_param_spec_boolean ("has-active",
138                           "Has Active",
139                           "Has Active",
140                           FALSE,
141                           (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
142 
143   /**
144    * IdeTransferManager:progress:
145    *
146    * A double between and including 0.0 and 1.0 describing the progress of
147    * all tasks.
148    *
149    * Since: 3.32
150    */
151   properties [PROP_PROGRESS] =
152     g_param_spec_double ("progress",
153                          "Progress",
154                          "Progress",
155                          0.0,
156                          1.0,
157                          0.0,
158                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
159 
160   g_object_class_install_properties (object_class, N_PROPS, properties);
161 
162   /**
163    * IdeTransferManager::all-transfers-completed:
164    *
165    * This signal is emitted when all of the transfers have completed or failed.
166    *
167    * Since: 3.32
168    */
169   signals [ALL_TRANSFERS_COMPLETED] =
170     g_signal_new ("all-transfers-completed",
171                   G_TYPE_FROM_CLASS (klass),
172                   G_SIGNAL_RUN_LAST,
173                   0, NULL, NULL, NULL, G_TYPE_NONE, 0);
174 
175   /**
176    * IdeTransferManager::transfer-completed:
177    * @self: An #IdeTransferManager
178    * @transfer: An #IdeTransfer
179    *
180    * This signal is emitted when a transfer has completed successfully.
181    *
182    * Since: 3.32
183    */
184   signals [TRANSFER_COMPLETED] =
185     g_signal_new ("transfer-completed",
186                   G_TYPE_FROM_CLASS (klass),
187                   G_SIGNAL_RUN_LAST,
188                   0,
189                   NULL, NULL, NULL,
190                   G_TYPE_NONE, 1, IDE_TYPE_TRANSFER);
191 
192   /**
193    * IdeTransferManager::transfer-failed:
194    * @self: An #IdeTransferManager
195    * @transfer: An #IdeTransfer
196    * @reason: (in): The reason for the failure.
197    *
198    * This signal is emitted when a transfer has failed to complete
199    * successfully.
200    *
201    * Since: 3.32
202    */
203   signals [TRANSFER_FAILED] =
204     g_signal_new ("transfer-failed",
205                   G_TYPE_FROM_CLASS (klass),
206                   G_SIGNAL_RUN_LAST,
207                   0,
208                   NULL, NULL, NULL,
209                   G_TYPE_NONE, 2, IDE_TYPE_TRANSFER, G_TYPE_ERROR);
210 }
211 
212 static void
ide_transfer_manager_init(IdeTransferManager * self)213 ide_transfer_manager_init (IdeTransferManager *self)
214 {
215   self->actions = g_simple_action_group_new ();
216   self->transfers = g_ptr_array_new_with_free_func (g_object_unref);
217 }
218 
219 static void
ide_transfer_manager_notify_progress(IdeTransferManager * self,GParamSpec * pspec,IdeTransfer * transfer)220 ide_transfer_manager_notify_progress (IdeTransferManager *self,
221                                       GParamSpec         *pspec,
222                                       IdeTransfer        *transfer)
223 {
224   g_assert (IDE_IS_TRANSFER_MANAGER (self));
225   g_assert (IDE_IS_TRANSFER (transfer));
226 
227   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
228 }
229 
230 static gboolean
ide_transfer_manager_append(IdeTransferManager * self,IdeTransfer * transfer)231 ide_transfer_manager_append (IdeTransferManager *self,
232                              IdeTransfer        *transfer)
233 {
234   g_autofree gchar *action_name = NULL;
235   g_autoptr(GSimpleAction) action = NULL;
236   guint position;
237 
238   IDE_ENTRY;
239 
240   g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), FALSE);
241   g_return_val_if_fail (IDE_IS_TRANSFER (transfer), FALSE);
242 
243   for (guint i = 0; i < self->transfers->len; i++)
244     {
245       if (transfer == (IdeTransfer *)g_ptr_array_index (self->transfers, i))
246         IDE_RETURN (FALSE);
247     }
248 
249   g_signal_connect_object (transfer,
250                            "notify::progress",
251                            G_CALLBACK (ide_transfer_manager_notify_progress),
252                            self,
253                            G_CONNECT_SWAPPED);
254 
255   position = self->transfers->len;
256   g_ptr_array_add (self->transfers, g_object_ref (transfer));
257   g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
258 
259   action_name = g_strdup_printf ("cancel-%d", _ide_transfer_get_id (transfer));
260   action = g_simple_action_new (action_name, NULL);
261   g_signal_connect_object (action,
262                            "activate",
263                            G_CALLBACK (ide_transfer_cancel),
264                            transfer,
265                            G_CONNECT_SWAPPED);
266   g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
267 
268   IDE_RETURN (TRUE);
269 }
270 
271 void
ide_transfer_manager_cancel_all(IdeTransferManager * self)272 ide_transfer_manager_cancel_all (IdeTransferManager *self)
273 {
274   IDE_ENTRY;
275 
276   g_return_if_fail (IDE_IS_TRANSFER_MANAGER (self));
277 
278   for (guint i = 0; i < self->transfers->len; i++)
279     {
280       IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
281 
282       ide_transfer_cancel (transfer);
283     }
284 
285   IDE_EXIT;
286 }
287 
288 /**
289  * ide_transfer_manager_clear:
290  *
291  * Removes all transfers from the manager that are completed.
292  *
293  * Since: 3.32
294  */
295 void
ide_transfer_manager_clear(IdeTransferManager * self)296 ide_transfer_manager_clear (IdeTransferManager *self)
297 {
298   IDE_ENTRY;
299 
300   g_return_if_fail (IDE_IS_TRANSFER_MANAGER (self));
301 
302   for (guint i = self->transfers->len; i > 0; i--)
303     {
304       IdeTransfer *transfer = g_ptr_array_index (self->transfers, i - 1);
305 
306       if (!ide_transfer_get_active (transfer))
307         {
308           g_autofree gchar *action_name = NULL;
309 
310           action_name = g_strdup_printf ("cancel-%d", _ide_transfer_get_id (transfer));
311           g_action_map_remove_action (G_ACTION_MAP (self->actions), action_name);
312 
313           g_ptr_array_remove_index (self->transfers, i - 1);
314           g_list_model_items_changed (G_LIST_MODEL (self), i - 1, 1, 0);
315         }
316     }
317 
318   IDE_EXIT;
319 }
320 
321 static GType
ide_transfer_manager_get_item_type(GListModel * model)322 ide_transfer_manager_get_item_type (GListModel *model)
323 {
324   return IDE_TYPE_TRANSFER;
325 }
326 
327 static guint
ide_transfer_manager_get_n_items(GListModel * model)328 ide_transfer_manager_get_n_items (GListModel *model)
329 {
330   IdeTransferManager *self = (IdeTransferManager *)model;
331 
332   g_assert (IDE_IS_TRANSFER_MANAGER (self));
333 
334   return self->transfers->len;
335 }
336 
337 static gpointer
ide_transfer_manager_get_item(GListModel * model,guint position)338 ide_transfer_manager_get_item (GListModel *model,
339                                guint       position)
340 {
341   IdeTransferManager *self = (IdeTransferManager *)model;
342 
343   g_assert (IDE_IS_TRANSFER_MANAGER (self));
344 
345   if G_UNLIKELY (position >= self->transfers->len)
346     return NULL;
347 
348   return g_object_ref (g_ptr_array_index (self->transfers, position));
349 }
350 
351 static void
list_model_iface_init(GListModelInterface * iface)352 list_model_iface_init (GListModelInterface *iface)
353 {
354   iface->get_item_type = ide_transfer_manager_get_item_type;
355   iface->get_n_items = ide_transfer_manager_get_n_items;
356   iface->get_item = ide_transfer_manager_get_item;
357 }
358 
359 gdouble
ide_transfer_manager_get_progress(IdeTransferManager * self)360 ide_transfer_manager_get_progress (IdeTransferManager *self)
361 {
362   gdouble total = 0.0;
363 
364   g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), 0.0);
365 
366   if (self->transfers->len > 0)
367     {
368       guint count = 0;
369 
370       for (guint i = 0; i < self->transfers->len; i++)
371         {
372           IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
373           gdouble progress = ide_transfer_get_progress (transfer);
374 
375           if (ide_transfer_get_completed (transfer) || ide_transfer_get_active (transfer))
376             {
377               total += MAX (0.0, MIN (1.0, progress));
378               count++;
379             }
380         }
381 
382       if (count != 0)
383         total /= (gdouble)count;
384     }
385 
386   return total;
387 }
388 
389 static void
ide_transfer_manager_execute_cb(GObject * object,GAsyncResult * result,gpointer user_data)390 ide_transfer_manager_execute_cb (GObject      *object,
391                                  GAsyncResult *result,
392                                  gpointer      user_data)
393 {
394   IdeTransfer *transfer = (IdeTransfer *)object;
395   IdeTransferManager *self;
396   g_autoptr(GTask) task = user_data;
397   g_autoptr(GError) error = NULL;
398 
399   IDE_ENTRY;
400 
401   g_assert (IDE_IS_TRANSFER (transfer));
402   g_assert (G_IS_TASK (task));
403 
404   self = g_task_get_source_object (task);
405 
406   if (!ide_transfer_execute_finish (transfer, result, &error))
407     {
408       g_signal_emit (self, signals[TRANSFER_FAILED], 0, transfer, error);
409       g_task_return_error (task, g_steal_pointer (&error));
410       IDE_GOTO (notify_properties);
411     }
412   else
413     {
414       g_signal_emit (self, signals[TRANSFER_COMPLETED], 0, transfer);
415       g_task_return_boolean (task, TRUE);
416     }
417 
418   if (!ide_transfer_manager_get_has_active (self))
419     g_signal_emit (self, signals[ALL_TRANSFERS_COMPLETED], 0);
420 
421 notify_properties:
422   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_ACTIVE]);
423   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
424 
425   IDE_EXIT;
426 }
427 
428 /**
429  * ide_transfer_manager_execute_async:
430  * @self: An #IdeTransferManager
431  * @cancellable: (nullable): a #GCancellable
432  * @callback: (nullable): A callback or %NULL
433  * @user_data: user data for @callback
434  *
435  * This is a convenience function that will queue @transfer into the transfer
436  * manager and execute callback upon completion of the transfer. The success
437  * or failure #GError will be propagated to the caller via
438  * ide_transfer_manager_execute_finish().
439  *
440  * Since: 3.32
441  */
442 void
ide_transfer_manager_execute_async(IdeTransferManager * self,IdeTransfer * transfer,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)443 ide_transfer_manager_execute_async (IdeTransferManager  *self,
444                                     IdeTransfer         *transfer,
445                                     GCancellable        *cancellable,
446                                     GAsyncReadyCallback  callback,
447                                     gpointer             user_data)
448 {
449   g_autoptr(GTask) task = NULL;
450 
451   IDE_ENTRY;
452 
453   g_return_if_fail (!self || IDE_IS_TRANSFER_MANAGER (self));
454   g_return_if_fail (IDE_IS_TRANSFER (transfer));
455   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
456 
457   if (self == NULL)
458     self = ide_transfer_manager_get_default ();
459 
460   task = g_task_new (self, cancellable, callback, user_data);
461   g_task_set_source_tag (task, ide_transfer_manager_execute_async);
462 
463   if (!ide_transfer_manager_append (self, transfer))
464     {
465       if (ide_transfer_get_active (transfer))
466         {
467           g_warning ("%s is already active, ignoring transfer request",
468                      G_OBJECT_TYPE_NAME (transfer));
469           IDE_EXIT;
470         }
471     }
472 
473   ide_transfer_execute_async (transfer,
474                               cancellable,
475                               ide_transfer_manager_execute_cb,
476                               g_steal_pointer (&task));
477 
478   IDE_EXIT;
479 }
480 
481 gboolean
ide_transfer_manager_execute_finish(IdeTransferManager * self,GAsyncResult * result,GError ** error)482 ide_transfer_manager_execute_finish (IdeTransferManager  *self,
483                                      GAsyncResult        *result,
484                                      GError             **error)
485 {
486   g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), FALSE);
487   g_return_val_if_fail (G_IS_TASK (result), FALSE);
488 
489   return g_task_propagate_boolean (G_TASK (result), error);
490 }
491 
492 /**
493  * ide_transfer_manager_get_default:
494  *
495  * Gets the #IdeTransferManager singleton.
496  *
497  * Returns: (transfer none): an #IdeTransferManager
498  *
499  * Since: 3.32
500  */
501 IdeTransferManager *
ide_transfer_manager_get_default(void)502 ide_transfer_manager_get_default (void)
503 {
504   static IdeTransferManager *instance;
505 
506   g_assert (IDE_IS_MAIN_THREAD ());
507   g_assert (!instance || IDE_IS_TRANSFER_MANAGER (instance));
508 
509   if (g_once_init_enter (&instance))
510     g_once_init_leave (&instance, g_object_new (IDE_TYPE_TRANSFER_MANAGER, NULL));
511 
512   return instance;
513 }
514 
515 void
_ide_transfer_manager_cancel_by_id(IdeTransferManager * self,gint unique_id)516 _ide_transfer_manager_cancel_by_id (IdeTransferManager *self,
517                                     gint                unique_id)
518 {
519   g_return_if_fail (IDE_IS_MAIN_THREAD ());
520   g_return_if_fail (IDE_IS_TRANSFER_MANAGER (self));
521 
522   g_print ("Cancelling transfer %i\n", unique_id);
523 
524   for (guint i = 0; i < self->transfers->len; i++)
525     {
526       IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
527 
528       if (_ide_transfer_get_id (transfer) == unique_id)
529         {
530           ide_transfer_cancel (transfer);
531           break;
532         }
533     }
534 }
535 
536 GActionGroup *
_ide_transfer_manager_get_actions(IdeTransferManager * self)537 _ide_transfer_manager_get_actions (IdeTransferManager *self)
538 {
539   if (self == NULL)
540     self = ide_transfer_manager_get_default ();
541 
542   g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), NULL);
543 
544   return G_ACTION_GROUP (self->actions);
545 }
546