// vim: colorcolumn=80 ts=4 sw=4

#include "gfi-progress.h"

/* SIGNAL GLOBALS */

enum signal_types {
	UPDATE_PROGRESS,
	UPDATE_STATUS,
	NEW_TRANSACTION,
	ERROR,
	TRANSACTION_DONE,
	READY_TO_START,
	READY_TO_CONFIRM,
	READY_TO_PROCEED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

/* INTERNAL DECLARATIONS */

static void		handle_error (GfiProgress *self, const char *func_name);
static void		gfi_progress_flatpak_transaction_run_async (GfiProgress *self,
	   	GCancellable *cancellable, GAsyncReadyCallback callback,
		gpointer user_data);
static gboolean	gfi_progress_flatpak_transaction_run_finish (GfiProgress *self,
		GAsyncResult *result);
static void		flatpak_transaction_run_async_ready_cb (GObject *source_object,
		GAsyncResult *res, gpointer user_data);

/* GOBJECT DEFINITION */

struct _GfiProgress
{
	GObject parent_instance;

	GError						*error;
	GCancellable				*cancellable;
	GTask						*task;
	GFile						*flatpakref_file;
	/* Used for uninstall and upgrade operations since those flatpak
	 * subroutines want the 'ref' in C-string format */
	char 						*ref_str;
	FlatpakInstallation			*installation;
	FlatpakTransaction			*transaction;
	char						*status;
	int							progress_percent;
	GfiProgressTransactionType	transaction_type;
	GfiProgressActionType		action_type;
	char						*appname;
	char						*transaction_name;
	char						*last_error_msg;
	guint64						total_download_size;
	/* list of GfiProgressData */
	GSList						*deplist;
	gboolean					ready_to_confirm;
	gboolean					ready_to_proceed;
};

G_DEFINE_TYPE (GfiProgress, gfi_progress, G_TYPE_OBJECT)

/* INTERNAL FUNCTION DEFINITIONS */

/* :: add-new-remote cb
 * This is our signal handler for when flatpak wants to add a new remote
 * for whatever reason.  This callback returns TRUE to add the remote (acc. to
 * TFM), so we do so if the remote needs to be added to satisfy deps.
 */
static gboolean
add_new_remote_cb (FlatpakTransaction *transaction,
		int	reason,
		char *from_id,
		char *suggested_remote_name,
		char *url,
		gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);
	gboolean retval = FALSE;

	switch (reason)
	{
		/* I don't think I really need to handle this case.
		 */
		case FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO:
			g_debug ("%s: reason: "
					"The remote specified in the flatpakref has other apps too",
					__func__);

			retval = FALSE;
			break;

		/* if a remote needs to be added to satisfy deps, just allow it and
		 * move on.
		 */
		case FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS:
			g_debug ("%s: reason: "
					"The remote has runtimes needed for the app",
					__func__);

			retval = TRUE;
			break;

		default:
			/* we have no other reasons... */
			g_error ("%s: Invalid reason. API misuse. Abort.", __func__);
			break;
	}
	return retval;
}

/* ::choose-remote-for-ref cb
 * returns (acc. to TFM):
 * ``the index of the remote to use, or -1 to not pick one (and fail)''
 */
static int
choose_remote_cb (FlatpakTransaction *object,
		char *for_ref,
		char *runtime_ref,
		GStrv remotes,		/* just a NULL-term'd char **	*/
		gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	/* Automatically select the first available remote thas has the dependency
	 * we need to install.
	 *
	 * TODO: Possibly be more clever here, but this currently matches AppCenter
	 * & GNOME Software (thanks to `sideload' of elementaryOS sources for this
	 * info).
	 */
	if (remotes[0])
		return 0; 
	else
		return -1;
}

static gboolean
emit_ready_to_proceed (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	self->ready_to_proceed = TRUE;

	g_signal_emit (self, signals[READY_TO_PROCEED], 0);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_ready_to_start (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	self->ready_to_confirm = TRUE;
	g_signal_emit (self, signals[READY_TO_START], 0);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_ready_to_confirm (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	self->ready_to_confirm = TRUE;
	g_signal_emit (self, signals[READY_TO_CONFIRM], 0);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_transaction_done (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	g_signal_emit (self, signals[TRANSACTION_DONE], 0);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_error (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	g_signal_emit (self, signals[ERROR], 0);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_new_transaction (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	g_signal_emit (self, signals[NEW_TRANSACTION], 0, self->transaction_type);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_update_progress (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	g_signal_emit (self, signals[UPDATE_PROGRESS], 0, self->progress_percent);

	return G_SOURCE_REMOVE;
}

static gboolean
emit_update_status (gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_assert (IN_MAIN_CONTEXT);

	g_signal_emit (self, signals[UPDATE_STATUS], 0);

	return G_SOURCE_REMOVE;
}

static void
handle_error (GfiProgress *self, const char *func_name)
{
	g_return_if_fail (self->error);

	/* Firstly, just print a debugging message on the screen.
	 */
	if (! func_name)
		func_name = "";

	g_debug ("%s: ERROR: %s",
			func_name, self->error->message);

	g_free (self->last_error_msg);
	self->last_error_msg = g_strdup (self->error->message);

	/* Emit a signal that an error occurred, so that a nice gui can display
	 * it if desired. Emit explicitly in the main context to be on safe side.
	 */
	g_main_context_invoke (NULL, emit_error, self);

	g_clear_error (&self->error);
}

static void
build_dep_list_and_calculate_download_data (GfiProgress *self)
{
	GList *op_list = NULL;
	GSList *deplist = NULL;
	guint64 total_dl_size = 0;

	op_list = flatpak_transaction_get_operations (self->transaction);

	while (op_list)
	{
		GKeyFile *keyfile;
		FlatpakTransactionOperation *op;
		char *name = NULL;
		GfiProgressData *data;
		guint64 dl_size = 0;

		op = op_list->data;
		keyfile = flatpak_transaction_operation_get_metadata (op);	

		/* DEBUG */
		debug_print_keyfile (keyfile);

		name = g_key_file_get_string (keyfile,
				"Application", "name", NULL);
		if (! name) name = g_key_file_get_string (keyfile,
				"Runtime", "name", NULL);
		if (! name) name = g_strdup ("(Unknown)");

		dl_size = flatpak_transaction_operation_get_download_size (op);

		total_dl_size += dl_size;

		data = gfi_progress_data_new ();
		data->name = name;
		data->dl_size = dl_size;

		deplist = g_slist_append (deplist, data);

		op_list = op_list->next;
	}

	self->total_download_size = total_dl_size;
	self->deplist = deplist;
}

static void
transaction_type_and_name_from_keyfile (GfiProgress *self, GKeyFile *keyfile)
{
	char *tmp_name;

	/* Find out if we're working on a dep, or an app.
	 * Set self->transaction_name && self->transaction_type
	 */

	/* Clear transaction_name if necessary. */
	g_free (self->transaction_name);

	/* First, check if it's an app: */
	tmp_name = g_key_file_get_string (keyfile,
			"Application", "name", NULL);
	if (tmp_name) {
		self->transaction_type = GFI_PROGRESS_TRANSACTION_TYPE_APP;
	} else {
		/* If it's not an app, see if it's a dep: */
		tmp_name = g_key_file_get_string (keyfile,
				"Runtime", "name", NULL);
		if (tmp_name) {
			self->transaction_type = GFI_PROGRESS_TRANSACTION_TYPE_DEP;
		/* If it's neither, just mark it as unknown. */
		} else {
			self->transaction_type = GFI_PROGRESS_TRANSACTION_TYPE_UNKNOWN;
		}
	}

	/* Set transaction_name, which will work out to either a strdup'd char*
	 * to the name of the app or dep, or NULL if the type is unknown.
	 */
	self->transaction_name = tmp_name;

	g_debug ("%s: got transaction_type: %d (1=app,2=dep,0=unkwn)",
			__func__, self->transaction_type);
	g_debug ("%s: got transaction_name: %s",
			__func__, self->transaction_name);
}

static void
changed_cb (FlatpakTransactionProgress *progress, gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);
	int new_progress_percent;

	/* free status string if required. */
	g_free (self->status);

	self->status = flatpak_transaction_progress_get_status (progress);
	g_debug ("%s: self->status changed to: %s", __func__, self->status);

	/* Emit the update-status signal in the main context */
	g_main_context_invoke (NULL, emit_update_status, self);

	/* Emit signal that percentage of operation should be updated if required.
	 */
	new_progress_percent = flatpak_transaction_progress_get_progress (progress);

	if (new_progress_percent != self->progress_percent)
	{
		self->progress_percent = new_progress_percent;

		/* Emit the update-progress signal in the main context */
		g_main_context_invoke (NULL, emit_update_progress, self);
	}
}

/* Process the "ready" signal from flatpak.
 * Return TRUE when complete to allow the operation to continue.
 */
static gboolean
ready_cb (FlatpakTransaction *transaction, gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);

	g_return_val_if_fail (transaction == self->transaction, FALSE);

	if (self->ready_to_confirm)
	{
		return TRUE;
	}
	else
	{
		build_dep_list_and_calculate_download_data (self);

		/* Emit the ready-to-confirm signal in the main context */
		g_main_context_invoke (NULL, emit_ready_to_confirm, self);

		return FALSE;
	}
}

static void
operation_done_cb (FlatpakTransaction *transaction,
		FlatpakTransactionOperation *operation,
		char *commit,
		int result,
		gpointer user_data)
{
	/* FIXME - don't know if I need to actually do anything here. */

	if (result == FLATPAK_TRANSACTION_RESULT_NO_CHANGE)
		g_debug ("%s: The update caused no changes.", __func__);
}

static gboolean
operation_error_cb (FlatpakTransaction *transaction,
		FlatpakTransactionOperation *operation,
		GError *error,
		int details,
		gpointer user_data)
{
	if (error)
		g_debug ("%s: error: %s", __func__, error->message);

	if (details == FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL)
	{
		g_debug ("%s: the transaction error was not fatal.", __func__);
		return TRUE;
	}
	else
	{
		g_debug ("%s: the transaction error WAS fatal.", __func__);
		return FALSE;
	}
}

static void
new_operation_cb (FlatpakTransaction *transaction,
		FlatpakTransactionOperation *operation,
		FlatpakTransactionProgress *progress,
		gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(user_data);
	GKeyFile *keyfile;

	char **groups;
	char **keys;

	/* Connect changed signal - we can't do this earlier because we don't
	 * have access to the progress object until this callback is called from
	 * the new-operation signal.
	 */
	g_signal_connect (progress, "changed",
			G_CALLBACK(changed_cb), self);
	
	/* Get metadata of the new operation in key-value format (ini-file)
	 */
	keyfile = flatpak_transaction_operation_get_metadata (operation);
	transaction_type_and_name_from_keyfile (self, keyfile);

	/* Emit the new-transaction signal in the main context */
	g_main_context_invoke (NULL, emit_new_transaction, self);
}


static gboolean
gfi_progress_flatpak_transaction_run_finish (GfiProgress *self,
		GAsyncResult *result)
{
	g_return_val_if_fail (g_task_is_valid (result, self), FALSE);

	return g_task_propagate_boolean (G_TASK(result), NULL);
}

static void
flatpak_transaction_run_task_thread_func (GTask *task,
                    gpointer source_object,
                    gpointer task_data,
                    GCancellable *cancellable)
{
	GfiProgress *self = GFI_PROGRESS(source_object);
	gboolean retval = FALSE;

	g_clear_error (&self->error);

	retval = flatpak_transaction_run (self->transaction,
			cancellable,
			&self->error);

	/* this is a bit cheap but also makes some sense - the dry_run is not
	 * cancellable, so that's how we test whether that's what it is. We don't
	 * care about the retval for the dry run. We need to also *not* handle the
	 * 'error' of a cancellation during the dry run, since we're doing that
	 * on purpose to gather data.
	 */
	if (cancellable)	/* real run */
	{
		if (self->error) {
			handle_error (self, __func__);
		}
		g_task_return_boolean (task, retval);
	}
	else				/* dry_run */
	{
		if (self->error && ! g_error_matches (self->error, FLATPAK_ERROR,
					FLATPAK_ERROR_ABORTED)) {
			handle_error (self, __func__);
		}
		g_task_return_boolean (task, TRUE);
	}
}

/* This gets run when dry run is done.
 *
 * Must fit format of ``GAsyncReadyCallback''
 */
static void
dry_run_async_ready_cb (GObject *source_object,
                        GAsyncResult *res,
                        gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(source_object);
	GTask *task = G_TASK(res);
	gboolean success;

	g_return_if_fail (g_task_is_valid (res, self));

	success = gfi_progress_flatpak_transaction_run_finish (self, res);
	g_debug ("%s: success: %d", __func__, success);

	if (success)
	{
		/* Emit the private ready-to-start signal in the main context */
		g_main_context_invoke (NULL, emit_ready_to_start, self);
	}
}

/* This gets run when real run is done.
 *
 * Must fit format of ``GAsyncReadyCallback''
 */
static void
real_run_async_ready_cb (GObject *source_object,
                        GAsyncResult *res,
                        gpointer user_data)
{
	GfiProgress *self = GFI_PROGRESS(source_object);
	GTask *task = G_TASK(res);
	gboolean success;

	g_return_if_fail (g_task_is_valid (res, self));

	success = gfi_progress_flatpak_transaction_run_finish (self, res);
	g_debug ("%s: success: %d", __func__, success);

	/* Don't need to check for !success here because it will already be caught
	 * by GError and reported from flatpak_transaction_run_task_thread_func
	 */
	if (success)
		g_main_context_invoke (NULL, emit_transaction_done, self);
	else
		handle_error (self, __func__);
}

static void
gfi_progress_flatpak_transaction_run_async (GfiProgress *self,
	   	GCancellable *cancellable,
		GAsyncReadyCallback callback,
		gpointer user_data)
{
	GTask *task;

	g_return_if_fail (GFI_IS_PROGRESS (self));

	task = g_task_new (self, cancellable, callback, user_data);
	g_task_run_in_thread (task, flatpak_transaction_run_task_thread_func);
	g_object_unref (task);
}

static void
set_appname_from_ref_str (GfiProgress *self)
{
	g_autoptr(FlatpakRef) tmp_ref = flatpak_ref_parse (self->ref_str, NULL);
	self->appname = g_strdup(flatpak_ref_get_name (tmp_ref));
}

/* helper macro for setup_stuff */
#define HANDLE_ANY_ERRORS \
	if (self->error) { handle_error (self, __func__); return; }

static void
setup_stuff (GfiProgress *self, gboolean dry_run)
{
	char *tmp_appname = NULL;

	/* Sanity check */
	if (! self->action_type)
	{
		g_error ("%s: Programmer error - no action type associated with the "
				"GfiProgress object. Did you forget to load a flatpakref "
				"for installation, or a ref for uninstall?",
				__func__);
	}

	/* Clean up after ourselves after dry run as necessary. */

	g_clear_error (&self->error);

	/* Unref the flatpak related objects if necessary (ie, if this is the
	 * not-a-drill run after the dry run. Not going to use g_clear_pointer
	 * here because I want to see runtime errors if this is done incorrectly.
	 */
	if (! dry_run)
	{
		g_object_unref (self->installation);
		g_object_unref (self->transaction);
	}

	/* Setup objects common for both install and remove actions.
	 */
	self->installation = flatpak_installation_new_user (self->cancellable,
			&self->error);
	HANDLE_ANY_ERRORS
		
	self->transaction = flatpak_transaction_new_for_installation (
			self->installation,
			self->cancellable,
			&self->error);
	HANDLE_ANY_ERRORS

	/* self->transaction: Setup signals
	 */
	g_signal_connect (self->transaction, "ready",
			G_CALLBACK(ready_cb), self);
	g_signal_connect (self->transaction, "new-operation",
			G_CALLBACK(new_operation_cb), self);
	g_signal_connect (self->transaction, "choose-remote-for-ref",
			G_CALLBACK(choose_remote_cb), self);
	g_signal_connect (self->transaction, "add-new-remote",
			G_CALLBACK(add_new_remote_cb), self);
	g_signal_connect (self->transaction, "operation-done",
			G_CALLBACK(operation_done_cb), self);
	g_signal_connect (self->transaction, "operation-error",
			G_CALLBACK(operation_error_cb), self);


	switch (self->action_type)
	{
		case GFI_PROGRESS_ACTION_TYPE_INSTALL:
		{
			g_autoptr(GBytes) flatpakref_data = NULL;
			g_autoptr(GKeyFile) keyfile = NULL;

			flatpakref_data = g_file_load_bytes (self->flatpakref_file,
					NULL, NULL, &self->error);
			HANDLE_ANY_ERRORS

			/* The dry run should set self->appname fine. No need to re-set
			 * unnecessarily.
			 */
			if (! self->appname)
			{
				keyfile = g_key_file_new ();

				if (g_key_file_load_from_bytes (keyfile, flatpakref_data,
							G_KEY_FILE_NONE, NULL))
				{
					tmp_appname = get_name_from_keyfile (keyfile,
							KEYFILE_NAME_FLATPAKREF);
				}
				self->appname = tmp_appname;
			}

			flatpak_transaction_add_install_flatpakref (self->transaction,
					flatpakref_data,
					&self->error);
			HANDLE_ANY_ERRORS
		}
		break;

		case GFI_PROGRESS_ACTION_TYPE_UPGRADE:
		{
			if (! self->appname)
				set_appname_from_ref_str (self);

			flatpak_transaction_add_update (self->transaction,
					self->ref_str,
					/* FIXME - I don't quite get what this does, so using
					 * NULL for the default. */
					NULL,			/* const char **subpaths, */
					NULL,			/* use latest update */
					&self->error);
			HANDLE_ANY_ERRORS
		}
		break;

		case GFI_PROGRESS_ACTION_TYPE_REMOVE:
		{
			if (! self->appname)
				set_appname_from_ref_str (self);

			flatpak_transaction_add_uninstall (self->transaction,
					self->ref_str,
					&self->error);
			HANDLE_ANY_ERRORS
		}
		break;

		default:
		{
			g_error ("%s: Programmer error - invalid GfiProgressActionType.",
					__func__);
		}
		break;
	}

	flatpak_transaction_add_default_dependency_sources (self->transaction);

	/* Run flatpak transaction as a DRY RUN just to calculate stuff. */
	if (dry_run)
	{
		gfi_progress_flatpak_transaction_run_async (self,
				NULL,	/* cancellable */
				dry_run_async_ready_cb,
				NULL);	/* user_data */
	}
}
#undef HANDLE_ANY_ERRORS

void
gfi_progress_confirm (GfiProgress *self)
{
	if (! self->ready_to_confirm)
	{
		g_debug ("%s: Not yet ready to confirm. Returning.", __func__);
		return;
	}

	/* Emit the ready-to-confirm signal in the main context */
	g_main_context_invoke (NULL, emit_ready_to_proceed, self);
}

void
gfi_progress_proceed (GfiProgress *self)
{
	g_autoptr(GTask) task = NULL;

	if (! self->ready_to_proceed)
	{
		g_debug ("%s: Not yet ready to proceed. Returning.", __func__);
		return;
	}

	/* Run flatpak transaction in a worker thread, since it is a blocking
	 * operation. This is the REAL RUN (not a drill!)
	 */
	gfi_progress_flatpak_transaction_run_async (self,
			self->cancellable,
			real_run_async_ready_cb,
			NULL);	/* user_data */
}

static void
gfi_progress_real_start (GfiProgress *self, gpointer user_data)
{
	setup_stuff (self, /* dry_run */ FALSE);

	/* don't run any user handlers - this is not a public signal */
	g_signal_stop_emission (self, signals[READY_TO_START], 0);
}

void
gfi_progress_start (GfiProgress *self)
{
	setup_stuff (self, /* dry_run */ TRUE);
}

/* INTERNAL CONSTRUCTORS AND DESTRUCTORS */

static void
gfi_progress_init (GfiProgress *self)
{
	self->cancellable = g_cancellable_new ();

}

static void
gfi_progress_dispose (GObject *object)
{
	GfiProgress *self = GFI_PROGRESS(object);

	g_clear_error (&self->error);
	g_clear_object (&self->cancellable);
	g_clear_object (&self->flatpakref_file);
	g_clear_object (&self->installation);
	g_clear_object (&self->transaction);

	/* Chain up */
	G_OBJECT_CLASS(gfi_progress_parent_class)->dispose(object);
}

static void
gfi_progress_finalize (GObject *object)
{
	GfiProgress *self = GFI_PROGRESS(object);

	g_free (self->status);
	g_free (self->appname);
	g_free (self->transaction_name);
	g_free (self->last_error_msg);
	g_slist_free_full (g_steal_pointer (&self->deplist),
			(GDestroyNotify)gfi_progress_data_free);

	/* Chain up */
	G_OBJECT_CLASS(gfi_progress_parent_class)->finalize(object);
}

static void
gfi_progress_class_init (GfiProgressClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);

	object_class->dispose = gfi_progress_dispose;
	object_class->finalize = gfi_progress_finalize;
	
	/* SIGNALS */

	signals[UPDATE_STATUS] = g_signal_new_class_handler ("update-status",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type or params. */
			G_TYPE_NONE, 0);

	signals[UPDATE_PROGRESS] = g_signal_new_class_handler ("update-progress",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 1 param - the progress percentage, as int. */
			1,
			G_TYPE_INT);

	signals[NEW_TRANSACTION] = g_signal_new_class_handler ("new-transaction",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 1 param - the transaction type, as int (really enum; could
		 * register a GType, but I'm lazy). */
			1,
			G_TYPE_INT);

	signals[ERROR] = g_signal_new_class_handler ("error",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 0 params */
			0);

	signals[TRANSACTION_DONE] = g_signal_new_class_handler ("transaction-done",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 0 params */
			0);

	/* private signal - ready-to-start */
	signals[READY_TO_START] = g_signal_new_class_handler ("ready-to-start",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
			G_CALLBACK(gfi_progress_real_start),
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 0 params */
			0);

	signals[READY_TO_CONFIRM] = g_signal_new_class_handler ("ready-to-confirm",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 0 params */
			0);

	signals[READY_TO_PROCEED] = g_signal_new_class_handler ("ready-to-proceed",
			G_OBJECT_CLASS_TYPE(object_class),
			G_SIGNAL_RUN_LAST,
		/* no default C function */
			NULL,
		/* defaults for accumulator, marshaller &c. */
			NULL, NULL, NULL,	
		/* No return type */
			G_TYPE_NONE,
		/* 0 params */
			0);
}

/* PUBLIC METHOD DEFINITIONS */

GfiProgress *
gfi_progress_new (void)
{
	return g_object_new (GFI_TYPE_PROGRESS, NULL);
}

gboolean
gfi_progress_load_flatpakref (GfiProgress *self, GFile *flatpakref_file)
{
	const char *ext = ".flatpakref";
	g_autofree char *basename = NULL;

	g_return_val_if_fail (GFI_IS_PROGRESS (self), FALSE);
	g_return_val_if_fail (G_IS_FILE (flatpakref_file), FALSE);

	basename = g_file_get_basename (flatpakref_file);
	
	/* If *non*-zero, the extension isn't .flatpakref */
	if (g_ascii_strcasecmp (&basename[strlen(basename) - strlen(ext)], ext))
	{
		g_debug ("%s: The file passed does not appear to be a .flatpakref",
				__func__);
		self->flatpakref_file = NULL;
		return FALSE;
	}
	else
	{
		self->action_type = GFI_PROGRESS_ACTION_TYPE_INSTALL;
		self->flatpakref_file = flatpakref_file;
		return TRUE;
	}
}

gboolean
gfi_progress_load_ref_str (GfiProgress *self,
		const char *ref_str,
		GfiProgressActionType type)
{
	g_autoptr(FlatpakRef) tmp_ref = NULL;

	g_return_val_if_fail (GFI_IS_PROGRESS (self), FALSE);

	tmp_ref = flatpak_ref_parse (ref_str, NULL);

	if (! tmp_ref)
	{
		return FALSE;
	}
	else
	{
		self->action_type = type;
		self->ref_str = g_strdup (ref_str);
		return TRUE;
	}
}

gboolean 
gfi_progress_load_ref (GfiProgress *self, 
			FlatpakRef *ref,
			GfiProgressActionType type) 
{
		char *tmp_ref_str = NULL;
		
		g_return_val_if_fail (GFI_IS_PROGRESS (self), FALSE);
		g_return_val_if_fail (FLATPAK_IS_REF (ref), FALSE);

		tmp_ref_str = flatpak_ref_format_ref (ref);

		if (tmp_ref_str)
		{
			self->action_type = type;
			self->ref_str = tmp_ref_str;
			return TRUE;
		}
		else
		{
			return FALSE;
		}
}

const char *
gfi_progress_get_status (GfiProgress *self)
{
	g_return_val_if_fail (GFI_IS_PROGRESS (self), NULL);

	return self->status;
}

GfiProgressTransactionType
gfi_progress_get_transaction_type (GfiProgress *self)
{
	g_assert (GFI_IS_PROGRESS (self));

	return self->transaction_type;
}

const char *
gfi_progress_get_transaction_name (GfiProgress *self)
{
	g_return_val_if_fail (GFI_IS_PROGRESS (self), NULL);

	return self->transaction_name;
}

const char *
gfi_progress_get_last_error_msg (GfiProgress *self)
{
	g_return_val_if_fail (GFI_IS_PROGRESS (self), NULL);

	return self->last_error_msg;
}

guint64
gfi_progress_get_total_download_size (GfiProgress *self)
{
	g_assert (GFI_IS_PROGRESS (self));

	return self->total_download_size;
}

GSList *
gfi_progress_get_deplist (GfiProgress *self)
{
	g_return_val_if_fail (GFI_IS_PROGRESS (self), NULL);

	return self->deplist;
}

const char *
gfi_progress_get_appname (GfiProgress *self)
{
	g_return_val_if_fail (GFI_IS_PROGRESS (self), NULL);

	return self->appname;
}

GCancellable *
gfi_progress_get_cancellable (GfiProgress *self)
{
	g_return_val_if_fail (GFI_IS_PROGRESS (self), NULL);

	return self->cancellable;
}

GfiProgressActionType
gfi_progress_get_action_type (GfiProgress *self)
{
	g_assert (GFI_IS_PROGRESS (self));

	return self->action_type;
}

/* GfiProgressData methods */

GfiProgressData *
gfi_progress_data_new (void)
{
	GfiProgressData *data;

	data = g_new0 (GfiProgressData, 1);

	return data;
}

void
gfi_progress_data_free (GfiProgressData *data)
{
	g_free (data->name);
	g_free (data);
}
