diff --git a/libdiscover/UpdateModel/UpdateItem.h b/libdiscover/UpdateModel/UpdateItem.h index 567752ac..430aeee3 100644 --- a/libdiscover/UpdateModel/UpdateItem.h +++ b/libdiscover/UpdateModel/UpdateItem.h @@ -1,71 +1,63 @@ /*************************************************************************** * Copyright © 2011 Jonathan Thomas * * * * 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 . * ***************************************************************************/ #ifndef UPDATEITEM_H #define UPDATEITEM_H // Qt includes #include #include #include "discovercommon_export.h" #include class AbstractResource; class DISCOVERCOMMON_EXPORT UpdateItem { public: explicit UpdateItem(AbstractResource *app); ~UpdateItem(); - enum Section { - ApplicationSection, - SystemSection - }; - - void setSection(const Section §ion) { m_section = section; } - Section section() const { return m_section; } void setProgress(qreal progress); qreal progress() const; QString changelog() const; void setChangelog(const QString &changelog); AbstractResource *app() const; QString name() const; QString version() const; QVariant icon() const; qint64 size() const; Qt::CheckState checked() const; AbstractResource* resource() const { return m_app; } private: AbstractResource * const m_app; const QString m_categoryName; const QIcon m_categoryIcon; qreal m_progress; QString m_changelog; - Section m_section; }; #endif // UPDATEITEM_H diff --git a/libdiscover/UpdateModel/UpdateModel.cpp b/libdiscover/UpdateModel/UpdateModel.cpp index ac890130..e049abe4 100644 --- a/libdiscover/UpdateModel/UpdateModel.cpp +++ b/libdiscover/UpdateModel/UpdateModel.cpp @@ -1,333 +1,340 @@ /*************************************************************************** * Copyright © 2011 Jonathan Thomas * * * * 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 "UpdateModel.h" // Qt includes #include #include #include "libdiscover_debug.h" // KDE includes #include #include // Own includes #include "UpdateItem.h" #include #include #include UpdateModel::UpdateModel(QObject *parent) : QAbstractListModel(parent) , m_updateSizeTimer(new QTimer(this)) , m_updates(nullptr) { connect(ResourcesModel::global(), &ResourcesModel::fetchingChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::updatesCountChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &UpdateModel::resourceDataChanged); connect(this, &UpdateModel::toUpdateChanged, this, &UpdateModel::updateSizeChanged); m_updateSizeTimer->setInterval(100); m_updateSizeTimer->setSingleShot(true); connect(m_updateSizeTimer, &QTimer::timeout, this, &UpdateModel::updateSizeChanged); } UpdateModel::~UpdateModel() { qDeleteAll(m_updateItems); m_updateItems.clear(); } QHash UpdateModel::roleNames() const { return QAbstractItemModel::roleNames().unite({ { Qt::CheckStateRole, "checked" }, { ResourceProgressRole, "resourceProgress" }, { ResourceRole, "resource" }, { SizeRole, "size" }, { VersionRole, "version" }, { SectionRole, "section" }, { ChangelogRole, "changelog" } } ); } void UpdateModel::setBackend(ResourcesUpdatesModel* updates) { if (m_updates) { disconnect(m_updates, nullptr, this, nullptr); } m_updates = updates; connect(m_updates, &ResourcesUpdatesModel::progressingChanged, this, &UpdateModel::activityChanged); connect(m_updates, &ResourcesUpdatesModel::resourceProgressed, this, &UpdateModel::resourceHasProgressed); activityChanged(); } void UpdateModel::resourceHasProgressed(AbstractResource* res, qreal progress) { UpdateItem* item = itemFromResource(res); if (!item) return; item->setProgress(progress); const QModelIndex idx = indexFromItem(item); Q_EMIT dataChanged(idx, idx, { ResourceProgressRole, SectionResourceProgressRole }); } void UpdateModel::activityChanged() { if (m_updates) { if (!m_updates->isProgressing()) { m_updates->prepare(); setResources(m_updates->toUpdate()); for(auto item : qAsConst(m_updateItems)) { item->setProgress(0); } } else setResources(m_updates->toUpdate()); } } QVariant UpdateModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } UpdateItem *item = itemFromIndex(index); switch (role) { case Qt::DisplayRole: return item->name(); case Qt::DecorationRole: return item->icon(); case Qt::CheckStateRole: return item->checked(); case VersionRole: return item->version(); case SizeRole: return KFormat().formatByteSize(item->size()); case ResourceRole: return QVariant::fromValue(item->resource()); case ResourceProgressRole: return item->progress(); case ChangelogRole: return item->changelog(); case SectionRole: { static const QString appUpdatesSection = i18nc("@item:inlistbox", "Application Updates"); static const QString systemUpdateSection = i18nc("@item:inlistbox", "System Updates"); - switch(item->section()) { - case UpdateItem::ApplicationSection: return appUpdatesSection; - case UpdateItem::SystemSection: return systemUpdateSection; + static const QString addonsSection = i18nc("@item:inlistbox", "Addons"); + switch(item->resource()->type()) { + case AbstractResource::Application: return appUpdatesSection; + case AbstractResource::Technical: return systemUpdateSection; + case AbstractResource::Addon: return addonsSection; } - return {}; + Q_UNREACHABLE(); } case SectionResourceProgressRole: - return (100-item->progress()) + (101 * item->section()); + return (100-item->progress()) + (101 * item->resource()->type()); default: break; } return QVariant(); } void UpdateModel::checkResources(const QList& resource, bool checked) { if(checked) m_updates->addResources(resource); else m_updates->removeResources(resource); } Qt::ItemFlags UpdateModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int UpdateModel::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? m_updateItems.count() : 0; } bool UpdateModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { UpdateItem *item = itemFromIndex(idx); const bool newValue = value.toInt() == Qt::Checked; const QList apps = { item->app() }; checkResources(apps, newValue); Q_ASSERT(idx.data(Qt::CheckStateRole) == value); //When un/checking some backends will decide to add or remove a bunch of packages, so refresh it all auto m = idx.model(); Q_EMIT dataChanged(m->index(0, 0), m->index(m->rowCount()-1, 0), { Qt::CheckStateRole }); Q_EMIT toUpdateChanged(); return true; } return false; } void UpdateModel::fetchChangelog(int row) { UpdateItem *item = itemFromIndex(index(row, 0)); Q_ASSERT(item); if (!item) return; item->app()->fetchChangelog(); } void UpdateModel::integrateChangelog(const QString &changelog) { auto app = qobject_cast(sender()); Q_ASSERT(app); auto item = itemFromResource(app); if (!item) return; item->setChangelog(changelog); const QModelIndex idx = indexFromItem(item); Q_ASSERT(idx.isValid()); emit dataChanged(idx, idx, { ChangelogRole }); } void UpdateModel::setResources(const QList& resources) { if (resources == m_resources) { return; } m_resources = resources; beginResetModel(); qDeleteAll(m_updateItems); m_updateItems.clear(); - QVector appItems, systemItems; + QVector appItems, systemItems, addonItems; foreach(AbstractResource* res, resources) { connect(res, &AbstractResource::changelogFetched, this, &UpdateModel::integrateChangelog, Qt::UniqueConnection); UpdateItem *updateItem = new UpdateItem(res); - if(!res->isTechnical()) { - updateItem->setSection(UpdateItem::ApplicationSection); - appItems += updateItem; - } else { - updateItem->setSection(UpdateItem::SystemSection); - systemItems += updateItem; + switch(res->type()) { + case AbstractResource::Technical: + systemItems += updateItem; + break; + case AbstractResource::Application: + appItems += updateItem; + break; + case AbstractResource::Addon: + addonItems += updateItem; + break; } } const auto sortUpdateItems = [](UpdateItem *a, UpdateItem *b) { return a->name() < b->name(); }; qSort(appItems.begin(), appItems.end(), sortUpdateItems); qSort(systemItems.begin(), systemItems.end(), sortUpdateItems); - m_updateItems = (QVector() << appItems << systemItems); + qSort(addonItems.begin(), addonItems.end(), sortUpdateItems); + m_updateItems = (QVector() << appItems << addonItems << systemItems); endResetModel(); Q_EMIT hasUpdatesChanged(!resources.isEmpty()); Q_EMIT toUpdateChanged(); } bool UpdateModel::hasUpdates() const { return rowCount() > 0; } ResourcesUpdatesModel* UpdateModel::backend() const { return m_updates; } int UpdateModel::toUpdateCount() const { int ret = 0; QSet packages; foreach (UpdateItem* item, m_updateItems) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += item->checked() != Qt::Unchecked ? 1 : 0; } return ret; } int UpdateModel::totalUpdatesCount() const { int ret = 0; QSet packages; foreach (UpdateItem* item, m_updateItems) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += 1; } return ret; } UpdateItem * UpdateModel::itemFromResource(AbstractResource* res) { foreach (UpdateItem* item, m_updateItems) { if (item->app() == res) return item; } return nullptr; } QString UpdateModel::updateSize() const { return KFormat().formatByteSize(m_updates->updateSize()); } QModelIndex UpdateModel::indexFromItem(UpdateItem* item) const { return index(m_updateItems.indexOf(item), 0, {}); } UpdateItem * UpdateModel::itemFromIndex(const QModelIndex& index) const { return m_updateItems[index.row()]; } void UpdateModel::resourceDataChanged(AbstractResource* res, const QVector& properties) { auto item = itemFromResource(res); if (!item) return; const auto index = indexFromItem(item); if (properties.contains("state")) Q_EMIT dataChanged(index, index, {SizeRole, VersionRole}); else if (properties.contains("size")) { Q_EMIT dataChanged(index, index, {SizeRole}); m_updateSizeTimer->start(); } } diff --git a/libdiscover/backends/DummyBackend/DummyBackend.cpp b/libdiscover/backends/DummyBackend/DummyBackend.cpp index ce3659b3..cf8fa411 100644 --- a/libdiscover/backends/DummyBackend/DummyBackend.cpp +++ b/libdiscover/backends/DummyBackend/DummyBackend.cpp @@ -1,179 +1,188 @@ /*************************************************************************** * Copyright © 2013 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 "DummyBackend.h" #include "DummyResource.h" #include "DummyReviewsBackend.h" #include "DummyTransaction.h" #include "DummySourcesBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include DISCOVER_BACKEND_PLUGIN(DummyBackend) DummyBackend::DummyBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(new DummyReviewsBackend(this)) , m_fetching(true) , m_startElements(120) { QTimer::singleShot(500, this, &DummyBackend::toggleFetching); connect(m_reviews, &DummyReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady); connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &DummyBackend::updatesCountChanged); populate(QStringLiteral("Dummy")); if (!m_fetching) m_reviews->initialize(); SourcesModel::global()->addSourcesBackend(new DummySourcesBackend(this)); } void DummyBackend::populate(const QString& n) { const int start = m_resources.count(); for(int i=start; isetSize(100+(m_startElements-i)); res->setState(AbstractResource::State(1+(i%3))); m_resources.insert(name.toLower(), res); connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); } + for(int i=start; isetState(AbstractResource::State(1+(i%3))); + res->setSize(300+(m_startElements-i)); + m_resources.insert(name, res); + connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); + } + for(int i=start; isetState(AbstractResource::State(1+(i%3))); res->setSize(300+(m_startElements-i)); m_resources.insert(name, res); connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); } } void DummyBackend::toggleFetching() { m_fetching = !m_fetching; // qDebug() << "fetching..." << m_fetching; emit fetchingChanged(); if (!m_fetching) m_reviews->initialize(); } int DummyBackend::updatesCount() const { return m_updater->updatesCount(); } ResultsStream* DummyBackend::search(const AbstractResourcesBackend::Filters& filter) { QVector ret; if (!filter.resourceUrl.isEmpty()) return findResourceByPackageName(filter.resourceUrl); else foreach(AbstractResource* r, m_resources) { - if (r->isTechnical() && filter.state != AbstractResource::Upgradeable) { + if (r->type() == AbstractResource::Technical && filter.state != AbstractResource::Upgradeable) { continue; } if (r->state() < filter.state) continue; if(r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) ret += r; } return new ResultsStream(QStringLiteral("DummyStream"), ret); } ResultsStream * DummyBackend::findResourceByPackageName(const QUrl& search) { auto res = search.scheme() == QLatin1String("dummy") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr; if (!res) { return new ResultsStream(QStringLiteral("DummyStream"), {}); } else return new ResultsStream(QStringLiteral("DummyStream"), { res }); } AbstractBackendUpdater* DummyBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend* DummyBackend::reviewsBackend() const { return m_reviews; } Transaction* DummyBackend::installApplication(AbstractResource* app, const AddonList& addons) { return new DummyTransaction(qobject_cast(app), addons, Transaction::InstallRole); } Transaction* DummyBackend::installApplication(AbstractResource* app) { return new DummyTransaction(qobject_cast(app), Transaction::InstallRole); } Transaction* DummyBackend::removeApplication(AbstractResource* app) { return new DummyTransaction(qobject_cast(app), Transaction::RemoveRole); } void DummyBackend::checkForUpdates() { if(m_fetching) return; toggleFetching(); populate(QStringLiteral("Moar")); QTimer::singleShot(500, this, &DummyBackend::toggleFetching); qDebug() << "DummyBackend::checkForUpdates"; } AbstractResource * DummyBackend::resourceForFile(const QUrl& path) { - DummyResource* res = new DummyResource(path.fileName(), true, this); + DummyResource* res = new DummyResource(path.fileName(), AbstractResource::Technical, this); res->setSize(666); res->setState(AbstractResource::None); m_resources.insert(res->packageName(), res); connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); return res; } QString DummyBackend::displayName() const { return QStringLiteral("Dummy"); } bool DummyBackend::hasApplications() const { return true; } #include "DummyBackend.moc" diff --git a/libdiscover/backends/DummyBackend/DummyResource.cpp b/libdiscover/backends/DummyBackend/DummyResource.cpp index 96b51886..a4cf34b9 100644 --- a/libdiscover/backends/DummyBackend/DummyResource.cpp +++ b/libdiscover/backends/DummyBackend/DummyResource.cpp @@ -1,195 +1,196 @@ /*************************************************************************** * Copyright © 2013 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 "DummyResource.h" #include #include #include #include #include Q_GLOBAL_STATIC_WITH_ARGS(QVector, s_icons, ({ QLatin1String("kdevelop"), QLatin1String("kalgebra"), QLatin1String("kmail"), QLatin1String("akregator"), QLatin1String("korganizer") })) -DummyResource::DummyResource(QString name, bool isTechnical, AbstractResourcesBackend* parent) +DummyResource::DummyResource(QString name, AbstractResource::Type type, AbstractResourcesBackend* parent) : AbstractResource(parent) , m_name(std::move(name)) , m_state(State::Broken) , m_iconName((*s_icons)[KRandom::random() % s_icons->size()]) , m_addons({ PackageState(QStringLiteral("a"), QStringLiteral("aaaaaa"), false), PackageState(QStringLiteral("b"), QStringLiteral("aaaaaa"), false), PackageState(QStringLiteral("c"), QStringLiteral("aaaaaa"), false)}) - , m_isTechnical(isTechnical) + , m_type(type) { const int nofScreenshots = KRandom::random() % 5; m_screenshots = QList{ QUrl(QStringLiteral("http://screenshots.debian.net/screenshots/000/014/863/large.png")), QUrl(QStringLiteral("https://c2.staticflickr.com/6/5656/21772158034_dc84382527_o.jpg")), QUrl(QStringLiteral("https://c1.staticflickr.com/9/8479/8166397343_b78106f353_k.jpg")), QUrl(QStringLiteral("https://c2.staticflickr.com/4/3685/9954407993_dad10a6943_k.jpg")), QUrl(QStringLiteral("https://c1.staticflickr.com/1/653/22527103378_8ce572e1de_k.jpg")) }.mid(nofScreenshots); m_screenshotThumbnails = m_screenshots; } QList DummyResource::addonsInformation() { return m_addons; } QString DummyResource::availableVersion() const { return QStringLiteral("3.0"); } QStringList DummyResource::categories() { return { QStringLiteral("dummy"), m_name.endsWith(QLatin1Char('3')) ? QStringLiteral("three") : QStringLiteral("notthree") }; } QString DummyResource::comment() { return QStringLiteral("A reasonably short comment ")+name(); } int DummyResource::size() { return m_size; } QUrl DummyResource::homepage() { return QUrl(QStringLiteral("http://kde.org")); } QUrl DummyResource::helpURL() { return QUrl(QStringLiteral("http://very-very-excellent-docs.lol")); } QUrl DummyResource::bugURL() { return QUrl(QStringLiteral("file:///dev/null")); } QUrl DummyResource::donationURL() { return QUrl(QStringLiteral("https://youtu.be/0o8XMlL8rqY")); } QVariant DummyResource::icon() const { - return isTechnical() ? QStringLiteral("kalarm") : m_iconName; + static const QVector icons = { QStringLiteral("device-notifier"), QStringLiteral("media-floppy"), QStringLiteral("drink-beer") }; + return icons[type()]; } QString DummyResource::installedVersion() const { return QStringLiteral("2.3"); } QString DummyResource::license() { return QStringLiteral("GPL"); } QString DummyResource::longDescription() { return QStringLiteral("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ultricies consequat nulla, ut vulputate nulla ultricies ac. Suspendisse lacinia commodo lacus, non tristique mauris dictum vitae. Sed adipiscing augue nec nisi aliquet viverra. Etiam sit amet nulla in tellus consectetur feugiat. Cras in sem tortor. Fusce a nulla at justo accumsan gravida. Maecenas dui felis, lacinia at ornare sed, aliquam et purus. Sed ut sagittis lacus. Etiam dictum pharetra rhoncus. Suspendisse auctor orci ipsum. Pellentesque vitae urna nec felis consequat lobortis dictum in urna. Phasellus a mi ac leo adipiscing varius eget a felis. Cras magna augue, commodo sed placerat vel, tempus vel ligula. In feugiat quam quis est lobortis sed accumsan nunc malesuada. Mauris quis massa sit amet felis tempus suscipit a quis diam.\n\n" "Aenean quis nulla erat, vel sagittis sem. Praesent vitae mauris arcu. Cras porttitor, ante at scelerisque sodales, nibh felis consectetur orci, ut hendrerit urna urna non urna. Duis eu magna id mi scelerisque adipiscing. Aliquam sed quam in eros sodales accumsan. Phasellus tempus sagittis suscipit. Aliquam rutrum dictum justo ut viverra. Nulla felis sem, molestie sed scelerisque non, consequat vitae nulla. Aliquam ullamcorper malesuada mi, vel vestibulum magna vulputate eget. In hac habitasse platea dictumst. Cras sed lacus dui, vel semper sem. Aenean sodales porta leo vel fringilla.\n\n" "Ut tempus massa et urna porta non mollis metus ultricies. Duis nec nulla ac metus auctor porta id et mi. Mauris aliquam nibh a ligula malesuada sed tincidunt nibh varius. Sed felis metus, porta et adipiscing non, faucibus id leo. Donec ipsum nibh, hendrerit eget aliquam nec, tempor ut mauris. Suspendisse potenti. Vestibulum scelerisque adipiscing libero tristique eleifend. Donec quis tortor eget elit mollis iaculis ac sit amet nisi. Proin non massa sed nunc rutrum pellentesque. Sed dui lectus, laoreet sed condimentum id, commodo sed urna.\n\n" "Praesent tincidunt mattis massa mattis porta. Nullam posuere neque at mauris vestibulum vitae elementum leo sodales. Quisque condimentum lectus in libero luctus egestas. Fusce tempor neque ac dui tincidunt eget viverra quam suscipit. In hac habitasse platea dictumst. Etiam metus mi, adipiscing nec suscipit id, aliquet sed sem. Duis urna ligula, ornare sed vestibulum vel, molestie ac nisi. Morbi varius iaculis ligula. Nunc in augue leo, sit amet aliquam elit. Suspendisse rutrum sem diam. Proin eu orci nisl. Praesent porttitor dignissim est, id fermentum arcu venenatis vitae.\n\n" "Integer in sapien eget quam vulputate lobortis. Morbi nibh elit, elementum vitae vehicula sed, consequat nec erat. Donec placerat porttitor est ut dapibus. Fusce augue orci, dictum et convallis vel, blandit eu tortor. Phasellus non eros nulla. In iaculis nulla fermentum nulla gravida eu mattis purus consectetur. Integer dui nunc, sollicitudin ac tincidunt nec, hendrerit bibendum nunc. Proin sit amet augue ac velit egestas varius. Sed eu ante quis orci vestibulum sagittis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus vitae urna odio, at molestie leo. In convallis neque vel mi dictum convallis lobortis turpis sagittis.\n\n"); } QString DummyResource::name() const { return m_name; } QString DummyResource::origin() const { return QStringLiteral("DummySource1"); } QString DummyResource::packageName() const { return m_name; } QString DummyResource::section() { return QStringLiteral("dummy"); } AbstractResource::State DummyResource::state() { return m_state; } void DummyResource::fetchChangelog() { QString log = longDescription(); log.replace(QLatin1Char('\n'), QLatin1String("
")); emit changelogFetched(log); } void DummyResource::fetchScreenshots() { Q_EMIT screenshotsFetched(m_screenshotThumbnails, m_screenshots); } void DummyResource::setState(AbstractResource::State state) { m_state = state; emit stateChanged(); } void DummyResource::setAddons(const AddonList& addons) { Q_FOREACH (const QString& toInstall, addons.addonsToInstall()) { setAddonInstalled(toInstall, true); } Q_FOREACH (const QString& toRemove, addons.addonsToRemove()) { setAddonInstalled(toRemove, false); } } void DummyResource::setAddonInstalled(const QString& addon, bool installed) { for(auto & elem : m_addons) { if(elem.name() == addon) { elem.setInstalled(installed); } } } void DummyResource::invokeApplication() const { QDesktopServices d; d.openUrl(QUrl(QStringLiteral("https://projects.kde.org/projects/extragear/sysadmin/muon"))); } QUrl DummyResource::url() const { return QUrl(QLatin1String("dummy://") + packageName().replace(QLatin1Char(' '), QLatin1Char('.'))); } diff --git a/libdiscover/backends/DummyBackend/DummyResource.h b/libdiscover/backends/DummyBackend/DummyResource.h index cee6bfe0..14dafd6b 100644 --- a/libdiscover/backends/DummyBackend/DummyResource.h +++ b/libdiscover/backends/DummyBackend/DummyResource.h @@ -1,76 +1,76 @@ /*************************************************************************** * Copyright © 2013 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 . * ***************************************************************************/ #ifndef DUMMYRESOURCE_H #define DUMMYRESOURCE_H #include class AddonList; class DummyResource : public AbstractResource { Q_OBJECT public: - explicit DummyResource(QString name, bool isTechnical, AbstractResourcesBackend* parent); + explicit DummyResource(QString name, AbstractResource::Type type, AbstractResourcesBackend* parent); QList addonsInformation() override; QString section() override; QString origin() const override; QString longDescription() override; QString availableVersion() const override; QString installedVersion() const override; QString license() override; int size() override; QUrl homepage() override; QUrl helpURL() override; QUrl bugURL() override; QUrl donationURL() override; QStringList categories() override; AbstractResource::State state() override; QVariant icon() const override; QString comment() override; QString name() const override; QString packageName() const override; - bool isTechnical() const override { return m_isTechnical; } + AbstractResource::Type type() const override { return m_type; } bool canExecute() const override { return true; } void invokeApplication() const override; void fetchChangelog() override; void fetchScreenshots() override; QUrl url() const override; void setState(State state); void setSize(int size) { m_size = size; } void setAddons(const AddonList& addons); void setAddonInstalled(const QString& addon, bool installed); QString sourceIcon() const override { return QStringLiteral("player-time"); } QDate releaseDate() const override { return {}; } public: - QString m_name; + const QString m_name; AbstractResource::State m_state; QList m_screenshots; QList m_screenshotThumbnails; QString m_iconName; QList m_addons; - bool m_isTechnical; + const AbstractResource::Type m_type; int m_size; }; #endif // DUMMYRESOURCE_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp index 16341639..d9776651 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp @@ -1,1299 +1,1299 @@ /*************************************************************************** * Copyright © 2013 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 "FlatpakBackend.h" #include "FlatpakFetchDataJob.h" #include "FlatpakResource.h" #include "FlatpakSourcesBackend.h" #include "FlatpakJobTransaction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FlatpakSourcesBackend.h" DISCOVER_BACKEND_PLUGIN(FlatpakBackend) static FlatpakResource::Id idForInstalledRef(FlatpakInstallation *installation, FlatpakInstalledRef *ref) { const FlatpakResource::ResourceType appType = flatpak_ref_get_kind(FLATPAK_REF(ref)) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime; const QString name = QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))); const QString appId = appType == FlatpakResource::DesktopApp ? QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))) + QStringLiteral(".desktop") : name; const QString arch = QString::fromUtf8(flatpak_ref_get_arch(FLATPAK_REF(ref))); const QString branch = QString::fromUtf8(flatpak_ref_get_branch(FLATPAK_REF(ref))); return { installation, QString::fromUtf8(flatpak_installed_ref_get_origin(ref)), appType, appId, branch, arch }; } FlatpakBackend::FlatpakBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(AppStreamIntegration::global()->reviews()) , m_refreshAppstreamMetadataJobs(0) , m_threadPool(new QThreadPool(this)) { g_autoptr(GError) error = nullptr; m_cancellable = g_cancellable_new(); connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &FlatpakBackend::updatesCountChanged); // Load flatpak installation if (!setupFlatpakInstallations(&error)) { qWarning() << "Failed to setup flatpak installations:" << error->message; } else { loadAppsFromAppstreamData(); m_sources = new FlatpakSourcesBackend(m_installations, this); SourcesModel::global()->addSourcesBackend(m_sources); } connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, &FlatpakBackend::announceRatingsReady); } FlatpakBackend::~FlatpakBackend() { g_cancellable_cancel(m_cancellable); m_threadPool.waitForDone(200); m_threadPool.clear(); for(auto inst : m_installations) g_object_unref(inst); g_object_unref(m_cancellable); } bool FlatpakBackend::isValid() const { return m_sources && !m_installations.isEmpty(); } void FlatpakBackend::announceRatingsReady() { emitRatingsReady(); const auto ids = m_reviews->appstreamIds().toSet(); foreach(AbstractResource* res, m_resources) { if (ids.contains(res->appstreamId())) { res->ratingFetched(); } } } class FlatpakFetchRemoteResourceJob : public QNetworkAccessManager { Q_OBJECT public: FlatpakFetchRemoteResourceJob(const QUrl &url, FlatpakBackend *backend) : QNetworkAccessManager(backend) , m_backend(backend) , m_url(url) { } void start() { auto replyGet = get(QNetworkRequest(m_url)); connect(replyGet, &QNetworkReply::finished, this, [this, replyGet] { const QUrl originalUrl = replyGet->request().url(); if (replyGet->error() != QNetworkReply::NoError) { qWarning() << "couldn't download" << originalUrl << replyGet->errorString(); Q_EMIT jobFinished(false, nullptr); return; } const QUrl fileUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/') + originalUrl.fileName()); auto replyPut = put(QNetworkRequest(fileUrl), replyGet->readAll()); connect(replyPut, &QNetworkReply::finished, this, [this, originalUrl, fileUrl, replyPut]() { if (replyPut->error() == QNetworkReply::NoError) { auto res = m_backend->resourceForFile(fileUrl); if (res) { FlatpakResource *resource = qobject_cast(res); resource->setResourceFile(originalUrl); Q_EMIT jobFinished(true, resource); } else { qWarning() << "couldn't create resource from" << fileUrl.toLocalFile(); Q_EMIT jobFinished(false, nullptr); } } else { qWarning() << "couldn't save" << originalUrl << replyPut->errorString(); Q_EMIT jobFinished(false, nullptr); } }); }); } Q_SIGNALS: void jobFinished(bool success, FlatpakResource *resource); private: FlatpakBackend *m_backend; QUrl m_url; }; FlatpakRemote * FlatpakBackend::getFlatpakRemoteByUrl(const QString &url, FlatpakInstallation *installation) const { auto remotes = flatpak_installation_list_remotes(installation, m_cancellable, nullptr); if (!remotes) { return nullptr; } const QByteArray comparableUrl = url.toUtf8(); for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); if (comparableUrl == flatpak_remote_get_url(remote)) { return remote; } } return nullptr; } FlatpakInstalledRef * FlatpakBackend::getInstalledRefForApp(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) const { FlatpakInstalledRef *ref = nullptr; g_autoptr(GError) localError = nullptr; if (!flatpakInstallation) { return ref; } - const auto type = resource->type() == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME; + const auto type = resource->resourceType() == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME; return flatpak_installation_get_installed_ref(flatpakInstallation, type, resource->flatpakName().toUtf8().constData(), resource->arch().toUtf8().constData(), resource->branch().toUtf8().constData(), m_cancellable, &localError); } FlatpakResource * FlatpakBackend::getAppForInstalledRef(FlatpakInstallation *flatpakInstallation, FlatpakInstalledRef *ref) const { return m_resources.value(idForInstalledRef(flatpakInstallation, ref)); } FlatpakResource * FlatpakBackend::getRuntimeForApp(FlatpakResource *resource) const { FlatpakResource *runtime = nullptr; const auto runtimeInfo = resource->runtime().split(QLatin1Char('/')); if (runtimeInfo.count() != 3) { return runtime; } for(auto it = m_resources.constBegin(), itEnd = m_resources.constEnd(); it!=itEnd; ++it) { const auto id = it.key(); if (id.type == FlatpakResource::Runtime && id.id == runtimeInfo.at(0) && id.branch == runtimeInfo.at(2)) { runtime = *it; break; } } // TODO if runtime wasn't found, create a new one from available info if (!runtime) { qWarning() << "could not find runtime" << runtimeInfo << resource; } return runtime; } FlatpakResource * FlatpakBackend::addAppFromFlatpakBundle(const QUrl &url) { g_autoptr(GBytes) appstreamGz = nullptr; g_autoptr(GError) localError = nullptr; g_autoptr(GFile) file = nullptr; g_autoptr(FlatpakBundleRef) bundleRef = nullptr; AppStream::Component asComponent; file = g_file_new_for_path(url.toLocalFile().toUtf8().constData()); bundleRef = flatpak_bundle_ref_new(file, &localError); if (!bundleRef) { qWarning() << "Failed to load bundle:" << localError->message; return nullptr; } g_autoptr(GBytes) metadata = flatpak_bundle_ref_get_metadata(bundleRef); appstreamGz = flatpak_bundle_ref_get_appstream(bundleRef); if (appstreamGz) { g_autoptr(GZlibDecompressor) decompressor = nullptr; g_autoptr(GInputStream) streamGz = nullptr; g_autoptr(GInputStream) streamData = nullptr; g_autoptr(GBytes) appstream = nullptr; /* decompress data */ decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); streamGz = g_memory_input_stream_new_from_bytes (appstreamGz); if (!streamGz) { return nullptr; } streamData = g_converter_input_stream_new (streamGz, G_CONVERTER (decompressor)); appstream = g_input_stream_read_bytes (streamData, 0x100000, m_cancellable, &localError); if (!appstream) { qWarning() << "Failed to extract appstream metadata from bundle:" << localError->message; return nullptr; } gsize len = 0; gconstpointer data = g_bytes_get_data(appstream, &len); AppStream::Metadata metadata; metadata.setFormatStyle(AppStream::Metadata::FormatStyleCollection); AppStream::Metadata::MetadataError error = metadata.parse(QString::fromUtf8((char*)data, len), AppStream::Metadata::FormatKindXml); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; return nullptr; } const QList components = metadata.components(); if (components.size()) { asComponent = AppStream::Component(components.first()); } else { qWarning() << "Failed to parse appstream metadata"; return nullptr; } } else { qWarning() << "No appstream metadata in bundle"; QTemporaryFile tempFile; tempFile.setAutoRemove(false); if (!tempFile.open()) { qWarning() << "Failed to get metadata file"; return nullptr; } gsize len = 0; QByteArray metadataContent = QByteArray((char *)g_bytes_get_data(metadata, &len)); tempFile.write(metadataContent); tempFile.close(); // Parse the temporary file QSettings setting(tempFile.fileName(), QSettings::NativeFormat); setting.beginGroup(QLatin1String("Application")); asComponent.setName(setting.value(QLatin1String("name")).toString()); tempFile.remove(); } FlatpakResource *resource = new FlatpakResource(asComponent, preferredInstallation(), this); gsize len = 0; QByteArray metadataContent = QByteArray((char *)g_bytes_get_data(metadata, &len)); if (!updateAppMetadata(resource, metadataContent)) { delete resource; qWarning() << "Failed to update metadata from app bundle"; return nullptr; } g_autoptr(GBytes) iconData = flatpak_bundle_ref_get_icon(bundleRef, 128); if (!iconData) { iconData = flatpak_bundle_ref_get_icon(bundleRef, 64); } if (iconData) { gsize len = 0; char * data = (char *)g_bytes_get_data(iconData, &len); QPixmap pixmap; pixmap.loadFromData(QByteArray(data, len), "PNG"); resource->setBundledIcon(pixmap); } const QString origin = QString::fromUtf8(flatpak_bundle_ref_get_origin(bundleRef)); resource->setDownloadSize(0); resource->setInstalledSize(flatpak_bundle_ref_get_installed_size(bundleRef)); resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::AlreadyKnown); resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::AlreadyKnown); resource->setFlatpakFileType(QStringLiteral("flatpak")); resource->setOrigin(origin.isEmpty() ? i18n("Local bundle") : origin); resource->setResourceFile(url); resource->setState(FlatpakResource::None); resource->setType(FlatpakResource::DesktopApp); addResource(resource); return resource; } FlatpakResource * FlatpakBackend::addAppFromFlatpakRef(const QUrl &url) { QSettings settings(url.toLocalFile(), QSettings::NativeFormat); const QString refurl = settings.value(QStringLiteral("Flatpak Ref/Url")).toString(); g_autoptr(GError) error = nullptr; g_autoptr(FlatpakRemoteRef) remoteRef = nullptr; { QFile f(url.toLocalFile()); if (!f.open(QFile::ReadOnly | QFile::Text)) { return nullptr; } QByteArray contents = f.readAll(); g_autoptr(GBytes) bytes = g_bytes_new (contents.data(), contents.size()); remoteRef = flatpak_installation_install_ref_file (preferredInstallation(), bytes, m_cancellable, &error); if (!remoteRef) { qWarning() << "Failed to create install ref file:" << error->message; const auto resources = resourcesByAppstreamName(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); if (!resources.isEmpty()) { return qobject_cast(resources.constFirst()); } return nullptr; } } const auto remoteName = flatpak_remote_ref_get_remote_name(remoteRef); auto ref = FLATPAK_REF(remoteRef); AppStream::Component asComponent; asComponent.addUrl(AppStream::Component::UrlKindHomepage, settings.value(QStringLiteral("Flatpak Ref/Homepage")).toString()); asComponent.setDescription(settings.value(QStringLiteral("Flatpak Ref/Description")).toString()); asComponent.setName(settings.value(QStringLiteral("Flatpak Ref/Title")).toString()); asComponent.setSummary(settings.value(QStringLiteral("Flatpak Ref/Comment")).toString()); asComponent.setId(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); const QString iconUrl = settings.value(QStringLiteral("Flatpak Ref/Icon")).toString(); if (!iconUrl.isEmpty()) { AppStream::Icon icon; icon.setKind(AppStream::Icon::KindRemote); icon.setUrl(QUrl(iconUrl)); asComponent.addIcon(icon); } auto resource = new FlatpakResource(asComponent, preferredInstallation(), this); resource->setFlatpakFileType(QStringLiteral("flatpakref")); resource->setOrigin(QString::fromUtf8(remoteName)); resource->updateFromRef(ref); QUrl runtimeUrl = QUrl(settings.value(QStringLiteral("Flatpak Ref/RuntimeRepo")).toString()); if (!runtimeUrl.isEmpty()) { auto installation = preferredInstallation(); // We need to fetch metadata to find information about required runtime auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchMetadata, installation, resource)); connect(fw, &QFutureWatcher::finished, this, [this, installation, resource, fw, runtimeUrl]() { const auto metadata = fw->result(); // Even when we failed to fetch information about runtime we still want to show the application if (metadata.isEmpty()) { onFetchMetadataFinished(installation, resource, metadata); } else { updateAppMetadata(resource, metadata); auto runtime = getRuntimeForApp(resource); if (!runtime || (runtime && !runtime->isInstalled())) { FlatpakFetchRemoteResourceJob *fetchRemoteResource = new FlatpakFetchRemoteResourceJob(runtimeUrl, this); connect(fetchRemoteResource, &FlatpakFetchRemoteResourceJob::jobFinished, this, [this, resource] (bool success, FlatpakResource *repoResource) { if (success) { installApplication(repoResource); } addResource(resource); }); fetchRemoteResource->start(); return; } else { addResource(resource); } } fw->deleteLater(); }); } else { addResource(resource); } return resource; } FlatpakResource * FlatpakBackend::addSourceFromFlatpakRepo(const QUrl &url) { Q_ASSERT(url.isLocalFile()); QSettings settings(url.toLocalFile(), QSettings::NativeFormat); const QString gpgKey = settings.value(QStringLiteral("Flatpak Repo/GPGKey")).toString(); const QString title = settings.value(QStringLiteral("Flatpak Repo/Title")).toString(); const QString repoUrl = settings.value(QStringLiteral("Flatpak Repo/Url")).toString(); if (gpgKey.isEmpty() || title.isEmpty() || repoUrl.isEmpty()) { return nullptr; } if (gpgKey.startsWith(QStringLiteral("http://")) || gpgKey.startsWith(QStringLiteral("https://"))) { return nullptr; } AppStream::Component asComponent; asComponent.addUrl(AppStream::Component::UrlKindHomepage, settings.value(QStringLiteral("Flatpak Repo/Homepage")).toString()); asComponent.setSummary(settings.value(QStringLiteral("Flatpak Repo/Comment")).toString()); asComponent.setDescription(settings.value(QStringLiteral("Flatpak Repo/Description")).toString()); asComponent.setName(title); asComponent.setId(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); const QString iconUrl = settings.value(QStringLiteral("Flatpak Repo/Icon")).toString(); if (!iconUrl.isEmpty()) { AppStream::Icon icon; icon.setKind(AppStream::Icon::KindRemote); icon.setUrl(QUrl(iconUrl)); asComponent.addIcon(icon); } auto resource = new FlatpakResource(asComponent, preferredInstallation(), this); // Use metadata only for stuff which are not common for all resources resource->addMetadata(QStringLiteral("gpg-key"), gpgKey); resource->addMetadata(QStringLiteral("repo-url"), repoUrl); resource->setBranch(settings.value(QStringLiteral("Flatpak Repo/DefaultBranch")).toString()); resource->setFlatpakName(url.fileName().remove(QStringLiteral(".flatpakrepo"))); resource->setType(FlatpakResource::Source); auto repo = flatpak_installation_get_remote_by_name(preferredInstallation(), resource->flatpakName().toUtf8().constData(), m_cancellable, nullptr); if (!repo) { resource->setState(AbstractResource::State::None); } else { resource->setState(AbstractResource::State::Installed); } return resource; } void FlatpakBackend::addResource(FlatpakResource *resource) { // Update app with all possible information we have if (!parseMetadataFromAppBundle(resource)) { qWarning() << "Failed to parse metadata from app bundle for" << resource->name(); } auto installation = resource->installation(); updateAppState(installation, resource); // This will update also metadata (required runtime) updateAppSize(installation, resource); m_resources.insert(resource->uniqueId(), resource); } class FlatpakSource { public: FlatpakSource(FlatpakRemote* remote) : m_remote(remote) {} bool isEnabled() const { return !flatpak_remote_get_disabled(m_remote); } QString appstreamDir() const { g_autoptr(GFile) appstreamDir = flatpak_remote_get_appstream_dir(m_remote, nullptr); if (!appstreamDir) { qWarning() << "No appstream dir for" << flatpak_remote_get_name(m_remote); return {}; } return QString::fromUtf8(g_file_get_path(appstreamDir)); } QString name() const { return QString::fromUtf8(flatpak_remote_get_name(m_remote)); } private: FlatpakRemote* m_remote; }; void FlatpakBackend::loadAppsFromAppstreamData() { for (auto installation : qAsConst(m_installations)) { // Load applications from appstream metadata if (!loadAppsFromAppstreamData(installation)) { qWarning() << "Failed to load packages from appstream data from installation" << installation; } } } bool FlatpakBackend::loadAppsFromAppstreamData(FlatpakInstallation *flatpakInstallation) { Q_ASSERT(flatpakInstallation); GPtrArray *remotes = flatpak_installation_list_remotes(flatpakInstallation, m_cancellable, nullptr); if (!remotes) { return false; } m_refreshAppstreamMetadataJobs += remotes->len; for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); g_autoptr(GFile) fileTimestamp = flatpak_remote_get_appstream_timestamp(remote, nullptr); QFileInfo fileInfo = QFileInfo(QString::fromUtf8(g_file_get_path(fileTimestamp))); // 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) { refreshAppstreamMetadata(flatpakInstallation, remote); } else { integrateRemote(flatpakInstallation, remote); } } return true; } void FlatpakBackend::integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote) { Q_ASSERT(m_refreshAppstreamMetadataJobs != 0); m_refreshAppstreamMetadataJobs--; FlatpakSource source(remote); if (!source.isEnabled() || flatpak_remote_get_noenumerate(remote)) { return; } const QString appstreamDirPath = source.appstreamDir(); const QString appstreamIconsPath = source.appstreamDir() + QLatin1String("/icons/"); const QString appDirFileName = appstreamDirPath + QLatin1String("/appstream.xml.gz"); if (!QFile::exists(appDirFileName)) { qWarning() << "No" << appDirFileName << "appstream metadata found for" << source.name(); return; } auto fw = new QFutureWatcher>(this); fw->setFuture(QtConcurrent::run(&m_threadPool, [appDirFileName]() -> QList { AppStream::Metadata metadata; metadata.setFormatStyle(AppStream::Metadata::FormatStyleCollection); AppStream::Metadata::MetadataError error = metadata.parseFile(appDirFileName, AppStream::Metadata::FormatKindXml); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; return {}; } return metadata.components(); })); const auto sourceName = source.name(); acquireFetching(true); connect(fw, &QFutureWatcher>::finished, this, [this, fw, flatpakInstallation, appstreamIconsPath, sourceName]() { const auto components = fw->result(); foreach (const AppStream::Component& appstreamComponent, components) { FlatpakResource *resource = new FlatpakResource(appstreamComponent, flatpakInstallation, this); resource->setIconPath(appstreamIconsPath); resource->setOrigin(sourceName); addResource(resource); } if (!m_refreshAppstreamMetadataJobs) { loadInstalledApps(); checkForUpdates(); } acquireFetching(false); fw->deleteLater(); }); } void FlatpakBackend::loadInstalledApps() { for (auto installation : qAsConst(m_installations)) { // Load installed applications and update existing resources with info from installed application if (!loadInstalledApps(installation)) { qWarning() << "Failed to load installed packages from installation" << installation; } } } bool FlatpakBackend::loadInstalledApps(FlatpakInstallation *flatpakInstallation) { Q_ASSERT(flatpakInstallation); g_autoptr(GError) localError = nullptr; g_autoptr(GPtrArray) refs = flatpak_installation_list_installed_refs(flatpakInstallation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates:" << localError->message; return false; } const QString pathExports = FlatpakResource::installationPath(flatpakInstallation) + QLatin1String("/exports/"); const QString pathApps = pathExports + QLatin1String("share/applications/"); for (uint i = 0; i < refs->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(refs, i)); if (flatpak_ref_get_kind(FLATPAK_REF(ref)) == FLATPAK_REF_KIND_RUNTIME) { continue; } const auto res = getAppForInstalledRef(flatpakInstallation, ref); if (res) { res->setState(AbstractResource::Installed); continue; } const auto name = QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))); AppStream::Metadata metadata; const QString fnDesktop = pathApps + name + QLatin1String(".desktop"); AppStream::Metadata::MetadataError error = metadata.parseFile(fnDesktop, AppStream::Metadata::FormatKindDesktopEntry); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error << fnDesktop; continue; } auto component = metadata.component(); component.setId(name + QLatin1String(".desktop")); FlatpakResource *resource = new FlatpakResource(component, flatpakInstallation, this); resource->setIconPath(pathExports); resource->setState(AbstractResource::Installed); resource->setOrigin(QString::fromUtf8(flatpak_installed_ref_get_origin(ref))); resource->updateFromRef(FLATPAK_REF(ref)); addResource(resource); } return true; } void FlatpakBackend::loadLocalUpdates(FlatpakInstallation *flatpakInstallation) { g_autoptr(GError) localError = nullptr; g_autoptr(GPtrArray) refs = nullptr; refs = flatpak_installation_list_installed_refs(flatpakInstallation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates:" << localError->message; return; } for (uint i = 0; i < refs->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(refs, i)); const gchar *latestCommit = flatpak_installed_ref_get_latest_commit(ref); if (!latestCommit) { qWarning() << "Couldn't get latest commit for" << flatpak_ref_get_name(FLATPAK_REF(ref)); continue; } const gchar *commit = flatpak_ref_get_commit(FLATPAK_REF(ref)); if (g_strcmp0(commit, latestCommit) == 0) { continue; } FlatpakResource *resource = getAppForInstalledRef(flatpakInstallation, ref); if (resource) { resource->setState(AbstractResource::Upgradeable); updateAppSize(flatpakInstallation, resource); } } } void FlatpakBackend::loadRemoteUpdates(FlatpakInstallation* installation) { auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run(&m_threadPool, [installation, this]() -> GPtrArray * { g_autoptr(GError) localError = nullptr; GPtrArray *refs = flatpak_installation_list_installed_refs_for_update(installation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; } return refs; })); connect(fw, &QFutureWatcher::finished, this, [this, installation, fw](){ auto refs = fw->result(); onFetchUpdatesFinished(installation, refs); fw->deleteLater(); }); } void FlatpakBackend::onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates) { if (!updates) { qWarning() << "could not get updates for" << flatpakInstallation; return; } g_autoptr(GPtrArray) fetchedUpdates = updates; for (uint i = 0; i < fetchedUpdates->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(fetchedUpdates, i)); FlatpakResource *resource = getAppForInstalledRef(flatpakInstallation, ref); if (resource) { resource->setState(AbstractResource::Upgradeable); updateAppSize(flatpakInstallation, resource); } } } bool FlatpakBackend::parseMetadataFromAppBundle(FlatpakResource *resource) { g_autoptr(FlatpakRef) ref = nullptr; g_autoptr(GError) localError = nullptr; AppStream::Bundle bundle = resource->appstreamComponent().bundle(AppStream::Bundle::KindFlatpak); // Get arch/branch/commit/name from FlatpakRef if (!bundle.isEmpty()) { ref = flatpak_ref_parse(bundle.id().toUtf8().constData(), &localError); if (!ref) { qWarning() << "Failed to parse" << bundle.id() << localError->message; return false; } else { resource->updateFromRef(ref); } } return true; } class FlatpakRefreshAppstreamMetadataJob : public QThread { Q_OBJECT public: FlatpakRefreshAppstreamMetadataJob(FlatpakInstallation *installation, FlatpakRemote *remote) : QThread() , m_cancellable(g_cancellable_new()) , m_installation(installation) , m_remote(remote) { connect(this, &FlatpakRefreshAppstreamMetadataJob::finished, this, &QObject::deleteLater); } ~FlatpakRefreshAppstreamMetadataJob() { g_object_unref(m_cancellable); } void cancel() { g_cancellable_cancel(m_cancellable); } void run() override { g_autoptr(GError) localError = nullptr; #if FLATPAK_CHECK_VERSION(0,9,4) // With Flatpak 0.9.4 we can use flatpak_installation_update_appstream_full_sync() providing progress reporting which we don't use at this moment, but still // better to use newer function in case the previous one gets deprecated if (!flatpak_installation_update_appstream_full_sync(m_installation, flatpak_remote_get_name(m_remote), nullptr, nullptr, nullptr, nullptr, m_cancellable, &localError)) { #else if (!flatpak_installation_update_appstream_sync(m_installation, flatpak_remote_get_name(m_remote), nullptr, nullptr, m_cancellable, &localError)) { #endif qWarning() << "Failed to refresh appstream metadata for " << flatpak_remote_get_name(m_remote) << ": " << (localError ? localError->message : ""); Q_EMIT jobRefreshAppstreamMetadataFailed(); } else { Q_EMIT jobRefreshAppstreamMetadataFinished(m_installation, m_remote); } } Q_SIGNALS: void jobRefreshAppstreamMetadataFailed(); void jobRefreshAppstreamMetadataFinished(FlatpakInstallation *installation, FlatpakRemote *remote); private: GCancellable *m_cancellable; FlatpakInstallation *m_installation; FlatpakRemote *m_remote; }; void FlatpakBackend::refreshAppstreamMetadata(FlatpakInstallation *installation, FlatpakRemote *remote) { FlatpakRefreshAppstreamMetadataJob *job = new FlatpakRefreshAppstreamMetadataJob(installation, remote); connect(job, &FlatpakRefreshAppstreamMetadataJob::jobRefreshAppstreamMetadataFailed, this, [this] () { m_refreshAppstreamMetadataJobs--; }); connect(job, &FlatpakRefreshAppstreamMetadataJob::jobRefreshAppstreamMetadataFinished, this, &FlatpakBackend::integrateRemote); job->start(); } bool FlatpakBackend::setupFlatpakInstallations(GError **error) { if (qEnvironmentVariableIsSet("FLATPAK_TEST_MODE")) { const QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/discover-flatpak-test"); qDebug() << "running flatpak backend on test mode" << path; g_autoptr(GFile) file = g_file_new_for_path(QFile::encodeName(path).constData()); m_installations << flatpak_installation_new_for_path(file, true, m_cancellable, error); return true; } GPtrArray *installations = flatpak_get_system_installations(m_cancellable, error); if (*error) { qWarning() << "Failed to call flatpak_get_system_installations:" << (*error)->message; } for (uint i = 0; installations && i < installations->len; i++) { m_installations << FLATPAK_INSTALLATION(g_ptr_array_index(installations, i)); } auto user = flatpak_installation_new_user(m_cancellable, error); if (user) { m_installations << user; } return !m_installations.isEmpty(); } void FlatpakBackend::updateAppInstalledMetadata(FlatpakInstalledRef *installedRef, FlatpakResource *resource) { // Update the rest resource->updateFromRef(FLATPAK_REF(installedRef)); resource->setInstalledSize(flatpak_installed_ref_get_installed_size(installedRef)); resource->setOrigin(QString::fromUtf8(flatpak_installed_ref_get_origin(installedRef))); if (resource->state() < AbstractResource::Installed) resource->setState(AbstractResource::Installed); } bool FlatpakBackend::updateAppMetadata(FlatpakInstallation* flatpakInstallation, FlatpakResource *resource) { g_autoptr(GFile) installationPath = nullptr; - if (resource->type() != FlatpakResource::DesktopApp) { + if (resource->resourceType() != FlatpakResource::DesktopApp) { return true; } installationPath = flatpak_installation_get_path(flatpakInstallation); const QString path = QString::fromUtf8(g_file_get_path(installationPath)) + QStringLiteral("/app/%1/%2/%3/active/metadata").arg(resource->flatpakName()).arg(resource->arch()).arg(resource->branch()); if (QFile::exists(path)) { return updateAppMetadata(resource, path); } else { auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchMetadata, flatpakInstallation, resource)); connect(fw, &QFutureWatcher::finished, this, [this, flatpakInstallation, resource, fw]() { const auto metadata = fw->result(); if (!metadata.isEmpty()) onFetchMetadataFinished(flatpakInstallation, resource, metadata); fw->deleteLater(); }); // Return false to indicate we cannot continue (right now used only in updateAppSize()) return false; } } void FlatpakBackend::onFetchMetadataFinished(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, const QByteArray &metadata) { updateAppMetadata(resource, metadata); // Right now we attempt to update metadata for calculating the size so call updateSizeFromRemote() // as it's what we want. In future if there are other reason to update metadata we will need to somehow // distinguish betwen these calls updateAppSizeFromRemote(flatpakInstallation, resource); } bool FlatpakBackend::updateAppMetadata(FlatpakResource *resource, const QString &path) { // Parse the temporary file QSettings setting(path, QSettings::NativeFormat); setting.beginGroup(QLatin1String("Application")); // Set the runtime in form of name/arch/version which can be later easily parsed resource->setRuntime(setting.value(QLatin1String("runtime")).toString()); // TODO get more information? return true; } bool FlatpakBackend::updateAppMetadata(FlatpakResource *resource, const QByteArray &data) { //We just find the runtime with a regex, QSettings only can read from disk (and so does KConfig) const QRegularExpression rx(QStringLiteral("runtime=(.*)")); const auto match = rx.match(QString::fromUtf8(data)); if (!match.isValid()) { return false; } resource->setRuntime(match.captured(1)); return true; } bool FlatpakBackend::updateAppSize(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { // Check if the size is already set, we should also distiguish between download and installed size, // right now it doesn't matter whether we get size for installed or not installed app, but if we // start making difference then for not installed app check download and install size separately if (resource->state() == AbstractResource::Installed) { // The size appears to be already set (from updateAppInstalledMetadata() apparently) if (resource->installedSize() > 0) { return true; } } else { if (resource->installedSize() > 0 && resource->downloadSize() > 0) { return true; } } // Check if we know the needed runtime which is needed for calculating the size if (resource->runtime().isEmpty()) { if (!updateAppMetadata(flatpakInstallation, resource)) { return false; } } return updateAppSizeFromRemote(flatpakInstallation, resource); } bool FlatpakBackend::updateAppSizeFromRemote(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { // Calculate the runtime size - if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { + if (resource->state() == AbstractResource::None && resource->resourceType() == FlatpakResource::DesktopApp) { auto runtime = getRuntimeForApp(resource); if (runtime) { // Re-check runtime state if case a new one was created updateAppState(flatpakInstallation, runtime); if (!runtime->isInstalled()) { if (!updateAppSize(flatpakInstallation, runtime)) { qWarning() << "Failed to get runtime size needed for total size of" << resource->name(); return false; } // Set required download size to include runtime size even now, in case we fail to // get the app size (e.g. when installing bundles where download size is 0) resource->setDownloadSize(runtime->downloadSize()); } } } if (resource->state() == AbstractResource::Installed) { g_autoptr(FlatpakInstalledRef) ref = nullptr; ref = getInstalledRefForApp(flatpakInstallation, resource); if (!ref) { qWarning() << "Failed to get installed size of" << resource->name(); return false; } resource->setInstalledSize(flatpak_installed_ref_get_installed_size(ref)); } else { if (resource->origin().isEmpty()) { qWarning() << "Failed to get size of" << resource->name() << " because of missing origin"; return false; } auto futureWatcher = new QFutureWatcher(this); futureWatcher->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchFlatpakSize, flatpakInstallation, resource)); connect(futureWatcher, &QFutureWatcher::finished, this, [this, resource, futureWatcher]() { auto value = futureWatcher->result(); if (value.valid) { onFetchSizeFinished(resource, value.downloadSize, value.installedSize); } else { resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::UnknownOrFailed); resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::UnknownOrFailed); } futureWatcher->deleteLater(); }); } return true; } void FlatpakBackend::onFetchSizeFinished(FlatpakResource *resource, guint64 downloadSize, guint64 installedSize) { FlatpakResource *runtime = nullptr; - if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { + if (resource->state() == AbstractResource::None && resource->resourceType() == FlatpakResource::DesktopApp) { runtime = getRuntimeForApp(resource); } if (runtime && !runtime->isInstalled()) { resource->setDownloadSize(runtime->downloadSize() + downloadSize); resource->setInstalledSize(installedSize); } else { resource->setDownloadSize(downloadSize); resource->setInstalledSize(installedSize); } } void FlatpakBackend::updateAppState(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { FlatpakInstalledRef *ref = getInstalledRefForApp(flatpakInstallation, resource); if (ref) { // If the app is installed, we can set information about commit, arch etc. updateAppInstalledMetadata(ref, resource); } else { // TODO check if the app is actuall still available resource->setState(AbstractResource::None); } } void FlatpakBackend::acquireFetching(bool f) { if (f) m_isFetching++; else m_isFetching--; if ((!f && m_isFetching==0) || (f && m_isFetching==1)) { emit fetchingChanged(); } if (m_isFetching==0) Q_EMIT initialized(); } int FlatpakBackend::updatesCount() const { return m_updater->updatesCount(); } bool FlatpakBackend::flatpakResourceLessThan(AbstractResource* l, AbstractResource* r) const { return (l->isInstalled() != r->isInstalled()) ? l->isInstalled() : (l->origin() != r->origin()) ? m_sources->originIndex(l->origin()) < m_sources->originIndex(r->origin()) : l < r; } ResultsStream * FlatpakBackend::search(const AbstractResourcesBackend::Filters &filter) { if (filter.resourceUrl.fileName().endsWith(QLatin1String(".flatpakrepo")) || filter.resourceUrl.fileName().endsWith(QLatin1String(".flatpakref"))) { auto stream = new ResultsStream(QStringLiteral("FlatpakStream-http-")+filter.resourceUrl.fileName()); FlatpakFetchRemoteResourceJob *fetchResourceJob = new FlatpakFetchRemoteResourceJob(filter.resourceUrl, this); connect(fetchResourceJob, &FlatpakFetchRemoteResourceJob::jobFinished, this, [fetchResourceJob, stream] (bool success, FlatpakResource *resource) { if (success) { stream->resourcesFound({resource}); } stream->finish(); fetchResourceJob->deleteLater(); }); fetchResourceJob->start(); return stream; } else if(filter.resourceUrl.scheme() == QLatin1String("appstream")) { return findResourceByPackageName(filter.resourceUrl); } else if (!filter.resourceUrl.isEmpty() || !filter.extends.isEmpty()) return new ResultsStream(QStringLiteral("FlatpakStream-void"), {}); auto stream = new ResultsStream(QStringLiteral("FlatpakStream")); auto f = [this, stream, filter] () { QVector ret; foreach(AbstractResource* r, m_resources) { - if (r->isTechnical() && filter.state != AbstractResource::Upgradeable) { + if (r->type() == AbstractResource::Technical && filter.state != AbstractResource::Upgradeable) { continue; } if (r->state() < filter.state) continue; if (filter.search.isEmpty() || r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) { ret += r; } } auto f = [this](AbstractResource* l, AbstractResource* r) { return flatpakResourceLessThan(l,r); }; std::sort(ret.begin(), ret.end(), f); if (!ret.isEmpty()) Q_EMIT stream->resourcesFound(ret); stream->finish(); }; if (isFetching()) { connect(this, &FlatpakBackend::initialized, stream, f); } else { QTimer::singleShot(0, this, f); } return stream; } QVector FlatpakBackend::resourcesByAppstreamName(const QString& name) const { QVector resources; foreach(FlatpakResource* res, m_resources) { if (QString::compare(res->appstreamId(), name, Qt::CaseInsensitive)==0 || QString::compare(res->appstreamId(), name + QLatin1String(".desktop"), Qt::CaseInsensitive)==0) resources << res; } auto f = [this](AbstractResource* l, AbstractResource* r) { return flatpakResourceLessThan(l, r); }; std::sort(resources.begin(), resources.end(), f); return resources; } ResultsStream * FlatpakBackend::findResourceByPackageName(const QUrl &url) { if (url.scheme() == QLatin1String("appstream")) { if (url.host().isEmpty()) passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else { auto stream = new ResultsStream(QStringLiteral("FlatpakStream")); auto f = [this, stream, url] () { const auto resources = resourcesByAppstreamName(url.host()); if (!resources.isEmpty()) Q_EMIT stream->resourcesFound(resources); stream->finish(); }; if (isFetching()) { connect(this, &FlatpakBackend::initialized, stream, f); } else { QTimer::singleShot(0, this, f); } return stream; } } return new ResultsStream(QStringLiteral("FlatpakStream-packageName-void"), {}); } AbstractBackendUpdater * FlatpakBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend * FlatpakBackend::reviewsBackend() const { return m_reviews.data(); } Transaction* FlatpakBackend::installApplication(AbstractResource *app, const AddonList &addons) { Q_UNUSED(addons); FlatpakResource *resource = qobject_cast(app); - if (resource->type() == FlatpakResource::Source) { + if (resource->resourceType() == FlatpakResource::Source) { // Let source backend handle this FlatpakRemote *remote = m_sources->installSource(resource); if (remote) { resource->setState(AbstractResource::Installed); m_refreshAppstreamMetadataJobs++; // 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 refreshAppstreamMetadata(preferredInstallation(), flatpak_installation_get_remote_by_name(preferredInstallation(), flatpak_remote_get_name(remote), nullptr, nullptr)); } return nullptr; } FlatpakJobTransaction *transaction = nullptr; FlatpakInstallation *installation = resource->installation(); - if (resource->propertyState(FlatpakResource::RequiredRuntime) == FlatpakResource::NotKnownYet && resource->type() == FlatpakResource::DesktopApp) { + if (resource->propertyState(FlatpakResource::RequiredRuntime) == FlatpakResource::NotKnownYet && resource->resourceType() == FlatpakResource::DesktopApp) { transaction = new FlatpakJobTransaction(resource, Transaction::InstallRole, true); connect(resource, &FlatpakResource::propertyStateChanged, [resource, transaction, this] (FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState state) { if (kind != FlatpakResource::RequiredRuntime) { return; } if (state == FlatpakResource::AlreadyKnown) { FlatpakResource *runtime = getRuntimeForApp(resource); if (runtime && !runtime->isInstalled()) { transaction->setRuntime(runtime); } } transaction->start(); }); } else { FlatpakResource *runtime = getRuntimeForApp(resource); if (runtime && !runtime->isInstalled()) { transaction = new FlatpakJobTransaction(resource, runtime, Transaction::InstallRole); } else { transaction = new FlatpakJobTransaction(resource, Transaction::InstallRole); } } connect(transaction, &FlatpakJobTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppState(installation, resource); } }); return transaction; } Transaction* FlatpakBackend::installApplication(AbstractResource *app) { return installApplication(app, {}); } Transaction* FlatpakBackend::removeApplication(AbstractResource *app) { FlatpakResource *resource = qobject_cast(app); - if (resource->type() == FlatpakResource::Source) { + if (resource->resourceType() == FlatpakResource::Source) { // Let source backend handle this if (m_sources->removeSource(resource->flatpakName())) { resource->setState(AbstractResource::None); } return nullptr; } FlatpakInstallation *installation = resource->installation(); FlatpakJobTransaction *transaction = new FlatpakJobTransaction(resource, Transaction::RemoveRole); connect(transaction, &FlatpakJobTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppSize(installation, resource); } }); return transaction; } void FlatpakBackend::checkForUpdates() { for (auto installation : qAsConst(m_installations)) { // Load local updates, comparing current and latest commit loadLocalUpdates(installation); // Load updates from remote repositories loadRemoteUpdates(installation); } } AbstractResource * FlatpakBackend::resourceForFile(const QUrl &url) { if (!url.isLocalFile()) { return nullptr; } FlatpakResource *resource = nullptr; if (url.path().endsWith(QLatin1String(".flatpak"))) { resource = addAppFromFlatpakBundle(url); } else if (url.path().endsWith(QLatin1String(".flatpakref"))) { resource = addAppFromFlatpakRef(url); } else if (url.path().endsWith(QLatin1String(".flatpakrepo"))) { resource = addSourceFromFlatpakRepo(url); } return resource; } QString FlatpakBackend::displayName() const { return QStringLiteral("Flatpak"); } #include "FlatpakBackend.moc" diff --git a/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp b/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp index 86a69d54..a9d4f4fa 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp @@ -1,540 +1,540 @@ /*************************************************************************** * Copyright © 2013 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 "FlatpakResource.h" #include "FlatpakBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QString iconCachePath(const AppStream::Icon &icon) { Q_ASSERT(icon.kind() == AppStream::Icon::KindRemote); return QStringLiteral("%1/icons/%2").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).arg(icon.url().fileName()); } FlatpakResource::FlatpakResource(const AppStream::Component &component, FlatpakInstallation* installation, FlatpakBackend *parent) : AbstractResource(parent) , m_appdata(component) , m_id({installation, QString(), FlatpakResource::DesktopApp, component.id(), QString(), QString() }) , m_downloadSize(0) , m_installedSize(0) , m_propertyStates({{DownloadSize, NotKnownYet}, {InstalledSize, NotKnownYet},{RequiredRuntime, NotKnownYet}}) , m_state(AbstractResource::None) { setObjectName(packageName()); // Start fetching remote icons during initialization const auto icons = m_appdata.icons(); if (!icons.isEmpty()) { foreach (const AppStream::Icon &icon, icons) { if (icon.kind() == AppStream::Icon::KindRemote) { const QString fileName = iconCachePath(icon); if (!QFileInfo::exists(fileName)) { const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); // Create $HOME/.cache/discover/icons folder cacheDir.mkdir(QStringLiteral("icons")); QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, [this, icon, fileName, manager] (QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray iconData = reply->readAll(); QFile file(fileName); if (file.open(QIODevice::WriteOnly)) { file.write(iconData); } file.close(); Q_EMIT iconChanged(); } manager->deleteLater(); }); manager->get(QNetworkRequest(icon.url())); } } } } } AppStream::Component FlatpakResource::appstreamComponent() const { return m_appdata; } QList FlatpakResource::addonsInformation() { return {}; } QString FlatpakResource::availableVersion() const { QString theBranch = branch(); if (theBranch.isEmpty()) { theBranch = i18n("Unknown"); } if (!m_appdata.releases().isEmpty()) { auto release = m_appdata.releases().constFirst(); return i18n("%1 (%2)", release.version(), theBranch); } return theBranch; } QString FlatpakResource::appstreamId() const { return m_id.id; } QString FlatpakResource::arch() const { return m_id.arch; } QString FlatpakResource::branch() const { return m_id.branch; } bool FlatpakResource::canExecute() const { return (m_id.type == DesktopApp && (m_state == AbstractResource::Installed || m_state == AbstractResource::Upgradeable)); } void FlatpakResource::updateFromRef(FlatpakRef* ref) { setArch(QString::fromUtf8(flatpak_ref_get_arch(ref))); setBranch(QString::fromUtf8(flatpak_ref_get_branch(ref))); setCommit(QString::fromUtf8(flatpak_ref_get_commit(ref))); setFlatpakName(QString::fromUtf8(flatpak_ref_get_name(ref))); setType(flatpak_ref_get_kind(ref) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime); setObjectName(packageName()); } QStringList FlatpakResource::categories() { auto cats = m_appdata.categories(); if (m_appdata.kind() != AppStream::Component::KindAddon) cats.append(QStringLiteral("Application")); return cats; } QString FlatpakResource::comment() { const auto summary = m_appdata.summary(); if (!summary.isEmpty()) { return summary; } return QString(); } QString FlatpakResource::commit() const { return m_commit; } int FlatpakResource::downloadSize() const { return m_downloadSize; } QVariant FlatpakResource::icon() const { QIcon ret; const auto icons = m_appdata.icons(); if (!m_bundledIcon.isNull()) { ret = QIcon(m_bundledIcon); } else if (icons.isEmpty()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } else foreach(const AppStream::Icon &icon, icons) { switch (icon.kind()) { case AppStream::Icon::KindLocal: case AppStream::Icon::KindCached: { const QString path = m_iconPath + icon.url().path(); if (QFileInfo::exists(path)) { ret.addFile(path, icon.size()); } else { const QString altPath = m_iconPath + QStringLiteral("%1x%2/").arg(icon.size().width()).arg(icon.size().height()) + icon.url().path(); if (QFileInfo::exists(altPath)) { ret.addFile(altPath, icon.size()); } } } break; case AppStream::Icon::KindStock: { const auto ret = QIcon::fromTheme(icon.name()); if (!ret.isNull()) return ret; break; } case AppStream::Icon::KindRemote: { const QString fileName = iconCachePath(icon); if (QFileInfo::exists(fileName)) { ret.addFile(fileName, icon.size()); } break; } case AppStream::Icon::KindUnknown: break; } } if (ret.isNull()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } return ret; } QString FlatpakResource::installedVersion() const { // TODO check if there is actually version available QString version = branch(); if (version.isEmpty()) { version = i18n("Unknown"); } return version; } int FlatpakResource::installedSize() const { return m_installedSize; } -bool FlatpakResource::isTechnical() const +AbstractResource::Type FlatpakResource::type() const { - return m_id.type == FlatpakResource::Runtime; + return m_id.type == FlatpakResource::Runtime ? Technical : Application; } QUrl FlatpakResource::homepage() { return m_appdata.url(AppStream::Component::UrlKindHomepage); } QUrl FlatpakResource::helpURL() { return m_appdata.url(AppStream::Component::UrlKindHelp); } QUrl FlatpakResource::bugURL() { return m_appdata.url(AppStream::Component::UrlKindBugtracker); } QUrl FlatpakResource::donationURL() { return m_appdata.url(AppStream::Component::UrlKindDonation); } QString FlatpakResource::flatpakFileType() const { return m_flatpakFileType; } QString FlatpakResource::flatpakName() const { // If the flatpak name is not known (known only for installed apps), then use // appstream id instead; if (m_flatpakName.isEmpty()) { return m_id.id; } return m_flatpakName; } QString FlatpakResource::license() { return m_appdata.projectLicense(); } QString FlatpakResource::longDescription() { return m_appdata.description(); } QString FlatpakResource::name() const { QString name = m_appdata.name(); if (name.isEmpty()) { name = flatpakName(); } if (name.startsWith(QLatin1String("(Nightly) "))) { return name.mid(10); } return name; } QString FlatpakResource::origin() const { return m_id.origin; } QString FlatpakResource::packageName() const { return flatpakName() + QLatin1Char('/') + arch() + QLatin1Char('/') + branch(); } FlatpakResource::PropertyState FlatpakResource::propertyState(FlatpakResource::PropertyKind kind) const { return m_propertyStates[kind]; } QUrl FlatpakResource::resourceFile() const { return m_resourceFile; } QString FlatpakResource::runtime() const { return m_runtime; } QString FlatpakResource::section() { return QString(); } int FlatpakResource::size() { if (m_state == Installed) { return m_installedSize; } else { return m_downloadSize; } } QString FlatpakResource::sizeDescription() { KFormat f; if (!isInstalled() || canUpgrade()) { if (propertyState(DownloadSize) == NotKnownYet || propertyState(InstalledSize) == NotKnownYet) { return i18n("Retrieving size information"); } else if (propertyState(DownloadSize) == UnknownOrFailed || propertyState(InstalledSize) == UnknownOrFailed) { return i18n("Unknown size"); } else { return i18nc("@info app size", "%1 to download, %2 on disk", f.formatByteSize(downloadSize()), f.formatByteSize(installedSize())); } } else { if (propertyState(InstalledSize) == NotKnownYet) { return i18n("Retrieving size information"); } else if (propertyState(InstalledSize) == UnknownOrFailed) { return i18n("Unknown size"); } else { return i18nc("@info app size", "%1 on disk", f.formatByteSize(installedSize())); } } } AbstractResource::State FlatpakResource::state() { return m_state; } -FlatpakResource::ResourceType FlatpakResource::type() const +FlatpakResource::ResourceType FlatpakResource::resourceType() const { return m_id.type; } QString FlatpakResource::typeAsString() const { switch (m_id.type) { case FlatpakResource::DesktopApp: case FlatpakResource::Source: return QLatin1String("app"); case FlatpakResource::Runtime: return QLatin1String("runtime"); default: return QLatin1String("app"); } } FlatpakResource::Id FlatpakResource::uniqueId() const { return m_id; } void FlatpakResource::invokeApplication() const { g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) localError = nullptr; if (!flatpak_installation_launch(m_id.installation, flatpakName().toUtf8().constData(), arch().toUtf8().constData(), branch().toUtf8().constData(), nullptr, cancellable, &localError)) { qWarning() << "Failed to launch " << m_appdata.name() << ": " << localError->message; } } void FlatpakResource::fetchChangelog() { emit changelogFetched(AppStreamUtils::changelogToHtml(m_appdata)); } void FlatpakResource::fetchScreenshots() { const auto sc = AppStreamUtils::fetchScreenshots(m_appdata); Q_EMIT screenshotsFetched(sc.first, sc.second); } void FlatpakResource::setArch(const QString &arch) { m_id.arch = arch; } void FlatpakResource::setBranch(const QString &branch) { m_id.branch = branch; } void FlatpakResource::setBundledIcon(const QPixmap &pixmap) { m_bundledIcon = pixmap; } void FlatpakResource::setCommit(const QString &commit) { m_commit = commit; } void FlatpakResource::setDownloadSize(int size) { m_downloadSize = size; setPropertyState(DownloadSize, AlreadyKnown); Q_EMIT sizeChanged(); } void FlatpakResource::setFlatpakFileType(const QString &fileType) { m_flatpakFileType = fileType; } void FlatpakResource::setFlatpakName(const QString &name) { m_flatpakName = name; } void FlatpakResource::setIconPath(const QString &path) { m_iconPath = path; } void FlatpakResource::setInstalledSize(int size) { m_installedSize = size; setPropertyState(InstalledSize, AlreadyKnown); Q_EMIT sizeChanged(); } void FlatpakResource::setOrigin(const QString &origin) { m_id.origin = origin; } void FlatpakResource::setPropertyState(FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState newState) { auto & state = m_propertyStates[kind]; if (state != newState) { state = newState; Q_EMIT propertyStateChanged(kind, newState); } } void FlatpakResource::setResourceFile(const QUrl &url) { m_resourceFile = url; } void FlatpakResource::setRuntime(const QString &runtime) { m_runtime = runtime; setPropertyState(RequiredRuntime, AlreadyKnown); } void FlatpakResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; if (!backend()->isFetching()) Q_EMIT stateChanged(); } } void FlatpakResource::setType(FlatpakResource::ResourceType type) { m_id.type = type; } QString FlatpakResource::installationPath() const { return installationPath(m_id.installation); } QString FlatpakResource::installationPath(FlatpakInstallation* flatpakInstallation) { g_autoptr(GFile) path = flatpak_installation_get_path(flatpakInstallation); return QString::fromUtf8(g_file_get_path(path)); } QUrl FlatpakResource::url() const { return m_resourceFile.isEmpty() ? QUrl(QStringLiteral("appstream://") + appstreamId()) : m_resourceFile; } QDate FlatpakResource::releaseDate() const { if (!m_appdata.releases().isEmpty()) { auto release = m_appdata.releases().constFirst(); return release.timestamp().date(); } return {}; } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakResource.h b/libdiscover/backends/FlatpakBackend/FlatpakResource.h index c57b3666..39ac176c 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakResource.h +++ b/libdiscover/backends/FlatpakBackend/FlatpakResource.h @@ -1,187 +1,187 @@ /*************************************************************************** * Copyright © 2013 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 . * ***************************************************************************/ #ifndef FLATPAKRESOURCE_H #define FLATPAKRESOURCE_H #include extern "C" { #include } #include #include class AddonList; class FlatpakBackend; class FlatpakResource : public AbstractResource { Q_OBJECT public: explicit FlatpakResource(const AppStream::Component &component, FlatpakInstallation* installation, FlatpakBackend *parent); enum PropertyKind { DownloadSize = 0, InstalledSize, RequiredRuntime }; enum PropertyState { NotKnownYet = 0, AlreadyKnown, UnknownOrFailed, }; enum ResourceType { DesktopApp = 0, Runtime, Source }; struct Id { FlatpakInstallation * installation; QString origin; FlatpakResource::ResourceType type; QString id; QString branch; QString arch; bool operator!=(const Id& other) const { return !operator==(other); } bool operator==(const Id& other) const { return &other == this || ( other.installation == installation && other.origin == origin && other.type == type && other.id == id && other.branch == branch && other.arch == arch ); } }; static QString typeAsString(ResourceType type) { if (type == DesktopApp) { return QLatin1String("app"); } return QLatin1String("runtime"); } QString installationPath() const; static QString installationPath(FlatpakInstallation* installation); AppStream::Component appstreamComponent() const; QList addonsInformation() override; QString availableVersion() const override; QString appstreamId() const override; QString arch() const; QString branch() const; bool canExecute() const override; QStringList categories() override; QString comment() override; QString commit() const; int downloadSize() const; QVariant icon() const override; QString installedVersion() const override; int installedSize() const; - bool isTechnical() const override; + AbstractResource::Type type() const override; QUrl homepage() override; QUrl helpURL() override; QUrl bugURL() override; QUrl donationURL() override; QString flatpakFileType() const; QString flatpakName() const; QString license() override; QString longDescription() override; QString name() const override; QString origin() const override; QString packageName() const override; PropertyState propertyState(PropertyKind kind) const; QUrl resourceFile() const; QString runtime() const; QString section() override; int size() override; QString sizeDescription() override; AbstractResource::State state() override; - ResourceType type() const; + ResourceType resourceType() const; QString typeAsString() const; FlatpakResource::Id uniqueId() const; QUrl url() const override; QDate releaseDate() const override; FlatpakInstallation* installation() const { return m_id.installation; } void invokeApplication() const override; void fetchChangelog() override; void fetchScreenshots() override; void setBranch(const QString &branch); void setBundledIcon(const QPixmap &pixmap); void setDownloadSize(int size); void setIconPath(const QString &path); void setInstalledSize(int size); void setFlatpakFileType(const QString &fileType); void setFlatpakName(const QString &name); void setOrigin(const QString &origin); void setPropertyState(PropertyKind kind, PropertyState state); void setResourceFile(const QUrl &url); void setRuntime(const QString &runtime); void setState(State state); void setType(ResourceType type); // void setAddons(const AddonList& addons); // void setAddonInstalled(const QString& addon, bool installed); void updateFromRef(FlatpakRef* ref); QString sourceIcon() const override { return QStringLiteral("https://flatpak.org/img/logo.svg"); } Q_SIGNALS: void propertyStateChanged(PropertyKind kind, PropertyState state); private: void setArch(const QString &arch); void setCommit(const QString &commit); const AppStream::Component m_appdata; FlatpakResource::Id m_id; FlatpakRefKind m_flatpakRefKind; QPixmap m_bundledIcon; QString m_commit; int m_downloadSize; QString m_flatpakFileType; QString m_flatpakName; QString m_iconPath; int m_installedSize; QHash m_propertyStates; QUrl m_resourceFile; QString m_runtime; AbstractResource::State m_state; }; inline uint qHash(const FlatpakResource::Id &key) { return qHash(key.installation) ^ qHash(key.origin) ^ qHash(key.type) ^ qHash(key.id) ^ qHash(key.branch) ^ qHash(key.arch) ; } #endif // FLATPAKRESOURCE_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakTransactionThread.cpp b/libdiscover/backends/FlatpakBackend/FlatpakTransactionThread.cpp index c8f12938..48530b68 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakTransactionThread.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakTransactionThread.cpp @@ -1,179 +1,179 @@ /*************************************************************************** * 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 "FlatpakTransactionThread.h" #include "FlatpakResource.h" #include static void flatpakInstallationProgressCallback(const gchar *stats, guint progress, gboolean estimating, gpointer userData) { Q_UNUSED(estimating); Q_UNUSED(stats); FlatpakTransactionThread *transactionJob = (FlatpakTransactionThread*)userData; if (!transactionJob) { return; } transactionJob->setProgress(progress); } FlatpakTransactionThread::FlatpakTransactionThread(FlatpakResource *app, const QPair &relatedRef, Transaction::Role role) : QThread() , m_result(false) , m_progress(0) , m_relatedRef(relatedRef.first) , m_relatedRefKind(relatedRef.second) , m_app(app) , m_role(role) { m_cancellable = g_cancellable_new(); } FlatpakTransactionThread::~FlatpakTransactionThread() { g_object_unref(m_cancellable); } void FlatpakTransactionThread::cancel() { g_cancellable_cancel(m_cancellable); } void FlatpakTransactionThread::run() { g_autoptr(GError) localError = nullptr; g_autoptr(FlatpakInstalledRef) ref = nullptr; const QString refName = m_relatedRef.isEmpty() ? m_app->flatpakName() : m_relatedRef; - const uint kind = m_relatedRef.isEmpty() ? (uint)m_app->type() : m_relatedRefKind; + const uint kind = m_relatedRef.isEmpty() ? (uint)m_app->resourceType() : m_relatedRefKind; if (m_role == Transaction::Role::InstallRole) { bool installRelatedRef = false; // Before we attempt to upgrade related refs we should verify whether they are installed in first place if (m_app->state() == AbstractResource::Upgradeable && !m_relatedRef.isEmpty()) { g_autoptr(GError) installedRefError = nullptr; FlatpakInstalledRef *installedRef = flatpak_installation_get_installed_ref(m_app->installation(), kind == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME, refName.toUtf8().constData(), m_app->arch().toUtf8().constData(), m_app->branch().toUtf8().constData(), m_cancellable, &installedRefError); if (installedRefError) { qWarning() << "Failed to check whether related ref is installed: " << installedRefError; } installRelatedRef = installedRef == nullptr; } if (m_app->state() == AbstractResource::Upgradeable && !installRelatedRef) { ref = flatpak_installation_update(m_app->installation(), FLATPAK_UPDATE_FLAGS_NONE, kind == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME, refName.toUtf8().constData(), m_app->arch().toUtf8().constData(), m_app->branch().toUtf8().constData(), flatpakInstallationProgressCallback, this, m_cancellable, &localError); } else { if (m_app->flatpakFileType() == QStringLiteral("flatpak")) { g_autoptr(GFile) file = g_file_new_for_path(m_app->resourceFile().toLocalFile().toUtf8().constData()); if (!file) { qWarning() << "Failed to install bundled application" << refName; } ref = flatpak_installation_install_bundle(m_app->installation(), file, flatpakInstallationProgressCallback, this, m_cancellable, &localError); } else { ref = flatpak_installation_install(m_app->installation(), m_app->origin().toUtf8().constData(), kind == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME, refName.toUtf8().constData(), m_app->arch().toUtf8().constData(), m_app->branch().toUtf8().constData(), flatpakInstallationProgressCallback, this, m_cancellable, &localError); } } if (!ref) { m_result = false; m_errorMessage = QString::fromUtf8(localError->message); // We are done so we can set the progress to 100 setProgress(100); qWarning() << "Failed to install" << refName << ':' << m_errorMessage; return; } } else if (m_role == Transaction::Role::RemoveRole) { if (!flatpak_installation_uninstall(m_app->installation(), kind == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME, refName.toUtf8().constData(), m_app->arch().toUtf8().constData(), m_app->branch().toUtf8().constData(), flatpakInstallationProgressCallback, this, m_cancellable, &localError)) { m_result = false; m_errorMessage = QString::fromUtf8(localError->message); // We are done so we can set the progress to 100 setProgress(100); qWarning() << "Failed to uninstall" << refName << ':' << m_errorMessage; return; } } // We are done so we can set the progress to 100 m_result = true; setProgress(100); } FlatpakResource * FlatpakTransactionThread::app() const { return m_app; } bool FlatpakTransactionThread::isRelated() const { return !m_relatedRef.isEmpty(); } int FlatpakTransactionThread::progress() const { return m_progress; } void FlatpakTransactionThread::setProgress(int progress) { if (m_progress != progress) { m_progress = progress; Q_EMIT progressChanged(m_progress); } } QString FlatpakTransactionThread::errorMessage() const { return m_errorMessage; } bool FlatpakTransactionThread::result() const { return m_result; } diff --git a/libdiscover/backends/FwupdBackend/FwupdResource.h b/libdiscover/backends/FwupdBackend/FwupdResource.h index 6edc067d..41a6f979 100644 --- a/libdiscover/backends/FwupdBackend/FwupdResource.h +++ b/libdiscover/backends/FwupdBackend/FwupdResource.h @@ -1,117 +1,117 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * 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 . * ***************************************************************************/ #ifndef FWUPDRESOURCE_H #define FWUPDRESOURCE_H #include "FwupdBackend.h" #include #include class AddonList; class FwupdResource : public AbstractResource { Q_OBJECT public: explicit FwupdResource(QString name, AbstractResourcesBackend* parent); QList addonsInformation() override; QString section() override; QString origin() const override; QString longDescription() override; QString availableVersion() const override; QString installedVersion() const override; QString license() override; int size() override; QUrl homepage() override; QUrl helpURL() override; QUrl bugURL() override; QUrl donationURL() override; QStringList categories() override; AbstractResource::State state() override; QVariant icon() const override; QString comment() override; QString name() const override; QString packageName() const override; QString vendor() const; - bool isTechnical() const override { return true; } + AbstractResource::Type type() const override { return Technical; } bool canExecute() const override { return false; } void invokeApplication() const override; void fetchChangelog() override; QUrl url() const override; QString executeLabel() const override; void setState(State state); void setSize(int size) { m_size = size; } void setAddons(const AddonList& addons); void setId(const QString &id){m_id = id;} void setName(const QString &name){ m_name = name;} void setSummary(const QString &summary){ m_summary = summary;} void setDescription(const QString &description){ m_description = description;} void setVersion(const QString &version){ m_version = version;} void setVendor(const QString &vendor){ m_vendor = vendor;} void setHomePage(const QUrl &homepage){ m_homepage = homepage;} void setLicense(const QString &license){ m_license = license;} void setIconName(const QString &iconName){ m_iconName = iconName;} void setReleaseDate(const QDate &date){ m_releaseDate = date;} void setOrigin(const QString &origin){ m_origin = origin;} virtual QStringList allResourceNames() const; void setIsDeviceLocked(bool status){ isDeviceLocked = status;} void setDeviceID(const QString &deviceID){ m_deviceID = deviceID;} void setUpdateURI(const QString &updateURI){m_updateURI = updateURI;} void setAddonInstalled(const QString& addon, bool installed); QString sourceIcon() const override { return m_iconName; } QDate releaseDate() const override { return m_releaseDate; } public: QString m_id; QString m_name; QString m_summary; QString m_description; QString m_version; QString m_vendor; QStringList m_categories; QString m_license; QDate m_releaseDate; AbstractResource::State m_state; QUrl m_homepage; QString m_iconName; QList m_addons; int m_size; QString m_deviceID; QString m_updateURI; QString m_file; bool isDeviceLocked = false; // True if device is locked! bool isOnlyOffline = false; // True if only offline updates bool isLiveUpdatable = false; // True if device is live updatable bool needsReboot = false; // True if device needs Reboot bool isDeviceRemoval = false; //True if device is Removal bool needsBootLoader = false; //True if BootLoader Required QString guidString; QString m_origin; }; #endif // FWUPDRESOURCE_H diff --git a/libdiscover/backends/KNSBackend/KNSResource.h b/libdiscover/backends/KNSBackend/KNSResource.h index 720eff7e..1d287233 100644 --- a/libdiscover/backends/KNSBackend/KNSResource.h +++ b/libdiscover/backends/KNSBackend/KNSResource.h @@ -1,83 +1,84 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef KNSRESOURCE_H #define KNSRESOURCE_H #include #include #include #include #include "discovercommon_export.h" class KNSBackend; class DISCOVERCOMMON_EXPORT KNSResource : public AbstractResource { Q_OBJECT public: explicit KNSResource(const KNSCore::EntryInternal & c, QStringList categories, KNSBackend* parent); ~KNSResource() override; AbstractResource::State state() override; QVariant icon() const override; QString comment() override; QString name() const override; QString packageName() const override; QStringList categories() override; QUrl homepage() override; QString license() override; QString longDescription() override; QList addonsInformation() override { return QList(); } QString availableVersion() const override; QString installedVersion() const override; QString origin() const override; QString section() override; void fetchScreenshots() override; int size() override; void fetchChangelog() override; QStringList extends() const override; + AbstractResource::Type type() const override { return Addon; } KNSBackend* knsBackend() const; void setEntry(const KNSCore::EntryInternal& entry); KNSCore::EntryInternal entry() const; bool canExecute() const override { return !executables().isEmpty(); } QStringList executables() const; void invokeApplication() const override; QUrl url() const override; QString executeLabel() const override; QString sourceIcon() const override { return QStringLiteral("get-hot-new-stuff"); } QDate releaseDate() const override; QVector linkIds() const; QUrl donationURL() override; Rating* ratingInstance(); private: const QStringList m_categories; KNSCore::EntryInternal m_entry; KNS3::Entry::Status m_lastStatus; QPointer m_rating; }; #endif // KNSRESOURCE_H diff --git a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp index 10115165..66deb5de 100644 --- a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp +++ b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp @@ -1,239 +1,241 @@ /*************************************************************************** * Copyright © 2013 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 "AppPackageKitResource.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "config-paths.h" #include "utils.h" AppPackageKitResource::AppPackageKitResource(const AppStream::Component& data, const QString &packageName, PackageKitBackend* parent) : PackageKitResource(packageName, QString(), parent) , m_appdata(data) { Q_ASSERT(data.isValid()); } QString AppPackageKitResource::name() const { QString ret; if (!m_appdata.extends().isEmpty()) { auto components = backend()->componentsById(m_appdata.extends().constFirst()); if (components.isEmpty()) qWarning() << "couldn't find" << m_appdata.extends() << "which is supposedly extended by" << m_appdata.id(); else ret = components.constFirst().name() + QStringLiteral(" - ") + m_appdata.name(); } if (ret.isEmpty()) ret = m_appdata.name(); return ret; } QString AppPackageKitResource::longDescription() { const auto desc = m_appdata.description(); if (!desc.isEmpty()) return desc; return PackageKitResource::longDescription(); } static QIcon componentIcon(const AppStream::Component &comp) { QIcon ret; foreach(const AppStream::Icon &icon, comp.icons()) { QStringList stock; switch(icon.kind()) { case AppStream::Icon::KindLocal: ret.addFile(icon.url().toLocalFile(), icon.size()); break; case AppStream::Icon::KindCached: ret.addFile(icon.url().toLocalFile(), icon.size()); break; case AppStream::Icon::KindStock: { const auto ret = QIcon::fromTheme(icon.name()); if (!ret.isNull()) return ret; break; } default: break; } } if (ret.isNull()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } return ret; } QVariant AppPackageKitResource::icon() const { return componentIcon(m_appdata); } QString AppPackageKitResource::license() { const auto license = m_appdata.projectLicense(); return license.isEmpty() ? PackageKitResource::license() : license; } QStringList AppPackageKitResource::mimetypes() const { return m_appdata.provided(AppStream::Provided::KindMimetype).items(); } QStringList AppPackageKitResource::categories() { auto cats = m_appdata.categories(); if (m_appdata.kind() != AppStream::Component::KindAddon) cats.append(QStringLiteral("Application")); return cats; } QString AppPackageKitResource::comment() { const auto summary = m_appdata.summary(); if (!summary.isEmpty()) return summary; return PackageKitResource::comment(); } QString AppPackageKitResource::appstreamId() const { return m_appdata.id(); } QUrl AppPackageKitResource::homepage() { return m_appdata.url(AppStream::Component::UrlKindHomepage); } QUrl AppPackageKitResource::helpURL() { return m_appdata.url(AppStream::Component::UrlKindHelp); } QUrl AppPackageKitResource::bugURL() { return m_appdata.url(AppStream::Component::UrlKindBugtracker); } QUrl AppPackageKitResource::donationURL() { return m_appdata.url(AppStream::Component::UrlKindDonation); } -bool AppPackageKitResource::isTechnical() const +AbstractResource::Type AppPackageKitResource::type() const { static QString desktop = QString::fromUtf8(qgetenv("XDG_CURRENT_DESKTOP")); const auto desktops = m_appdata.compulsoryForDesktops(); - return (!desktops.isEmpty() && !desktops.contains(desktop)) || m_appdata.kind() == AppStream::Component::KindAddon; + return (!desktops.isEmpty() && !desktops.contains(desktop)) ? Application + : m_appdata.kind() == AppStream::Component::KindAddon ? Addon + : Technical; } void AppPackageKitResource::fetchScreenshots() { const auto sc = AppStreamUtils::fetchScreenshots(m_appdata); Q_EMIT screenshotsFetched(sc.first, sc.second); } QStringList AppPackageKitResource::allPackageNames() const { auto ret = m_appdata.packageNames(); if (ret.isEmpty()) { ret = QStringList{ PackageKit::Daemon::packageName(availablePackageId()) }; } return ret; } QList AppPackageKitResource::addonsInformation() { const PackageKitBackend* p = static_cast(parent()); const QVector res = p->extendedBy(m_appdata.id()); QList ret; Q_FOREACH (AppPackageKitResource* r, res) { ret += PackageState(r->appstreamId(), r->name(), r->comment(), r->isInstalled()); } return ret; } QStringList AppPackageKitResource::extends() const { return m_appdata.extends(); } QString AppPackageKitResource::changelog() const { return AppStreamUtils::changelogToHtml(m_appdata); } void AppPackageKitResource::invokeApplication() const { auto trans = PackageKit::Daemon::getFiles({installedPackageId()}); connect(trans, &PackageKit::Transaction::files, this, [this](const QString &/*packageID*/, const QStringList &filenames) { const auto allServices = QStandardPaths::locateAll(QStandardPaths::ApplicationsLocation, m_appdata.id()); if (!allServices.isEmpty()) { const auto packageServices = kFilter(allServices, [filenames](const QString &file) { return filenames.contains(file); }); QProcess::startDetached(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/discover/runservice"), {packageServices}); } else { const QStringList exes = m_appdata.provided(AppStream::Provided::KindBinary).items(); const auto packageExecutables = kFilter(allServices, [filenames](const QString &exe) { return filenames.contains(QLatin1Char('/') + exe); }); if (!packageExecutables.isEmpty()) { QProcess::startDetached(exes.constFirst()); } else { const auto locations = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); const auto desktopFiles = kFilter(filenames, [locations](const QString &exe) { for (const auto &location: locations) { if (exe.startsWith(location)) return exe.contains(QLatin1String(".desktop")); } return false; }); if (!desktopFiles.isEmpty()) { QProcess::startDetached(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/discover/runservice"), { desktopFiles }); } } qWarning() << "Could not find any executables" << exes << filenames; } }); } QDate AppPackageKitResource::releaseDate() const { if (!m_appdata.releases().isEmpty()) { auto release = m_appdata.releases().constFirst(); return release.timestamp().date(); } return {}; } diff --git a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.h b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.h index 9d6e4b71..e32b6c1e 100644 --- a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.h +++ b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.h @@ -1,60 +1,60 @@ /*************************************************************************** * Copyright © 2013 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 . * ***************************************************************************/ #ifndef APPPACKAGEKITRESOURCE_H #define APPPACKAGEKITRESOURCE_H #include "PackageKitResource.h" #include "PackageKitBackend.h" class AppPackageKitResource : public PackageKitResource { Q_OBJECT public: explicit AppPackageKitResource(const AppStream::Component& data, const QString &packageName, PackageKitBackend* parent); QString appstreamId() const override; - bool isTechnical() const override; + AbstractResource::Type type() const override; QString name() const override; QVariant icon() const override; QStringList mimetypes() const override; QStringList categories() override; QString longDescription() override; QUrl homepage() override; QUrl helpURL() override; QUrl bugURL() override; QUrl donationURL() override; QString comment() override; QString license() override; QStringList allPackageNames() const override; QList addonsInformation() override; QStringList extends() const override; void fetchScreenshots() override; void invokeApplication() const override; bool canExecute() const override { return true; } QDate releaseDate() const override; QString changelog() const override; private: const AppStream::Component m_appdata; }; #endif // APPPACKAGEKITRESOURCE_H diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp index 26274f9a..a0159847 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp @@ -1,700 +1,700 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * Copyright © 2013 Lukas Appelhans * * * * 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 "PackageKitBackend.h" #include "PackageKitSourcesBackend.h" #include "PackageKitResource.h" #include "PackageKitUpdater.h" #include "AppPackageKitResource.h" #include "PKTransaction.h" #include "LocalFilePKResource.h" #include "TransactionSet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "config-paths.h" DISCOVER_BACKEND_PLUGIN(PackageKitBackend) template static void setWhenAvailable(const QDBusPendingReply& pending, W func, QObject* parent) { QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pending, parent); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, parent, [func](QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); QDBusPendingReply reply = *watcher; func(reply.value()); }); } QString PackageKitBackend::locateService(const QString &filename) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/")+filename); } PackageKitBackend::PackageKitBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_appdata(new AppStream::Pool) , m_updater(new PackageKitUpdater(this)) , m_refresher(nullptr) , m_isFetching(0) , m_reviews(AppStreamIntegration::global()->reviews()) { QTimer* t = new QTimer(this); connect(t, &QTimer::timeout, this, &PackageKitBackend::checkForUpdates); t->setInterval(60 * 60 * 1000); t->setSingleShot(false); t->start(); m_delayedDetailsFetch.setSingleShot(true); m_delayedDetailsFetch.setInterval(0); connect(&m_delayedDetailsFetch, &QTimer::timeout, this, &PackageKitBackend::performDetailsFetch); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::updatesChanged, this, &PackageKitBackend::fetchUpdates); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::isRunningChanged, this, &PackageKitBackend::checkDaemonRunning); connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady); auto proxyWatch = new QFileSystemWatcher(this); proxyWatch->addPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/kioslaverc")); connect(proxyWatch, &QFileSystemWatcher::fileChanged, this, [this](){ KProtocolManager::reparseConfiguration(); updateProxy(); }); SourcesModel::global()->addSourcesBackend(new PackageKitSourcesBackend(this)); reloadPackageList(); setWhenAvailable(PackageKit::Daemon::getTimeSinceAction(PackageKit::Transaction::RoleRefreshCache), [this](uint timeSince) { if (timeSince > 3600) checkForUpdates(); }, this); } PackageKitBackend::~PackageKitBackend() = default; void PackageKitBackend::updateProxy() { if (PackageKit::Daemon::isRunning()) { static bool everHad = KProtocolManager::useProxy(); if (!everHad && !KProtocolManager::useProxy()) return; everHad = KProtocolManager::useProxy(); PackageKit::Daemon::global()->setProxy(KProtocolManager::proxyFor(QLatin1String("http")), KProtocolManager::proxyFor(QLatin1String("https")), KProtocolManager::proxyFor(QLatin1String("ftp")), KProtocolManager::proxyFor(QLatin1String("socks")), {}, {}); } } bool PackageKitBackend::isFetching() const { return m_isFetching; } void PackageKitBackend::acquireFetching(bool f) { if (f) m_isFetching++; else m_isFetching--; if ((!f && m_isFetching==0) || (f && m_isFetching==1)) { emit fetchingChanged(); } Q_ASSERT(m_isFetching>=0); } void PackageKitBackend::reloadPackageList() { acquireFetching(true); if (m_refresher) { disconnect(m_refresher.data(), &PackageKit::Transaction::finished, this, &PackageKitBackend::reloadPackageList); } QString error; m_appdata.reset(new AppStream::Pool); const bool b = m_appdata->load(&error); if (!b && m_packages.packages.isEmpty()) { qWarning() << "Could not open the AppStream metadata pool" << error; QTimer::singleShot(0, this, [this]() { Q_EMIT passiveMessage(i18n("Please make sure that Appstream is properly set up on your system")); }); } const auto components = m_appdata->components(); QStringList neededPackages; neededPackages.reserve(components.size()); foreach(const AppStream::Component& component, components) { if (component.kind() == AppStream::Component::KindFirmware) continue; const auto pkgNames = component.packageNames(); if (pkgNames.isEmpty()) { auto launchable = component.launchable(AppStream::Launchable::KindDesktopId); if (component.kind() == AppStream::Component::KindDesktopApp && !launchable.entries().isEmpty()) { const QString file = locateService(launchable.entries().constFirst()); if (!file.isEmpty()) { acquireFetching(true); auto trans = PackageKit::Daemon::searchFiles(file); connect(trans, &PackageKit::Transaction::package, this, [trans](PackageKit::Transaction::Info info, const QString &packageID){ if (info == PackageKit::Transaction::InfoInstalled) trans->setProperty("installedPackage", packageID); }); connect(trans, &PackageKit::Transaction::finished, this, [this, trans, component](PackageKit::Transaction::Exit status) { const auto pkgidVal = trans->property("installedPackage"); if (status == PackageKit::Transaction::ExitSuccess && !pkgidVal.isNull()) { const auto pkgid = pkgidVal.toString(); auto res = addComponent(component, {PackageKit::Daemon::packageName(pkgid)}); res->clearPackageIds(); res->addPackageId(PackageKit::Transaction::InfoInstalled, pkgid, true); } acquireFetching(false); }); continue; } } qDebug() << "no packages for" << component.id(); continue; } neededPackages += pkgNames; addComponent(component, pkgNames); } acquireFetching(false); if (!neededPackages.isEmpty()) { neededPackages.removeDuplicates(); resolvePackages(neededPackages); } else { qDebug() << "empty appstream db"; if (PackageKit::Daemon::backendName() == QLatin1String("aptcc") || PackageKit::Daemon::backendName().isEmpty()) { checkForUpdates(); } } } AppPackageKitResource* PackageKitBackend::addComponent(const AppStream::Component& component, const QStringList& pkgNames) { Q_ASSERT(isFetching()); Q_ASSERT(!pkgNames.isEmpty()); AppPackageKitResource* res = qobject_cast(m_packages.packages[component.id()]); if (!res) { res = new AppPackageKitResource(component, pkgNames.at(0), this); m_packages.packages[component.id()] = res; } else { res->clearPackageIds(); } foreach (const QString& pkg, pkgNames) { m_packages.packageToApp[pkg] += component.id(); } foreach (const QString& pkg, component.extends()) { m_packages.extendedBy[pkg] += res; } return res; } void PackageKitBackend::clearPackages(const QStringList& packageNames) { const auto resources = resourcesByPackageNames>(packageNames); for(auto res: resources) { qobject_cast(res)->clearPackageIds(); } } void PackageKitBackend::resolvePackages(const QStringList &packageNames) { PackageKit::Transaction * tArch = PackageKit::Daemon::resolve(packageNames, PackageKit::Transaction::FilterArch); connect(tArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageArch); connect(tArch, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); PackageKit::Transaction * tNotArch = PackageKit::Daemon::resolve(packageNames, PackageKit::Transaction::FilterNotArch); connect(tNotArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageNotArch); connect(tNotArch, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); TransactionSet* merge = new TransactionSet({tArch, tNotArch}); connect(merge, &TransactionSet::allFinished, this, &PackageKitBackend::getPackagesFinished); fetchUpdates(); } void PackageKitBackend::fetchUpdates() { if (m_updater->isProgressing()) return; PackageKit::Transaction * tUpdates = PackageKit::Daemon::getUpdates(); connect(tUpdates, &PackageKit::Transaction::finished, this, &PackageKitBackend::getUpdatesFinished); connect(tUpdates, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageToUpdate); connect(tUpdates, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); m_updatesPackageId.clear(); m_hasSecurityUpdates = false; m_updater->setProgressing(true); } void PackageKitBackend::addPackageArch(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary) { addPackage(info, packageId, summary, true); } void PackageKitBackend::addPackageNotArch(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary) { addPackage(info, packageId, summary, false); } void PackageKitBackend::addPackage(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary, bool arch) { const QString packageName = PackageKit::Daemon::packageName(packageId); QSet r = resourcesByPackageName(packageName); if (r.isEmpty()) { auto pk = new PackageKitResource(packageName, summary, this); r = { pk }; m_packagesToAdd.insert(pk); } foreach(auto res, r) static_cast(res)->addPackageId(info, packageId, arch); } void PackageKitBackend::getPackagesFinished() { for(auto it = m_packages.packages.cbegin(); it != m_packages.packages.cend(); ++it) { auto pkr = qobject_cast(it.value()); if (pkr->packages().isEmpty()) { // qWarning() << "Failed to find package for" << it.key(); m_packagesToDelete += pkr; } } includePackagesToAdd(); } void PackageKitBackend::includePackagesToAdd() { if (m_packagesToAdd.isEmpty() && m_packagesToDelete.isEmpty()) return; acquireFetching(true); foreach(PackageKitResource* res, m_packagesToAdd) { m_packages.packages[res->packageName()] = res; } foreach(PackageKitResource* res, m_packagesToDelete) { const auto pkgs = m_packages.packageToApp.value(res->packageName(), {res->packageName()}); foreach(const auto &pkg, pkgs) { auto res = m_packages.packages.take(pkg); if (res) { if (AppPackageKitResource* ares = qobject_cast(res)) { for(const auto &ext: res->extends()) m_packages.extendedBy[ext].removeAll(ares); } emit resourceRemoved(res); res->deleteLater(); } } } m_packagesToAdd.clear(); m_packagesToDelete.clear(); acquireFetching(false); } void PackageKitBackend::transactionError(PackageKit::Transaction::Error, const QString& message) { qWarning() << "Transaction error: " << message << sender(); Q_EMIT passiveMessage(message); } void PackageKitBackend::packageDetails(const PackageKit::Details& details) { const QSet resources = resourcesByPackageName(PackageKit::Daemon::packageName(details.packageId())); if (resources.isEmpty()) qWarning() << "couldn't find package for" << details.packageId(); foreach(AbstractResource* res, resources) { qobject_cast(res)->setDetails(details); } } QSet PackageKitBackend::resourcesByPackageName(const QString& name) const { return resourcesByPackageNames>({name}); } template T PackageKitBackend::resourcesByPackageNames(const QStringList &pkgnames) const { T ret; ret.reserve(pkgnames.size()); for(const QString &name : pkgnames) { const QStringList names = m_packages.packageToApp.value(name, QStringList(name)); foreach(const QString& name, names) { AbstractResource* res = m_packages.packages.value(name); if (res) ret += res; } } return ret; } void PackageKitBackend::checkForUpdates() { if (!m_refresher) { acquireFetching(true); m_refresher = PackageKit::Daemon::refreshCache(false); connect(m_refresher.data(), &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); connect(m_refresher.data(), &PackageKit::Transaction::finished, this, [this]() { m_refresher = nullptr; reloadPackageList(); acquireFetching(false); }); } else { qWarning() << "already resetting"; } } QList PackageKitBackend::componentsById(const QString& id) const { return m_appdata->componentsById(id); } ResultsStream* PackageKitBackend::search(const AbstractResourcesBackend::Filters& filter) { if (!filter.resourceUrl.isEmpty()) { return findResourceByPackageName(filter.resourceUrl); } else if (!filter.extends.isEmpty()) { const auto ext = kTransform>(m_packages.extendedBy[filter.extends], [](AppPackageKitResource* a){ return a; }); return new ResultsStream(QStringLiteral("PackageKitStream-extends"), ext); } else if (filter.search.isEmpty()) { - return new ResultsStream(QStringLiteral("PackageKitStream-all"), kFilter>(m_packages.packages, [](AbstractResource* res) { return !res->isTechnical(); })); + return new ResultsStream(QStringLiteral("PackageKitStream-all"), kFilter>(m_packages.packages, [](AbstractResource* res) { return res->type() != AbstractResource::Technical; })); } else { const QList components = m_appdata->search(filter.search); const QStringList ids = kTransform(components, [](const AppStream::Component& comp) { return comp.id(); }); auto stream = new ResultsStream(QStringLiteral("PackageKitStream-search")); if (!ids.isEmpty()) { const auto resources = resourcesByPackageNames>(ids); QTimer::singleShot(0, this, [stream, resources] () { Q_EMIT stream->resourcesFound(resources); }); } PackageKit::Transaction * tArch = PackageKit::Daemon::resolve(filter.search, PackageKit::Transaction::FilterArch); connect(tArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageArch); connect(tArch, &PackageKit::Transaction::package, stream, [stream](PackageKit::Transaction::Info /*info*/, const QString &packageId){ stream->setProperty("packageId", packageId); }); connect(tArch, &PackageKit::Transaction::finished, stream, [stream, ids, this](PackageKit::Transaction::Exit status) { getPackagesFinished(); if (status == PackageKit::Transaction::Exit::ExitSuccess) { const auto packageId = stream->property("packageId"); if (!packageId.isNull()) { const auto res = resourcesByPackageNames>({PackageKit::Daemon::packageName(packageId.toString())}); Q_EMIT stream->resourcesFound(kFilter>(res, [ids](AbstractResource* res){ return !ids.contains(res->appstreamId()); })); } } stream->finish(); }, Qt::QueuedConnection); return stream; } } ResultsStream * PackageKitBackend::findResourceByPackageName(const QUrl& url) { AbstractResource* pkg = nullptr; if (url.host().isEmpty()) Q_EMIT passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else if (url.scheme() == QLatin1String("appstream")) { static const QMap deprecatedAppstreamIds = { { QStringLiteral("org.kde.krita.desktop"), QStringLiteral("krita.desktop") }, { QStringLiteral("org.kde.digikam.desktop"), QStringLiteral("digikam.desktop") }, { QStringLiteral("org.kde.ktorrent.desktop"), QStringLiteral("ktorrent.desktop") }, { QStringLiteral("org.kde.gcompris.desktop"), QStringLiteral("gcompris.desktop") }, { QStringLiteral("org.kde.kmymoney.desktop"), QStringLiteral("kmymoney.desktop") }, { QStringLiteral("org.kde.kolourpaint.desktop"), QStringLiteral("kolourpaint.desktop") }, { QStringLiteral("org.blender.blender.desktop"), QStringLiteral("blender.desktop") }, }; const auto host = url.host(); if (host.isEmpty()) Q_EMIT passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else { const auto deprecatedHost = deprecatedAppstreamIds.value(host); //try this as fallback for (auto it = m_packages.packages.constBegin(), itEnd = m_packages.packages.constEnd(); it != itEnd; ++it) { if (it.key().compare(host, Qt::CaseInsensitive) == 0 || it.key().compare(deprecatedHost, Qt::CaseInsensitive) == 0 || (host.endsWith(QLatin1String(".desktop")) && host.compare(it.key()+QLatin1String(".desktop"), Qt::CaseInsensitive) == 0)) { pkg = it.value(); break; } } if (!pkg) qDebug() << "could not find" << host << deprecatedHost; } } return new ResultsStream(QStringLiteral("PackageKitStream-url"), pkg ? QVector{pkg} : QVector{}); } bool PackageKitBackend::hasSecurityUpdates() const { return m_hasSecurityUpdates; } int PackageKitBackend::updatesCount() const { int ret = 0; QSet packages; for(auto res: upgradeablePackages()) { const auto packageName = res->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += 1; } return ret; } Transaction* PackageKitBackend::installApplication(AbstractResource* app, const AddonList& addons) { Transaction* t = nullptr; if(!addons.addonsToInstall().isEmpty()) { QVector appsToInstall; if(!app->isInstalled()) appsToInstall << app; foreach(const QString& toInstall, addons.addonsToInstall()) { appsToInstall += m_packages.packages.value(toInstall); Q_ASSERT(appsToInstall.last()); } t = new PKTransaction(appsToInstall, Transaction::ChangeAddonsRole); } if (!addons.addonsToRemove().isEmpty()) { QVector appsToRemove = kTransform>(addons.addonsToRemove(), [this](const QString& toRemove){ return m_packages.packages.value(toRemove); }); t = new PKTransaction(appsToRemove, Transaction::RemoveRole); } if (!app->isInstalled()) t = installApplication(app); return t; } Transaction* PackageKitBackend::installApplication(AbstractResource* app) { return new PKTransaction({app}, Transaction::InstallRole); } Transaction* PackageKitBackend::removeApplication(AbstractResource* app) { Q_ASSERT(!isFetching()); return new PKTransaction({app}, Transaction::RemoveRole); } QSet PackageKitBackend::upgradeablePackages() const { QSet ret; ret.reserve(m_updatesPackageId.size()); Q_FOREACH (const QString& pkgid, m_updatesPackageId) { const QString pkgname = PackageKit::Daemon::packageName(pkgid); const auto pkgs = resourcesByPackageName(pkgname); if (pkgs.isEmpty()) { qWarning() << "couldn't find resource for" << pkgid; } ret.unite(pkgs); } return ret; } void PackageKitBackend::addPackageToUpdate(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary) { if (info == PackageKit::Transaction::InfoBlocked) { return; } if (info == PackageKit::Transaction::InfoSecurity) m_hasSecurityUpdates = true; m_updatesPackageId += packageId; addPackage(info, packageId, summary, true); } void PackageKitBackend::getUpdatesFinished(PackageKit::Transaction::Exit, uint) { if (!m_updatesPackageId.isEmpty()) { PackageKit::Transaction* transaction = PackageKit::Daemon::getDetails(m_updatesPackageId.toList()); connect(transaction, &PackageKit::Transaction::details, this, &PackageKitBackend::packageDetails); connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); connect(transaction, &PackageKit::Transaction::finished, this, &PackageKitBackend::getUpdatesDetailsFinished); } m_updater->setProgressing(false); includePackagesToAdd(); emit updatesCountChanged(); } void PackageKitBackend::getUpdatesDetailsFinished(PackageKit::Transaction::Exit exit, uint) { if (exit != PackageKit::Transaction::ExitSuccess) { qWarning() << "Couldn't figure out the updates on PackageKit backend" << exit; } } bool PackageKitBackend::isPackageNameUpgradeable(const PackageKitResource* res) const { return !upgradeablePackageId(res).isEmpty(); } QString PackageKitBackend::upgradeablePackageId(const PackageKitResource* res) const { QString name = res->packageName(); foreach (const QString& pkgid, m_updatesPackageId) { if (PackageKit::Daemon::packageName(pkgid) == name) return pkgid; } return QString(); } void PackageKitBackend::fetchDetails(const QString& pkgid) { if (!m_delayedDetailsFetch.isActive()) { m_delayedDetailsFetch.start(); } m_packageNamesToFetchDetails += pkgid; } void PackageKitBackend::performDetailsFetch() { Q_ASSERT(!m_packageNamesToFetchDetails.isEmpty()); const auto ids = m_packageNamesToFetchDetails.toList(); PackageKit::Transaction* transaction = PackageKit::Daemon::getDetails(ids); connect(transaction, &PackageKit::Transaction::details, this, &PackageKitBackend::packageDetails); connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); } void PackageKitBackend::checkDaemonRunning() { if (!PackageKit::Daemon::isRunning()) { qWarning() << "PackageKit stopped running!"; } else updateProxy(); } AbstractBackendUpdater* PackageKitBackend::backendUpdater() const { return m_updater; } QVector PackageKitBackend::extendedBy(const QString& id) const { return m_packages.extendedBy[id]; } AbstractReviewsBackend* PackageKitBackend::reviewsBackend() const { return m_reviews.data(); } AbstractResource * PackageKitBackend::resourceForFile(const QUrl& file) { QMimeDatabase db; const auto mime = db.mimeTypeForUrl(file); if ( mime.inherits(QLatin1String("application/vnd.debian.binary-package")) || mime.inherits(QLatin1String("application/x-rpm")) || mime.inherits(QLatin1String("application/x-tar")) || mime.inherits(QLatin1String("application/x-xz-compressed-tar")) ) { return new LocalFilePKResource(file, this); } return nullptr; } static QString readDistroName() { const QStringList osreleasenames = (QStringList() << QStringLiteral("/etc/os-release") << QStringLiteral("/usr/lib/os-release")); foreach (QString osrelease, osreleasenames) { QFile file(osrelease); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QByteArray line; while (!file.atEnd()) { line = file.readLine().trimmed(); if (line.startsWith("NAME=")) { auto output = line.right(line.length()-5); output = output.replace('\"',""); return QString::fromLocal8Bit(output); } } } } QProcess process; process.setEnvironment({QStringLiteral("LC_ALL=C")}); process.start(QStringLiteral("lsb_release"), {QStringLiteral("-sd")}); process.waitForFinished(); auto output = process.readAll().trimmed(); if (output.startsWith('\"') && output.endsWith('\"')) output = output.mid(1, output.length()-2); return QString::fromLocal8Bit(output); } QString PackageKitBackend::displayName() const { static const QString distro = readDistroName(); return distro; } #include "PackageKitBackend.moc" diff --git a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp index 6d63793d..490356d4 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp @@ -1,304 +1,304 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * Copyright © 2013 Lukas Appelhans * * * * 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 "PackageKitResource.h" #include "PackageKitBackend.h" #include "PackageKitMessages.h" #include #include #include #include #include const QStringList PackageKitResource::m_objects({ QStringLiteral("qrc:/qml/DependenciesButton.qml") }); PackageKitResource::PackageKitResource(QString packageName, QString summary, PackageKitBackend* parent) : AbstractResource(parent) , m_summary(std::move(summary)) , m_name(std::move(packageName)) { setObjectName(m_name); connect(this, &PackageKitResource::dependenciesFound, this, [this](const QJsonObject& obj) { setDependenciesCount(obj.size()); }); } QString PackageKitResource::name() const { return m_name; } QString PackageKitResource::packageName() const { return m_name; } QStringList PackageKitResource::allPackageNames() const { return { m_name }; } QString PackageKitResource::availablePackageId() const { //First we check if it's upgradeable and use this version to display const QString pkgid = backend()->upgradeablePackageId(this); if (!pkgid.isEmpty()) return pkgid; QMap::const_iterator it = m_packages.constFind(PackageKit::Transaction::InfoAvailable); if (it != m_packages.constEnd()) return it->last(); return installedPackageId(); } QString PackageKitResource::installedPackageId() const { const auto installed = m_packages[PackageKit::Transaction::InfoInstalled]; return installed.isEmpty() ? QString() : installed.last(); } QString PackageKitResource::comment() { return m_summary; } QString PackageKitResource::longDescription() { fetchDetails(); return m_details.description(); } QUrl PackageKitResource::homepage() { fetchDetails(); return QUrl(m_details.url()); } QVariant PackageKitResource::icon() const { return QStringLiteral("applications-other"); } QString PackageKitResource::license() { fetchDetails(); return m_details.license().isEmpty() ? i18n("Unknown") : m_details.license(); } QList PackageKitResource::addonsInformation() { return QList(); } QString PackageKitResource::availableVersion() const { return PackageKit::Daemon::packageVersion(availablePackageId()); } QString PackageKitResource::installedVersion() const { return PackageKit::Daemon::packageVersion(installedPackageId()); } int PackageKitResource::size() { fetchDetails(); return m_details.size(); } QString PackageKitResource::origin() const { auto pkgid = availablePackageId(); return PackageKit::Daemon::packageData(pkgid); } QString PackageKitResource::section() { return QString(); } AbstractResource::State PackageKitResource::state() { if (backend()->isPackageNameUpgradeable(this)) return Upgradeable; else if(m_packages.contains(PackageKit::Transaction::InfoInstalled)) return Installed; else if(m_packages.contains(PackageKit::Transaction::InfoAvailable)) return None; else return Broken; } void PackageKitResource::addPackageId(PackageKit::Transaction::Info info, const QString &packageId, bool arch) { if (arch) m_packages[info].append(packageId); else m_packages[info].prepend(packageId); emit stateChanged(); } QStringList PackageKitResource::categories() { return { QStringLiteral("Unknown") }; } -bool PackageKitResource::isTechnical() const +AbstractResource::Type PackageKitResource::type() const { - return true; + return Technical; } void PackageKitResource::fetchDetails() { const QString pkgid = availablePackageId(); if (!m_details.isEmpty() || pkgid.isEmpty()) return; m_details.insert(QStringLiteral("fetching"), true);//we add an entry so it's not re-fetched. backend()->fetchDetails(pkgid); } void PackageKitResource::failedFetchingDetails(PackageKit::Transaction::Error, const QString& msg) { qWarning() << "error fetching details" << msg; } void PackageKitResource::setDependenciesCount(int deps) { if (deps != m_dependenciesCount) { m_dependenciesCount = deps; Q_EMIT sizeChanged(); } } void PackageKitResource::setDetails(const PackageKit::Details & details) { const bool ourDetails = details.packageId() == availablePackageId(); if (!ourDetails) return; if (m_details != details) { m_details = details; emit stateChanged(); if (!backend()->isFetching()) Q_EMIT backend()->resourcesChanged(this, {"size", "homepage", "license"}); } } void PackageKitResource::fetchChangelog() { const auto pkgid = availablePackageId(); if (pkgid.isEmpty()) { connect(this, &PackageKitResource::stateChanged, this, &PackageKitResource::fetchChangelog); return; } PackageKit::Transaction* t = PackageKit::Daemon::getUpdateDetail(availablePackageId()); connect(t, &PackageKit::Transaction::updateDetail, this, &PackageKitResource::updateDetail); connect(t, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error err, const QString & error) { qWarning() << "error fetching updates:" << err << error; emit changelogFetched(QString()); }); } static void addIfNotEmpty(const QString& title, const QString& content, QString& where) { if (!content.isEmpty()) where += QStringLiteral("

") + title + QStringLiteral(" ") + QString(content).replace(QStringLiteral("\n"), QStringLiteral("
")) + QStringLiteral("

"); } QString PackageKitResource::joinPackages(const QStringList& pkgids, const QString &_sep, const QString &shadowPackage) { QStringList ret; foreach(const QString& pkgid, pkgids) { const auto pkgname = PackageKit::Daemon::packageName(pkgid); if (pkgname == shadowPackage) ret += PackageKit::Daemon::packageVersion(pkgid); else ret += i18nc("package-name (version)", "%1 (%2)", pkgname, PackageKit::Daemon::packageVersion(pkgid)); } const QString sep = _sep.isEmpty() ? i18nc("comma separating package names", ", ") : _sep; return ret.join(sep); } static QStringList urlToLinks(const QStringList& urls) { QStringList ret; foreach(const QString& in, urls) ret += QStringLiteral("%1").arg(in); return ret; } void PackageKitResource::updateDetail(const QString& packageID, const QStringList& updates, const QStringList& obsoletes, const QStringList& vendorUrls, const QStringList& /*bugzillaUrls*/, const QStringList& /*cveUrls*/, PackageKit::Transaction::Restart restart, const QString& updateText, const QString& /*changelog*/, PackageKit::Transaction::UpdateState state, const QDateTime& /*issued*/, const QDateTime& /*updated*/) { const auto name = PackageKit::Daemon::packageName(packageID); QString info; addIfNotEmpty(i18n("Current Version:"), joinPackages(updates, {}, name), info); addIfNotEmpty(i18n("Obsoletes:"), joinPackages(obsoletes, {}, name), info); addIfNotEmpty(i18n("New Version:"), updateText, info); addIfNotEmpty(i18n("Update State:"), PackageKitMessages::updateStateMessage(state), info); addIfNotEmpty(i18n("Restart:"), PackageKitMessages::restartMessage(restart), info); if (!vendorUrls.isEmpty()) addIfNotEmpty(i18n("Vendor:"), urlToLinks(vendorUrls).join(QStringLiteral(", ")), info); emit changelogFetched(changelog() + info); } PackageKitBackend* PackageKitResource::backend() const { return qobject_cast(parent()); } QString PackageKitResource::sizeDescription() { if (m_dependenciesCount < 0) { fetchDetails(); fetchDependencies(); } if (m_dependenciesCount <= 0) return AbstractResource::sizeDescription(); else return i18np("%2 (plus %1 dependency)", "%2 (plus %1 dependencies)", m_dependenciesCount, AbstractResource::sizeDescription()); } QString PackageKitResource::sourceIcon() const { return QStringLiteral("package-available"); } void PackageKitResource::fetchDependencies() { const auto id = availablePackageId(); if (id.isEmpty()) return; m_dependenciesCount = 0; QSharedPointer packageDependencies(new QJsonObject); auto trans = PackageKit::Daemon::installPackage(id, PackageKit::Transaction::TransactionFlagSimulate); connect(trans, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error, const QString& message) { qWarning() << "Transaction error: " << message << sender(); }); connect(trans, &PackageKit::Transaction::package, this, [packageDependencies](PackageKit::Transaction::Info /*info*/, const QString &packageID, const QString &summary) { (*packageDependencies)[PackageKit::Daemon::packageName(packageID)] = summary ; }); connect(trans, &PackageKit::Transaction::finished, this, [this, packageDependencies](PackageKit::Transaction::Exit /*status*/) { Q_EMIT dependenciesFound(*packageDependencies); }); } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitResource.h b/libdiscover/backends/PackageKitBackend/PackageKitResource.h index 37751efd..f74f6204 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitResource.h +++ b/libdiscover/backends/PackageKitBackend/PackageKitResource.h @@ -1,116 +1,116 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef PACKAGEKITRESOURCE_H #define PACKAGEKITRESOURCE_H #include #include #include class PackageKitBackend; class PackageKitResource : public AbstractResource { Q_OBJECT Q_PROPERTY(QStringList objects MEMBER m_objects CONSTANT) public: explicit PackageKitResource(QString packageName, QString summary, PackageKitBackend* parent); QString packageName() const override; QString name() const override; QString comment() override; QString longDescription() override; QUrl homepage() override; QVariant icon() const override; QStringList categories() override; QString license() override; QString origin() const override; QString section() override; - bool isTechnical() const override; + AbstractResource::Type type() const override; int size() override; void fetchChangelog() override; QList addonsInformation() override; State state() override; QString installedVersion() const override; QString availableVersion() const override; virtual QStringList allPackageNames() const; QString installedPackageId() const; QString availablePackageId() const; void clearPackageIds() { m_packages.clear(); } QMap packages() const { return m_packages; } PackageKitBackend* backend() const; static QString joinPackages(const QStringList& pkgids, const QString &_sep, const QString &shadowPackageName); void invokeApplication() const override {} bool canExecute() const override { return false; } QString sizeDescription() override; void setDependenciesCount(int count); QString sourceIcon() const override; QDate releaseDate() const override { return {}; } virtual QString changelog() const { return {}; } Q_SIGNALS: void dependenciesFound(const QJsonObject& dependencies); public Q_SLOTS: void addPackageId(PackageKit::Transaction::Info info, const QString &packageId, bool arch); void setDetails(const PackageKit::Details& details); void updateDetail(const QString &packageID, const QStringList &updates, const QStringList &obsoletes, const QStringList &vendorUrls, const QStringList &bugzillaUrls, const QStringList &cveUrls, PackageKit::Transaction::Restart restart, const QString &updateText, const QString &changelog, PackageKit::Transaction::UpdateState state, const QDateTime &issued, const QDateTime &updated); private Q_SLOTS: void failedFetchingDetails(PackageKit::Transaction::Error, const QString& msg); private: void fetchDependencies(); void setDependencies(const QStringList &deps); /** fetches details individually, it's better if done in batch, like for updates */ void fetchDetails(); QMap m_packages; const QString m_summary; const QString m_name; PackageKit::Details m_details; int m_dependenciesCount = -1; static const QStringList m_objects; }; #endif // PACKAGEKITRESOURCE_H diff --git a/libdiscover/backends/SnapBackend/SnapResource.cpp b/libdiscover/backends/SnapBackend/SnapResource.cpp index 34baad4d..981c2f4d 100644 --- a/libdiscover/backends/SnapBackend/SnapResource.cpp +++ b/libdiscover/backends/SnapBackend/SnapResource.cpp @@ -1,447 +1,447 @@ /*************************************************************************** * Copyright © 2013 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 "SnapResource.h" #include "SnapBackend.h" #include #include #include #include #include #include #include #include QDebug operator<<(QDebug debug, const QSnapdPlug& plug) { QDebugStateSaver saver(debug); debug.nospace() << "QSnapdPlug("; debug.nospace() << "name:" << plug.name() << ','; debug.nospace() << "snap:" << plug.snap() << ','; debug.nospace() << "label:" << plug.label() << ','; debug.nospace() << "interface:" << plug.interface() << ','; debug.nospace() << "connectionCount:" << plug.connectionCount(); debug.nospace() << ')'; return debug; } QDebug operator<<(QDebug debug, const QSnapdSlot& slot) { QDebugStateSaver saver(debug); debug.nospace() << "QSnapdSlot("; debug.nospace() << "name:" << slot.name() << ','; debug.nospace() << "label:" << slot.label() << ','; debug.nospace() << "snap:" << slot.snap() << ','; debug.nospace() << "interface:" << slot.interface() << ','; debug.nospace() << "connectionCount:" << slot.connectionCount(); debug.nospace() << ')'; return debug; } QDebug operator<<(QDebug debug, const QSnapdPlug* plug) { QDebugStateSaver saver(debug); debug.nospace() << "*" << *plug; return debug; } QDebug operator<<(QDebug debug, const QSnapdSlot* slot) { QDebugStateSaver saver(debug); debug.nospace() << "*" << *slot; return debug; } const QStringList SnapResource::m_objects({ QStringLiteral("qrc:/qml/PermissionsButton.qml") #ifdef SNAP_CHANNELS , QStringLiteral("qrc:/qml/ChannelsButton.qml") #endif }); SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* backend) : AbstractResource(backend) , m_state(state) , m_snap(snap) { setObjectName(snap->name()); } QSnapdClient * SnapResource::client() const { auto backend = qobject_cast(parent()); return backend->client(); } QString SnapResource::availableVersion() const { return installedVersion(); } QStringList SnapResource::categories() { return { QStringLiteral("Application") }; } QString SnapResource::comment() { return m_snap->summary(); } int SnapResource::size() { // return isInstalled() ? m_snap->installedSize() : m_snap->downloadSize(); return m_snap->downloadSize(); } QVariant SnapResource::icon() const { if (m_icon.isNull()) { m_icon = [this]() -> QVariant { const auto iconPath = m_snap->icon(); if (iconPath.isEmpty()) return QStringLiteral("package-x-generic"); if (!iconPath.startsWith(QLatin1Char('/'))) return QUrl(iconPath); auto req = client()->getIcon(packageName()); connect(req, &QSnapdGetIconRequest::complete, this, &SnapResource::gotIcon); req->runAsync(); return {}; }(); } return m_icon; } void SnapResource::gotIcon() { auto req = qobject_cast(sender()); if (req->error()) { qWarning() << "icon error" << req->errorString(); return; } auto icon = req->icon(); QBuffer buffer; buffer.setData(icon->data()); QImageReader reader(&buffer); auto theIcon = QVariant::fromValue(reader.read()); if (theIcon != m_icon) { m_icon = theIcon; iconChanged(); } } QString SnapResource::installedVersion() const { return m_snap->version(); } QString SnapResource::license() { return m_snap->license(); } QString SnapResource::longDescription() { return m_snap->description(); } QString SnapResource::name() const { return m_snap->title().isEmpty() ? m_snap->name() : m_snap->title(); } QString SnapResource::origin() const { return QStringLiteral("Snap"); } QString SnapResource::packageName() const { return m_snap->name(); } QString SnapResource::section() { return QStringLiteral("snap"); } AbstractResource::State SnapResource::state() { return m_state; } void SnapResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(); } } void SnapResource::fetchChangelog() { QString log; emit changelogFetched(log); } void SnapResource::fetchScreenshots() { QList screenshots; for(int i = 0, c = m_snap->screenshotCount(); i screenshot(m_snap->screenshot(i)); screenshots << QUrl(screenshot->url()); } Q_EMIT screenshotsFetched(screenshots, screenshots); } void SnapResource::invokeApplication() const { QProcess::startDetached(QStringLiteral("snap"), {QStringLiteral("run"), packageName()}); } -bool SnapResource::isTechnical() const +AbstractResource::Type SnapResource::type() const { - return m_snap->snapType() != QLatin1String("app"); + return m_snap->snapType() != QLatin1String("app") ? Application : Technical; } void SnapResource::setSnap(const QSharedPointer& snap) { Q_ASSERT(snap->name() == m_snap->name()); if (m_snap == snap) return; const bool newSize = m_snap->installedSize() != snap->installedSize() || m_snap->downloadSize() != snap->downloadSize(); m_snap = snap; if (newSize) Q_EMIT sizeChanged(); Q_EMIT newSnap(); } QDate SnapResource::releaseDate() const { return {}; } class PlugsModel : public QStandardItemModel { public: enum Roles { PlugNameRole = Qt::UserRole + 1, SlotSnapRole, SlotNameRole }; PlugsModel(SnapResource* res, SnapBackend* backend, QObject* parent) : QStandardItemModel(parent) , m_res(res) , m_backend(backend) { setItemRoleNames(roleNames().unite( { {Qt::CheckStateRole, "checked"} } )); auto req = backend->client()->getInterfaces(); req->runSync(); QHash> slotsForInterface; for (int i = 0; islotCount(); ++i) { const auto slot = req->slot(i); slot->setParent(this); slotsForInterface[slot->interface()].append(slot); } const auto snap = m_res->snap(); for (int i = 0; iplugCount(); ++i) { const QScopedPointer plug(req->plug(i)); if (plug->snap() == snap->name()) { if (plug->interface() == QLatin1String("content")) continue; for (auto slot: slotsForInterface[plug->interface()]) { auto item = new QStandardItem; if (plug->label().isEmpty()) item->setText(plug->name()); else item->setText(i18n("%1 - %2", plug->name(), plug->label())); // qDebug() << "xxx" << plug->name() << plug->label() << plug->interface() << slot->snap() << "slot:" << slot->name() << slot->snap() << slot->interface() << slot->label(); item->setCheckable(true); item->setCheckState(plug->connectionCount()>0 ? Qt::Checked : Qt::Unchecked); item->setData(plug->name(), PlugNameRole); item->setData(slot->snap(), SlotSnapRole); item->setData(slot->name(), SlotNameRole); appendRow(item); } } } } private: bool setData(const QModelIndex & index, const QVariant & value, int role) override { if (role != Qt::CheckStateRole) return QStandardItemModel::setData(index, value, role); auto item = itemFromIndex(index); Q_ASSERT(item); const QString plugName = item->data(PlugNameRole).toString(); const QString slotSnap = item->data(SlotSnapRole).toString(); const QString slotName = item->data(SlotNameRole).toString(); QSnapdRequest* req; const auto snap = m_res->snap(); if (item->checkState() == Qt::Checked) { req = m_backend->client()->disconnectInterface(snap->name(), plugName, slotSnap, slotName); } else { req = m_backend->client()->connectInterface(snap->name(), plugName, slotSnap, slotName); } req->runSync(); if (req->error()) { qWarning() << "snapd error" << req->errorString(); m_res->backend()->passiveMessage(req->errorString()); } return req->error() == QSnapdRequest::NoError; } SnapResource* const m_res; SnapBackend* const m_backend; }; QAbstractItemModel* SnapResource::plugs(QObject* p) { if (!isInstalled()) return new QStandardItemModel(p); return new PlugsModel(this, qobject_cast(parent()), p); } QString SnapResource::appstreamId() const { const QStringList ids #if defined(SNAP_COMMON_IDS) = m_snap->commonIds() #endif ; return ids.isEmpty() ? QLatin1String("com.snap.") + m_snap->name() : ids.first(); } QString SnapResource::channel() const { auto req = client()->listOne(packageName()); req->runSync(); return req->error() ? QString() : req->snap()->trackingChannel(); } void SnapResource::setChannel(const QString& channelName) { #ifdef SNAP_CHANNELS Q_ASSERT(isInstalled()); auto request = client()->switchChannel(m_snap->name(), channelName); const auto currentChannel = channel(); auto dest = new CallOnDestroy([this, currentChannel]() { const auto newChannel = channel(); if (newChannel != currentChannel) { Q_EMIT channelChanged(newChannel); } }); request->runAsync(); connect(request, &QSnapdRequest::complete, dest, &QObject::deleteLater); #endif } void SnapResource::refreshSnap() { auto request = client()->find(QSnapdClient::FindFlag::MatchName, m_snap->name()); connect(request, &QSnapdRequest::complete, this, [this, request](){ if (request->error()) { qWarning() << "error" << request->error() << ": " << request->errorString(); return; } Q_ASSERT(request->snapCount() == 1); setSnap(QSharedPointer(request->snap(0))); }); request->runAsync(); } #ifdef SNAP_CHANNELS class Channels : public QObject { Q_OBJECT Q_PROPERTY(QList channels READ channels NOTIFY channelsChanged) public: Channels(SnapResource* res, QObject* parent) : QObject(parent), m_res(res) { if (res->snap()->channelCount() == 0) res->refreshSnap(); else refreshChannels(); connect(res, &SnapResource::newSnap, this, &Channels::refreshChannels); } void refreshChannels() { qDeleteAll(m_channels); m_channels.clear(); auto s = m_res->snap(); for(int i=0, c=s->channelCount(); ichannel(i); channel->setParent(this); m_channels << channel; } Q_EMIT channelsChanged(); } QList channels() const { return m_channels; } Q_SIGNALS: void channelsChanged(); private: QList m_channels; SnapResource* const m_res; }; #endif QObject * SnapResource::channels(QObject* parent) { #ifdef SNAP_CHANNELS return new Channels(this, parent); #else return nullptr; #endif } #include "SnapResource.moc" diff --git a/libdiscover/backends/SnapBackend/SnapResource.h b/libdiscover/backends/SnapBackend/SnapResource.h index 0e1a8f27..fefce9d6 100644 --- a/libdiscover/backends/SnapBackend/SnapResource.h +++ b/libdiscover/backends/SnapBackend/SnapResource.h @@ -1,93 +1,93 @@ /*************************************************************************** * Copyright © 2013 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 . * ***************************************************************************/ #ifndef SNAPRESOURCE_H #define SNAPRESOURCE_H #include #include #include #include class SnapBackend; class QAbstractItemModel; class QSnapdClient; class QSnapdChannel; class SnapResource : public AbstractResource { Q_OBJECT Q_PROPERTY(QStringList objects MEMBER m_objects CONSTANT) Q_PROPERTY(QString channel READ channel WRITE setChannel NOTIFY channelChanged) public: explicit SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* parent); ~SnapResource() override = default; QString section() override; QString origin() const override; QString longDescription() override; QString availableVersion() const override; QString installedVersion() const override; QString license() override; int size() override; QStringList categories() override; AbstractResource::State state() override; QVariant icon() const override; QString comment() override; QString name() const override; QString packageName() const override; - bool isTechnical() const override; + AbstractResource::Type type() const override; bool canExecute() const override { return true; } void invokeApplication() const override; void fetchChangelog() override; void fetchScreenshots() override; QList addonsInformation() override { return {}; } void setSnap(const QSharedPointer &snap); void setState(AbstractResource::State state); QString sourceIcon() const override { return QStringLiteral("snap"); } QDate releaseDate() const override; Q_SCRIPTABLE QAbstractItemModel* plugs(QObject* parentC); Q_SCRIPTABLE QObject* channels(QObject* parent); QString appstreamId() const override; QString channel() const; void setChannel(const QString &channel); QSharedPointer snap() const { return m_snap; } Q_SIGNALS: void channelChanged(const QString &channel); void newSnap(); public: QSnapdClient* client() const; void refreshSnap(); void gotIcon(); AbstractResource::State m_state; QSharedPointer m_snap; mutable QVariant m_icon; static const QStringList m_objects; }; #endif // SNAPRESOURCE_H diff --git a/libdiscover/resources/AbstractResource.cpp b/libdiscover/resources/AbstractResource.cpp index 0ca116c2..cd85f1b2 100644 --- a/libdiscover/resources/AbstractResource.cpp +++ b/libdiscover/resources/AbstractResource.cpp @@ -1,253 +1,248 @@ /*************************************************************************** * 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 "AbstractResource.h" #include "AbstractResourcesBackend.h" #include #include #include #include #include #include #include #include "libdiscover_debug.h" AbstractResource::AbstractResource(AbstractResourcesBackend* parent) : QObject(parent) { connect(this, &AbstractResource::stateChanged, this, &AbstractResource::sizeChanged); connect(this, &AbstractResource::stateChanged, this, &AbstractResource::reportNewState); } AbstractResource::~AbstractResource() = default; QUrl AbstractResource::homepage() { return QUrl(); } QUrl AbstractResource::helpURL() { return QUrl(); } QUrl AbstractResource::bugURL() { return QUrl(); } QUrl AbstractResource::donationURL() { return QUrl(); } -bool AbstractResource::isTechnical() const -{ - return false; -} - void AbstractResource::addMetadata(const QString &key, const QJsonValue &value) { m_metadata.insert(key, value); } QJsonValue AbstractResource::getMetadata(const QString &key) { return m_metadata.value(key); } bool AbstractResource::canUpgrade() { return state() == Upgradeable; } bool AbstractResource::isInstalled() { return state() >= Installed; } void AbstractResource::fetchScreenshots() { emit screenshotsFetched({}, {}); } QStringList AbstractResource::mimetypes() const { return QStringList(); } AbstractResourcesBackend* AbstractResource::backend() const { return static_cast(parent()); } QString AbstractResource::status() { switch(state()) { case Broken: return i18n("Broken"); case None: return i18n("Available"); case Installed: return i18n("Installed"); case Upgradeable: return i18n("Upgradeable"); } return QString(); } QString AbstractResource::sizeDescription() { return KFormat().formatByteSize(size()); } QCollatorSortKey AbstractResource::nameSortKey() { if (!m_collatorKey) { m_collatorKey.reset(new QCollatorSortKey(QCollator().sortKey(name()))); } return *m_collatorKey; } Rating* AbstractResource::rating() const { AbstractReviewsBackend* ratings = backend()->reviewsBackend(); return ratings ? ratings->ratingForApplication(const_cast(this)) : nullptr; } QStringList AbstractResource::extends() const { return {}; } QString AbstractResource::appstreamId() const { return {}; } void AbstractResource::reportNewState() { if (backend()->isFetching()) return; static const QVector ns = {"state", "status", "canUpgrade", "size", "sizeDescription", "installedVersion", "availableVersion" }; emit backend()->resourcesChanged(this, ns); } static bool shouldFilter(AbstractResource* res, const QPair& filter) { bool ret = true; switch (filter.first) { case CategoryFilter: ret = res->categories().contains(filter.second); break; case PkgSectionFilter: ret = res->section() == filter.second; break; case PkgWildcardFilter: { QString wildcard = filter.second; wildcard.remove(QLatin1Char('*')); ret = res->packageName().contains(wildcard); } break; case AppstreamIdWildcardFilter: { QString wildcard = filter.second; wildcard.remove(QLatin1Char('*')); ret = res->appstreamId().contains(wildcard); } break; case PkgNameFilter: // Only useful in the not filters ret = res->packageName() == filter.second; break; case InvalidFilter: break; } return ret; } bool AbstractResource::categoryMatches(Category* cat) { { const auto orFilters = cat->orFilters(); bool orValue = orFilters.isEmpty(); for (const auto& filter: orFilters) { if(shouldFilter(this, filter)) { orValue = true; break; } } if(!orValue) return false; } Q_FOREACH (const auto &filter, cat->andFilters()) { if(!shouldFilter(this, filter)) return false; } Q_FOREACH (const auto &filter, cat->notFilters()) { if(shouldFilter(this, filter)) return false; } return true; } static QSet walkCategories(AbstractResource* res, const QVector& cats) { QSet ret; foreach (Category* cat, cats) { if (res->categoryMatches(cat)) { const auto subcats = walkCategories(res, cat->subCategories()); if (subcats.isEmpty()) { ret += cat; } else { ret += subcats; } } } return ret; } QSet AbstractResource::categoryObjects(const QVector& cats) const { return walkCategories(const_cast(this), cats); } QString AbstractResource::categoryDisplay() const { const auto matchedCategories = categoryObjects(CategoryModel::global()->rootCategories()); QStringList ret; foreach(auto cat, matchedCategories) { ret.append(cat->name()); } ret.sort(); return ret.join(QStringLiteral(", ")); } QUrl AbstractResource::url() const { const QString asid = appstreamId(); return asid.isEmpty() ? QUrl(backend()->name() + QStringLiteral("://") + packageName()) : QUrl(QStringLiteral("appstream://") + asid); } QString AbstractResource::displayOrigin() const { return i18nc("origin (backend name)", "%1 (%2)", origin(), backend()->displayName()); } QString AbstractResource::executeLabel() const { return i18n("Launch"); } diff --git a/libdiscover/resources/AbstractResource.h b/libdiscover/resources/AbstractResource.h index febf4e50..835d3363 100644 --- a/libdiscover/resources/AbstractResource.h +++ b/libdiscover/resources/AbstractResource.h @@ -1,237 +1,238 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef ABSTRACTRESOURCE_H #define ABSTRACTRESOURCE_H #include #include #include #include #include #include #include #include #include "discovercommon_export.h" #include "PackageState.h" class Category; class Rating; class AbstractResourcesBackend; /** * \class AbstractResource AbstractResource.h "AbstractResource.h" * * \brief This is the base class of all resources. * * Each backend must reimplement its own resource class which needs to derive from this one. */ class DISCOVERCOMMON_EXPORT AbstractResource : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString packageName READ packageName CONSTANT) Q_PROPERTY(QString comment READ comment CONSTANT) Q_PROPERTY(QVariant icon READ icon NOTIFY iconChanged) Q_PROPERTY(bool canExecute READ canExecute CONSTANT) Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(QString status READ status NOTIFY stateChanged) Q_PROPERTY(QStringList category READ categories CONSTANT) - Q_PROPERTY(bool isTechnical READ isTechnical CONSTANT) Q_PROPERTY(QUrl homepage READ homepage CONSTANT) Q_PROPERTY(QUrl helpURL READ helpURL CONSTANT) Q_PROPERTY(QUrl bugURL READ bugURL CONSTANT) Q_PROPERTY(QUrl donationURL READ donationURL CONSTANT) Q_PROPERTY(bool canUpgrade READ canUpgrade NOTIFY stateChanged) Q_PROPERTY(bool isInstalled READ isInstalled NOTIFY stateChanged) Q_PROPERTY(QString license READ license CONSTANT) Q_PROPERTY(QString longDescription READ longDescription CONSTANT) Q_PROPERTY(QString origin READ origin CONSTANT) Q_PROPERTY(QString displayOrigin READ displayOrigin CONSTANT) Q_PROPERTY(int size READ size NOTIFY sizeChanged) Q_PROPERTY(QString sizeDescription READ sizeDescription NOTIFY sizeChanged) Q_PROPERTY(QString installedVersion READ installedVersion NOTIFY stateChanged) Q_PROPERTY(QString availableVersion READ availableVersion NOTIFY stateChanged) Q_PROPERTY(QString section READ section CONSTANT) Q_PROPERTY(QStringList mimetypes READ mimetypes CONSTANT) Q_PROPERTY(AbstractResourcesBackend* backend READ backend CONSTANT) Q_PROPERTY(Rating* rating READ rating NOTIFY ratingFetched) Q_PROPERTY(QString appstreamId READ appstreamId CONSTANT) Q_PROPERTY(QString categoryDisplay READ categoryDisplay CONSTANT) Q_PROPERTY(QUrl url READ url CONSTANT) Q_PROPERTY(QString executeLabel READ executeLabel CONSTANT) Q_PROPERTY(QString sourceIcon READ sourceIcon CONSTANT) Q_PROPERTY(QDate releaseDate READ releaseDate NOTIFY stateChanged) public: /** * This describes the state of the resource */ enum State { /** * When the resource is somehow broken */ Broken, /** * This means that the resource is neither installed nor broken */ None, /** * The resource is installed and up-to-date */ Installed, /** * The resource is installed and an update is available */ Upgradeable }; Q_ENUM(State) /** * Constructs the AbstractResource with its corresponding backend */ explicit AbstractResource(AbstractResourcesBackend* parent); ~AbstractResource() override; ///used as internal identification of a resource virtual QString packageName() const = 0; ///resource name to be displayed virtual QString name() const = 0; ///short description of the resource virtual QString comment() = 0; ///xdg-compatible icon name to represent the resource, url or QIcon virtual QVariant icon() const = 0; ///@returns whether invokeApplication makes something /// false if not overridden virtual bool canExecute() const = 0; ///executes the resource, if applies. Q_SCRIPTABLE virtual void invokeApplication() const = 0; virtual State state() = 0; virtual QStringList categories() = 0; ///@returns a URL that points to the app's website virtual QUrl homepage(); ///@returns a URL that points to the app's online documentation virtual QUrl helpURL(); ///@returns a URL that points to the place where you can file a bug virtual QUrl bugURL(); ///@returns a URL that points to the place where you can donate money to the app developer virtual QUrl donationURL(); - virtual bool isTechnical() const; + enum Type { Application, Addon, Technical }; + Q_ENUM(Type); + virtual Type type() const = 0; virtual int size() = 0; virtual QString sizeDescription(); virtual QString license() = 0; virtual QString installedVersion() const = 0; virtual QString availableVersion() const = 0; virtual QString longDescription() = 0; virtual QString origin() const = 0; QString displayOrigin() const; virtual QString section() = 0; ///@returns what kind of mime types the resource can consume virtual QStringList mimetypes() const; virtual QList addonsInformation() = 0; virtual QStringList extends() const; virtual QString appstreamId() const; void addMetadata(const QString &key, const QJsonValue &value); QJsonValue getMetadata(const QString &key); bool canUpgrade(); bool isInstalled(); ///@returns a user-readable explaination of the resource status ///by default, it will specify what state() is returning virtual QString status(); AbstractResourcesBackend* backend() const; /** * @returns a name sort key for faster sorting */ QCollatorSortKey nameSortKey(); /** * Convenience method to fetch the resource's rating * * @returns the rating for the resource or null if not available */ Rating* rating() const; /** * @returns a string defining the categories the resource belongs to */ QString categoryDisplay() const; bool categoryMatches(Category* cat); QSet categoryObjects(const QVector& cats) const; /** * @returns a url that uniquely identifies the application */ virtual QUrl url() const; virtual QString executeLabel() const; virtual QString sourceIcon() const = 0; /** * @returns the date of the resource's most recent release */ virtual QDate releaseDate() const = 0; public Q_SLOTS: virtual void fetchScreenshots(); virtual void fetchChangelog() = 0; Q_SIGNALS: void iconChanged(); void sizeChanged(); void stateChanged(); void ratingFetched(); ///response to the fetchScreenshots method ///@p thumbnails and @p screenshots should have the same number of elements void screenshotsFetched(const QList& thumbnails, const QList& screenshots); void changelogFetched(const QString& changelog); private: void reportNewState(); // TODO: make it std::optional or make QCollatorSortKey() QScopedPointer m_collatorKey; QJsonObject m_metadata; }; Q_DECLARE_METATYPE(QVector) #endif // ABSTRACTRESOURCE_H