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