gem-graph-client/libide/core/ide-transfer-manager.c

546 lines
15 KiB
C

/* ide-transfer-manager.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-transfer-manager"
#include "config.h"
#include "ide-context.h"
#include "ide-debug.h"
#include "ide-macros.h"
#include "ide-transfer.h"
#include "ide-transfer-manager.h"
#include "ide-transfer-manager-private.h"
struct _IdeTransferManager
{
GObject parent_instance;
GPtrArray *transfers;
GSimpleActionGroup *actions;
};
static void list_model_iface_init (GListModelInterface *iface);
G_DEFINE_FINAL_TYPE_WITH_CODE (IdeTransferManager, ide_transfer_manager, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
enum {
PROP_0,
PROP_HAS_ACTIVE,
PROP_PROGRESS,
N_PROPS
};
enum {
TRANSFER_COMPLETED,
TRANSFER_FAILED,
ALL_TRANSFERS_COMPLETED,
N_SIGNALS
};
static GParamSpec *properties [N_PROPS];
static guint signals [N_SIGNALS];
/**
* ide_transfer_manager_get_has_active:
*
* Gets if there are active transfers.
*
* Returns: %TRUE if there are active transfers.
*
* Since: 3.32
*/
gboolean
ide_transfer_manager_get_has_active (IdeTransferManager *self)
{
g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), FALSE);
for (guint i = 0; i < self->transfers->len; i++)
{
IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
if (ide_transfer_get_active (transfer))
return TRUE;
}
return FALSE;
}
static void
ide_transfer_manager_finalize (GObject *object)
{
IdeTransferManager *self = (IdeTransferManager *)object;
g_clear_pointer (&self->transfers, g_ptr_array_unref);
g_clear_object (&self->actions);
G_OBJECT_CLASS (ide_transfer_manager_parent_class)->finalize (object);
}
static void
ide_transfer_manager_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeTransferManager *self = IDE_TRANSFER_MANAGER (object);
switch (prop_id)
{
case PROP_HAS_ACTIVE:
g_value_set_boolean (value, ide_transfer_manager_get_has_active (self));
break;
case PROP_PROGRESS:
g_value_set_double (value, ide_transfer_manager_get_progress (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_transfer_manager_class_init (IdeTransferManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = ide_transfer_manager_finalize;
object_class->get_property = ide_transfer_manager_get_property;
/**
* IdeTransferManager:has-active:
*
* If there are transfers active, this will be set.
*
* Since: 3.32
*/
properties [PROP_HAS_ACTIVE] =
g_param_spec_boolean ("has-active",
"Has Active",
"Has Active",
FALSE,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* IdeTransferManager:progress:
*
* A double between and including 0.0 and 1.0 describing the progress of
* all tasks.
*
* Since: 3.32
*/
properties [PROP_PROGRESS] =
g_param_spec_double ("progress",
"Progress",
"Progress",
0.0,
1.0,
0.0,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
/**
* IdeTransferManager::all-transfers-completed:
*
* This signal is emitted when all of the transfers have completed or failed.
*
* Since: 3.32
*/
signals [ALL_TRANSFERS_COMPLETED] =
g_signal_new ("all-transfers-completed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
/**
* IdeTransferManager::transfer-completed:
* @self: An #IdeTransferManager
* @transfer: An #IdeTransfer
*
* This signal is emitted when a transfer has completed successfully.
*
* Since: 3.32
*/
signals [TRANSFER_COMPLETED] =
g_signal_new ("transfer-completed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, IDE_TYPE_TRANSFER);
/**
* IdeTransferManager::transfer-failed:
* @self: An #IdeTransferManager
* @transfer: An #IdeTransfer
* @reason: (in): The reason for the failure.
*
* This signal is emitted when a transfer has failed to complete
* successfully.
*
* Since: 3.32
*/
signals [TRANSFER_FAILED] =
g_signal_new ("transfer-failed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, IDE_TYPE_TRANSFER, G_TYPE_ERROR);
}
static void
ide_transfer_manager_init (IdeTransferManager *self)
{
self->actions = g_simple_action_group_new ();
self->transfers = g_ptr_array_new_with_free_func (g_object_unref);
}
static void
ide_transfer_manager_notify_progress (IdeTransferManager *self,
GParamSpec *pspec,
IdeTransfer *transfer)
{
g_assert (IDE_IS_TRANSFER_MANAGER (self));
g_assert (IDE_IS_TRANSFER (transfer));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
}
static gboolean
ide_transfer_manager_append (IdeTransferManager *self,
IdeTransfer *transfer)
{
g_autofree gchar *action_name = NULL;
g_autoptr(GSimpleAction) action = NULL;
guint position;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), FALSE);
g_return_val_if_fail (IDE_IS_TRANSFER (transfer), FALSE);
for (guint i = 0; i < self->transfers->len; i++)
{
if (transfer == (IdeTransfer *)g_ptr_array_index (self->transfers, i))
IDE_RETURN (FALSE);
}
g_signal_connect_object (transfer,
"notify::progress",
G_CALLBACK (ide_transfer_manager_notify_progress),
self,
G_CONNECT_SWAPPED);
position = self->transfers->len;
g_ptr_array_add (self->transfers, g_object_ref (transfer));
g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
action_name = g_strdup_printf ("cancel-%d", _ide_transfer_get_id (transfer));
action = g_simple_action_new (action_name, NULL);
g_signal_connect_object (action,
"activate",
G_CALLBACK (ide_transfer_cancel),
transfer,
G_CONNECT_SWAPPED);
g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
IDE_RETURN (TRUE);
}
void
ide_transfer_manager_cancel_all (IdeTransferManager *self)
{
IDE_ENTRY;
g_return_if_fail (IDE_IS_TRANSFER_MANAGER (self));
for (guint i = 0; i < self->transfers->len; i++)
{
IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
ide_transfer_cancel (transfer);
}
IDE_EXIT;
}
/**
* ide_transfer_manager_clear:
*
* Removes all transfers from the manager that are completed.
*
* Since: 3.32
*/
void
ide_transfer_manager_clear (IdeTransferManager *self)
{
IDE_ENTRY;
g_return_if_fail (IDE_IS_TRANSFER_MANAGER (self));
for (guint i = self->transfers->len; i > 0; i--)
{
IdeTransfer *transfer = g_ptr_array_index (self->transfers, i - 1);
if (!ide_transfer_get_active (transfer))
{
g_autofree gchar *action_name = NULL;
action_name = g_strdup_printf ("cancel-%d", _ide_transfer_get_id (transfer));
g_action_map_remove_action (G_ACTION_MAP (self->actions), action_name);
g_ptr_array_remove_index (self->transfers, i - 1);
g_list_model_items_changed (G_LIST_MODEL (self), i - 1, 1, 0);
}
}
IDE_EXIT;
}
static GType
ide_transfer_manager_get_item_type (GListModel *model)
{
return IDE_TYPE_TRANSFER;
}
static guint
ide_transfer_manager_get_n_items (GListModel *model)
{
IdeTransferManager *self = (IdeTransferManager *)model;
g_assert (IDE_IS_TRANSFER_MANAGER (self));
return self->transfers->len;
}
static gpointer
ide_transfer_manager_get_item (GListModel *model,
guint position)
{
IdeTransferManager *self = (IdeTransferManager *)model;
g_assert (IDE_IS_TRANSFER_MANAGER (self));
if G_UNLIKELY (position >= self->transfers->len)
return NULL;
return g_object_ref (g_ptr_array_index (self->transfers, position));
}
static void
list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = ide_transfer_manager_get_item_type;
iface->get_n_items = ide_transfer_manager_get_n_items;
iface->get_item = ide_transfer_manager_get_item;
}
gdouble
ide_transfer_manager_get_progress (IdeTransferManager *self)
{
gdouble total = 0.0;
g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), 0.0);
if (self->transfers->len > 0)
{
guint count = 0;
for (guint i = 0; i < self->transfers->len; i++)
{
IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
gdouble progress = ide_transfer_get_progress (transfer);
if (ide_transfer_get_completed (transfer) || ide_transfer_get_active (transfer))
{
total += MAX (0.0, MIN (1.0, progress));
count++;
}
}
if (count != 0)
total /= (gdouble)count;
}
return total;
}
static void
ide_transfer_manager_execute_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeTransfer *transfer = (IdeTransfer *)object;
IdeTransferManager *self;
g_autoptr(GTask) task = user_data;
g_autoptr(GError) error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_TRANSFER (transfer));
g_assert (G_IS_TASK (task));
self = g_task_get_source_object (task);
if (!ide_transfer_execute_finish (transfer, result, &error))
{
g_signal_emit (self, signals[TRANSFER_FAILED], 0, transfer, error);
g_task_return_error (task, g_steal_pointer (&error));
IDE_GOTO (notify_properties);
}
else
{
g_signal_emit (self, signals[TRANSFER_COMPLETED], 0, transfer);
g_task_return_boolean (task, TRUE);
}
if (!ide_transfer_manager_get_has_active (self))
g_signal_emit (self, signals[ALL_TRANSFERS_COMPLETED], 0);
notify_properties:
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_ACTIVE]);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
IDE_EXIT;
}
/**
* ide_transfer_manager_execute_async:
* @self: An #IdeTransferManager
* @cancellable: (nullable): a #GCancellable
* @callback: (nullable): A callback or %NULL
* @user_data: user data for @callback
*
* This is a convenience function that will queue @transfer into the transfer
* manager and execute callback upon completion of the transfer. The success
* or failure #GError will be propagated to the caller via
* ide_transfer_manager_execute_finish().
*
* Since: 3.32
*/
void
ide_transfer_manager_execute_async (IdeTransferManager *self,
IdeTransfer *transfer,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
IDE_ENTRY;
g_return_if_fail (!self || IDE_IS_TRANSFER_MANAGER (self));
g_return_if_fail (IDE_IS_TRANSFER (transfer));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
if (self == NULL)
self = ide_transfer_manager_get_default ();
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, ide_transfer_manager_execute_async);
if (!ide_transfer_manager_append (self, transfer))
{
if (ide_transfer_get_active (transfer))
{
g_warning ("%s is already active, ignoring transfer request",
G_OBJECT_TYPE_NAME (transfer));
IDE_EXIT;
}
}
ide_transfer_execute_async (transfer,
cancellable,
ide_transfer_manager_execute_cb,
g_steal_pointer (&task));
IDE_EXIT;
}
gboolean
ide_transfer_manager_execute_finish (IdeTransferManager *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), FALSE);
g_return_val_if_fail (G_IS_TASK (result), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/**
* ide_transfer_manager_get_default:
*
* Gets the #IdeTransferManager singleton.
*
* Returns: (transfer none): an #IdeTransferManager
*
* Since: 3.32
*/
IdeTransferManager *
ide_transfer_manager_get_default (void)
{
static IdeTransferManager *instance;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (!instance || IDE_IS_TRANSFER_MANAGER (instance));
if (g_once_init_enter (&instance))
g_once_init_leave (&instance, g_object_new (IDE_TYPE_TRANSFER_MANAGER, NULL));
return instance;
}
void
_ide_transfer_manager_cancel_by_id (IdeTransferManager *self,
gint unique_id)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_TRANSFER_MANAGER (self));
g_print ("Cancelling transfer %i\n", unique_id);
for (guint i = 0; i < self->transfers->len; i++)
{
IdeTransfer *transfer = g_ptr_array_index (self->transfers, i);
if (_ide_transfer_get_id (transfer) == unique_id)
{
ide_transfer_cancel (transfer);
break;
}
}
}
GActionGroup *
_ide_transfer_manager_get_actions (IdeTransferManager *self)
{
if (self == NULL)
self = ide_transfer_manager_get_default ();
g_return_val_if_fail (IDE_IS_TRANSFER_MANAGER (self), NULL);
return G_ACTION_GROUP (self->actions);
}