diff --git a/libdiscover/backends/ApplicationBackend/Application.cpp b/libdiscover/backends/ApplicationBackend/Application.cpp index 4584ffab..ba7d75b3 100644 --- a/libdiscover/backends/ApplicationBackend/Application.cpp +++ b/libdiscover/backends/ApplicationBackend/Application.cpp @@ -1,630 +1,631 @@ /*************************************************************************** * Copyright © 2010-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 "Application.h" // Qt includes #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include // QApt includes #include #include #include #include #include #include "ApplicationBackend.h" #include "resources/PackageState.h" Application::Application(const Appstream::Component &component, QApt::Backend* backend) : AbstractResource(0) , m_data(component) , m_package(0) , m_isValid(true) , m_isTechnical(component.kind() != Appstream::Component::KindDesktop) , m_isExtrasApp(false) , m_sourceHasScreenshot(true) { static QByteArray currentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); Q_ASSERT(component.packageNames().count() == 1); - m_packageName = component.packageNames().at(0); + if (!component.packageNames().empty()) + m_packageName = component.packageNames().at(0); m_package = backend->package(packageName()); m_isValid = bool(m_package); } Application::Application(QApt::Package* package, QApt::Backend* backend) : AbstractResource(0) , m_package(package) , m_packageName(m_package->name()) , m_isValid(true) , m_isTechnical(true) , m_isExtrasApp(false) { QString arch = m_package->architecture(); if (arch != backend->nativeArchitecture() && arch != QLatin1String("all")) { m_packageName.append(QLatin1Char(':')); m_packageName.append(arch); } if (m_package->origin() == QLatin1String("LP-PPA-app-review-board")) { if (!m_package->controlField(QLatin1String("Appname")).isEmpty()) { m_isExtrasApp = true; m_isTechnical = false; } } } QString Application::name() { QString name = m_data.isValid() ? m_data.name() : QString(); if (name.isEmpty() && package()) { // extras.ubuntu.com packages can have this if (m_isExtrasApp) name = m_package->controlField(QLatin1String("Appname")); else name = m_package->name(); } if (package() && m_package->isForeignArch()) name = i18n("%1 (%2)", name, m_package->architecture()); return name; } QString Application::comment() { QString comment = m_data.isValid() ? m_data.description() : QString(); if (comment.isEmpty()) { return package()->shortDescription(); } return i18n(comment.toUtf8().constData()); } QString Application::packageName() const { return m_packageName; } QApt::Package *Application::package() { if (!m_package && parent()) { m_package = backend()->package(packageName()); Q_EMIT stateChanged(); } // Packages removed from archive will remain in app-install-data until the // next refresh, so we can have valid .desktops with no package if (!m_package) { m_isValid = false; } return m_package; } QString Application::icon() const { QString anIcon = m_data.icon(); if (anIcon.isEmpty()) { QUrl iconUrl = m_data.iconUrl(QSize()); if (iconUrl.isLocalFile()) anIcon = iconUrl.toLocalFile(); } return anIcon; } QStringList Application::findProvides(Appstream::Provides::Kind kind) const { QStringList ret; Q_FOREACH (Appstream::Provides p, m_data.provides()) if (p.kind() == kind) ret += p.value(); return ret; } QStringList Application::mimetypes() const { return findProvides(Appstream::Provides::KindMimetype); } QString Application::menuPath() { QString path; QString arrow(QString::fromUtf8(" ➜ ")); // Take the file name and remove the .desktop ending QVector execs = findExecutables(); if(execs.isEmpty()) return path; KService::Ptr service = execs.first(); QVector > ret; if (service) { ret = locateApplication(QString(), service->menuId()); } if (!ret.isEmpty()) { path.append(QStringLiteral("") .arg(KIconLoader::global()->iconPath(QStringLiteral("kde"), KIconLoader::Small))); path.append(QStringLiteral(" %1  %3") .arg(arrow) .arg(KIconLoader::global()->iconPath(QStringLiteral("applications-other"), KIconLoader::Small)) .arg(i18n("Applications"))); for (int i = 0; i < ret.size(); i++) { path.append(QStringLiteral(" %1  %3") .arg(arrow) .arg(KIconLoader::global()->iconPath(ret.at(i).second, KIconLoader::Small)) .arg(ret.at(i).first)); } } return path; } QVector > Application::locateApplication(const QString &_relPath, const QString &menuId) const { QVector > ret; KServiceGroup::Ptr root = KServiceGroup::group(_relPath); if (!root || !root->isValid()) { return ret; } const KServiceGroup::List list = root->entries(false /* sorted */, true /* exclude no display entries */, false /* allow separators */); for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { const KSycocaEntry::Ptr p = (*it); // Static cast to specific classes according to isType(). if (p->isType(KST_KService)) { const KService::Ptr service = KService::Ptr(static_cast(p.data())); if (service->noDisplay()) { continue; } if (service->menuId() == menuId) { QPair pair; pair.first = service->name(); pair.second = service->icon(); ret << pair; return ret; } } else if (p->isType(KST_KServiceGroup)) { const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr(static_cast(p.data())); if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) { continue; } QVector > found; found = locateApplication(serviceGroup->relPath(), menuId); if (!found.isEmpty()) { QPair pair; pair.first = serviceGroup->caption(); pair.second = serviceGroup->icon(); ret << pair; ret << found; return ret; } } else { continue; } } return ret; } QStringList Application::categories() { QStringList categories = m_data.isValid() ? m_data.categories() : QStringList(); if (categories.isEmpty()) { // extras.ubuntu.com packages can have this field if (m_isExtrasApp) categories = package()->controlField(QLatin1String("Category")).split(QLatin1Char(';')); } return categories; } QUrl Application::thumbnailUrl() { QUrl url(package()->controlField(QLatin1String("Thumbnail-Url"))); if(m_sourceHasScreenshot) { url = QUrl(MuonDataSources::screenshotsSource().toString() + QStringLiteral("/thumbnail/") + packageName()); } return url; } QUrl Application::screenshotUrl() { QUrl url(package()->controlField(QLatin1String("Screenshot-Url"))); if(m_sourceHasScreenshot) { url = QUrl(MuonDataSources::screenshotsSource().toString() + QStringLiteral("/screenshot/") + packageName()); } return url; } QString Application::license() { QString component = package()->component(); if (component == QLatin1String("main") || component == QLatin1String("universe")) { return i18nc("@info license", "Open Source"); } else if (component == QLatin1String("restricted")) { return i18nc("@info license", "Proprietary"); } else { return i18nc("@info license", "Unknown"); } } QApt::PackageList Application::addons() { QApt::PackageList addons; QApt::Package *pkg = package(); if (!pkg) { return addons; } QStringList tempList; // Only add recommends or suggests to the list if they aren't already going to be // installed if (!backend()->config()->readEntry(QStringLiteral("APT::Install-Recommends"), true)) { tempList << m_package->recommendsList(); } if (!backend()->config()->readEntry(QStringLiteral("APT::Install-Suggests"), false)) { tempList << m_package->suggestsList(); } tempList << m_package->enhancedByList(); QStringList languagePackages; QFile l10nFilterFile(QStringLiteral("/usr/share/language-selector/data/pkg_depends")); if (l10nFilterFile.open(QFile::ReadOnly)) { QString contents = QString::fromLatin1(l10nFilterFile.readAll()); foreach (const QString &line, contents.split(QLatin1Char('\n'))) { if (line.startsWith(QLatin1Char('#'))) { continue; } languagePackages << line.split(QLatin1Char(':')).last(); } languagePackages.removeAll(QString()); } foreach (const QString &addon, tempList) { bool shouldShow = true; QApt::Package *package = backend()->package(addon); if (!package || QString(package->section()).contains(QLatin1String("lib")) || addons.contains(package)) { continue; } foreach (const QString &langpack, languagePackages) { if (addon.contains(langpack)) { shouldShow = false; break; } } if (shouldShow) { addons << package; } } return addons; } QList Application::addonsInformation() { QList ret; QApt::PackageList pkgs = addons(); foreach(QApt::Package* p, pkgs) { ret += PackageState(p->name(), p->shortDescription(), p->isInstalled()); } return ret; } bool Application::isValid() const { return m_isValid; } bool Application::isTechnical() const { return m_isTechnical; } QUrl Application::homepage() { if(!m_package) return QUrl(); return QUrl(m_package->homepage()); } QString Application::origin() const { if(!m_package) return QString(); return m_package->origin(); } QString Application::longDescription() { if(!m_package) return QString(); return m_package->longDescription(); } QString Application::availableVersion() const { if(!m_package) return QString(); return m_package->availableVersion(); } QString Application::installedVersion() const { if(!m_package) return QString(); return m_package->installedVersion(); } QString Application::sizeDescription() { KFormat f; if (!isInstalled()) { return i18nc("@info app size", "%1 to download, %2 on disk", f.formatByteSize(package()->downloadSize()), f.formatByteSize(package()->availableInstalledSize())); } else { return i18nc("@info app size", "%1 on disk", f.formatByteSize(package()->currentInstalledSize())); } } int Application::size() { return m_package->downloadSize(); } void Application::clearPackage() { m_package = 0; } QVector Application::findExecutables() const { QVector ret; if (!m_package) { qWarning() << "trying to find the executables for an uninitialized package!" << packageName(); return ret; } QRegExp rx(QStringLiteral(".+\\.desktop$"), Qt::CaseSensitive); foreach (const QString &desktop, m_package->installedFilesList().filter(rx)) { // Important to use serviceByStorageId to ensure we get a service even // if the KSycoca database doesn't have our .desktop file yet. KService::Ptr service = KService::serviceByStorageId(desktop); if (service && service->isApplication() && !service->noDisplay() && !service->exec().isEmpty()) { ret << service; } } return ret; } void Application::emitStateChanged() { emit stateChanged(); } void Application::invokeApplication() const { QVector< KService::Ptr > execs = findExecutables(); Q_ASSERT(!execs.isEmpty()); KToolInvocation::startServiceByDesktopName(execs.first()->desktopEntryName()); } bool Application::canExecute() const { return !findExecutables().isEmpty(); } QString Application::section() { return package()->section(); } AbstractResource::State Application::state() { if (!package()) return Broken; int s = package()->state(); if (s & QApt::Package::Upgradeable) { #if QAPT_VERSION >= QT_VERSION_CHECK(3, 1, 0) if (package()->isInUpdatePhase()) return Upgradeable; #else return Upgradeable; #endif } if (s & QApt::Package::Installed) { return Installed; } return None; // Actually: none of interest to us here in plasma-discover. } void Application::fetchScreenshots() { if(!m_sourceHasScreenshot) return; QString dest = QStandardPaths::locate(QStandardPaths::TempLocation, QStringLiteral("screenshots.")+m_packageName); const QUrl packageUrl(MuonDataSources::screenshotsSource().toString() + QStringLiteral("/json/package/")+m_packageName); KIO::StoredTransferJob* job = KIO::storedGet(packageUrl, KIO::NoReload, KIO::HideProgressInfo); connect(job, &KIO::StoredTransferJob::finished, this, &Application::downloadingScreenshotsFinished); } void Application::downloadingScreenshotsFinished(KJob* j) { KIO::StoredTransferJob* job = qobject_cast< KIO::StoredTransferJob* >(j); bool done = false; if(job) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(job->data(), &error); if(error.error != QJsonParseError::NoError) { QVariantMap values = doc.toVariant().toMap(); QVariantList screenshots = values[QStringLiteral("screenshots")].toList(); QList thumbnailUrls, screenshotUrls; foreach(const QVariant& screenshot, screenshots) { QVariantMap s = screenshot.toMap(); thumbnailUrls += s[QStringLiteral("small_image_url")].toUrl(); screenshotUrls += s[QStringLiteral("large_image_url")].toUrl(); } emit screenshotsFetched(thumbnailUrls, screenshotUrls); done = true; } } if(!done) { QList thumbnails, screenshots; if(!thumbnailUrl().isEmpty()) { thumbnails += thumbnailUrl(); screenshots += screenshotUrl(); } emit screenshotsFetched(thumbnails, screenshots); } } void Application::setHasScreenshot(bool has) { m_sourceHasScreenshot = has; } QStringList Application::executables() const { QStringList ret; const QVector exes = findExecutables(); for(KService::Ptr exe : exes) { ret += exe->exec(); } return ret; } bool Application::isFromSecureOrigin() const { Q_FOREACH (const QString &archive, m_package->archives()) { if (archive.contains(QLatin1String("security"))) { return true; } } return false; } void Application::fetchChangelog() { KIO::StoredTransferJob* getJob = KIO::storedGet(m_package->changelogUrl(), KIO::NoReload, KIO::HideProgressInfo); connect(getJob, &KIO::StoredTransferJob::result, this, &Application::processChangelog); } void Application::processChangelog(KJob* j) { KIO::StoredTransferJob* job = qobject_cast(j); if (!m_package || !job) { return; } QString changelog; if(j->error()==0) changelog = buildDescription(job->data(), m_package->sourcePackage()); if (changelog.isEmpty()) { if (m_package->origin() == QStringLiteral("Ubuntu")) { changelog = xi18nc("@info/rich", "The list of changes is not yet available. " "Please use Launchpad instead.", QStringLiteral("http://launchpad.net/ubuntu/+source/") + m_package->sourcePackage()); } else { changelog = xi18nc("@info", "The list of changes is not yet available."); } } emit changelogFetched(changelog); } QString Application::buildDescription(const QByteArray& data, const QString& source) { QApt::Changelog changelog(QString::fromLatin1(data), source); QString description; QApt::ChangelogEntryList entries = changelog.newEntriesSince(m_package->installedVersion()); if (entries.size() < 1) { return description; } foreach(const QApt::ChangelogEntry &entry, entries) { description += i18nc("@info:label Refers to a software version, Ex: Version 1.2.1:", "Version %1:", entry.version()); KFormat f; QString issueDate = entry.issueDateTime().toString(Qt::DefaultLocaleShortDate); description += QLatin1String("

") + i18nc("@info:label", "This update was issued on %1", issueDate) + QLatin1String("

"); QString updateText = entry.description(); updateText.replace(QLatin1Char('\n'), QLatin1String("
")); description += QLatin1String("

") + updateText + QLatin1String("

"); } return description; } QApt::Backend *Application::backend() const { return qobject_cast(parent())->backend(); }