mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-31 02:51:22 -05:00
3211 lines
108 KiB
C
3211 lines
108 KiB
C
/*
|
|
* Copyright © 2016 Red Hat, Inc
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Alexander Larsson <alexl@redhat.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <libsoup/soup.h>
|
|
|
|
#include "flatpak-transaction-private.h"
|
|
#include "flatpak-installation-private.h"
|
|
#include "flatpak-utils-private.h"
|
|
#include "flatpak-error.h"
|
|
|
|
/**
|
|
* SECTION:flatpak-transaction
|
|
* @Title: FlatpakTransaction
|
|
* @Short_description: Transaction information
|
|
*
|
|
* FlatpakTransaction is an object representing an install/update
|
|
* transaction. You create an object like this using flatpak_transaction_new_for_installation()
|
|
* and then you add all the operations (installs, updates, etc) you wish to do. Then
|
|
* you start the transaction with flatpak_transaction_run() which will resolve all kinds
|
|
* of dependencies and report progress and status while downloading and installing these.
|
|
*
|
|
* The dependency resolution that is the first step of executing a transaction can
|
|
* be influenced by flatpak_transaction_set_disable_dependencies(),
|
|
* flatpak_transaction_set_disable_related(), flatpak_transaction_add_dependency_source()
|
|
* and flatpak_transaction_add_default_dependency_sources().
|
|
*
|
|
* The underlying operations that get orchestrated by a FlatpakTransaction are: pulling
|
|
* new data from remote repositories, deploying newer applications or runtimes and pruning
|
|
* old deployments. Which of these operations are carried out can be controlled with
|
|
* flatpak_transaction_set_no_pull(), flatpak_transaction_set_no_deploy() and
|
|
* flatpak_transaction_set_disable_prune().
|
|
*
|
|
* A transaction is a blocking operation, and all signals are emitted in the same thread.
|
|
* This means you should either handle the signals directly (say, by doing blocking console
|
|
* interaction, or by just returning without interaction), or run the operation in a separate
|
|
* thread and do your own forwarding to the GUI thread.
|
|
*
|
|
* Despite the name, a FlatpakTransaction is more like a batch operation than a transaction
|
|
* in the database sense. Individual operations are carried out sequentially, and are atomic.
|
|
* They become visible to the system as they are completed. When an error occurs, already
|
|
* completed operations are not rolled back.
|
|
*
|
|
* For each operation that is executed during a transaction, you first get a
|
|
* #FlatpakTransaction::new-operation signal, followed by either a
|
|
* #FlatpakTransaction::operation-done or #FlatpakTransaction::operation-error.
|
|
|
|
* The FlatpakTransaction API is threadsafe in the sense that it is safe to run two
|
|
* transactions at the same time, in different threads (or processes).
|
|
*
|
|
* Note: Transactions (or any other install/update operation) to a
|
|
* system installation rely on the ability to create files that are readable
|
|
* by other users. Some users set a umask that prohibits this. Unfortunately
|
|
* there is no good way to work around this in a threadsafe, local way, so
|
|
* such setups will break by default. The flatpak commandline app works
|
|
* around this by calling umask(022) in the early setup, and it is recommended
|
|
* that other apps using libflatpak do this too.
|
|
*/
|
|
|
|
/* This is an internal-only element of FlatpakTransactionOperationType */
|
|
#define FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE FLATPAK_TRANSACTION_OPERATION_LAST_TYPE + 1
|
|
|
|
enum {
|
|
RUNTIME_UPDATE,
|
|
RUNTIME_INSTALL,
|
|
APP_UPDATE,
|
|
APP_INSTALL
|
|
};
|
|
|
|
struct _FlatpakTransactionOperation
|
|
{
|
|
GObject parent;
|
|
|
|
char *remote;
|
|
char *ref;
|
|
/* NULL means unspecified (normally keep whatever was there before), [] means force everything */
|
|
char **subpaths;
|
|
char **previous_ids;
|
|
char *commit;
|
|
GFile *bundle;
|
|
GBytes *external_metadata;
|
|
FlatpakTransactionOperationType kind;
|
|
gboolean non_fatal;
|
|
gboolean failed;
|
|
gboolean skip;
|
|
gboolean update_only_deploy;
|
|
|
|
gboolean resolved;
|
|
char *resolved_commit;
|
|
GBytes *resolved_metadata;
|
|
GKeyFile *resolved_metakey;
|
|
GBytes *resolved_old_metadata;
|
|
GKeyFile *resolved_old_metakey;
|
|
guint64 download_size;
|
|
guint64 installed_size;
|
|
char *eol;
|
|
char *eol_rebase;
|
|
int run_after_count;
|
|
int run_after_prio; /* Higher => run later (when it becomes runnable). Used to run related ops (runtime extensions) before deps (apps using the runtime) */
|
|
GList *run_before_ops;
|
|
FlatpakTransactionOperation *fail_if_op_fails; /* main app/runtime for related extensions, runtime for apps */
|
|
};
|
|
|
|
typedef struct _FlatpakTransactionPrivate FlatpakTransactionPrivate;
|
|
|
|
typedef struct _BundleData BundleData;
|
|
|
|
struct _BundleData
|
|
{
|
|
GFile *file;
|
|
GBytes *gpg_data;
|
|
};
|
|
|
|
struct _FlatpakTransactionPrivate
|
|
{
|
|
GObject parent;
|
|
|
|
FlatpakInstallation *installation;
|
|
FlatpakDir *dir;
|
|
GHashTable *last_op_for_ref;
|
|
GHashTable *remote_states; /* (element-type utf8 FlatpakRemoteState) */
|
|
GPtrArray *extra_dependency_dirs;
|
|
GList *ops;
|
|
GPtrArray *added_origin_remotes;
|
|
|
|
GList *flatpakrefs; /* GKeyFiles */
|
|
GList *bundles; /* BundleData */
|
|
|
|
FlatpakTransactionOperation *current_op;
|
|
|
|
gboolean no_pull;
|
|
gboolean no_deploy;
|
|
gboolean disable_static_deltas;
|
|
gboolean disable_prune;
|
|
gboolean disable_deps;
|
|
gboolean disable_related;
|
|
gboolean reinstall;
|
|
gboolean force_uninstall;
|
|
gboolean can_run;
|
|
char *default_arch;
|
|
guint max_op;
|
|
|
|
gboolean needs_resolve;
|
|
};
|
|
|
|
enum {
|
|
NEW_OPERATION,
|
|
OPERATION_DONE,
|
|
OPERATION_ERROR,
|
|
CHOOSE_REMOTE_FOR_REF,
|
|
END_OF_LIFED,
|
|
END_OF_LIFED_WITH_REBASE,
|
|
READY,
|
|
ADD_NEW_REMOTE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_INSTALLATION,
|
|
};
|
|
|
|
struct _FlatpakTransactionProgress
|
|
{
|
|
GObject parent;
|
|
|
|
OstreeAsyncProgress *ostree_progress;
|
|
char *status;
|
|
gboolean estimating;
|
|
int progress;
|
|
guint64 total_transferred;
|
|
guint64 start_time;
|
|
|
|
gboolean done;
|
|
};
|
|
|
|
enum {
|
|
CHANGED,
|
|
LAST_PROGRESS_SIGNAL
|
|
};
|
|
|
|
static BundleData *
|
|
bundle_data_new (GFile *file,
|
|
GBytes *gpg_data)
|
|
{
|
|
BundleData *data = g_new0 (BundleData, 1);
|
|
|
|
data->file = g_object_ref (file);
|
|
if (gpg_data)
|
|
data->gpg_data = g_bytes_ref (gpg_data);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
bundle_data_free (BundleData *data)
|
|
{
|
|
g_clear_object (&data->file);
|
|
g_clear_object (&data->gpg_data);
|
|
g_free (data);
|
|
}
|
|
|
|
static guint progress_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
/**
|
|
* SECTION:flatpak-transaction-progress
|
|
* @Title: FlatpakTransactionProgress
|
|
* @Short_description: Progress of an operation
|
|
*
|
|
* FlatpakTransactionProgress is an object that represents the progress
|
|
* of a single operation in a transaction. You obtain a FlatpakTransactionProgress
|
|
* with the #FlatpakTransaction::new-operation signal.
|
|
*/
|
|
|
|
G_DEFINE_TYPE (FlatpakTransactionProgress, flatpak_transaction_progress, G_TYPE_OBJECT)
|
|
|
|
/**
|
|
* flatpak_transaction_progress_set_update_frequency:
|
|
* @self: a #FlatpakTransactionProgress
|
|
* @update_frequency: the update frequency, in milliseconds
|
|
*
|
|
* Sets how often progress should be updated.
|
|
*/
|
|
void
|
|
flatpak_transaction_progress_set_update_frequency (FlatpakTransactionProgress *self,
|
|
guint update_frequency)
|
|
{
|
|
g_object_set_data (G_OBJECT (self->ostree_progress), "update-frequency", GUINT_TO_POINTER (update_frequency));
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_progress_get_status:
|
|
* @self: a #FlatpakTransactionProgress
|
|
*
|
|
* Gets the current status string
|
|
*
|
|
* Returns: (transfer none): the current status
|
|
*/
|
|
char *
|
|
flatpak_transaction_progress_get_status (FlatpakTransactionProgress *self)
|
|
{
|
|
return g_strdup (self->status);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_progress_get_is_estimating:
|
|
* @self: a #FlatpakTransactionProgress
|
|
*
|
|
* Gets whether the progress is currently estimating
|
|
*
|
|
* Returns: whether we're estimating
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_progress_get_is_estimating (FlatpakTransactionProgress *self)
|
|
{
|
|
return self->estimating;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_progress_get_progress:
|
|
* @self: a #FlatpakTransactionProgress
|
|
*
|
|
* Gets the current progress.
|
|
*
|
|
* Returns: the current progress, as an integer between 0 and 100
|
|
*/
|
|
int
|
|
flatpak_transaction_progress_get_progress (FlatpakTransactionProgress *self)
|
|
{
|
|
return self->progress;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_progress_get_bytes_transferred:
|
|
* @self: a #FlatpakTransactionProgress
|
|
*
|
|
* Gets the number of bytes that have been transferred.
|
|
*
|
|
* Returns: the number of bytes transferred
|
|
* Since: 1.1.2
|
|
*/
|
|
guint64
|
|
flatpak_transaction_progress_get_bytes_transferred (FlatpakTransactionProgress *self)
|
|
{
|
|
return self->total_transferred;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_progress_get_start_time:
|
|
* @self: a #FlatpakTransactionProgress
|
|
*
|
|
* Gets the time at which this operation has started, as monotonic time.
|
|
*
|
|
* Returns: the start time
|
|
* Since: 1.1.2
|
|
*/
|
|
guint64
|
|
flatpak_transaction_progress_get_start_time (FlatpakTransactionProgress *self)
|
|
{
|
|
return self->start_time;
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_progress_finalize (GObject *object)
|
|
{
|
|
FlatpakTransactionProgress *self = (FlatpakTransactionProgress *) object;
|
|
|
|
g_free (self->status);
|
|
g_object_unref (self->ostree_progress);
|
|
|
|
G_OBJECT_CLASS (flatpak_transaction_progress_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_progress_class_init (FlatpakTransactionProgressClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = flatpak_transaction_progress_finalize;
|
|
|
|
/**
|
|
* FlatpakTransactionProgress::changed:
|
|
* @object: A #FlatpakTransactionProgress
|
|
*
|
|
* Emitted when some detail of the progress object changes, you can call the various methods to get the current status.
|
|
*/
|
|
progress_signals[CHANGED] =
|
|
g_signal_new ("changed",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static void
|
|
got_progress_cb (const char *status,
|
|
guint progress,
|
|
gboolean estimating,
|
|
gpointer user_data)
|
|
{
|
|
FlatpakTransactionProgress *p = user_data;
|
|
guint64 bytes_transferred;
|
|
guint64 transferred_extra_data_bytes;
|
|
guint64 start_time;
|
|
|
|
ostree_async_progress_get (p->ostree_progress,
|
|
"bytes-transferred", "t", &bytes_transferred,
|
|
"transferred-extra-data-bytes", "t", &transferred_extra_data_bytes,
|
|
"start-time", "t", &start_time,
|
|
NULL);
|
|
|
|
g_free (p->status);
|
|
p->status = g_strdup (status);
|
|
p->progress = progress;
|
|
p->estimating = estimating;
|
|
p->total_transferred = bytes_transferred + transferred_extra_data_bytes;
|
|
p->start_time = start_time;
|
|
|
|
if (!p->done)
|
|
g_signal_emit (p, progress_signals[CHANGED], 0);
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_progress_init (FlatpakTransactionProgress *self)
|
|
{
|
|
self->status = g_strdup ("Initializing");
|
|
self->estimating = TRUE;
|
|
self->ostree_progress = flatpak_progress_new (got_progress_cb, self);
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_progress_done (FlatpakTransactionProgress *self)
|
|
{
|
|
ostree_async_progress_finish (self->ostree_progress);
|
|
self->done = TRUE;
|
|
}
|
|
|
|
static FlatpakTransactionProgress *
|
|
flatpak_transaction_progress_new (void)
|
|
{
|
|
return g_object_new (FLATPAK_TYPE_TRANSACTION_PROGRESS, NULL);
|
|
}
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void initable_iface_init (GInitableIface *initable_iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (FlatpakTransaction, flatpak_transaction, G_TYPE_OBJECT,
|
|
G_ADD_PRIVATE (FlatpakTransaction)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
|
|
|
|
static gboolean
|
|
transaction_is_local_only (FlatpakTransaction *self,
|
|
FlatpakTransactionOperationType kind)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
return priv->no_pull || kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL;
|
|
}
|
|
|
|
static gboolean
|
|
remote_name_is_file (const char *remote_name)
|
|
{
|
|
return remote_name != NULL &&
|
|
g_str_has_prefix (remote_name, "file://");
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_dependency_source:
|
|
* @self: a #FlatpakTransaction
|
|
* @installation: a #FlatpakInstallation
|
|
*
|
|
* Adds an extra installation as a source for application dependencies.
|
|
* This means that applications can be installed in this transaction relying
|
|
* on runtimes from this additional installation (whereas it would normally
|
|
* install required runtimes that are not installed in the installation
|
|
* the transaction works on).
|
|
*
|
|
* Also see flatpak_transaction_add_default_dependency_sources().
|
|
*/
|
|
void
|
|
flatpak_transaction_add_dependency_source (FlatpakTransaction *self,
|
|
FlatpakInstallation *installation)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
g_ptr_array_add (priv->extra_dependency_dirs,
|
|
flatpak_installation_clone_dir_noensure (installation));
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_default_dependency_sources:
|
|
* @self: a #FlatpakTransaction
|
|
*
|
|
* Similar to flatpak_transaction_add_dependency_source(), but adds
|
|
* all the default installations, which means all the defined system-wide
|
|
* (but not per-user) installations.
|
|
*/
|
|
void
|
|
flatpak_transaction_add_default_dependency_sources (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(GPtrArray) system_dirs = NULL;
|
|
GFile *path = flatpak_dir_get_path (priv->dir);
|
|
int i;
|
|
|
|
system_dirs = flatpak_dir_get_system_list (NULL, NULL);
|
|
if (system_dirs == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < system_dirs->len; i++)
|
|
{
|
|
FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
|
|
GFile *system_path = flatpak_dir_get_path (system_dir);
|
|
|
|
if (g_file_equal (path, system_path))
|
|
continue;
|
|
|
|
g_ptr_array_add (priv->extra_dependency_dirs, g_object_ref (system_dir));
|
|
}
|
|
}
|
|
|
|
/* Check if the ref is in the dir, or in the extra dependency source dir, in case its a
|
|
* user-dir or another system-wide installation. We want to avoid depending
|
|
* on user-installed things when installing to the system dir.
|
|
*/
|
|
static gboolean
|
|
ref_is_installed (FlatpakTransaction *self,
|
|
const char *ref,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(GFile) deploy_dir = NULL;
|
|
FlatpakDir *dir = priv->dir;
|
|
int i;
|
|
|
|
deploy_dir = flatpak_dir_get_if_deployed (dir, ref, NULL, NULL);
|
|
if (deploy_dir != NULL)
|
|
return TRUE;
|
|
|
|
for (i = 0; i < priv->extra_dependency_dirs->len; i++)
|
|
{
|
|
FlatpakDir *dependency_dir = g_ptr_array_index (priv->extra_dependency_dirs, i);
|
|
|
|
deploy_dir = flatpak_dir_get_if_deployed (dependency_dir, ref, NULL, NULL);
|
|
if (deploy_dir != NULL)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
dir_ref_is_installed (FlatpakDir *dir, const char *ref, char **remote_out, GVariant **deploy_data_out)
|
|
{
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
|
|
deploy_data = flatpak_dir_get_deploy_data (dir, ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
|
|
if (deploy_data == NULL)
|
|
return FALSE;
|
|
|
|
if (remote_out)
|
|
*remote_out = g_strdup (flatpak_deploy_data_get_origin (deploy_data));
|
|
|
|
if (deploy_data_out)
|
|
*deploy_data_out = g_variant_ref (deploy_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* SECTION:flatpak-transaction-operation
|
|
* @Title: FlatpakTransactionOperation
|
|
* @Short_description: Operation in a transaction
|
|
*
|
|
* FlatpakTransactionOperation is an object that represents a single operation
|
|
* in a transaction. You receive a FlatpakTransactionOperation object with the
|
|
* #FlatpakTransaction::new-operation signal.
|
|
*/
|
|
|
|
G_DEFINE_TYPE (FlatpakTransactionOperation, flatpak_transaction_operation, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
flatpak_transaction_operation_finalize (GObject *object)
|
|
{
|
|
FlatpakTransactionOperation *self = (FlatpakTransactionOperation *) object;
|
|
|
|
g_free (self->remote);
|
|
g_free (self->ref);
|
|
g_free (self->commit);
|
|
g_strfreev (self->subpaths);
|
|
g_clear_object (&self->bundle);
|
|
g_free (self->eol);
|
|
g_free (self->eol_rebase);
|
|
if (self->previous_ids)
|
|
g_strfreev (self->previous_ids);
|
|
if (self->external_metadata)
|
|
g_bytes_unref (self->external_metadata);
|
|
g_free (self->resolved_commit);
|
|
if (self->resolved_metadata)
|
|
g_bytes_unref (self->resolved_metadata);
|
|
if (self->resolved_metakey)
|
|
g_key_file_unref (self->resolved_metakey);
|
|
if (self->resolved_old_metadata)
|
|
g_bytes_unref (self->resolved_old_metadata);
|
|
if (self->resolved_old_metakey)
|
|
g_key_file_unref (self->resolved_old_metakey);
|
|
g_list_free (self->run_before_ops);
|
|
|
|
G_OBJECT_CLASS (flatpak_transaction_operation_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_operation_class_init (FlatpakTransactionOperationClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = flatpak_transaction_operation_finalize;
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_operation_init (FlatpakTransactionOperation *self)
|
|
{
|
|
}
|
|
|
|
static FlatpakTransactionOperation *
|
|
flatpak_transaction_operation_new (const char *remote,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
const char **previous_ids,
|
|
const char *commit,
|
|
GFile *bundle,
|
|
FlatpakTransactionOperationType kind)
|
|
{
|
|
FlatpakTransactionOperation *self;
|
|
|
|
self = g_object_new (FLATPAK_TYPE_TRANSACTION_OPERATION, NULL);
|
|
|
|
self->remote = g_strdup (remote);
|
|
self->ref = g_strdup (ref);
|
|
self->subpaths = g_strdupv ((char **) subpaths);
|
|
self->previous_ids = g_strdupv ((char **) previous_ids);
|
|
self->commit = g_strdup (commit);
|
|
if (bundle)
|
|
self->bundle = g_object_ref (bundle);
|
|
self->kind = kind;
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_operation_type:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the type of the operation.
|
|
*
|
|
* Returns: the type of operation, as #FlatpakTransactionOperationType
|
|
*/
|
|
FlatpakTransactionOperationType
|
|
flatpak_transaction_operation_get_operation_type (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->kind;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_ref:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the ref that the operation applies to.
|
|
*
|
|
* Returns: (transfer none): the ref
|
|
*/
|
|
const char *
|
|
flatpak_transaction_operation_get_ref (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->ref;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_remote:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the remote that the operation applies to.
|
|
*
|
|
* Returns: (transfer none): the remote
|
|
*/
|
|
const char *
|
|
flatpak_transaction_operation_get_remote (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->remote;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_type_to_string:
|
|
* @kind: a #FlatpakTransactionOperationType
|
|
*
|
|
* Converts the operation type to a string.
|
|
*
|
|
* Returns: (transfer none): a string representing @kind
|
|
*/
|
|
const char *
|
|
flatpak_transaction_operation_type_to_string (FlatpakTransactionOperationType kind)
|
|
{
|
|
if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
|
|
return "install";
|
|
if (kind == FLATPAK_TRANSACTION_OPERATION_UPDATE)
|
|
return "update";
|
|
if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
|
|
return "install-bundle";
|
|
if (kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
return "uninstall";
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_bundle_path:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the path to the bundle.
|
|
*
|
|
* Returns: (transfer none): the bundle #GFile or %NULL
|
|
*/
|
|
GFile *
|
|
flatpak_transaction_operation_get_bundle_path (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->bundle;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_commit:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the commit ID for the operation.
|
|
*
|
|
* This information is available when the transaction is resolved,
|
|
* i.e. when #FlatpakTransaction::ready is emitted.
|
|
*
|
|
* Returns: (transfer none): the commit ID
|
|
*/
|
|
const char *
|
|
flatpak_transaction_operation_get_commit (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->resolved_commit;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_download_size:
|
|
* @self: a #flatpakTransactionOperation
|
|
*
|
|
* Gets the maximum download size for the operation.
|
|
*
|
|
* Note that this does not include the size of dependencies, and
|
|
* the actual download may be smaller, if some of the data is already
|
|
* available locally.
|
|
*
|
|
* For uninstall operations, this returns 0.
|
|
*
|
|
* This information is available when the transaction is resolved,
|
|
* i.e. when #FlatpakTransaction::ready is emitted.
|
|
*
|
|
* Returns: the download size
|
|
* Since: 1.1.2
|
|
*/
|
|
guint64
|
|
flatpak_transaction_operation_get_download_size (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->download_size;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_installed_size:
|
|
* @self: a #flatpakTransactionOperation
|
|
*
|
|
* Gets the installed size for the operation.
|
|
*
|
|
* Note that even for a new install, the extra space required on
|
|
* disk may be smaller than this number, if some of the data is already
|
|
* available locally.
|
|
*
|
|
* For uninstall operations, this returns 0.
|
|
*
|
|
* This information is available when the transaction is resolved,
|
|
* i.e. when #FlatpakTransaction::ready is emitted.
|
|
*
|
|
* Returns: the installed size
|
|
* Since: 1.1.2
|
|
*/
|
|
guint64
|
|
flatpak_transaction_operation_get_installed_size (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->installed_size;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_metadata:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the metadata that will be applicable when the
|
|
* operation is done.
|
|
*
|
|
* This can be compared to the current metadata returned
|
|
* by flatpak_transaction_operation_get_old_metadata()
|
|
* to find new required permissions and similar changes.
|
|
*
|
|
* This information is available when the transaction is resolved,
|
|
* i.e. when #FlatpakTransaction::ready is emitted.
|
|
*
|
|
* Returns: (transfer none): the metadata #GKeyFile
|
|
*/
|
|
GKeyFile *
|
|
flatpak_transaction_operation_get_metadata (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->resolved_metakey;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_operation_get_old_metadata:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the metadata current metadata for the ref that @self works on.
|
|
* Also see flatpak_transaction_operation_get_metadata().
|
|
*
|
|
* This information is available when the transaction is resolved,
|
|
* i.e. when #FlatpakTransaction::ready is emitted.
|
|
*
|
|
* Returns: (transfer none): the old metadata #GKeyFile
|
|
*/
|
|
GKeyFile *
|
|
flatpak_transaction_operation_get_old_metadata (FlatpakTransactionOperation *self)
|
|
{
|
|
return self->resolved_old_metakey;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_is_empty:
|
|
* @self: a #FlatpakTransaction
|
|
*
|
|
* Returns whether the transaction contains any operations.
|
|
*
|
|
* Returns: %TRUE if the transaction is empty
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_is_empty (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *l;
|
|
|
|
for (l = priv->ops; l; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
|
|
if (!op->skip)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_finalize (GObject *object)
|
|
{
|
|
FlatpakTransaction *self = (FlatpakTransaction *) object;
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
g_clear_object (&priv->installation);
|
|
|
|
g_list_free_full (priv->flatpakrefs, (GDestroyNotify) g_key_file_unref);
|
|
g_list_free_full (priv->bundles, (GDestroyNotify) bundle_data_free);
|
|
g_free (priv->default_arch);
|
|
g_hash_table_unref (priv->last_op_for_ref);
|
|
g_hash_table_unref (priv->remote_states);
|
|
g_list_free_full (priv->ops, (GDestroyNotify) g_object_unref);
|
|
g_clear_object (&priv->dir);
|
|
|
|
g_ptr_array_unref (priv->added_origin_remotes);
|
|
|
|
g_ptr_array_free (priv->extra_dependency_dirs, TRUE);
|
|
|
|
G_OBJECT_CLASS (flatpak_transaction_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
FlatpakTransaction *self = FLATPAK_TRANSACTION (object);
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_INSTALLATION:
|
|
g_clear_object (&priv->installation);
|
|
priv->installation = g_value_dup_object (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
signal_accumulator_false_abort (GSignalInvocationHint *ihint,
|
|
GValue *return_accu,
|
|
const GValue *handler_return,
|
|
gpointer dummy)
|
|
{
|
|
gboolean continue_emission;
|
|
gboolean signal_continue;
|
|
|
|
signal_continue = g_value_get_boolean (handler_return);
|
|
g_value_set_boolean (return_accu, signal_continue);
|
|
continue_emission = signal_continue;
|
|
|
|
return continue_emission;
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
FlatpakTransaction *self = FLATPAK_TRANSACTION (object);
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_INSTALLATION:
|
|
g_value_set_object (value, priv->installation);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_ready (FlatpakTransaction *transaction)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_add_new_remote (FlatpakTransaction *transaction,
|
|
FlatpakTransactionRemoteReason reason,
|
|
const char *from_id,
|
|
const char *suggested_remote_name,
|
|
const char *url)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean flatpak_transaction_real_run (FlatpakTransaction *transaction,
|
|
GCancellable *cancellable,
|
|
GError **error);
|
|
|
|
static void
|
|
flatpak_transaction_class_init (FlatpakTransactionClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
klass->ready = flatpak_transaction_ready;
|
|
klass->add_new_remote = flatpak_transaction_add_new_remote;
|
|
klass->run = flatpak_transaction_real_run;
|
|
object_class->finalize = flatpak_transaction_finalize;
|
|
object_class->get_property = flatpak_transaction_get_property;
|
|
object_class->set_property = flatpak_transaction_set_property;
|
|
|
|
/**
|
|
* FlatpakTransaction:installation:
|
|
*
|
|
* The installation that the transaction operates on.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_INSTALLATION,
|
|
g_param_spec_object ("installation",
|
|
"Installation",
|
|
"The installation instance",
|
|
FLATPAK_TYPE_INSTALLATION,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* FlatpakTransaction::new-operation:
|
|
* @object: A #FlatpakTransaction
|
|
* @operation: The new #FlatpakTransactionOperation
|
|
* @progress: A #FlatpakTransactionProgress for @operation
|
|
*
|
|
* The ::new-operation signal gets emitted during the execution of
|
|
* the transaction when a new operation is beginning.
|
|
*/
|
|
signals[NEW_OPERATION] =
|
|
g_signal_new ("new-operation",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, new_operation),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 2, FLATPAK_TYPE_TRANSACTION_OPERATION, FLATPAK_TYPE_TRANSACTION_PROGRESS);
|
|
|
|
/**
|
|
* FlatpakTransaction::operation-error:
|
|
* @object: A #FlatpakTransaction
|
|
* @operation: The #FlatpakTransactionOperation which failed
|
|
* @error: A #GError
|
|
* @details: A #FlatpakTransactionErrorDetails with details about the error
|
|
*
|
|
* The ::operation-error signal gets emitted when an error occurs during the
|
|
* execution of the transaction.
|
|
*
|
|
* Returns: the %TRUE to contine transaction, %FALSE to stop
|
|
*/
|
|
signals[OPERATION_ERROR] =
|
|
g_signal_new ("operation-error",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, operation_error),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_BOOLEAN, 3, FLATPAK_TYPE_TRANSACTION_OPERATION, G_TYPE_ERROR, G_TYPE_INT);
|
|
|
|
/**
|
|
* FlatpakTransaction::operation-done:
|
|
* @object: A #FlatpakTransaction
|
|
* @operation: The #FlatpakTransactionOperation which finished
|
|
* @commit: The commit
|
|
* @result: A #FlatpakTransactionResult giving details about the result
|
|
*
|
|
* The ::operation-done signal gets emitted during the execution of
|
|
* the transaction when an operation is finished.
|
|
*/
|
|
signals[OPERATION_DONE] =
|
|
g_signal_new ("operation-done",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, operation_done),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 3, FLATPAK_TYPE_TRANSACTION_OPERATION, G_TYPE_STRING, G_TYPE_INT);
|
|
|
|
/**
|
|
* FlatpakTransaction::choose-remote-for-ref:
|
|
* @object: A #FlatpakTransaction
|
|
* @for_ref: The ref we are installing
|
|
* @runtime_ref: The ref we are looking for
|
|
* @remotes: the remotes that has the ref, sorted in prio order
|
|
*
|
|
* The ::choose-remote-for-ref signal gets emitted when a
|
|
* remote needs to be selected during the execution of the transaction.
|
|
*
|
|
* Returns: the index of the remote to use, or -1 to not pick one (and fail)
|
|
*/
|
|
signals[CHOOSE_REMOTE_FOR_REF] =
|
|
g_signal_new ("choose-remote-for-ref",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, choose_remote_for_ref),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_INT, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRV);
|
|
|
|
/**
|
|
* FlatpakTransaction::end-of-lifed:
|
|
* @object: A #FlatpakTransaction
|
|
* @ref: The ref we are installing
|
|
* @reason: The eol reason, or %NULL
|
|
* @rebase: The new name, if rebased, or %NULL
|
|
*
|
|
* The ::end-of-lifed signal gets emitted when a ref is found to
|
|
* be marked as end-of-life during the execution of the transaction.
|
|
*/
|
|
signals[END_OF_LIFED] =
|
|
g_signal_new ("end-of-lifed",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, end_of_lifed),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
|
|
|
|
/**
|
|
* FlatpakTransaction::end-of-lifed-with-rebase:
|
|
* @object: A #FlatpakTransaction
|
|
* @remote: The remote for the ref we are processing
|
|
* @ref: The ref we are processing
|
|
* @reason: The eol reason, or %NULL
|
|
* @rebased_to_ref: The new name, if rebased, or %NULL
|
|
* @previous_ids: The previous names for the rebased ref (if any), including the one from @ref
|
|
*
|
|
* The ::end-of-lifed-with-rebase signal gets emitted when a ref is found
|
|
* to be marked as end-of-life before the transaction begins. Unlike
|
|
* #FlatpakTransaction::end-of-lifed, this signal allows for the
|
|
* transaction to be modified in order to e.g. install the rebased
|
|
* ref.
|
|
*
|
|
* Returns: %TRUE if the operation on this end-of-lifed ref should
|
|
* be skipped, %FALSE if it should remain.
|
|
*/
|
|
signals[END_OF_LIFED_WITH_REBASE] =
|
|
g_signal_new ("end-of-lifed-with-rebase",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, end_of_lifed_with_rebase),
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_BOOLEAN, 5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRV);
|
|
|
|
/**
|
|
* FlatpakTransaction::ready:
|
|
* @object: A #FlatpakTransaction
|
|
*
|
|
* The ::ready signal is emitted when all the refs involved in the operation
|
|
* have been resolved to commits. At this point flatpak_transaction_get_operations()
|
|
* will return all the operations that will be executed as part of the
|
|
* transaction.
|
|
*
|
|
* Returns: %TRUE to carry on with the transaction, %FALSE to abort
|
|
*/
|
|
signals[READY] =
|
|
g_signal_new ("ready",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, ready),
|
|
signal_accumulator_false_abort, NULL,
|
|
NULL,
|
|
G_TYPE_BOOLEAN, 0);
|
|
|
|
/**
|
|
* FlatpakTransaction::add-new-remote:
|
|
* @object: A #FlatpakTransaction
|
|
* @reason: A #FlatpakTransactionRemoteReason for this suggestion
|
|
* @from_id: The id of the app/runtime
|
|
* @suggested_remote_name: The suggested remote name
|
|
* @url: The repo url
|
|
*
|
|
* The ::add-new-remote signal gets emitted if, as part of the transaction,
|
|
* it is required or recommended that a new remote is added, for the reason
|
|
* described in @reason.
|
|
*
|
|
* Returns: %TRUE to add the remote
|
|
*/
|
|
signals[ADD_NEW_REMOTE] =
|
|
g_signal_new ("add-new-remote",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (FlatpakTransactionClass, add_new_remote),
|
|
g_signal_accumulator_first_wins, NULL,
|
|
NULL,
|
|
G_TYPE_BOOLEAN, 4, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
|
|
}
|
|
|
|
static void
|
|
flatpak_transaction_init (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->last_op_for_ref = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
priv->remote_states = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) flatpak_remote_state_unref);
|
|
priv->added_origin_remotes = g_ptr_array_new_with_free_func (g_free);
|
|
priv->extra_dependency_dirs = g_ptr_array_new_with_free_func (g_object_unref);
|
|
priv->can_run = TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
initable_init (GInitable *initable,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransaction *self = FLATPAK_TRANSACTION (initable);
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(FlatpakDir) dir = NULL;
|
|
|
|
if (priv->installation == NULL)
|
|
return flatpak_fail (error, "No installation specified");
|
|
|
|
dir = flatpak_installation_clone_dir (priv->installation, cancellable, error);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
|
|
priv->dir = g_steal_pointer (&dir);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
initable_iface_init (GInitableIface *initable_iface)
|
|
{
|
|
initable_iface->init = initable_init;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_new_for_installation:
|
|
* @installation: a #FlatpakInstallation
|
|
* @cancellable: (nullable): a #GCancellable
|
|
* @error: return location for a #GError
|
|
*
|
|
* Creates a new #FlatpakTransaction object that can be used to do installation
|
|
* and updates of multiple refs, as well as their dependencies, in a single
|
|
* operation. Set the options you want on the transaction and add the
|
|
* refs you want to install/update, then start the transaction with
|
|
* flatpak_transaction_run ().
|
|
*
|
|
* Returns: (transfer full): a #FlatpakTransaction, or %NULL on failure.
|
|
*/
|
|
FlatpakTransaction *
|
|
flatpak_transaction_new_for_installation (FlatpakInstallation *installation,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
return g_initable_new (FLATPAK_TYPE_TRANSACTION,
|
|
cancellable, error,
|
|
"installation", installation,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_no_pull:
|
|
* @self: a #FlatpakTransaction
|
|
* @no_pull: whether to avoid pulls
|
|
*
|
|
* Sets whether the transaction should operate only on locally
|
|
* available data.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_no_pull (FlatpakTransaction *self,
|
|
gboolean no_pull)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->no_pull = no_pull;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_no_deploy:
|
|
* @self: a #FlatpakTransaction
|
|
* @no_deploy: whether to avoid deploying
|
|
*
|
|
* Sets whether the transaction should download updates, but
|
|
* not deploy them.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_no_deploy (FlatpakTransaction *self,
|
|
gboolean no_deploy)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->no_deploy = no_deploy;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_disable_static_deltas:
|
|
* @self: a #FlatpakTransaction
|
|
* @disable_static_deltas: whether to avoid static deltas
|
|
*
|
|
* Sets whether the transaction should avoid using static
|
|
* deltas when pulling.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_disable_static_deltas (FlatpakTransaction *self,
|
|
gboolean disable_static_deltas)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->disable_static_deltas = disable_static_deltas;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_disable_prune:
|
|
* @self: a #FlatpakTransaction
|
|
* @disable_prune: whether to avoid pruning
|
|
*
|
|
* Sets whether the transaction should avoid pruning the local OSTree
|
|
* repository after updating.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_disable_prune (FlatpakTransaction *self,
|
|
gboolean disable_prune)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->disable_prune = disable_prune;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_disable_dependencies:
|
|
* @self: a #FlatpakTransaction
|
|
* @disable_dependencies: whether to disable runtime dependencies
|
|
*
|
|
* Sets whether the transaction should ignore runtime dependencies
|
|
* when resolving operations for applications.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_disable_dependencies (FlatpakTransaction *self,
|
|
gboolean disable_dependencies)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->disable_deps = disable_dependencies;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_disable_related:
|
|
* @self: a #FlatpakTransaction
|
|
* @disable_related: whether to avoid adding related refs
|
|
*
|
|
* Sets whether the transaction should avoid adding related refs
|
|
* when resolving operations. Related refs are extensions that are
|
|
* suggested by apps, such as locales.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_disable_related (FlatpakTransaction *self,
|
|
gboolean disable_related)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->disable_related = disable_related;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_reinstall:
|
|
* @self: a #FlatpakTransaction
|
|
* @reinstall: whether to reinstall refs
|
|
*
|
|
* Sets whether the transaction should uninstall first if a
|
|
* ref is already installed.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_reinstall (FlatpakTransaction *self,
|
|
gboolean reinstall)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->reinstall = reinstall;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_force_uninstall:
|
|
* @self: a #FlatpakTransaction
|
|
* @force_uninstall: whether to force-uninstall refs
|
|
*
|
|
* Sets whether the transaction should uninstall files even
|
|
* if they're used by a running application.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_force_uninstall (FlatpakTransaction *self,
|
|
gboolean force_uninstall)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->force_uninstall = force_uninstall;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_set_default_arch:
|
|
* @self: a #FlatpakTransaction
|
|
* @arch: the arch to make default
|
|
*
|
|
* Sets the architecture to default to where it is unspecified.
|
|
*/
|
|
void
|
|
flatpak_transaction_set_default_arch (FlatpakTransaction *self,
|
|
const char *arch)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
g_free (priv->default_arch);
|
|
priv->default_arch = g_strdup (arch);
|
|
}
|
|
|
|
static FlatpakTransactionOperation *
|
|
flatpak_transaction_get_last_op_for_ref (FlatpakTransaction *self,
|
|
const char *ref)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
FlatpakTransactionOperation *op;
|
|
|
|
op = g_hash_table_lookup (priv->last_op_for_ref, ref);
|
|
|
|
return op;
|
|
}
|
|
|
|
static char *
|
|
subpaths_to_string (const char **subpaths)
|
|
{
|
|
GString *s = NULL;
|
|
int i;
|
|
|
|
if (subpaths == NULL)
|
|
return g_strdup ("[$old]");
|
|
|
|
if (*subpaths == 0)
|
|
return g_strdup ("[*]");
|
|
|
|
s = g_string_new ("[");
|
|
for (i = 0; subpaths[i] != NULL; i++)
|
|
{
|
|
if (i != 0)
|
|
g_string_append (s, ", ");
|
|
g_string_append (s, subpaths[i]);
|
|
}
|
|
g_string_append (s, "]");
|
|
|
|
return g_string_free (s, FALSE);
|
|
}
|
|
|
|
static const char *
|
|
kind_to_str (FlatpakTransactionOperationType kind)
|
|
{
|
|
switch ((int) kind)
|
|
{
|
|
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
|
|
return "install";
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
|
|
return "update";
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE:
|
|
return "install/update";
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
|
|
return "install bundle";
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
|
|
return "uninstall";
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_LAST_TYPE:
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static FlatpakRemoteState *
|
|
flatpak_transaction_ensure_remote_state (FlatpakTransaction *self,
|
|
FlatpakTransactionOperationType kind,
|
|
const char *remote,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
FlatpakRemoteState *state;
|
|
|
|
/* We don't cache local-only states, as we might later need the same state with non-local state */
|
|
if (transaction_is_local_only (self, kind))
|
|
return flatpak_dir_get_remote_state_local_only (priv->dir, remote, NULL, error);
|
|
|
|
state = g_hash_table_lookup (priv->remote_states, remote);
|
|
if (state)
|
|
return flatpak_remote_state_ref (state);
|
|
|
|
state = flatpak_dir_get_remote_state_optional (priv->dir, remote, NULL, error);
|
|
|
|
if (state)
|
|
g_hash_table_insert (priv->remote_states, state->remote_name, flatpak_remote_state_ref (state));
|
|
|
|
return state;
|
|
}
|
|
|
|
static gboolean
|
|
kind_compatible (FlatpakTransactionOperationType a,
|
|
FlatpakTransactionOperationType b,
|
|
gboolean b_is_rebase)
|
|
{
|
|
if (a == b)
|
|
return TRUE;
|
|
|
|
if (a == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE &&
|
|
(b == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
|
|
b == FLATPAK_TRANSACTION_OPERATION_UPDATE))
|
|
return TRUE;
|
|
|
|
if (b == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE &&
|
|
(a == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
|
|
a == FLATPAK_TRANSACTION_OPERATION_UPDATE))
|
|
return TRUE;
|
|
|
|
/* If b is a rebase, the only reason it exists is so that the ref's previous-ids can be
|
|
updated. Therefore, it can be folded into any other install or update operation. */
|
|
if (b_is_rebase &&
|
|
(a == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
|
|
a == FLATPAK_TRANSACTION_OPERATION_UPDATE ||
|
|
a == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static FlatpakTransactionOperation *
|
|
flatpak_transaction_add_op (FlatpakTransaction *self,
|
|
const char *remote,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
const char **previous_ids,
|
|
const char *commit,
|
|
GFile *bundle,
|
|
FlatpakTransactionOperationType kind)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
FlatpakTransactionOperation *op;
|
|
g_autofree char *subpaths_str = NULL;
|
|
|
|
subpaths_str = subpaths_to_string (subpaths);
|
|
g_debug ("Transaction: %s %s:%s%s%s%s",
|
|
kind_to_str (kind), remote, ref,
|
|
commit != NULL ? "@" : "",
|
|
commit != NULL ? commit : "",
|
|
subpaths_str);
|
|
|
|
op = flatpak_transaction_get_last_op_for_ref (self, ref);
|
|
/* If previous_ids is given, then this is a rebase operation. */
|
|
if (op != NULL && kind_compatible (kind, op->kind, previous_ids != NULL))
|
|
{
|
|
g_auto(GStrv) old_subpaths = NULL;
|
|
g_auto(GStrv) old_previous_ids = NULL;
|
|
|
|
old_subpaths = op->subpaths;
|
|
op->subpaths = flatpak_subpaths_merge (old_subpaths, (char **) subpaths);
|
|
|
|
old_previous_ids = op->previous_ids;
|
|
op->previous_ids = flatpak_strv_merge (old_previous_ids, (char **) previous_ids);
|
|
|
|
return op;
|
|
}
|
|
|
|
op = flatpak_transaction_operation_new (remote, ref, subpaths, previous_ids, commit, bundle, kind);
|
|
g_hash_table_insert (priv->last_op_for_ref, g_strdup (ref), op);
|
|
|
|
priv->ops = g_list_prepend (priv->ops, op);
|
|
|
|
priv->needs_resolve = TRUE;
|
|
|
|
return op;
|
|
}
|
|
|
|
static void
|
|
run_operation_before (FlatpakTransactionOperation *op,
|
|
FlatpakTransactionOperation *before_this,
|
|
int prio)
|
|
{
|
|
if (op == before_this)
|
|
return; /* Don't cause unnecessary loops */
|
|
op->run_before_ops = g_list_prepend (op->run_before_ops, before_this);
|
|
before_this->run_after_count++;
|
|
before_this->run_after_prio = MAX (before_this->run_after_prio, prio);
|
|
}
|
|
|
|
static gboolean
|
|
add_related (FlatpakTransaction *self,
|
|
FlatpakTransactionOperation *op,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(FlatpakRemoteState) state = NULL;
|
|
g_autoptr(GPtrArray) related = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
int i;
|
|
|
|
if (priv->disable_related)
|
|
return TRUE;
|
|
|
|
state = flatpak_transaction_ensure_remote_state (self, op->kind, op->remote, error);
|
|
if (state == NULL)
|
|
return FALSE;
|
|
|
|
if (op->resolved_metakey == NULL)
|
|
{
|
|
g_debug ("no resolved metadata for related to %s", op->ref);
|
|
return TRUE;
|
|
}
|
|
|
|
if (transaction_is_local_only (self, op->kind))
|
|
related = flatpak_dir_find_local_related_for_metadata (priv->dir, op->ref, op->remote, op->resolved_metakey,
|
|
NULL, &local_error);
|
|
else
|
|
related = flatpak_dir_find_remote_related_for_metadata (priv->dir, state, op->ref, op->resolved_metakey,
|
|
NULL, &local_error);
|
|
if (related == NULL)
|
|
{
|
|
g_message (_("Warning: Problem looking for related refs: %s"), local_error->message);
|
|
return TRUE;
|
|
}
|
|
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
{
|
|
for (i = 0; i < related->len; i++)
|
|
{
|
|
FlatpakRelated *rel = g_ptr_array_index (related, i);
|
|
FlatpakTransactionOperation *related_op;
|
|
|
|
if (!rel->delete)
|
|
continue;
|
|
|
|
related_op = flatpak_transaction_add_op (self, op->remote, rel->ref,
|
|
NULL, NULL, NULL, NULL,
|
|
FLATPAK_TRANSACTION_OPERATION_UNINSTALL);
|
|
related_op->non_fatal = TRUE;
|
|
related_op->fail_if_op_fails = op;
|
|
run_operation_before (op, related_op, 1);
|
|
}
|
|
}
|
|
else /* install or update */
|
|
{
|
|
for (i = 0; i < related->len; i++)
|
|
{
|
|
FlatpakRelated *rel = g_ptr_array_index (related, i);
|
|
FlatpakTransactionOperation *related_op;
|
|
|
|
if (!rel->download)
|
|
continue;
|
|
|
|
related_op = flatpak_transaction_add_op (self, op->remote, rel->ref,
|
|
(const char **) rel->subpaths,
|
|
NULL, NULL, NULL,
|
|
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE);
|
|
related_op->non_fatal = TRUE;
|
|
related_op->fail_if_op_fails = op;
|
|
run_operation_before (op, related_op, 1);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static char *
|
|
find_runtime_remote (FlatpakTransaction *self,
|
|
const char *app_ref,
|
|
const char *runtime_ref,
|
|
FlatpakTransactionOperationType source_kind,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_auto(GStrv) remotes = NULL;
|
|
const char *app_pref;
|
|
const char *runtime_pref;
|
|
int res = -1;
|
|
|
|
app_pref = strchr (app_ref, '/') + 1;
|
|
runtime_pref = strchr (runtime_ref, '/') + 1;
|
|
|
|
if (transaction_is_local_only (self, source_kind))
|
|
remotes = flatpak_dir_search_for_local_dependency (priv->dir, runtime_ref, NULL, NULL);
|
|
else
|
|
remotes = flatpak_dir_search_for_dependency (priv->dir, runtime_ref, NULL, NULL);
|
|
|
|
if (remotes == NULL || *remotes == NULL)
|
|
{
|
|
flatpak_fail_error (error, FLATPAK_ERROR_RUNTIME_NOT_FOUND,
|
|
_("The application %s requires the runtime %s which was not found"),
|
|
app_pref, runtime_pref);
|
|
return NULL;
|
|
}
|
|
|
|
/* In the no-pull case, if only one local ref is available, assume that is the one because
|
|
the user chose it interactively when pulling */
|
|
if (priv->no_pull && g_strv_length (remotes) == 1)
|
|
res = 0;
|
|
else
|
|
g_signal_emit (self, signals[CHOOSE_REMOTE_FOR_REF], 0, app_ref, runtime_ref, remotes, &res);
|
|
|
|
if (res >= 0 && res < g_strv_length (remotes))
|
|
return g_strdup (remotes[res]);
|
|
|
|
flatpak_fail_error (error, FLATPAK_ERROR_RUNTIME_NOT_FOUND,
|
|
_("The application %s requires the runtime %s which is not installed"),
|
|
app_pref, runtime_pref);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
add_deps (FlatpakTransaction *self,
|
|
FlatpakTransactionOperation *op,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *runtime_ref = NULL;
|
|
g_autofree char *full_runtime_ref = NULL;
|
|
g_autofree char *runtime_remote = NULL;
|
|
FlatpakTransactionOperation *runtime_op = NULL;
|
|
|
|
if (!g_str_has_prefix (op->ref, "app/"))
|
|
return TRUE;
|
|
|
|
if (op->resolved_metakey)
|
|
runtime_ref = g_key_file_get_string (op->resolved_metakey, "Application", "runtime", NULL);
|
|
|
|
if (runtime_ref == NULL)
|
|
return TRUE;
|
|
|
|
full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL);
|
|
|
|
runtime_op = flatpak_transaction_get_last_op_for_ref (self, full_runtime_ref);
|
|
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
{
|
|
/* If the runtime this app uses is already to be uninstalled, then this uninstall must happen before
|
|
the runtime is uninstalled */
|
|
if (runtime_op && op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
run_operation_before (op, runtime_op, 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (priv->disable_deps)
|
|
return TRUE;
|
|
|
|
if (runtime_op == NULL)
|
|
{
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
if (!ref_is_installed (self, full_runtime_ref, &local_error))
|
|
{
|
|
if (local_error != NULL)
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
|
|
runtime_remote = find_runtime_remote (self, op->ref, full_runtime_ref, op->kind, error);
|
|
if (runtime_remote == NULL)
|
|
return FALSE;
|
|
|
|
runtime_op = flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, NULL, NULL, NULL,
|
|
FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE);
|
|
}
|
|
else
|
|
{
|
|
/* Update if in same dir */
|
|
if (dir_ref_is_installed (priv->dir, full_runtime_ref, &runtime_remote, NULL))
|
|
{
|
|
g_debug ("Updating dependent runtime %s", full_runtime_ref);
|
|
runtime_op = flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, NULL, NULL, NULL,
|
|
FLATPAK_TRANSACTION_OPERATION_UPDATE);
|
|
runtime_op->non_fatal = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Install/Update the runtime before the app */
|
|
if (runtime_op)
|
|
{
|
|
if (runtime_op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_RUNTIME_USED,
|
|
_("Can't uninstall %s which is needed by %s"),
|
|
runtime_op->ref, op->ref);
|
|
|
|
op->fail_if_op_fails = runtime_op;
|
|
run_operation_before (runtime_op, op, 2);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_add_ref (FlatpakTransaction *self,
|
|
const char *remote,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
const char **previous_ids,
|
|
const char *commit,
|
|
FlatpakTransactionOperationType kind,
|
|
GFile *bundle,
|
|
const char *external_metadata,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *origin = NULL;
|
|
g_auto(GStrv) parts = NULL;
|
|
const char *pref;
|
|
g_autofree char *origin_remote = NULL;
|
|
g_autoptr(FlatpakRemoteState) state = NULL;
|
|
FlatpakTransactionOperation *op;
|
|
|
|
parts = flatpak_decompose_ref (ref, error);
|
|
if (parts == NULL)
|
|
return FALSE;
|
|
|
|
if (remote_name_is_file (remote))
|
|
{
|
|
origin_remote = flatpak_dir_create_origin_remote (priv->dir,
|
|
remote, /* uri */
|
|
parts[1],
|
|
"Local repo",
|
|
ref,
|
|
NULL,
|
|
NULL,
|
|
NULL, error);
|
|
if (origin_remote == NULL)
|
|
return FALSE;
|
|
|
|
g_ptr_array_add (priv->added_origin_remotes, g_strdup (origin_remote));
|
|
|
|
remote = origin_remote;
|
|
}
|
|
|
|
/* safe because flatpak_decompose_ref() has validated ref */
|
|
pref = strchr (ref, '/') + 1;
|
|
|
|
/* install or update */
|
|
if (kind == FLATPAK_TRANSACTION_OPERATION_UPDATE)
|
|
{
|
|
if (!dir_ref_is_installed (priv->dir, ref, &origin, NULL))
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED,
|
|
_("%s not installed"), pref);
|
|
|
|
if (flatpak_dir_get_remote_disabled (priv->dir, origin))
|
|
{
|
|
g_debug (_("Remote %s disabled, ignoring %s update"), origin, pref);
|
|
return TRUE;
|
|
}
|
|
remote = origin;
|
|
}
|
|
else if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
|
|
{
|
|
if (!priv->reinstall &&
|
|
dir_ref_is_installed (priv->dir, ref, &origin, NULL))
|
|
{
|
|
if (g_strcmp0 (remote, origin) == 0)
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_ALREADY_INSTALLED,
|
|
_("%s is already installed"), pref);
|
|
else
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_DIFFERENT_REMOTE,
|
|
_("%s is already installed from remote %s"),
|
|
pref, origin);
|
|
}
|
|
}
|
|
else if (kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
{
|
|
if (!dir_ref_is_installed (priv->dir, ref, &origin, NULL))
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED,
|
|
_("%s not installed"), pref);
|
|
|
|
remote = origin;
|
|
}
|
|
|
|
/* This should have been passed in or found out above */
|
|
g_assert (remote != NULL);
|
|
|
|
state = flatpak_transaction_ensure_remote_state (self, kind, remote, error);
|
|
if (state == NULL)
|
|
return FALSE;
|
|
|
|
op = flatpak_transaction_add_op (self, remote, ref, subpaths, previous_ids, commit, bundle, kind);
|
|
|
|
if (external_metadata)
|
|
op->external_metadata = g_bytes_new (external_metadata, strlen (external_metadata) + 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_install:
|
|
* @self: a #FlatpakTransaction
|
|
* @remote: the name of the remote
|
|
* @ref: the ref
|
|
* @subpaths: (nullable) (array zero-terminated=1): subpaths to install, or the
|
|
* empty list or %NULL to pull all subpaths
|
|
* @error: return location for a #GError
|
|
*
|
|
* Adds installing the given ref to this transaction.
|
|
*
|
|
* The @remote can either be a configured remote of the installation,
|
|
* or a file:// uri pointing at a local repository to install from,
|
|
* in which case an origin remote is created.
|
|
*
|
|
* Returns: %TRUE on success; %FALSE with @error set on failure.
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_add_install (FlatpakTransaction *self,
|
|
const char *remote,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
GError **error)
|
|
{
|
|
const char *all_paths[] = { NULL };
|
|
|
|
g_return_val_if_fail (ref != NULL, FALSE);
|
|
g_return_val_if_fail (remote != NULL, FALSE);
|
|
|
|
/* If we install with no special args pull all subpaths */
|
|
if (subpaths == NULL)
|
|
subpaths = all_paths;
|
|
|
|
return flatpak_transaction_add_ref (self, remote, ref, subpaths, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_INSTALL, NULL, NULL, error);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_rebase:
|
|
* @self: a #FlatpakTransaction
|
|
* @remote: the name of the remote
|
|
* @ref: the ref
|
|
* @previous_ids: (nullable) (array zero-terminated=1): Previous ids to add to the
|
|
* given ref. These should simply be the ids, not the full ref names (e.g. org.foo.Bar,
|
|
* not org.foo.Bar/x86_64/master).
|
|
* @error: return location for a #GError
|
|
*
|
|
* Adds updating the previous-ids of the given ref to this transaction, via either
|
|
* installing the @ref if it was not already present. The will treat @ref as the
|
|
* result of following an eol-rebase, and data migration from the refs in
|
|
* @previous-ids will be set up.
|
|
*
|
|
* See flatpak_transaction_add_install() for a description of @remote.
|
|
*
|
|
* Returns: %TRUE on success; %FALSE with @error set on failure.
|
|
* Since: 1.3.3.
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_add_rebase (FlatpakTransaction *self,
|
|
const char *remote,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
const char **previous_ids,
|
|
GError **error)
|
|
{
|
|
const char *all_paths[] = { NULL };
|
|
|
|
g_return_val_if_fail (ref != NULL, FALSE);
|
|
g_return_val_if_fail (remote != NULL, FALSE);
|
|
/* flatpak_transaction_add_install_rebase without previous_ids doesn't make sense */
|
|
g_return_val_if_fail (previous_ids != NULL, FALSE);
|
|
|
|
/* If we install with no special args pull all subpaths */
|
|
if (subpaths == NULL)
|
|
subpaths = all_paths;
|
|
|
|
return flatpak_transaction_add_ref (self, remote, ref, subpaths, previous_ids, NULL, FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE, NULL, NULL, error);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_install_bundle:
|
|
* @self: a #FlatpakTransaction
|
|
* @file: a #GFile that is an flatpak bundle
|
|
* @gpg_data: (nullable): GPG key with which to check bundle signatures, or
|
|
* %NULL to use the key embedded in the bundle (if any)
|
|
* @error: return location for a #GError
|
|
*
|
|
* Adds installing the given bundle to this transaction.
|
|
*
|
|
* Returns: %TRUE on success; %FALSE with @error set on failure.
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_add_install_bundle (FlatpakTransaction *self,
|
|
GFile *file,
|
|
GBytes *gpg_data,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
priv->bundles = g_list_append (priv->flatpakrefs, bundle_data_new (file, gpg_data));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_install_flatpakref:
|
|
* @self: a #FlatpakTransaction
|
|
* @flatpakref_data: data from a flatpakref file
|
|
* @error: return location for a #GError
|
|
*
|
|
* Adds installing the given flatpakref to this transaction.
|
|
*
|
|
* Returns: %TRUE on success; %FALSE with @error set on failure.
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_add_install_flatpakref (FlatpakTransaction *self,
|
|
GBytes *flatpakref_data,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
g_return_val_if_fail (flatpakref_data != NULL, FALSE);
|
|
|
|
if (!g_key_file_load_from_data (keyfile, g_bytes_get_data (flatpakref_data, NULL),
|
|
g_bytes_get_size (flatpakref_data),
|
|
0, &local_error))
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid .flatpakref: %s"), local_error->message);
|
|
|
|
priv->flatpakrefs = g_list_append (priv->flatpakrefs, g_steal_pointer (&keyfile));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_update:
|
|
* @self: a #FlatpakTransaction
|
|
* @ref: the ref
|
|
* @subpaths: (nullable) (array zero-terminated=1): subpaths to install; %NULL
|
|
* to use the current set, or `{ "", NULL }` to pull all subpaths.
|
|
* @commit: (nullable): the commit to update to, or %NULL to use the latest
|
|
* @error: return location for a #GError
|
|
*
|
|
* Adds updating the given ref to this transaction.
|
|
*
|
|
* Returns: %TRUE on success; %FALSE with @error set on failure.
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_add_update (FlatpakTransaction *self,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
const char *commit,
|
|
GError **error)
|
|
{
|
|
const char *all_paths[] = { NULL };
|
|
|
|
g_return_val_if_fail (ref != NULL, FALSE);
|
|
|
|
/* If specify an empty subpath, that means all subpaths */
|
|
if (subpaths != NULL && subpaths[0] != NULL && subpaths[0][0] == 0)
|
|
subpaths = all_paths;
|
|
|
|
return flatpak_transaction_add_ref (self, NULL, ref, subpaths, NULL, commit, FLATPAK_TRANSACTION_OPERATION_UPDATE, NULL, NULL, error);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_add_uninstall:
|
|
* @self: a #FlatpakTransaction
|
|
* @ref: the ref
|
|
* @error: return location for a #GError
|
|
*
|
|
* Adds uninstalling the given ref to this transaction.
|
|
*
|
|
* Returns: %TRUE on success; %FALSE with @error set on failure.
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_add_uninstall (FlatpakTransaction *self,
|
|
const char *ref,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (ref != NULL, FALSE);
|
|
|
|
return flatpak_transaction_add_ref (self, NULL, ref, NULL, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_UNINSTALL, NULL, NULL, error);
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_update_metadata (FlatpakTransaction *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_auto(GStrv) remotes = NULL;
|
|
int i;
|
|
GList *l;
|
|
g_autoptr(GHashTable) ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
/* Collect all dir+remotes used in this transaction */
|
|
|
|
for (l = priv->ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
g_hash_table_add (ht, g_strdup (op->remote));
|
|
}
|
|
remotes = (char **) g_hash_table_get_keys_as_array (ht, NULL);
|
|
g_hash_table_steal_all (ht); /* Move ownership to remotes */
|
|
|
|
/* Update metadata for said remotes */
|
|
for (i = 0; remotes[i] != NULL; i++)
|
|
{
|
|
char *remote = remotes[i];
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
g_debug ("Updating remote metadata for %s", remote);
|
|
if (!flatpak_dir_update_remote_configuration (priv->dir, remote, cancellable, &my_error))
|
|
g_message (_("Error updating remote metadata for '%s': %s"), remote, my_error->message);
|
|
}
|
|
|
|
/* Reload changed configuration */
|
|
if (!flatpak_dir_recreate_repo (priv->dir, cancellable, error))
|
|
return FALSE;
|
|
|
|
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
|
|
|
|
/* These are potentially out of date now */
|
|
g_hash_table_remove_all (priv->remote_states);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
emit_new_op (FlatpakTransaction *self, FlatpakTransactionOperation *op, FlatpakTransactionProgress *progress)
|
|
{
|
|
g_signal_emit (self, signals[NEW_OPERATION], 0, op, progress);
|
|
}
|
|
|
|
static void
|
|
emit_op_done (FlatpakTransaction *self,
|
|
FlatpakTransactionOperation *op,
|
|
FlatpakTransactionResult details)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *commit = NULL;
|
|
|
|
if (priv->no_deploy)
|
|
commit = flatpak_dir_read_latest (priv->dir, op->remote, op->ref, NULL, NULL, NULL);
|
|
else
|
|
{
|
|
g_autoptr(GVariant) deploy_data = flatpak_dir_get_deploy_data (priv->dir, op->ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
|
|
if (deploy_data)
|
|
commit = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
|
|
}
|
|
|
|
g_signal_emit (self, signals[OPERATION_DONE], 0, op, commit, details);
|
|
}
|
|
|
|
static GBytes *
|
|
load_deployed_metadata (FlatpakTransaction *self, const char *ref)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(GFile) deploy_dir = NULL;
|
|
g_autoptr(GFile) metadata_file = NULL;
|
|
g_autofree char *metadata_contents = NULL;
|
|
gsize metadata_contents_length;
|
|
|
|
deploy_dir = flatpak_dir_get_if_deployed (priv->dir, ref, NULL, NULL);
|
|
if (deploy_dir == NULL)
|
|
return NULL;
|
|
|
|
metadata_file = g_file_get_child (deploy_dir, "metadata");
|
|
|
|
if (!g_file_load_contents (metadata_file, NULL, &metadata_contents, &metadata_contents_length, NULL, NULL))
|
|
{
|
|
g_debug ("No metadata in local deploy of %s", ref);
|
|
return NULL;
|
|
}
|
|
|
|
return g_bytes_new_take (g_steal_pointer (&metadata_contents), metadata_contents_length + 1);
|
|
}
|
|
|
|
static void
|
|
emit_eol_and_maybe_skip (FlatpakTransaction *self,
|
|
FlatpakTransactionOperation *op)
|
|
{
|
|
g_auto(GStrv) parts = NULL;
|
|
const char *previous_ids[] = { NULL, NULL };
|
|
|
|
if (op->skip || (!op->eol && !op->eol_rebase) ||
|
|
op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
return;
|
|
|
|
parts = flatpak_decompose_ref (op->ref, NULL);
|
|
if (parts != NULL)
|
|
previous_ids[0] = parts[1];
|
|
|
|
g_signal_emit (self, signals[END_OF_LIFED_WITH_REBASE], 0, op->remote, op->ref, op->eol, op->eol_rebase, previous_ids, &op->skip);
|
|
}
|
|
|
|
static void
|
|
mark_op_resolved (FlatpakTransactionOperation *op,
|
|
const char *commit,
|
|
GBytes *metadata,
|
|
GBytes *old_metadata)
|
|
{
|
|
g_debug ("marking op %s:%s resolved to %s", kind_to_str (op->kind), op->ref, commit ? commit : "-");
|
|
|
|
g_assert (op != NULL);
|
|
|
|
if (op->kind != FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
g_assert (commit != NULL);
|
|
|
|
op->resolved = TRUE;
|
|
op->resolved_commit = g_strdup (commit);
|
|
if (metadata)
|
|
{
|
|
g_autoptr(GKeyFile) metakey = g_key_file_new ();
|
|
if (g_key_file_load_from_bytes (metakey, metadata, G_KEY_FILE_NONE, NULL))
|
|
{
|
|
op->resolved_metadata = g_bytes_ref (metadata);
|
|
op->resolved_metakey = g_steal_pointer (&metakey);
|
|
}
|
|
else
|
|
g_message ("Warning: Failed to parse metadata for %s\n", op->ref);
|
|
}
|
|
if (old_metadata)
|
|
{
|
|
g_autoptr(GKeyFile) metakey = g_key_file_new ();
|
|
if (g_key_file_load_from_bytes (metakey, old_metadata, G_KEY_FILE_NONE, NULL))
|
|
{
|
|
op->resolved_old_metadata = g_bytes_ref (old_metadata);
|
|
op->resolved_old_metakey = g_steal_pointer (&metakey);
|
|
}
|
|
else
|
|
g_message ("Warning: Failed to parse old metadata for %s\n", op->ref);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
resolve_p2p_ops (FlatpakTransaction *self,
|
|
GList *p2p_ops,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(GPtrArray) resolves = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_dir_resolve_free);
|
|
GList *l;
|
|
int i;
|
|
|
|
for (l = p2p_ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
FlatpakDirResolve *resolve;
|
|
|
|
g_debug ("resolving %s using p2p", op->ref);
|
|
|
|
g_assert (op->kind != FLATPAK_TRANSACTION_OPERATION_UNINSTALL);
|
|
g_assert (op->kind != FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE);
|
|
g_assert (!op->resolved);
|
|
|
|
resolve = flatpak_dir_resolve_new (op->remote, op->ref, op->commit);
|
|
g_ptr_array_add (resolves, resolve);
|
|
}
|
|
|
|
g_ptr_array_add (resolves, NULL);
|
|
|
|
if (!flatpak_dir_resolve_p2p_refs (priv->dir, (FlatpakDirResolve **) resolves->pdata,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
for (i = 0, l = p2p_ops; l != NULL; i++, l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
FlatpakDirResolve *resolve = g_ptr_array_index (resolves, i);
|
|
g_autoptr(GBytes) old_metadata_bytes = NULL;
|
|
|
|
op->download_size = resolve->download_size;
|
|
op->installed_size = resolve->installed_size;
|
|
op->eol = g_strdup (resolve->eol);
|
|
op->eol_rebase = g_strdup (resolve->eol_rebase);
|
|
|
|
old_metadata_bytes = load_deployed_metadata (self, op->ref);
|
|
mark_op_resolved (op, resolve->resolved_commit, resolve->resolved_metadata, old_metadata_bytes);
|
|
|
|
emit_eol_and_maybe_skip (self, op);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Resolving an operation means figuring out the target commit
|
|
checksum and the metadata for that commit, so that we can handle
|
|
dependencies from it, and verify versions. */
|
|
static gboolean
|
|
resolve_ops (FlatpakTransaction *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *l;
|
|
g_autoptr(GList) collection_id_ops = NULL;
|
|
|
|
|
|
for (l = priv->ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
g_autoptr(FlatpakRemoteState) state = NULL;
|
|
g_autofree char *checksum = NULL;
|
|
g_autoptr(GVariant) commit_data = NULL;
|
|
g_autoptr(GVariant) commit_metadata = NULL;
|
|
g_autoptr(GBytes) metadata_bytes = NULL;
|
|
g_autoptr(GBytes) old_metadata_bytes = NULL;
|
|
guint64 download_size = 0;
|
|
guint64 installed_size = 0;
|
|
|
|
if (op->resolved)
|
|
continue;
|
|
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
{
|
|
/* We resolve to the deployed metadata, because we need it to uninstall related ops */
|
|
|
|
metadata_bytes = load_deployed_metadata (self, op->ref);
|
|
mark_op_resolved (op, NULL, metadata_bytes, NULL);
|
|
continue;
|
|
}
|
|
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
|
|
{
|
|
g_assert (op->commit != NULL);
|
|
mark_op_resolved (op, op->commit, op->external_metadata, NULL);
|
|
continue;
|
|
}
|
|
|
|
/* op->kind is INSTALL or UPDATE */
|
|
|
|
if (g_str_has_prefix (op->ref, "app/"))
|
|
{
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
|
|
priv->max_op = APP_INSTALL;
|
|
else
|
|
priv->max_op = MAX (priv->max_op, APP_UPDATE);
|
|
}
|
|
else if (g_str_has_prefix (op->ref, "runtime/"))
|
|
{
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
|
|
priv->max_op = MAX (priv->max_op, RUNTIME_INSTALL);
|
|
}
|
|
|
|
state = flatpak_transaction_ensure_remote_state (self, op->kind, op->remote, error);
|
|
if (state == NULL)
|
|
return FALSE;
|
|
|
|
/* Should we use local state */
|
|
if (transaction_is_local_only (self, op->kind))
|
|
{
|
|
const char *xa_metadata = NULL;
|
|
commit_data = flatpak_dir_read_latest_commit (priv->dir, op->remote, op->ref, &checksum, NULL, error);
|
|
if (commit_data == NULL)
|
|
return FALSE;
|
|
|
|
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
|
g_variant_lookup (commit_metadata, "xa.metadata", "&s", &xa_metadata);
|
|
if (xa_metadata == NULL)
|
|
g_message ("Warning: No xa.metadata in local commit %s ref %s", checksum, op->ref);
|
|
else
|
|
metadata_bytes = g_bytes_new (xa_metadata, strlen (xa_metadata) + 1);
|
|
|
|
if (g_variant_lookup (commit_metadata, "xa.download-size", "t", &download_size))
|
|
op->download_size = GUINT64_FROM_BE (download_size);
|
|
if (g_variant_lookup (commit_metadata, "xa.installed-size", "t", &installed_size))
|
|
op->installed_size = GUINT64_FROM_BE (installed_size);
|
|
|
|
g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "s", &op->eol);
|
|
g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "s", &op->eol_rebase);
|
|
|
|
old_metadata_bytes = load_deployed_metadata (self, op->ref);
|
|
mark_op_resolved (op, checksum, metadata_bytes, old_metadata_bytes);
|
|
|
|
emit_eol_and_maybe_skip (self, op);
|
|
}
|
|
else if (state->collection_id == NULL) /* In the non-p2p case we have all the info available in the summary, so use it */
|
|
{
|
|
const char *metadata = NULL;
|
|
g_autoptr(GVariant) sparse_cache = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
if (op->commit != NULL)
|
|
checksum = g_strdup (op->commit);
|
|
else if (!flatpak_dir_find_latest_rev (priv->dir, state, op->ref, op->commit, &checksum,
|
|
NULL, cancellable, &local_error))
|
|
{
|
|
/* An unavailable remote summary shouldn't be fatal if we already have the ref */
|
|
commit_data = flatpak_dir_read_latest_commit (priv->dir, op->remote, op->ref, &checksum, NULL, NULL);
|
|
if (commit_data == NULL)
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
g_message (_("Warning: Treating remote fetch error as non-fatal since %s is already installed: %s"),
|
|
op->ref, local_error->message);
|
|
g_clear_error (&local_error);
|
|
}
|
|
}
|
|
|
|
/* TODO: This only gets the metadata for the latest only, we need to handle the case
|
|
where the user specified a commit, or p2p doesn't have the latest commit available */
|
|
if (!flatpak_remote_state_lookup_cache (state, op->ref, &download_size, &installed_size, &metadata, &local_error))
|
|
{
|
|
g_message (_("Warning: Can't find %s metadata for dependencies: %s"), op->ref, local_error->message);
|
|
g_clear_error (&local_error);
|
|
}
|
|
else
|
|
metadata_bytes = g_bytes_new (metadata, strlen (metadata) + 1);
|
|
|
|
op->installed_size = installed_size;
|
|
op->download_size = download_size;
|
|
|
|
sparse_cache = flatpak_remote_state_lookup_sparse_cache (state, op->ref, NULL);
|
|
if (sparse_cache)
|
|
{
|
|
g_variant_lookup (sparse_cache, "eol", "s", &op->eol);
|
|
g_variant_lookup (sparse_cache, "eolr", "s", &op->eol_rebase);
|
|
}
|
|
|
|
old_metadata_bytes = load_deployed_metadata (self, op->ref);
|
|
mark_op_resolved (op, checksum, metadata_bytes, old_metadata_bytes);
|
|
|
|
emit_eol_and_maybe_skip (self, op);
|
|
}
|
|
else
|
|
{
|
|
/* This is a (potential) p2p operation, so rather than do these individually we queue them up in an operation later */
|
|
|
|
collection_id_ops = g_list_prepend (collection_id_ops, op);
|
|
}
|
|
}
|
|
|
|
if (collection_id_ops != NULL &&
|
|
!resolve_p2p_ops (self, collection_id_ops, cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
resolve_all_ops (FlatpakTransaction *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
while (priv->needs_resolve)
|
|
{
|
|
priv->needs_resolve = FALSE;
|
|
if (!resolve_ops (self, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
compare_op_ref (FlatpakTransactionOperation *a, FlatpakTransactionOperation *b)
|
|
{
|
|
char *aa = strchr (a->ref, '/');
|
|
char *bb = strchr (b->ref, '/');
|
|
|
|
return g_strcmp0 (aa, bb);
|
|
}
|
|
|
|
static int
|
|
compare_op_prio (FlatpakTransactionOperation *a, FlatpakTransactionOperation *b)
|
|
{
|
|
return b->run_after_prio - a->run_after_prio;
|
|
}
|
|
|
|
static void
|
|
sort_ops (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *sorted = NULL;
|
|
GList *remaining;
|
|
GList *runnable = NULL;
|
|
GList *l, *next;
|
|
|
|
remaining = priv->ops;
|
|
priv->ops = NULL;
|
|
|
|
/* First mark runnable all jobs that depend on nothing.
|
|
Note that this seesntially reverses the original list, so these
|
|
are in the same order as specified */
|
|
for (l = remaining; l != NULL; l = next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
next = l->next;
|
|
|
|
if (op->run_after_count == 0)
|
|
{
|
|
remaining = g_list_remove_link (remaining, l);
|
|
runnable = g_list_concat (l, runnable);
|
|
}
|
|
}
|
|
|
|
/* If no other order, start in alphabetical ref-order */
|
|
runnable = g_list_sort (runnable, (GCompareFunc) compare_op_ref);
|
|
|
|
while (runnable)
|
|
{
|
|
GList *run = runnable;
|
|
FlatpakTransactionOperation *run_op = run->data;
|
|
|
|
/* Put the first runnable on the sorted list */
|
|
runnable = g_list_remove_link (runnable, run);
|
|
sorted = g_list_concat (run, sorted); /* prepends, so reverse at the end */
|
|
|
|
/* Then greedily run ops that become runnable, in run_after_prio order, so that
|
|
related ops are run before dependencies */
|
|
run_op->run_before_ops = g_list_sort (run_op->run_before_ops, (GCompareFunc) compare_op_prio);
|
|
for (l = run_op->run_before_ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *after_op = l->data;
|
|
after_op->run_after_count--;
|
|
if (after_op->run_after_count == 0)
|
|
{
|
|
GList *after_l = g_list_find (remaining, after_op);
|
|
g_assert (after_l != NULL);
|
|
remaining = g_list_remove_link (remaining, after_l);
|
|
runnable = g_list_concat (after_l, runnable);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (remaining != NULL)
|
|
{
|
|
g_warning ("ops remaining after sort, maybe there is a dependency loop?");
|
|
sorted = g_list_concat (remaining, sorted);
|
|
}
|
|
|
|
priv->ops = g_list_reverse (sorted);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_get_operations:
|
|
* @self: a #FlatpakTransaction
|
|
*
|
|
* Gets the list of operations.
|
|
*
|
|
* Returns: (transfer full) (element-type FlatpakTransactionOperation): a #GList of operations
|
|
*/
|
|
GList *
|
|
flatpak_transaction_get_operations (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *l;
|
|
GList *non_skipped = NULL;
|
|
|
|
non_skipped = NULL;
|
|
for (l = priv->ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
if (!op->skip)
|
|
non_skipped = g_list_prepend (non_skipped, g_object_ref (op));
|
|
}
|
|
return g_list_reverse (non_skipped);
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_get_current_operation:
|
|
* @self: a #FlatpakTransaction
|
|
*
|
|
* Gets the current operation.
|
|
*
|
|
* Returns: (transfer full): the current #FlatpakTransactionOperation
|
|
*/
|
|
FlatpakTransactionOperation *
|
|
flatpak_transaction_get_current_operation (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
if (priv->current_op)
|
|
return g_object_ref (priv->current_op);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_get_installation:
|
|
* @self: a #FlatpakTransactionOperation
|
|
*
|
|
* Gets the installation this transaction was created for.
|
|
*
|
|
* Returns: (transfer full): a #FlatpakInstallation
|
|
*/
|
|
FlatpakInstallation *
|
|
flatpak_transaction_get_installation (FlatpakTransaction *self)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
|
|
return g_object_ref (priv->installation);
|
|
}
|
|
|
|
static gboolean
|
|
remote_is_already_configured (FlatpakTransaction *self,
|
|
const char *url,
|
|
const char *collection_id)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *old_remote = NULL;
|
|
|
|
old_remote = flatpak_dir_find_remote_by_uri (priv->dir, url, collection_id);
|
|
|
|
/* Note: we don't check priv->extra_dependency_dirs because the transaction
|
|
* can only operate on one installation so any install/update ops need to
|
|
* have a remote there. */
|
|
|
|
return old_remote != NULL;
|
|
}
|
|
|
|
static gboolean
|
|
handle_suggested_remote_name (FlatpakTransaction *self, GKeyFile *keyfile, GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *suggested_name = NULL;
|
|
g_autofree char *name = NULL;
|
|
g_autofree char *url = NULL;
|
|
g_autofree char *collection_id = NULL;
|
|
g_autoptr(GKeyFile) config = NULL;
|
|
g_autoptr(GBytes) gpg_key = NULL;
|
|
gboolean res;
|
|
|
|
suggested_name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
|
FLATPAK_REF_SUGGEST_REMOTE_NAME_KEY, NULL);
|
|
if (suggested_name == NULL)
|
|
return TRUE;
|
|
|
|
name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_NAME_KEY, NULL);
|
|
if (name == NULL)
|
|
return TRUE;
|
|
|
|
url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_URL_KEY, NULL);
|
|
if (url == NULL)
|
|
return TRUE;
|
|
|
|
collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_DEPLOY_COLLECTION_ID_KEY, NULL);
|
|
if (collection_id == NULL || *collection_id == '\0')
|
|
collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_COLLECTION_ID_KEY, NULL);
|
|
|
|
if (remote_is_already_configured (self, url, collection_id))
|
|
return TRUE;
|
|
|
|
/* The name is already used, ignore */
|
|
if (ostree_repo_remote_get_url (flatpak_dir_get_repo (priv->dir), suggested_name, NULL, NULL))
|
|
return TRUE;
|
|
|
|
res = FALSE;
|
|
g_signal_emit (self, signals[ADD_NEW_REMOTE], 0, FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO,
|
|
name, suggested_name, url, &res);
|
|
if (res)
|
|
{
|
|
config = flatpak_parse_repofile (suggested_name, TRUE, keyfile, &gpg_key, NULL, error);
|
|
if (config == NULL)
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_modify_remote (priv->dir, suggested_name, config, gpg_key, NULL, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
|
|
return FALSE;
|
|
|
|
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
handle_runtime_repo_deps (FlatpakTransaction *self,
|
|
const char *id,
|
|
const char *dep_url,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autoptr(GBytes) dep_data = NULL;
|
|
g_autofree char *runtime_url = NULL;
|
|
g_autofree char *new_remote = NULL;
|
|
g_autofree char *basename = NULL;
|
|
g_autoptr(SoupURI) uri = NULL;
|
|
g_auto(GStrv) remotes = NULL;
|
|
g_autoptr(GKeyFile) config = NULL;
|
|
g_autoptr(GKeyFile) dep_keyfile = g_key_file_new ();
|
|
g_autoptr(GBytes) gpg_key = NULL;
|
|
g_autofree char *group = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
g_autofree char *runtime_collection_id = NULL;
|
|
g_autoptr(SoupSession) soup_session = NULL;
|
|
char *t;
|
|
int i;
|
|
gboolean res;
|
|
|
|
if (priv->disable_deps)
|
|
return TRUE;
|
|
|
|
if (!g_str_has_prefix (dep_url, "http:") && !g_str_has_prefix (dep_url, "https:"))
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Flatpakrepo URL %s not HTTP or HTTPS"), dep_url);
|
|
|
|
soup_session = flatpak_create_soup_session (PACKAGE_STRING);
|
|
dep_data = flatpak_load_http_uri (soup_session, dep_url, 0, NULL, NULL, cancellable, error);
|
|
if (dep_data == NULL)
|
|
{
|
|
g_prefix_error (error, "Can't load dependent file %s", dep_url);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_key_file_load_from_data (dep_keyfile,
|
|
g_bytes_get_data (dep_data, NULL),
|
|
g_bytes_get_size (dep_data),
|
|
0, &local_error))
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid .flatpakrepo: %s"), local_error->message);
|
|
|
|
uri = soup_uri_new (dep_url);
|
|
basename = g_path_get_basename (soup_uri_get_path (uri));
|
|
/* Strip suffix */
|
|
t = strchr (basename, '.');
|
|
if (t != NULL)
|
|
*t = 0;
|
|
|
|
/* Find a free remote name */
|
|
remotes = flatpak_dir_list_remotes (priv->dir, NULL, NULL);
|
|
i = 0;
|
|
do
|
|
{
|
|
g_clear_pointer (&new_remote, g_free);
|
|
|
|
if (i == 0)
|
|
new_remote = g_strdup (basename);
|
|
else
|
|
new_remote = g_strdup_printf ("%s-%d", basename, i);
|
|
i++;
|
|
}
|
|
while (remotes != NULL && g_strv_contains ((const char * const *) remotes, new_remote));
|
|
|
|
config = flatpak_parse_repofile (new_remote, FALSE, dep_keyfile, &gpg_key, NULL, error);
|
|
if (config == NULL)
|
|
{
|
|
g_prefix_error (error, "Can't parse dependent file %s: ", dep_url);
|
|
return FALSE;
|
|
}
|
|
|
|
/* See if it already exists */
|
|
group = g_strdup_printf ("remote \"%s\"", new_remote);
|
|
runtime_url = g_key_file_get_string (config, group, "url", NULL);
|
|
g_assert (runtime_url != NULL);
|
|
runtime_collection_id = g_key_file_get_string (config, group, "collection-id", NULL);
|
|
|
|
if (remote_is_already_configured (self, runtime_url, runtime_collection_id))
|
|
return TRUE;
|
|
|
|
res = FALSE;
|
|
g_signal_emit (self, signals[ADD_NEW_REMOTE], 0, FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS,
|
|
id, new_remote, runtime_url, &res);
|
|
if (res)
|
|
{
|
|
if (!flatpak_dir_modify_remote (priv->dir, new_remote, config, gpg_key, NULL, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
|
|
return FALSE;
|
|
|
|
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
handle_runtime_repo_deps_from_keyfile (FlatpakTransaction *self,
|
|
GKeyFile *keyfile,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *dep_url = NULL;
|
|
g_autofree char *name = NULL;
|
|
|
|
if (priv->disable_deps)
|
|
return TRUE;
|
|
|
|
dep_url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
|
FLATPAK_REF_RUNTIME_REPO_KEY, NULL);
|
|
if (dep_url == NULL)
|
|
return TRUE;
|
|
|
|
name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, FLATPAK_REF_NAME_KEY, NULL);
|
|
if (name == NULL)
|
|
return TRUE;
|
|
|
|
return handle_runtime_repo_deps (self, name, dep_url, cancellable, error);
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_resolve_flatpakrefs (FlatpakTransaction *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *l;
|
|
|
|
for (l = priv->flatpakrefs; l != NULL; l = l->next)
|
|
{
|
|
GKeyFile *flatpakref = l->data;
|
|
g_autofree char *remote = NULL;
|
|
g_autofree char *ref = NULL;
|
|
|
|
/* Handle this before the runtime deps, because they might be the same */
|
|
if (!handle_suggested_remote_name (self, flatpakref, error))
|
|
return FALSE;
|
|
|
|
if (!handle_runtime_repo_deps_from_keyfile (self, flatpakref, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_create_remote_for_ref_file (priv->dir, flatpakref, priv->default_arch,
|
|
&remote, NULL, &ref, error))
|
|
return FALSE;
|
|
|
|
/* Need to pick up the new config, in case it was applied in the system helper. */
|
|
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
|
|
return FALSE;
|
|
|
|
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
|
|
|
|
if (!flatpak_transaction_add_install (self, remote, ref, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
handle_runtime_repo_deps_from_bundle (FlatpakTransaction *self,
|
|
GFile *file,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
g_autofree char *dep_url = NULL;
|
|
g_autofree char *ref = NULL;
|
|
g_auto(GStrv) ref_parts = NULL;
|
|
g_autoptr(GVariant) metadata = NULL;
|
|
|
|
if (priv->disable_deps)
|
|
return TRUE;
|
|
|
|
metadata = flatpak_bundle_load (file,
|
|
NULL,
|
|
&ref,
|
|
NULL,
|
|
&dep_url,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (metadata == NULL || dep_url == NULL || ref == NULL)
|
|
return TRUE;
|
|
|
|
ref_parts = g_strsplit (ref, "/", -1);
|
|
|
|
return handle_runtime_repo_deps (self, ref_parts[1], dep_url, cancellable, error);
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_resolve_bundles (FlatpakTransaction *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *l;
|
|
|
|
for (l = priv->bundles; l != NULL; l = l->next)
|
|
{
|
|
BundleData *data = l->data;
|
|
g_autofree char *remote = NULL;
|
|
g_autofree char *ref = NULL;
|
|
g_autofree char *commit = NULL;
|
|
g_autofree char *metadata = NULL;
|
|
gboolean created_remote;
|
|
|
|
if (!handle_runtime_repo_deps_from_bundle (self, data->file, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_ensure_repo (priv->dir, cancellable, error))
|
|
return FALSE;
|
|
|
|
remote = flatpak_dir_ensure_bundle_remote (priv->dir, data->file, data->gpg_data,
|
|
&ref, &commit, &metadata, &created_remote,
|
|
NULL, error);
|
|
if (remote == NULL)
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_recreate_repo (priv->dir, NULL, error))
|
|
return FALSE;
|
|
|
|
flatpak_installation_drop_caches (priv->installation, NULL, NULL);
|
|
|
|
if (!flatpak_transaction_add_ref (self, remote, ref, NULL, NULL, commit,
|
|
FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE,
|
|
data->file, metadata, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* flatpak_transaction_run:
|
|
* @transaction: a #FlatpakTransaction
|
|
* @cancellable: (nullable): a #GCancellable
|
|
* @error: return location for an error
|
|
*
|
|
* Executes the transaction.
|
|
*
|
|
* During the cause of the execution, various signals will get emitted.
|
|
* The FlatpakTransaction::choose-remote-for-ref and
|
|
* #FlatpakTransaction::add-new-remote signals may get emitted while
|
|
* resolving operations. #FlatpakTransaction::ready is emitted when
|
|
* the transaction has been fully resolved, and #FlatpakTransaction::new-operation
|
|
* and #FlatpakTransaction::operation-done are emitted while the operations
|
|
* are carried out. If an error occurs at any point during the execution,
|
|
* #FlatpakTransaction::operation-error is emitted.
|
|
*
|
|
* Note that this call blocks until the transaction is done.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE if an error occurred
|
|
*/
|
|
gboolean
|
|
flatpak_transaction_run (FlatpakTransaction *transaction,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
return FLATPAK_TRANSACTION_GET_CLASS (transaction)->run (transaction, cancellable, error);
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_transaction_real_run (FlatpakTransaction *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self);
|
|
GList *l, *next;
|
|
gboolean succeeded = TRUE;
|
|
gboolean needs_prune = FALSE;
|
|
gboolean needs_triggers = FALSE;
|
|
g_autoptr(GMainContextPopDefault) main_context = NULL;
|
|
gboolean ready_res = FALSE;
|
|
int i;
|
|
|
|
if (!priv->can_run)
|
|
return flatpak_fail (error, _("Transaction already executed"));
|
|
|
|
priv->can_run = FALSE;
|
|
|
|
priv->current_op = NULL;
|
|
|
|
if (flatpak_dir_is_user (priv->dir) && getuid () == 0)
|
|
{
|
|
struct stat st_buf;
|
|
g_autofree char *dir_path = NULL;
|
|
|
|
/* Check that it's not root's own user installation */
|
|
dir_path = g_file_get_path (flatpak_dir_get_path (priv->dir));
|
|
if (stat (dir_path, &st_buf) == 0 && st_buf.st_uid != 0)
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_WRONG_USER,
|
|
_("Refusing to operate on a user installation as root! "
|
|
"This can lead to incorrect file ownership and permission errors."));
|
|
}
|
|
|
|
if (!priv->no_pull &&
|
|
!flatpak_transaction_update_metadata (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
/* Work around ostree-pull spinning the default main context for the sync calls */
|
|
main_context = flatpak_main_context_new_default ();
|
|
|
|
if (!flatpak_transaction_resolve_flatpakrefs (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_transaction_resolve_bundles (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
/* Resolve initial ops */
|
|
if (!resolve_all_ops (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
/* Add all app -> runtime dependencies */
|
|
for (l = priv->ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
|
|
if (!add_deps (self, op, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Resolve new ops */
|
|
if (!resolve_all_ops (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
/* Add all related extensions */
|
|
for (l = priv->ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
|
|
if (!add_related (self, op, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Resolve new ops */
|
|
if (!resolve_all_ops (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
sort_ops (self);
|
|
|
|
/* Ensure the operation kind is normalized and not no-op */
|
|
for (l = priv->ops; l != NULL; l = next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
next = l->next;
|
|
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE)
|
|
{
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
|
|
if (dir_ref_is_installed (priv->dir, op->ref, NULL, &deploy_data))
|
|
{
|
|
/* Don't use the remote from related ref on update, always use
|
|
the current remote. */
|
|
g_free (op->remote);
|
|
op->remote = g_strdup (flatpak_deploy_data_get_origin (deploy_data));
|
|
|
|
op->kind = FLATPAK_TRANSACTION_OPERATION_UPDATE;
|
|
}
|
|
else
|
|
op->kind = FLATPAK_TRANSACTION_OPERATION_INSTALL;
|
|
}
|
|
|
|
if (op->kind == FLATPAK_TRANSACTION_OPERATION_UPDATE &&
|
|
!flatpak_dir_needs_update_for_commit_and_subpaths (priv->dir, op->remote, op->ref, op->resolved_commit,
|
|
(const char **) op->subpaths))
|
|
{
|
|
/* If this is a rebase, then at minimum a redeploy needs to happen */
|
|
if (op->previous_ids)
|
|
op->update_only_deploy = TRUE;
|
|
else
|
|
op->skip = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
g_signal_emit (self, signals[READY], 0, &ready_res);
|
|
if (!ready_res)
|
|
return flatpak_fail_error (error, FLATPAK_ERROR_ABORTED, _("Aborted by user"));
|
|
|
|
for (l = priv->ops; l != NULL; l = l->next)
|
|
{
|
|
FlatpakTransactionOperation *op = l->data;
|
|
g_autoptr(GError) local_error = NULL;
|
|
gboolean res = TRUE;
|
|
const char *pref;
|
|
FlatpakTransactionOperationType kind;
|
|
g_autoptr(FlatpakRemoteState) state = NULL;
|
|
|
|
if (op->skip)
|
|
continue;
|
|
|
|
priv->current_op = op;
|
|
|
|
kind = op->kind;
|
|
pref = strchr (op->ref, '/') + 1;
|
|
|
|
if (op->fail_if_op_fails && (op->fail_if_op_fails->failed) &&
|
|
/* Allow installing an app if the runtime failed to update (i.e. is installed) because
|
|
* the app should still run, and otherwise you could never install the app until the runtime
|
|
* remote is fixed. */
|
|
!(op->fail_if_op_fails->kind == FLATPAK_TRANSACTION_OPERATION_UPDATE && g_str_has_prefix (op->ref, "app/")))
|
|
{
|
|
flatpak_fail_error (&local_error, FLATPAK_ERROR_SKIPPED,
|
|
_("Skipping %s due to previous error"), pref);
|
|
res = FALSE;
|
|
}
|
|
else if ((state = flatpak_transaction_ensure_remote_state (self, op->kind, op->remote, &local_error)) == NULL)
|
|
{
|
|
res = FALSE;
|
|
}
|
|
else if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL)
|
|
{
|
|
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
|
|
|
|
emit_new_op (self, op, progress);
|
|
|
|
g_assert (op->resolved_commit != NULL); /* We resolved this before */
|
|
|
|
if (op->resolved_metakey && !flatpak_check_required_version (op->ref, op->resolved_metakey, &local_error))
|
|
res = FALSE;
|
|
else
|
|
res = flatpak_dir_install (priv->dir,
|
|
priv->no_pull,
|
|
priv->no_deploy,
|
|
priv->disable_static_deltas,
|
|
priv->reinstall,
|
|
priv->max_op >= APP_UPDATE,
|
|
state, op->ref, op->resolved_commit,
|
|
(const char **) op->subpaths,
|
|
(const char **) op->previous_ids,
|
|
progress->ostree_progress,
|
|
cancellable, &local_error);
|
|
|
|
flatpak_transaction_progress_done (progress);
|
|
if (res)
|
|
{
|
|
emit_op_done (self, op, 0);
|
|
|
|
/* Normally we don't need to prune after install, because it makes no old objects
|
|
stale. However if we reinstall, that is not true. */
|
|
if (!priv->no_pull && priv->reinstall)
|
|
needs_prune = TRUE;
|
|
|
|
if (g_str_has_prefix (op->ref, "app"))
|
|
needs_triggers = TRUE;
|
|
}
|
|
}
|
|
else if (kind == FLATPAK_TRANSACTION_OPERATION_UPDATE)
|
|
{
|
|
g_assert (op->resolved_commit != NULL); /* We resolved this before */
|
|
|
|
if (flatpak_dir_needs_update_for_commit_and_subpaths (priv->dir, op->remote, op->ref, op->resolved_commit,
|
|
(const char **) op->subpaths))
|
|
{
|
|
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
|
|
FlatpakTransactionResult result_details = 0;
|
|
|
|
emit_new_op (self, op, progress);
|
|
|
|
if (op->resolved_metakey && !flatpak_check_required_version (op->ref, op->resolved_metakey, &local_error))
|
|
res = FALSE;
|
|
else if (op->update_only_deploy)
|
|
res = flatpak_dir_deploy_update (priv->dir, op->ref, op->resolved_commit,
|
|
(const char **) op->subpaths,
|
|
(const char **) op->previous_ids,
|
|
cancellable, &local_error);
|
|
else
|
|
res = flatpak_dir_update (priv->dir,
|
|
priv->no_pull,
|
|
priv->no_deploy,
|
|
priv->disable_static_deltas,
|
|
op->commit != NULL, /* Allow downgrade if we specify commit */
|
|
priv->max_op >= APP_UPDATE,
|
|
priv->max_op == APP_INSTALL || priv->max_op == RUNTIME_INSTALL,
|
|
state, op->ref, op->resolved_commit,
|
|
NULL,
|
|
(const char **) op->subpaths,
|
|
(const char **) op->previous_ids,
|
|
progress->ostree_progress,
|
|
cancellable, &local_error);
|
|
flatpak_transaction_progress_done (progress);
|
|
|
|
/* Handle noop-updates */
|
|
if (!res && g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
|
|
{
|
|
res = TRUE;
|
|
g_clear_error (&local_error);
|
|
|
|
result_details |= FLATPAK_TRANSACTION_RESULT_NO_CHANGE;
|
|
}
|
|
|
|
if (res)
|
|
{
|
|
emit_op_done (self, op, result_details);
|
|
|
|
if (!priv->no_pull)
|
|
needs_prune = TRUE;
|
|
|
|
if (g_str_has_prefix (op->ref, "app"))
|
|
needs_triggers = TRUE;
|
|
}
|
|
}
|
|
else
|
|
g_debug ("%s need no update", op->ref);
|
|
}
|
|
else if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
|
|
{
|
|
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
|
|
emit_new_op (self, op, progress);
|
|
if (op->resolved_metakey && !flatpak_check_required_version (op->ref, op->resolved_metakey, &local_error))
|
|
res = FALSE;
|
|
else
|
|
res = flatpak_dir_install_bundle (priv->dir, op->bundle,
|
|
op->remote, NULL,
|
|
cancellable, &local_error);
|
|
flatpak_transaction_progress_done (progress);
|
|
|
|
if (res)
|
|
{
|
|
emit_op_done (self, op, 0);
|
|
needs_prune = TRUE;
|
|
needs_triggers = TRUE;
|
|
}
|
|
}
|
|
else if (kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
|
|
{
|
|
g_autoptr(FlatpakTransactionProgress) progress = flatpak_transaction_progress_new ();
|
|
FlatpakHelperUninstallFlags flags = 0;
|
|
|
|
if (priv->disable_prune)
|
|
flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF;
|
|
|
|
if (priv->force_uninstall)
|
|
flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE;
|
|
|
|
emit_new_op (self, op, progress);
|
|
|
|
res = flatpak_dir_uninstall (priv->dir, op->ref, flags,
|
|
cancellable, &local_error);
|
|
|
|
flatpak_transaction_progress_done (progress);
|
|
|
|
if (res)
|
|
{
|
|
emit_op_done (self, op, 0);
|
|
needs_prune = TRUE;
|
|
|
|
if (g_str_has_prefix (op->ref, "app"))
|
|
needs_triggers = TRUE;
|
|
}
|
|
}
|
|
else
|
|
g_assert_not_reached ();
|
|
|
|
if (res)
|
|
{
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
deploy_data = flatpak_dir_get_deploy_data (priv->dir, op->ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
|
|
|
|
if (deploy_data)
|
|
{
|
|
const char *eol = flatpak_deploy_data_get_eol (deploy_data);
|
|
const char *eol_rebase = flatpak_deploy_data_get_eol_rebase (deploy_data);
|
|
|
|
if (eol || eol_rebase)
|
|
g_signal_emit (self, signals[END_OF_LIFED], 0,
|
|
op->ref, eol, eol_rebase);
|
|
}
|
|
}
|
|
|
|
if (!res)
|
|
{
|
|
gboolean do_cont = FALSE;
|
|
FlatpakTransactionErrorDetails error_details = 0;
|
|
|
|
op->failed = TRUE;
|
|
|
|
if (op->non_fatal)
|
|
error_details |= FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL;
|
|
|
|
g_signal_emit (self, signals[OPERATION_ERROR], 0, op,
|
|
local_error, error_details,
|
|
&do_cont);
|
|
|
|
if (!do_cont)
|
|
{
|
|
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
|
{
|
|
succeeded = FALSE;
|
|
break;
|
|
}
|
|
|
|
flatpak_fail_error (error, FLATPAK_ERROR_ABORTED, _("Aborted due to failure"));
|
|
succeeded = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
priv->current_op = NULL;
|
|
|
|
if (needs_triggers)
|
|
flatpak_dir_run_triggers (priv->dir, cancellable, NULL);
|
|
|
|
if (needs_prune && !priv->disable_prune)
|
|
flatpak_dir_prune (priv->dir, cancellable, NULL);
|
|
|
|
for (i = 0; i < priv->added_origin_remotes->len; i++)
|
|
flatpak_dir_prune_origin_remote (priv->dir, g_ptr_array_index (priv->added_origin_remotes, i));
|
|
|
|
return succeeded;
|
|
}
|