File indexing completed on 2025-09-21 05:07:58

0001 /*
0002  *   SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0003  *   SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
0004  *
0005  *   SPDX-License-Identifier: LGPL-2.0-only
0006  */
0007 
0008 #include "comic.h"
0009 
0010 #include <QDate>
0011 #include <QDebug>
0012 #include <QFileInfo>
0013 #include <QImage>
0014 #include <QNetworkInformation>
0015 #include <QSettings>
0016 #include <QStandardPaths>
0017 
0018 #include <KPackage/PackageLoader>
0019 #include <qloggingcategory.h>
0020 
0021 #include "cachedprovider.h"
0022 #include "comic_debug.h"
0023 #include "comicprovider.h"
0024 #include "comicproviderkross.h"
0025 
0026 ComicEngine::ComicEngine(QObject *parent)
0027     : QObject(parent)
0028     , mEmptySuffix(false)
0029 {
0030     QNetworkInformation::instance()->loadBackendByFeatures(QNetworkInformation::Feature::Reachability);
0031     loadProviders();
0032 }
0033 
0034 QList<ComicProviderInfo> ComicEngine::loadProviders()
0035 {
0036     mProviders.clear();
0037     const auto comics = KPackage::PackageLoader::self()->listKPackages(QStringLiteral("Plasma/Comic"));
0038     QList<ComicProviderInfo> providers;
0039     for (auto comicPackage : comics) {
0040         const KPluginMetaData comic = comicPackage.metadata();
0041         qCDebug(PLASMA_COMIC) << "ComicEngine::loadProviders()  service name=" << comic.name();
0042         ComicProviderInfo data;
0043         data.pluginId = comic.pluginId();
0044         data.name = comic.name();
0045         QFileInfo file(comic.iconName());
0046         if (file.isRelative()) {
0047             data.icon =
0048                 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("plasma/comics/%1/%2").arg(comic.pluginId(), comic.iconName()));
0049         } else {
0050             data.icon = comic.iconName();
0051         }
0052         mProviders << comic.pluginId();
0053         providers << data;
0054     }
0055     return providers;
0056 }
0057 
0058 void ComicEngine::setMaxComicLimit(int maxComicLimit)
0059 {
0060     CachedProvider::setMaxComicLimit(maxComicLimit);
0061 }
0062 
0063 bool ComicEngine::requestSource(const QString &identifier)
0064 {
0065     if (m_jobs.contains(identifier)) {
0066         return true;
0067     }
0068 
0069     const QStringList parts = identifier.split(QLatin1Char(':'), Qt::KeepEmptyParts);
0070 
0071     // check whether it is cached, make sure second part present
0072     if (parts.count() > 1 && (CachedProvider::isCached(identifier) || !isOnline())) {
0073         ComicProvider *provider = new CachedProvider(this, KPluginMetaData{}, IdentifierType::StringIdentifier, identifier);
0074         m_jobs[identifier] = provider;
0075         connect(provider, &ComicProvider::finished, this, &ComicEngine::finished);
0076         connect(provider, &ComicProvider::error, this, &ComicEngine::error);
0077         return true;
0078     }
0079 
0080     // ... start a new query otherwise
0081     if (parts.count() < 2) {
0082         Q_EMIT requestFinished(ComicMetaData{.error = true});
0083         qWarning() << "Less than two arguments specified.";
0084         return false;
0085     }
0086     if (!mProviders.contains(parts[0])) {
0087         // User might have installed more from GHNS
0088         loadProviders();
0089         if (!mProviders.contains(parts[0])) {
0090             Q_EMIT requestFinished(ComicMetaData{.error = true});
0091             qWarning() << identifier << "comic plugin does not seem to be installed.";
0092             return false;
0093         }
0094     }
0095 
0096     // check if there is a connection
0097     if (!isOnline()) {
0098         qCDebug(PLASMA_COMIC) << "Currently offline, requested identifier was" << mIdentifierError;
0099         mIdentifierError = identifier;
0100         ComicMetaData data;
0101         data.error = true;
0102         data.errorAutomaticallyFixable = true;
0103         data.identifier = identifier;
0104         data.previousIdentifier = lastCachedIdentifier(identifier);
0105         Q_EMIT requestFinished(data);
0106         qCDebug(PLASMA_COMIC) << "No internet connection, using cached data";
0107         return true;
0108     }
0109 
0110     KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Comic"), parts[0]);
0111 
0112     bool isCurrentComic = parts[1].isEmpty();
0113 
0114     ComicProvider *provider = nullptr;
0115 
0116     QVariant data;
0117     const IdentifierType identifierType = stringToIdentifierType(pkg.metadata().value(QStringLiteral("X-KDE-PlasmaComicProvider-SuffixType")));
0118     if (identifierType == IdentifierType::DateIdentifier) {
0119         QDate date = QDate::fromString(parts[1], Qt::ISODate);
0120         if (!date.isValid()) {
0121             date = QDate::currentDate();
0122         }
0123 
0124         data = date;
0125     } else if (identifierType == IdentifierType::NumberIdentifier) {
0126         data = parts[1].toInt();
0127     } else if (identifierType == IdentifierType::StringIdentifier) {
0128         data = parts[1];
0129     }
0130     provider = new ComicProviderKross(this, pkg.metadata(), identifierType, data);
0131     provider->setIsCurrent(isCurrentComic);
0132 
0133     m_jobs[identifier] = provider;
0134 
0135     connect(provider, &ComicProvider::finished, this, &ComicEngine::finished);
0136     connect(provider, &ComicProvider::error, this, &ComicEngine::error);
0137     return true;
0138 }
0139 
0140 void ComicEngine::finished(ComicProvider *provider)
0141 {
0142     // sets the data
0143     if (provider->image().isNull()) {
0144         qCWarning(PLASMA_COMIC) << "Provider returned null image" << provider->name();
0145         error(provider);
0146         return;
0147     }
0148 
0149     ComicMetaData data = metaDataFromProvider(provider);
0150 
0151     // different comic -- with no error yet -- has been chosen, old error is invalidated
0152     QString temp = mIdentifierError.left(mIdentifierError.indexOf(QLatin1Char(':')) + 1);
0153     if (!mIdentifierError.isEmpty() && provider->identifier().indexOf(temp) == -1) {
0154         mIdentifierError.clear();
0155     }
0156     // comic strip with error worked now
0157     if (!mIdentifierError.isEmpty() && (mIdentifierError == provider->identifier())) {
0158         mIdentifierError.clear();
0159     }
0160 
0161     // store in cache if it's not the response of a CachedProvider,
0162     if (!provider->inherits("CachedProvider") && !provider->image().isNull()) {
0163         CachedProvider::storeInCache(provider->identifier(), provider->image(), data);
0164     }
0165     provider->deleteLater();
0166 
0167     const QString key = m_jobs.key(provider);
0168     if (!key.isEmpty()) {
0169         m_jobs.remove(key);
0170     }
0171     Q_EMIT requestFinished(data);
0172 }
0173 
0174 void ComicEngine::error(ComicProvider *provider)
0175 {
0176     QString identifier(provider->identifier());
0177     mIdentifierError = identifier;
0178 
0179     qWarning() << identifier << "plugging reported an error.";
0180 
0181     ComicMetaData data = metaDataFromProvider(provider);
0182     data.error = true;
0183 
0184     // if there was an error loading the last cached comic strip, do not return its id anymore
0185     const QString lastCachedId = lastCachedIdentifier(identifier);
0186     if (lastCachedId != provider->identifier().mid(provider->identifier().indexOf(QLatin1Char(':')) + 1)) {
0187         // sets the previousIdentifier to the identifier of a strip that has been cached before
0188         data.previousIdentifier = lastCachedId;
0189     }
0190     data.nextIdentifier = QString();
0191 
0192     const QString key = m_jobs.key(provider);
0193     if (!key.isEmpty()) {
0194         m_jobs.remove(key);
0195     }
0196 
0197     provider->deleteLater();
0198     Q_EMIT requestFinished(data);
0199 }
0200 
0201 ComicMetaData ComicEngine::metaDataFromProvider(ComicProvider *provider)
0202 {
0203     QString identifier(provider->identifier());
0204 
0205     /**
0206      * Requests for the current day have no suffix (date or id)
0207      * set initially, so we have to remove the 'faked' suffix
0208      * here again to not confuse the applet.
0209      */
0210     if (provider->isCurrent()) {
0211         identifier = identifier.left(identifier.indexOf(QLatin1Char(':')) + 1);
0212     }
0213 
0214     ComicMetaData data;
0215     data.imageUrl = provider->imageUrl();
0216     data.image = provider->image();
0217     data.websiteUrl = provider->websiteUrl();
0218     data.shopUrl = provider->shopUrl();
0219     data.nextIdentifier = provider->nextIdentifier();
0220     data.previousIdentifier = provider->previousIdentifier();
0221     data.comicAuthor = provider->comicAuthor();
0222     data.additionalText = provider->additionalText();
0223     data.stripTitle = provider->stripTitle();
0224     data.firstStripIdentifier = provider->firstStripIdentifier();
0225     data.identifier = identifier;
0226     data.providerName = provider->name();
0227     data.identifierType = provider->identifierType();
0228     data.isLeftToRight = provider->isLeftToRight();
0229     data.isTopToBottom = provider->isTopToBottom();
0230 
0231     return data;
0232 }
0233 
0234 QString ComicEngine::lastCachedIdentifier(const QString &identifier) const
0235 {
0236     const QString id = identifier.left(identifier.indexOf(QLatin1Char(':')));
0237     QString data = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_engine_comic/");
0238     data += QString::fromLatin1(QUrl::toPercentEncoding(id));
0239     QSettings settings(data + QLatin1String(".conf"), QSettings::IniFormat);
0240     QString previousIdentifier = settings.value(QLatin1String("lastCachedStripIdentifier"), QString()).toString();
0241 
0242     return previousIdentifier;
0243 }
0244 
0245 bool ComicEngine::isOnline() const
0246 {
0247     return QNetworkInformation::instance()->reachability() == QNetworkInformation::Reachability::Online;
0248 }