flatpak_dir_pull (FlatpakDir                           *self,
                  FlatpakRemoteState                   *state,
                  const char                           *ref,
                  const char                           *opt_rev,
                  const OstreeRepoFinderResult * const *opt_results,
                  const char                          **subpaths,
                  OstreeRepo                           *repo,
                  FlatpakPullFlags                      flatpak_flags,
                  OstreeRepoPullFlags                   flags,
                  OstreeAsyncProgress                  *progress,
                  GCancellable                         *cancellable,
                  GError                              **error)
{
  gboolean ret = FALSE;
  g_autofree char *rev = NULL;
  g_autofree char *url = NULL;

  g_autoptr(GPtrArray) subdirs_arg = NULL;
  g_auto(OstreeRepoFinderResultv) allocated_results = NULL;
  const OstreeRepoFinderResult * const *results;
  g_auto(GLnxLockFile) lock = { 0, };
  g_autofree char *name = NULL;
  g_autofree char *remote_and_branch = NULL;
  g_autofree char *current_checksum = NULL;

  /* If @opt_results is set, @opt_rev must be. */
  g_return_val_if_fail (opt_results == NULL || opt_rev != NULL, FALSE);

  if (!flatpak_dir_ensure_repo (self, cancellable, error))
    return FALSE;

  /* Keep a shared repo lock to avoid prunes removing objects we're relying on
   * while we do the pull. There are two cases we protect against. 1) objects we
   * need but that we already decided are locally available could be removed,
   * and 2) during the transaction commit objects that don't yet have a ref to
   * them could be considered unreachable.
   */
  if (repo == NULL && !flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
    return FALSE;

  if (flatpak_dir_get_remote_oci (self, state->remote_name))
    return flatpak_dir_pull_oci (self, state, ref, repo, flatpak_flags,
                                 flags, progress, cancellable, error);

  if (!ostree_repo_remote_get_url (self->repo,
                                   state->remote_name,
                                   &url,
                                   error))
    return FALSE;

  if (*url == 0)
    return TRUE; /* Empty url, silently disables updates */

  g_assert (progress != NULL);

  /* We get the rev ahead of time so that we know it for looking up e.g. extra-data
     and to make sure we're atomically using a single rev if we happen to do multiple
     pulls (e.g. with subpaths) */
  if (opt_rev != NULL)
    {
      rev = g_strdup (opt_rev);
      results = opt_results;
    }
  else
    {
      if (state->collection_id)
        {
          GVariantBuilder find_builder;
          g_autoptr(GVariant) find_options = NULL;
          g_autoptr(GAsyncResult) find_result = NULL;
          OstreeCollectionRef collection_ref;
          OstreeCollectionRef *collection_refs_to_fetch[2];
          gboolean force_disable_deltas = (flatpak_flags & FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS) != 0;
          guint update_freq = 0;
          gsize i;
          g_autoptr(GMainContextPopDefault) context = NULL;

          g_variant_builder_init (&find_builder, G_VARIANT_TYPE ("a{sv}"));

          if (force_disable_deltas)
            {
              g_variant_builder_add (&find_builder, "{s@v}", "disable-static-deltas",
                                     g_variant_new_variant (g_variant_new_boolean (TRUE)));
            }

          collection_ref.collection_id = state->collection_id;
          collection_ref.ref_name = (char *) ref;

          collection_refs_to_fetch[0] = &collection_ref;
          collection_refs_to_fetch[1] = NULL;

          if (progress != NULL)
            update_freq = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (progress), "update-frequency"));
          if (update_freq == 0)
            update_freq = FLATPAK_DEFAULT_UPDATE_FREQUENCY;

          g_variant_builder_add (&find_builder, "{s@v}", "update-frequency",
                                 g_variant_new_variant (g_variant_new_uint32 (update_freq)));

          find_options = g_variant_ref_sink (g_variant_builder_end (&find_builder));

          context = flatpak_main_context_new_default ();

          ostree_repo_find_remotes_async (self->repo, (const OstreeCollectionRef * const *) collection_refs_to_fetch,
                                          find_options,
                                          NULL /* default finders */, progress, cancellable,
                                          async_result_cb, &find_result);

          while (find_result == NULL)
            g_main_context_iteration (context, TRUE);

          allocated_results = ostree_repo_find_remotes_finish (self->repo, find_result, error);

          results = (const OstreeRepoFinderResult * const *) allocated_results;
          if (results == NULL)
            return FALSE;

          for (i = 0, rev = NULL; results[i] != NULL && rev == NULL; i++)
            rev = g_strdup (g_hash_table_lookup (results[i]->ref_to_checksum, &collection_ref));

          if (rev == NULL)
            return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No such ref (%s, %s) in remote %s or elsewhere"),
                                 collection_ref.collection_id, collection_ref.ref_name, state->remote_name);
        }
      else
        {
          flatpak_remote_state_lookup_ref (state, ref, &rev, NULL, error);
          if (rev == NULL && error != NULL && *error == NULL)
            flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Couldn't find latest checksum for ref %s in remote %s"),
                          ref, state->remote_name);

          results = NULL;
        }

      if (rev == NULL)
        {
          g_assert (error == NULL || *error != NULL);
          return FALSE;
        }
    }

  if (repo == NULL)
    repo = self->repo;

  /* Past this we must use goto out, so we clean up console and
     abort the transaction on error */

  if (subpaths != NULL && subpaths[0] != NULL)
    {
      subdirs_arg = g_ptr_array_new_with_free_func (g_free);
      int i;
      g_ptr_array_add (subdirs_arg, g_strdup ("/metadata"));
      for (i = 0; subpaths[i] != NULL; i++)
        g_ptr_array_add (subdirs_arg,
                         g_build_filename ("/files", subpaths[i], NULL));
      g_ptr_array_add (subdirs_arg, NULL);
    }

  if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
    goto out;

  /* Setup extra data information before starting to pull, so we can have precise
   * progress reports */
  if (!flatpak_dir_setup_extra_data (self, repo, state->remote_name,
                                     ref, rev, results,
                                     flatpak_flags,
                                     progress,
                                     cancellable,
                                     error))
    goto out;

  remote_and_branch = g_strdup_printf ("%s:%s", state->remote_name, ref);
  ostree_repo_resolve_rev (repo, remote_and_branch, TRUE, &current_checksum, NULL);

  if (!repo_pull (repo, state->remote_name,
                  subdirs_arg ? (const char **) subdirs_arg->pdata : NULL,
                  ref, rev, results, flatpak_flags, flags,
                  progress,
                  cancellable, error))
    {
      g_prefix_error (error, _("While pulling %s from remote %s: "), ref, state->remote_name);
      goto out;
    }

  if (!flatpak_dir_pull_extra_data (self, repo,
                                    state->remote_name,
                                    ref, rev,
                                    flatpak_flags,
                                    progress,
                                    cancellable,
                                    error))
    goto out;


  if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
    goto out;

  ret = TRUE;

  if (repo == self->repo)
    name = flatpak_dir_get_name (self);
  else
    {
      GFile *file = ostree_repo_get_path (repo);
      name = g_file_get_path (file);
    }

  (flatpak_dir_log) (self, __FILE__, __LINE__, __FUNCTION__, name,
                     "pull", state->remote_name, ref, rev, current_checksum, NULL,
                      "Pulled %s from %s", ref, state->remote_name);

out:
  if (!ret)
    ostree_repo_abort_transaction (repo, cancellable, NULL);

  if (progress)
    ostree_async_progress_finish (progress);

  return ret;
}