Commit 2ad3446a authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧
Browse files

flatpak: Centralise remote integration in FlatpakBackend

* Ensure remotes are integrated as sources when an installation
transaction adds them
* Make sure we have a source available as soon as we know a remote is
present, so it can be used.

BUG: 443745
parent 222122cb
......@@ -150,6 +150,25 @@ private:
const QString m_appstreamIconsDir;
};
static void populateRemote(FlatpakRemote *remote, const QString &name, const QString &url, const QString &gpgKey)
{
flatpak_remote_set_url(remote, url.toUtf8().constData());
flatpak_remote_set_noenumerate(remote, false);
flatpak_remote_set_title(remote, name.toUtf8().constData());
if (!gpgKey.isEmpty()) {
gsize dataLen = 0;
g_autofree guchar *data = nullptr;
g_autoptr(GBytes) bytes = nullptr;
data = g_base64_decode(gpgKey.toUtf8().constData(), &dataLen);
bytes = g_bytes_new(data, dataLen);
flatpak_remote_set_gpg_verify(remote, true);
flatpak_remote_set_gpg_key(remote, bytes);
} else {
flatpak_remote_set_gpg_verify(remote, false);
}
}
QDebug operator<<(QDebug debug, const FlatpakResource::Id &id)
{
QDebugStateSaver saver(debug);
......@@ -198,9 +217,9 @@ FlatpakBackend::FlatpakBackend(QObject *parent)
if (!setupFlatpakInstallations(&error)) {
qWarning() << "Failed to setup flatpak installations:" << error->message;
} else {
m_sources = new FlatpakSourcesBackend(m_installations, this);
loadAppsFromAppstreamData();
m_sources = new FlatpakSourcesBackend(m_installations, this);
SourcesModel::global()->addSourcesBackend(m_sources);
}
......@@ -610,7 +629,7 @@ AppStream::Component fetchComponentFromRemote(const QSettings &settings, GCancel
// There we will fetch the appstream metadata and then delete that temporary installation.
g_autoptr(GError) localError = nullptr;
const QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/discover-flatpak-temporary") + remoteName;
const QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/discover-flatpak-temporary-") + remoteName;
qDebug() << "Creating temporary installation" << path;
g_autoptr(GFile) file = g_file_new_for_path(QFile::encodeName(path).constData());
g_autoptr(FlatpakInstallation) tempInstallation = flatpak_installation_new_for_path(file, true, cancellable, &localError);
......@@ -622,10 +641,10 @@ AppStream::Component fetchComponentFromRemote(const QSettings &settings, GCancel
});
g_autoptr(FlatpakRemote) tempRemote = flatpak_remote_new(remoteName.toUtf8());
FlatpakSourcesBackend::populateRemote(tempRemote,
remoteName,
settings.value(QStringLiteral("Flatpak Ref/Url")).toString(),
settings.value(QStringLiteral("Flatpak Ref/GPGKey")).toString().toUtf8());
populateRemote(tempRemote,
remoteName,
settings.value(QStringLiteral("Flatpak Ref/Url")).toString(),
settings.value(QStringLiteral("Flatpak Ref/GPGKey")).toString().toUtf8());
if (!flatpak_installation_modify_remote(tempInstallation, tempRemote, cancellable, &localError)) {
qDebug() << "error adding temporary remote" << localError->message;
return {asComponent};
......@@ -739,6 +758,7 @@ void FlatpakBackend::addAppFromFlatpakRef(const QUrl &url, ResultsStream *stream
QUrl runtimeUrl = QUrl(settings.value(QStringLiteral("Flatpak Ref/RuntimeRepo")).toString());
auto refSource = QSharedPointer<FlatpakSource>::create(this, preferredInstallation());
resource->setTemporarySource(refSource);
m_flatpakSources += refSource;
if (!runtimeUrl.isEmpty()) {
// We need to fetch metadata to find information about required runtime
......@@ -873,14 +893,14 @@ void FlatpakBackend::loadRemote(FlatpakInstallation *installation, FlatpakRemote
{
g_autoptr(GFile) fileTimestamp = flatpak_remote_get_appstream_timestamp(remote, flatpak_get_default_arch());
m_refreshAppstreamMetadataJobs++;
integrateRemote(installation, remote);
g_autofree char *path_str = g_file_get_path(fileTimestamp);
QFileInfo fileInfo(QFile::encodeName(path_str));
// Refresh appstream metadata in case they have never been refreshed or the cache is older than 6 hours
if (!fileInfo.exists() || fileInfo.lastModified().toUTC().secsTo(QDateTime::currentDateTimeUtc()) > 21600) {
checkForUpdates(installation, remote);
} else {
m_refreshAppstreamMetadataJobs++;
integrateRemote(installation, remote);
}
}
......@@ -912,34 +932,17 @@ void FlatpakBackend::metadataRefreshed()
}
}
QSharedPointer<FlatpakSource> FlatpakBackend::integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote)
void FlatpakBackend::createPool(QSharedPointer<FlatpakSource> source)
{
Q_ASSERT(m_refreshAppstreamMetadataJobs != 0);
for (auto source : qAsConst(m_flatpakSources)) {
if (source->url() == flatpak_remote_get_url(remote) && source->installation() == flatpakInstallation) {
metadataRefreshed();
return source;
}
}
for (auto source : qAsConst(m_flatpakLoadingSources)) {
if (source->url() == flatpak_remote_get_url(remote) && source->installation() == flatpakInstallation) {
metadataRefreshed();
return source;
}
}
auto source = QSharedPointer<FlatpakSource>::create(this, flatpakInstallation, remote);
if (!source->isEnabled() || flatpak_remote_get_noenumerate(remote)) {
m_flatpakSources += source;
metadataRefreshed();
return {};
if (source->m_pool) {
return;
}
const QString appstreamDirPath = source->appstreamDir();
if (!QFile::exists(appstreamDirPath)) {
qWarning() << "No" << appstreamDirPath << "appstream metadata found for" << source->name();
metadataRefreshed();
return {};
return;
}
AppStream::Pool *pool = new AppStream::Pool(this);
......@@ -968,12 +971,41 @@ QSharedPointer<FlatpakSource> FlatpakBackend::integrateRemote(FlatpakInstallatio
pool->setFlags(AppStream::Pool::FlagReadCollection);
pool->setCacheFlags(AppStream::Pool::CacheFlagUseUser);
const QString subdir = flatpak_installation_get_id(flatpakInstallation) + QLatin1Char('/') + sourceName;
const QString subdir = flatpak_installation_get_id(source->installation()) + QLatin1Char('/') + sourceName;
pool->setCacheLocation(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/flatpak-appstream/" + subdir);
QDir().mkpath(pool->cacheLocation());
#endif
fw->setFuture(QtConcurrent::run(&m_threadPool, pool, &AppStream::Pool::load));
}
QSharedPointer<FlatpakSource> FlatpakBackend::integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote)
{
m_sources->addRemote(remote, flatpakInstallation);
Q_ASSERT(m_refreshAppstreamMetadataJobs != 0);
for (auto source : qAsConst(m_flatpakSources)) {
if (source->url() == flatpak_remote_get_url(remote) && source->installation() == flatpakInstallation) {
metadataRefreshed();
createPool(source);
return source;
}
}
for (auto source : qAsConst(m_flatpakLoadingSources)) {
if (source->url() == flatpak_remote_get_url(remote) && source->installation() == flatpakInstallation) {
metadataRefreshed();
createPool(source);
return source;
}
}
auto source = QSharedPointer<FlatpakSource>::create(this, flatpakInstallation, remote);
if (!source->isEnabled() || flatpak_remote_get_noenumerate(remote)) {
m_flatpakSources += source;
metadataRefreshed();
return {};
}
createPool(source);
m_flatpakLoadingSources << source;
return source;
}
......@@ -981,9 +1013,9 @@ QSharedPointer<FlatpakSource> FlatpakBackend::integrateRemote(FlatpakInstallatio
void FlatpakBackend::loadLocalUpdates(FlatpakInstallation *flatpakInstallation)
{
g_autoptr(GError) localError = nullptr;
g_autoptr(GPtrArray) refs = flatpak_installation_list_installed_refs(flatpakInstallation, m_cancellable, &localError);
g_autoptr(GPtrArray) refs = flatpak_installation_list_installed_refs_for_update(flatpakInstallation, m_cancellable, &localError);
if (!refs) {
qWarning() << "Failed to get list of installed refs for listing updates:" << localError->message;
qWarning() << "Failed to get list of installed refs for listing local updates:" << localError->message;
return;
}
......@@ -1530,6 +1562,59 @@ AbstractReviewsBackend *FlatpakBackend::reviewsBackend() const
return m_reviews.data();
}
void FlatpakBackend::checkRepositories(const QMap<QString, QStringList> &names)
{
auto flatpakInstallationByPath = [this](const QString &path) -> FlatpakInstallation * {
for (auto inst : m_installations) {
if (FlatpakResource::installationPath(inst) == path)
return inst;
}
return nullptr;
};
g_autoptr(GError) localError = nullptr;
for (auto it = names.begin(), itEnd = names.end(); it != itEnd; ++it) {
FlatpakInstallation *installation = flatpakInstallationByPath(it.key());
for (const QString &name : qAsConst(*it)) {
auto remote = flatpak_installation_get_remote_by_name(installation, name.toUtf8(), m_cancellable, &localError);
if (!remote) {
qWarning() << "Could not find remote" << name << "in" << it.key();
continue;
}
m_refreshAppstreamMetadataJobs++;
loadRemote(installation, remote);
}
}
}
FlatpakRemote *FlatpakBackend::installSource(FlatpakResource *resource)
{
g_autoptr(GCancellable) cancellable = g_cancellable_new();
auto remote = flatpak_installation_get_remote_by_name(preferredInstallation(), resource->flatpakName().toUtf8().constData(), cancellable, nullptr);
if (remote) {
qWarning() << "Source " << resource->flatpakName() << " already exists in" << flatpak_installation_get_path(preferredInstallation());
return nullptr;
}
remote = flatpak_remote_new(resource->flatpakName().toUtf8().constData());
populateRemote(remote,
resource->comment(),
resource->getMetadata(QStringLiteral("repo-url")).toString(),
resource->getMetadata(QStringLiteral("gpg-key")).toString());
if (!resource->branch().isEmpty()) {
flatpak_remote_set_default_branch(remote, resource->branch().toUtf8().constData());
}
g_autoptr(GError) error = nullptr;
if (!flatpak_installation_add_remote(preferredInstallation(), remote, false, cancellable, &error)) {
Q_EMIT passiveMessage(i18n("Failed to add source '%1': %2", resource->flatpakName(), error->message));
qWarning() << "Failed to add source " << resource->flatpakName() << error->message;
return nullptr;
}
return remote;
}
Transaction *FlatpakBackend::installApplication(AbstractResource *app, const AddonList &addons)
{
Q_UNUSED(addons);
......@@ -1538,21 +1623,32 @@ Transaction *FlatpakBackend::installApplication(AbstractResource *app, const Add
if (resource->resourceType() == FlatpakResource::Source) {
// Let source backend handle this
FlatpakRemote *remote = m_sources->installSource(resource);
FlatpakRemote *remote = installSource(resource);
if (remote) {
resource->setState(AbstractResource::Installed);
// Make sure we update appstream metadata first
// FIXME we have to let flatpak to return the remote as the one created by FlatpakSourcesBackend will not have appstream directory
g_autoptr(FlatpakRemote) repo =
flatpak_installation_get_remote_by_name(resource->installation(), flatpak_remote_get_name(remote), m_cancellable, nullptr);
loadRemote(resource->installation(), repo);
auto name = flatpak_remote_get_name(remote);
g_autoptr(FlatpakRemote) remote = flatpak_installation_get_remote_by_name(resource->installation(), name, m_cancellable, nullptr);
loadRemote(resource->installation(), remote);
}
return nullptr;
}
FlatpakJobTransaction *transaction = new FlatpakJobTransaction(resource, Transaction::InstallRole);
connect(transaction, &FlatpakJobTransaction::repositoriesAdded, this, &FlatpakBackend::checkRepositories);
connect(transaction, &FlatpakJobTransaction::statusChanged, this, [this, resource](Transaction::Status status) {
if (status == Transaction::Status::DoneStatus) {
if (auto tempSource = resource->temporarySource()) {
auto source = findSource(resource->installation(), resource->origin());
resource->setTemporarySource({});
const auto id = resource->uniqueId();
source->m_resources.insert(id, resource);
tempSource->m_resources.remove(id);
if (tempSource->m_resources.isEmpty()) {
const bool removed = m_flatpakSources.removeAll(tempSource) || m_flatpakLoadingSources.removeAll(tempSource);
Q_ASSERT(removed);
}
}
updateAppState(resource);
}
});
......@@ -1577,8 +1673,7 @@ Transaction *FlatpakBackend::removeApplication(AbstractResource *app)
}
FlatpakJobTransaction *transaction = new FlatpakJobTransaction(resource, Transaction::RemoveRole);
connect(transaction, &FlatpakJobTransaction::repositoriesAdded, m_sources, &FlatpakSourcesBackend::checkRepositories);
connect(transaction, &FlatpakJobTransaction::repositoriesAdded, this, &FlatpakBackend::checkRepositories);
connect(transaction, &FlatpakJobTransaction::statusChanged, this, [this, resource](Transaction::Status status) {
if (status == Transaction::Status::DoneStatus) {
updateAppSize(resource);
......@@ -1590,12 +1685,15 @@ Transaction *FlatpakBackend::removeApplication(AbstractResource *app)
void FlatpakBackend::checkForUpdates()
{
for (auto source : qAsConst(m_flatpakSources)) {
checkForUpdates(source->installation(), source->remote());
if (source->remote()) {
checkForUpdates(source->installation(), source->remote());
}
}
}
void FlatpakBackend::checkForUpdates(FlatpakInstallation *installation, FlatpakRemote *remote)
{
Q_ASSERT(remote);
m_refreshAppstreamMetadataJobs++;
if (flatpak_remote_get_disabled(remote)) {
integrateRemote(installation, remote);
......
......@@ -105,6 +105,7 @@ private:
FlatpakRemote *getFlatpakRemoteByUrl(const QString &url, FlatpakInstallation *installation) const;
FlatpakResource *getRuntimeForApp(FlatpakResource *resource) const;
FlatpakResource *resourceForComponent(const AppStream::Component &component, const QSharedPointer<FlatpakSource> &source) const;
void checkRepositories(const QMap<QString, QStringList> &names);
void loadAppsFromAppstreamData();
bool loadAppsFromAppstreamData(FlatpakInstallation *flatpakInstallation);
......@@ -122,6 +123,8 @@ private:
QVector<AbstractResource *> resourcesByAppstreamName(const QString &name) const;
void acquireFetching(bool f);
void checkForUpdates(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote);
void createPool(QSharedPointer<FlatpakSource> source);
FlatpakRemote *installSource(FlatpakResource *resource);
StandardBackendUpdater *m_updater;
FlatpakSourcesBackend *m_sources = nullptr;
......
......@@ -61,6 +61,10 @@ void FlatpakJobTransaction::finishTransaction()
m_app->setState(AbstractResource::None);
}
if (!m_appJob->addedRepositories().isEmpty()) {
Q_EMIT repositoriesAdded(m_appJob->addedRepositories());
}
if (!m_appJob->cancelled() && !m_appJob->errorMessage().isEmpty()) {
Q_EMIT passiveMessage(m_appJob->errorMessage());
}
......
......@@ -32,7 +32,7 @@ public Q_SLOTS:
void start();
Q_SIGNALS:
void repositoriesAdded(const QStringList &repositoryNames);
void repositoriesAdded(const QMap<QString, QStringList> &repositoryNames);
private:
void updateProgress();
......
......@@ -18,6 +18,8 @@
class AddonList;
class FlatpakBackend;
class FlatpakSource;
class FlatpakResource : public AbstractResource
{
Q_OBJECT
......@@ -168,6 +170,15 @@ public:
m_availableVersion = version;
}
void setTemporarySource(const QSharedPointer<FlatpakSource> &temp)
{
m_temp = temp;
}
QSharedPointer<FlatpakSource> temporarySource() const
{
return m_temp;
}
Q_SIGNALS:
void propertyStateChanged(FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState state);
......@@ -193,6 +204,7 @@ private:
QString m_origin;
QString m_availableVersion;
FlatpakResource::ResourceType m_type = DesktopApp;
QSharedPointer<FlatpakSource> m_temp;
static const QStringList m_objects;
};
......
......@@ -88,11 +88,6 @@ FlatpakSourcesBackend::FlatpakSourcesBackend(const QVector<FlatpakInstallation *
connect(m_flathubAction, &DiscoverAction::triggered, this, [this]() {
addSource(QStringLiteral("https://flathub.org/repo/flathub.flatpakrepo"));
});
for (auto installation : installations) {
if (!listRepositories(installation)) {
qWarning() << "Failed to list repositories from installation" << installation;
}
}
m_noSourcesItem->setEnabled(false);
if (m_sources->rowCount() == 0) {
......@@ -283,81 +278,11 @@ QVariantList FlatpakSourcesBackend::actions() const
return {QVariant::fromValue<QObject *>(m_flathubAction)};
}
bool FlatpakSourcesBackend::listRepositories(FlatpakInstallation *installation)
{
Q_ASSERT(installation);
g_autoptr(GCancellable) cancellable = g_cancellable_new();
g_autoptr(GPtrArray) remotes = flatpak_installation_list_remotes(installation, cancellable, nullptr);
if (!remotes) {
return false;
}
for (uint i = 0; i < remotes->len; i++) {
FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i));
if (flatpak_remote_get_noenumerate(remote)) {
continue;
}
addRemote(remote, installation);
}
return true;
}
void FlatpakSourcesBackend::populateRemote(FlatpakRemote *remote, const QString &name, const QString &url, const QString &gpgKey)
{
flatpak_remote_set_url(remote, url.toUtf8().constData());
flatpak_remote_set_noenumerate(remote, false);
flatpak_remote_set_title(remote, name.toUtf8().constData());
if (!gpgKey.isEmpty()) {
gsize dataLen = 0;
g_autofree guchar *data = nullptr;
g_autoptr(GBytes) bytes = nullptr;
data = g_base64_decode(gpgKey.toUtf8().constData(), &dataLen);
bytes = g_bytes_new(data, dataLen);
flatpak_remote_set_gpg_verify(remote, true);
flatpak_remote_set_gpg_key(remote, bytes);
} else {
flatpak_remote_set_gpg_verify(remote, false);
}
}
FlatpakRemote *FlatpakSourcesBackend::installSource(FlatpakResource *resource)
{
g_autoptr(GCancellable) cancellable = g_cancellable_new();
auto remote = flatpak_installation_get_remote_by_name(m_preferredInstallation, resource->flatpakName().toUtf8().constData(), cancellable, nullptr);
if (remote) {
qWarning() << "Source " << resource->flatpakName() << " already exists in" << flatpak_installation_get_path(m_preferredInstallation);
return nullptr;
}
remote = flatpak_remote_new(resource->flatpakName().toUtf8().constData());
populateRemote(remote,
resource->comment(),
resource->getMetadata(QStringLiteral("repo-url")).toString(),
resource->getMetadata(QStringLiteral("gpg-key")).toString());
if (!resource->branch().isEmpty()) {
flatpak_remote_set_default_branch(remote, resource->branch().toUtf8().constData());
}
g_autoptr(GError) error = nullptr;
if (!flatpak_installation_modify_remote(m_preferredInstallation, remote, cancellable, &error)) {
qWarning() << "Failed to add source " << resource->flatpakName() << error->message;
return nullptr;
}
addRemote(remote, m_preferredInstallation);
return remote;
}
void FlatpakSourcesBackend::addRemote(FlatpakRemote *remote, FlatpakInstallation *installation)
{
if (flatpak_remote_get_noenumerate(remote)) {
return;
}
const QString id = QString::fromUtf8(flatpak_remote_get_name(remote));
const QString title = QString::fromUtf8(flatpak_remote_get_title(remote));
const QUrl remoteUrl(QString::fromUtf8(flatpak_remote_get_url(remote)));
......@@ -376,6 +301,14 @@ void FlatpakSourcesBackend::addRemote(FlatpakRemote *remote, FlatpakInstallation
label = i18n("%1 (user)", label);
}
for (int i = 0, c = m_sources->rowCount(); i < c; ++i) {
FlatpakSourceItem *item = static_cast<FlatpakSourceItem *>(m_sources->item(i));
if (item->data(Qt::StatusTipRole) == remoteUrl && item->flatpakInstallation() == installation) {
qDebug() << "we already have an item for this" << remoteUrl;
return;
}
}
FlatpakBackend *backend = qobject_cast<FlatpakBackend *>(parent());
FlatpakSourceItem *it = new FlatpakSourceItem(label, remote, backend);
it->setData(remoteUrl.isLocalFile() ? remoteUrl.toLocalFile() : remoteUrl.host(), Qt::ToolTipRole);
......@@ -457,19 +390,3 @@ void FlatpakSourcesBackend::proceed()
{
m_proceedFunctions.pop()();
}
void FlatpakSourcesBackend::checkRepositories(const QStringList &repoNames)
{
FlatpakBackend *backend = qobject_cast<FlatpakBackend *>(parent());
const auto insts = backend->installations();
for (const QString &repoName : repoNames) {
const QByteArray name = repoName.toUtf8();
for (auto installation : insts) {
g_autoptr(GError) error = nullptr;
auto remote = flatpak_installation_get_remote_by_name(installation, name, nullptr, &error);
if (remote) {
addRemote(remote, installation);
}
}
}
}
......@@ -56,13 +56,9 @@ public:
void cancel() override;
void proceed() override;
static void populateRemote(FlatpakRemote *remote, const QString &name, const QString &url, const QString &gpgkey);
void checkRepositories(const QStringList &repoNames);
private:
bool listRepositories(FlatpakInstallation *installation);
void addRemote(FlatpakRemote *remote, FlatpakInstallation *installation);
private:
FlatpakInstallation *m_preferredInstallation;
QStandardItemModel *m_sources;
DiscoverAction *const m_flathubAction;
......
......@@ -12,7 +12,7 @@
static int FLATPAK_CLI_UPDATE_FREQUENCY = 150;
gboolean FlatpakTransactionThread::add_new_remote_cb(FlatpakTransaction * /*object*/,
gboolean FlatpakTransactionThread::add_new_remote_cb(FlatpakTransaction *object,
gint /*reason*/,
gchar *from_id,
gchar *suggested_remote_name,
......@@ -22,9 +22,9 @@ gboolean FlatpakTransactionThread::add_new_remote_cb(FlatpakTransaction * /*obje
FlatpakTransactionThread *obj = (FlatpakTransactionThread *)user_data;
// TODO ask instead
obj->m_addedRepositories << QString::fromUtf8(suggested_remote_name);
Q_EMIT obj->passiveMessage(
i18n("Adding remote '%1' in %2 from %3", obj->m_addedRepositories.constLast(), QString::fromUtf8(url), QString::fromUtf8(from_id)));
auto name = QString::fromUtf8(suggested_remote_name);
obj->m_addedRepositories[FlatpakResource::installationPath(flatpak_transaction_get_installation(object))].append(name);
Q_EMIT obj->passiveMessage(i18n("Adding remote '%1' in %2 from %3", name, QString::fromUtf8(url), QString::fromUtf8(from_id)));
return true;
}
......
......@@ -11,6 +11,8 @@
#include <gio/gio.h>
#include <glib.h>
#include <QMap>
#include <QStringList>
#include <QThread>
#include <Transaction/Transaction.h>
......@@ -40,7 +42,7 @@ public:
}
void addErrorMessage(const QString &error);
QStringList addedRepositories() const
QMap<QString, QStringList> addedRepositories() const
{
return m_addedRepositories;
}
......@@ -63,7 +65,7 @@ private:
GCancellable *m_cancellable;
FlatpakResource *const m_app;
const Transaction::Role m_role;
QStringList m_addedRepositories;
QMap<QString, QStringList> m_addedRepositories;
};
#endif // FLATPAKTRANSACTIONJOB_H
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment