installation: Reimplement flatpak_installation_list_installed_refs_for_update

Instead of doing a lot of FlatpakInstallation calls we do lower level
FlatpakDir calls, sharing a single RemoteState per remote for the
entire operation. Also, some parts of the checks are moved to FlatpakDir
as flatpak_dir_check_if_installed_ref_needs_update()
This commit is contained in:
Alexander Larsson 2020-03-25 08:27:18 +01:00
parent 102c710b39
commit c092fa4cb7
3 changed files with 155 additions and 125 deletions

View File

@ -702,6 +702,11 @@ gboolean flatpak_dir_needs_update_for_commit_and_subpaths (FlatpakDir *self,
const char *ref,
const char *target_commit,
const char **opt_subpaths);
gboolean flatpak_dir_check_if_installed_ref_needs_update (FlatpakDir *self,
FlatpakRemoteState *state,
const char *ref,
GBytes *deploy_data,
GCancellable *cancellable);
char * flatpak_dir_check_for_update (FlatpakDir *self,
FlatpakRemoteState *state,
const char *ref,

View File

@ -8997,6 +8997,81 @@ flatpak_dir_needs_update_for_commit_and_subpaths (FlatpakDir *self,
return FALSE;
}
/* This returns true if the remote version of the installed version is
* newer than the installed, or if some related ref it relies on is
* missing (which will be fixed by an update run of FlatpakTransaction). */
gboolean
flatpak_dir_check_if_installed_ref_needs_update (FlatpakDir *self,
FlatpakRemoteState *state,
const char *ref,
GBytes *deploy_data,
GCancellable *cancellable)
{
g_autofree char *latest_commit = NULL;
guint64 latest_timestamp;
guint64 current_timestamp;
g_autoptr(GFile) deploy_dir = NULL;
const gchar *current_commit = NULL;
if (flatpak_dir_ref_is_masked (self, ref))
return FALSE;
current_commit = flatpak_deploy_data_get_commit (deploy_data);
current_timestamp = flatpak_deploy_data_get_timestamp (deploy_data);
if (!flatpak_remote_state_lookup_ref (state, ref, &latest_commit, &latest_timestamp, NULL, NULL, NULL))
return FALSE;
/* Check if the latest is newer than the current installed, if so update */
if (current_timestamp == 0)
{
/* This happens during deploy data updates, fall back to commit comparisons */
if (strcmp (current_commit, latest_commit) != 0)
return TRUE;
}
else
{
if (latest_timestamp > current_timestamp ||
(latest_timestamp == current_timestamp &&
strcmp (current_commit, latest_commit) != 0))
return TRUE;
}
/* The ref itself doesn't need update, but we do some extra checks
* for related refs that can trigger an update. */
/* Check if all "should-download" related refs for the ref are installed.
* If not, add the ref in @updates array so that it can be installed via
* FlatpakTransaction's update-op.
*
* This makes sure that the ref (maybe an app or runtime) remains in usable
* state and fixes itself through an update.
*/
if (flatpak_dir_check_installed_ref_missing_related_ref (self, state, ref, cancellable))
return TRUE;
/* This checks if an already installed app has a missing runtime.
* If so, return that installed ref in the updates list, so that FlatpakTransaction
* can resolve one of its operation to install the runtime instead.
*
* Runtime of an app can go missing if an app upgrade makes an app dependent on a new runtime
* entirely. We had couple of cases like that in the past, for example, before it was updated
* to use FlatpakTransaction, updating an app in GNOME Software to a version which needs a
* different runtime would not install that new runtime, leaving the app unusable.
*/
if (g_str_has_prefix (ref, "app/"))
{
const gchar *runtime = flatpak_deploy_data_get_runtime (deploy_data);
g_autofree gchar *full_runtime_ref = g_strconcat ("runtime/", runtime, NULL);
deploy_dir = flatpak_dir_get_if_deployed (self, full_runtime_ref, NULL, cancellable);
if (deploy_dir == NULL)
return TRUE;
}
return FALSE;
}
/* This is called by the old-school non-transaction flatpak_installation_update, so doesn't do a lot. */
char *
flatpak_dir_check_for_update (FlatpakDir *self,
FlatpakRemoteState *state,

View File

@ -992,6 +992,29 @@ async_result_cb (GObject *obj,
*result_out = g_object_ref (result);
}
static gboolean
ref_check_for_update (FlatpakDir *dir,
const char *ref,
GHashTable *remote_states,
GCancellable *cancellable)
{
g_autoptr(GBytes) deploy_data = NULL;
FlatpakRemoteState *state;
const char *origin = NULL;
deploy_data = flatpak_dir_get_deploy_data (dir, ref, FLATPAK_DEPLOY_VERSION_CURRENT, cancellable, NULL);
if (deploy_data == NULL)
return FALSE;
origin = flatpak_deploy_data_get_origin (deploy_data);
state = g_hash_table_lookup (remote_states, origin);
if (state == NULL)
return FALSE;
return flatpak_dir_check_if_installed_ref_needs_update (dir, state, ref, deploy_data, cancellable);
}
/**
* flatpak_installation_list_installed_refs_for_update:
* @self: a #FlatpakInstallation
@ -1016,146 +1039,73 @@ flatpak_installation_list_installed_refs_for_update (FlatpakInstallation *self,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GPtrArray) updates = NULL; /* (element-type FlatpakInstalledRef) */
g_autoptr(GPtrArray) installed = NULL; /* (element-type FlatpakInstalledRef) */
g_autoptr(GPtrArray) remotes = NULL; /* (element-type FlatpakRemote) */
g_autoptr(GHashTable) remote_commits = NULL; /* (element-type utf8 utf8) */
g_autoptr(GHashTable) remote_states = NULL; /* (element-type utf8 FlatpakRemoteState) */
int i, j;
g_autoptr(FlatpakDir) dir_orig = flatpak_installation_get_dir_maybe_no_repo (self);
g_autoptr(FlatpakDir) dir = NULL;
g_auto(GStrv) remote_names = NULL;
g_autoptr(GPtrArray) updates = NULL; /* (element-type FlatpakInstalledRef) */
g_autoptr(GHashTable) remote_states = NULL; /* (element-type utf8 FlatpakRemoteState) */
g_auto(GStrv) refs_app = NULL;
g_auto(GStrv) refs_runtime = NULL;
remote_commits = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
remotes = flatpak_installation_list_remotes (self, cancellable, error);
if (remotes == NULL)
/* We clone the dir here to make sure we re-read the latest ostree repo config, in case
it has local changes */
dir = flatpak_dir_clone (dir_orig);
if (!flatpak_dir_maybe_ensure_repo (dir, cancellable, error))
return NULL;
for (i = 0; i < remotes->len; i++)
{
FlatpakRemote *remote = g_ptr_array_index (remotes, i);
g_autoptr(GPtrArray) refs = NULL;
g_autoptr(GError) local_error = NULL;
const char *remote_name = flatpak_remote_get_name (remote);
remote_names = flatpak_dir_list_remotes (dir, cancellable, error);
if (remote_names == NULL)
return NULL;
if (flatpak_remote_get_disabled (remote))
remote_states = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)flatpak_remote_state_unref);
for (int i = 0; remote_names[i] != NULL; ++i)
{
const char *remote = remote_names[i];
g_autoptr(FlatpakRemoteState) state = NULL;
g_autoptr(GError) local_error = NULL;
if (flatpak_dir_get_remote_disabled (dir, remote))
continue;
/* We ignore errors here. we don't want one remote to fail us */
refs = flatpak_installation_list_remote_refs_sync (self,
remote_name,
cancellable, &local_error);
if (refs != NULL)
state = flatpak_dir_get_remote_state_optional (dir, remote, FALSE, NULL, &local_error);
if (state == NULL)
{
for (j = 0; j < refs->len; j++)
{
FlatpakRemoteRef *remote_ref = g_ptr_array_index (refs, j);
g_autofree char *full_ref = flatpak_ref_format_ref (FLATPAK_REF (remote_ref));
g_autofree char *key = g_strdup_printf ("%s:%s", remote_name, full_ref);
g_hash_table_insert (remote_commits, g_steal_pointer (&key),
g_strdup (flatpak_ref_get_commit (FLATPAK_REF (remote_ref))));
}
}
else
{
g_debug ("Update: Failed to read remote %s: %s",
flatpak_remote_get_name (remote),
local_error->message);
g_debug ("Update: Failed to read remote %s: %s", remote, local_error->message);
continue;
}
g_hash_table_insert (remote_states, (char *)remote, g_steal_pointer (&state));
}
installed = flatpak_installation_list_installed_refs (self, cancellable, error);
if (installed == NULL)
return NULL;
updates = g_ptr_array_new_with_free_func (g_object_unref);
dir = flatpak_installation_get_dir (self, error);
if (dir == NULL)
return NULL;
remote_states = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) flatpak_remote_state_unref);
for (i = 0; i < installed->len; i++)
if (flatpak_dir_list_refs (dir, "app", &refs_app, cancellable, error))
{
FlatpakRemoteState *state;
FlatpakInstalledRef *installed_ref = g_ptr_array_index (installed, i);
const char *remote_name = flatpak_installed_ref_get_origin (installed_ref);
g_autofree char *full_ref = flatpak_ref_format_ref (FLATPAK_REF (installed_ref));
g_autofree char *key = g_strdup_printf ("%s:%s", remote_name, full_ref);
const char *remote_commit = g_hash_table_lookup (remote_commits, key);
const char *local_commit = flatpak_installed_ref_get_latest_commit (installed_ref);
g_autoptr(GError) local_error = NULL;
for (int i = 0; refs_app[i] != NULL; i++)
{
const char *ref = refs_app[i];
if (ref_check_for_update (dir, ref, remote_states, cancellable))
{
g_printerr ("adding update %s\n", ref);
FlatpakInstalledRef *installed_ref = get_ref (dir, ref, cancellable, NULL);
if (installed_ref)
g_ptr_array_add (updates, g_object_ref (installed_ref));
}
}
}
if (flatpak_dir_ref_is_masked (dir, full_ref))
continue;
/* Note: local_commit may be NULL here */
if (remote_commit != NULL &&
g_strcmp0 (remote_commit, local_commit) != 0)
{
g_ptr_array_add (updates, g_object_ref (installed_ref));
/* Don't check further, as we already added the installed_ref to @updates. */
continue;
}
/* Check if all "should-download" related refs for the ref are installed.
* If not, add the ref in @updates array so that it can be installed via
* FlatpakTransaction's update-op.
*
* This makes sure that the ref (maybe an app or runtime) remains in usable
* state and fixes itself through an update.
*/
state = g_hash_table_lookup (remote_states, remote_name);
if (state == NULL)
{
state = flatpak_dir_get_remote_state_optional (dir, remote_name, FALSE, cancellable, &local_error);
if (state == NULL)
{
g_debug ("Update: Failed to get remote state for %s: %s",
remote_name, local_error->message);
continue;
}
g_hash_table_insert (remote_states, g_strdup (remote_name), state);
}
if (flatpak_dir_check_installed_ref_missing_related_ref (dir, state, full_ref, cancellable))
{
g_ptr_array_add (updates, g_object_ref (installed_ref));
/* Don't check for runtime, if we already added the installed_ref to @updates. */
continue;
}
if (flatpak_ref_get_kind (FLATPAK_REF (installed_ref)) == FLATPAK_REF_KIND_APP)
{
g_autoptr(GBytes) deploy_data = NULL;
/* This checks if an already installed app has a missing runtime.
* If so, return that installed ref in the updates list, so that FlatpakTransaction
* can resolve one of its operation to install the runtime instead.
*
* Runtime of an app can go missing if an app upgrade makes an app dependent on a new runtime
* entirely. We had couple of cases like that in the past, for example, before it was updated
* to use FlatpakTransaction, updating an app in GNOME Software to a version which needs a
* different runtime would not install that new runtime, leaving the app unusable.
*/
deploy_data = flatpak_dir_get_deploy_data (dir, full_ref, FLATPAK_DEPLOY_VERSION_CURRENT, cancellable, NULL);
if (deploy_data != NULL)
{
g_autoptr(GFile) deploy_dir = NULL;
const gchar *runtime = NULL;
g_autofree gchar *full_runtime_ref = NULL;
runtime = flatpak_deploy_data_get_runtime (deploy_data);
full_runtime_ref = g_strconcat ("runtime/", runtime, NULL);
deploy_dir = flatpak_dir_get_if_deployed (dir, full_runtime_ref, NULL, cancellable);
if (deploy_dir == NULL)
g_ptr_array_add (updates, g_object_ref (installed_ref));
}
}
if (flatpak_dir_list_refs (dir, "runtime", &refs_runtime, cancellable, error))
{
for (int i = 0; refs_runtime[i] != NULL; i++)
{
const char *ref = refs_runtime[i];
if (ref_check_for_update (dir, ref, remote_states, cancellable))
{
g_printerr ("adding update %s\n", ref);
FlatpakInstalledRef *installed_ref = get_ref (dir, ref, cancellable, NULL);
if (installed_ref)
g_ptr_array_add (updates, g_object_ref (installed_ref));
}
}
}
return g_steal_pointer (&updates);