diff --git a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp index 6f537d1c..f7e3fe04 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp @@ -1,322 +1,322 @@ /*************************************************************************** * Copyright © 2014 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FlatpakSourcesBackend.h" #include "FlatpakResource.h" #include "FlatpakBackend.h" #include #include #include #include #include #include #include #include #include #include #include class FlatpakSourceItem : public QStandardItem { public: FlatpakSourceItem(const QString &text) : QStandardItem(text) { } void setFlatpakInstallation(FlatpakInstallation *installation) { m_installation = installation; } FlatpakInstallation *flatpakInstallation() const { return m_installation; } private: FlatpakInstallation *m_installation; }; FlatpakSourcesBackend::FlatpakSourcesBackend(const QVector &installations, AbstractResourcesBackend * parent) : AbstractSourcesBackend(parent) , m_preferredInstallation(installations.constFirst()) , m_sources(new QStandardItemModel(this)) , m_flathubAction(new QAction(i18n("Add Flathub"), this)) , m_noSourcesItem(new QStandardItem(QStringLiteral("-"))) { m_flathubAction->setToolTip(QStringLiteral("flathub")); connect(m_flathubAction, &QAction::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) { m_sources->appendRow(m_noSourcesItem); } } FlatpakSourcesBackend::~FlatpakSourcesBackend() { QStringList ids; for (int i = 0, c = m_sources->rowCount(); iitem(i); ids << it->data(IdRole).toString(); } auto conf = KSharedConfig::openConfig(); KConfigGroup group = conf->group("FlatpakSources"); group.writeEntry("Sources", ids); } QAbstractItemModel* FlatpakSourcesBackend::sources() { return m_sources; } bool FlatpakSourcesBackend::addSource(const QString &id) { FlatpakBackend* backend = qobject_cast(parent()); const QUrl flatpakrepoUrl(id); if (id.isEmpty() || !flatpakrepoUrl.isValid()) return false; auto addSource = [=](AbstractResource* res) { if (res) backend->installApplication(res); else Q_EMIT backend->passiveMessage(i18n("Could not add the source %1", flatpakrepoUrl.toDisplayString())); }; if (flatpakrepoUrl.isLocalFile()) { addSource(backend->addSourceFromFlatpakRepo(flatpakrepoUrl)); } else { AbstractResourcesBackend::Filters filter; filter.resourceUrl = flatpakrepoUrl; auto stream = new StoredResultsStream ({backend->search(filter)}); connect(stream, &StoredResultsStream::finished, this, [addSource, stream]() { const auto res = stream->resources(); addSource(res.value(0)); }); } return true; } QStandardItem * FlatpakSourcesBackend::sourceById(const QString& id) const { QStandardItem* sourceIt = nullptr; for (int i = 0, c = m_sources->rowCount(); iitem(i); if (it->data(IdRole) == id) { sourceIt = it; break; } } return sourceIt; } QStandardItem * FlatpakSourcesBackend::sourceByUrl(const QString& _url) const { QUrl url(_url); QStandardItem* sourceIt = nullptr; for (int i = 0, c = m_sources->rowCount(); iitem(i); if (url.matches(it->data(Qt::StatusTipRole).toUrl(), QUrl::StripTrailingSlash)) { sourceIt = it; break; } } return sourceIt; } bool FlatpakSourcesBackend::removeSource(const QString &id) { auto sourceIt = sourceById(id); if (sourceIt) { FlatpakSourceItem *sourceItem = static_cast(sourceIt); g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) error = nullptr; if (flatpak_installation_remove_remote(sourceItem->flatpakInstallation(), id.toUtf8().constData(), cancellable, &error)) { m_sources->removeRow(sourceItem->row()); if (m_sources->rowCount() == 0) { m_sources->appendRow(m_noSourcesItem); } return true; } else { qWarning() << "Failed to remove " << id << " remote repository:" << error->message; return false; } } else { qWarning() << "couldn't find " << id; return false; } return false; } QList FlatpakSourcesBackend::actions() const { return { 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; } 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"; + qWarning() << "Source " << resource->flatpakName() << " already exists in" << flatpak_installation_get_path(m_preferredInstallation); return nullptr; } remote = flatpak_remote_new(resource->flatpakName().toUtf8().constData()); flatpak_remote_set_url(remote, resource->getMetadata(QStringLiteral("repo-url")).toString().toUtf8().constData()); flatpak_remote_set_noenumerate(remote, false); flatpak_remote_set_title(remote, resource->comment().toUtf8().constData()); const QString gpgKey = resource->getMetadata(QStringLiteral("gpg-key")).toString(); 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); } if (!resource->branch().isEmpty()) { flatpak_remote_set_default_branch(remote, resource->branch().toUtf8().constData()); } if (!flatpak_installation_modify_remote(m_preferredInstallation, remote, cancellable, nullptr)) { qWarning() << "Failed to add source " << resource->flatpakName(); return nullptr; } addRemote(remote, m_preferredInstallation); return remote; } void FlatpakSourcesBackend::addRemote(FlatpakRemote *remote, FlatpakInstallation *installation) { 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))); const auto theActions = actions(); for(QAction *action: theActions) { if (action->toolTip() == id) { action->setEnabled(false); action->setVisible(false); } } FlatpakSourceItem *it = new FlatpakSourceItem(!title.isEmpty() ? title : id); it->setData(remoteUrl.isLocalFile() ? remoteUrl.toLocalFile() : remoteUrl.host(), Qt::ToolTipRole); it->setData(remoteUrl, Qt::StatusTipRole); it->setData(id, IdRole); it->setFlatpakInstallation(installation); int idx = -1; { const auto conf = KSharedConfig::openConfig(); const KConfigGroup group = conf->group("FlatpakSources"); const auto ids = group.readEntry("Sources", QStringList()); const auto ourIdx = ids.indexOf(id); if (ourIdx<0) { //If not present, we put it on top idx = 0; } else { idx=0; for(int c=m_sources->rowCount(); idxitem(idx); const int compIdx = ids.indexOf(compIt->data(IdRole).toString()); if (compIdx >= ourIdx) { break; } } } } m_sources->insertRow(idx, it); if (m_sources->rowCount() == 1) Q_EMIT firstSourceIdChanged(); Q_EMIT lastSourceIdChanged(); if (m_sources->rowCount() > 0) { m_sources->takeRow(m_noSourcesItem->row()); } } QString FlatpakSourcesBackend::idDescription() { return i18n("Flatpak repository URI (*.flatpakrepo)"); } bool FlatpakSourcesBackend::moveSource(const QString& sourceId, int delta) { auto item = sourceById(sourceId); if (!item) return false; const auto row = item->row(); auto prevRow = m_sources->takeRow(row); Q_ASSERT(!prevRow.isEmpty()); const auto destRow = row + (delta>0? delta : delta); m_sources->insertRow(destRow, prevRow); if (destRow == 0 || row == 0) Q_EMIT firstSourceIdChanged(); if (destRow == m_sources->rowCount() - 1 || row == m_sources->rowCount() - 1) Q_EMIT lastSourceIdChanged(); return true; } int FlatpakSourcesBackend::originIndex(const QString& sourceId) const { auto item = sourceById(sourceId); return item ? item->row() : INT_MAX; } diff --git a/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp b/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp index 94751ef8..87b3e483 100644 --- a/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp +++ b/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp @@ -1,163 +1,166 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include class FlatpakTest : public QObject { Q_OBJECT public: AbstractResourcesBackend* backendByName(ResourcesModel* m, const QString& name) { QVector backends = m->backends(); foreach(AbstractResourcesBackend* backend, backends) { if(QLatin1String(backend->metaObject()->className()) == name) { return backend; } } return nullptr; } FlatpakTest(QObject* parent = nullptr): QObject(parent) { qputenv("FLATPAK_TEST_MODE", "ON"); m_model = new ResourcesModel(QStringLiteral("flatpak-backend"), this); m_appBackend = backendByName(m_model, QStringLiteral("FlatpakBackend")); } private Q_SLOTS: void init() { + QStandardPaths::setTestModeEnabled(true); + QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/discover-flatpak-test")).removeRecursively(); + QVERIFY(m_appBackend); while(m_appBackend->isFetching()) { QSignalSpy spy(m_appBackend, &AbstractResourcesBackend::fetchingChanged); QVERIFY(spy.wait()); } } void testAddSource() { auto res = getAllResources(m_appBackend); QCOMPARE(res.count(), 0); auto m = SourcesModel::global(); auto bk = qobject_cast(m->index(0, 0).data(SourcesModel::SourcesBackend).value()); QSignalSpy initializedSpy(m_appBackend, SIGNAL(initialized())); if (m->rowCount() == 1) { QSignalSpy spy(m, &SourcesModel::rowsInserted); bk->actions().constFirst()->trigger(); QVERIFY(spy.count() || spy.wait()); } - QVERIFY(initializedSpy.count() || initializedSpy.wait()); + QVERIFY(initializedSpy.count() || initializedSpy.wait(20000)); auto resFlathub = getAllResources(m_appBackend); QVERIFY(resFlathub.count() > 0); } void testListOrigin() { AbstractResourcesBackend::Filters f; f.origin = QStringLiteral("flathub"); auto resources= getResources(m_appBackend->search(f), true); QVERIFY(resources.count()>0); } void testInstallApp() { AbstractResourcesBackend::Filters f; f.resourceUrl = QUrl(QStringLiteral("appstream://com.github.rssguard.desktop")); const auto res = getResources(m_appBackend->search(f)); QVERIFY(res.count() == 1); const auto resRssguard = res.constFirst(); QCOMPARE(resRssguard->state(), AbstractResource::None); waitTransaction(m_appBackend->installApplication(resRssguard)); QCOMPARE(resRssguard->state(), AbstractResource::Installed); waitTransaction(m_appBackend->removeApplication(resRssguard)); QCOMPARE(resRssguard->state(), AbstractResource::None); } void testCancelInstallation() { AbstractResourcesBackend::Filters f; f.resourceUrl = QUrl(QStringLiteral("appstream://com.github.rssguard.desktop")); const auto res = getResources(m_appBackend->search(f)); QVERIFY(res.count() == 1); const auto resRssguard = res.constFirst(); QCOMPARE(resRssguard->state(), AbstractResource::None); auto t = m_appBackend->installApplication(resRssguard); QSignalSpy spy(t, &Transaction::statusChanged); QVERIFY(spy.wait()); QCOMPARE(t->status(), Transaction::CommittingStatus); t->cancel(); QCOMPARE(t->status(), Transaction::CancelledStatus); } private: void waitTransaction(Transaction* t) { QSignalSpy spyInstalled(t->resource(), &AbstractResource::stateChanged); QSignalSpy destructionSpy(t, &QObject::destroyed); while (t && spyInstalled.count() == 0) { qDebug() << t->status() << t->progress(); spyInstalled.wait(100); } QVERIFY(destructionSpy.count() || destructionSpy.wait()); } QVector getResources(ResultsStream* stream, bool canBeEmpty = true) { Q_ASSERT(stream); QSignalSpy spyResources(stream, &ResultsStream::destroyed); QVector resources; connect(stream, &ResultsStream::resourcesFound, this, [&resources](const QVector& res) { resources += res; }); Q_ASSERT(spyResources.wait(10000)); Q_ASSERT(!resources.isEmpty() || canBeEmpty); return resources; } QVector getAllResources(AbstractResourcesBackend* backend) { AbstractResourcesBackend::Filters f; if (CategoryModel::global()->rootCategories().isEmpty()) CategoryModel::global()->populateCategories(); f.category = CategoryModel::global()->rootCategories().constFirst(); return getResources(backend->search(f), true); } ResourcesModel* m_model; AbstractResourcesBackend* m_appBackend; }; QTEST_MAIN(FlatpakTest) #include "FlatpakTest.moc"