From 9b27feea0474302be2b475a2ffebf55df4c1533e Mon Sep 17 00:00:00 2001 From: Fushan Wen Date: Wed, 25 May 2022 22:30:45 +0800 Subject: [PATCH] wallpapers/potd: fix multimonitor support After dataengine was ported away, it requires to use a self-maintained engine to properly support multi-screen wallpapers. 3 new classes are added: - PotdEngine: a class to manage clients, create and delete clients on demand. - Client: a class to process wallpaper data - Backend: a class used in QML side, to relay information from the client The major part of PotdProviderModel are moved to the 3 classes, so the model class is now a pure model. BUG: 454333 FIXED-IN: 5.25.1 (cherry picked from commit 86ef83c6b3f2e20e5c5ecbc0fa52584180c14546) wallpapers/potd: increase m_updateCount before `updateSource` When cache is available, `PotdClient::done` will be directly emitted. (cherry picked from commit 9a1180f16122734d2917ddbf9d4397c269f2c5f3) wallpapers/potd: remove redundant if statement The while loop already checks the condition. (cherry picked from commit 6633fa6070f99167d450559c2b78a79ae768e888) wallpapers/potd: add some debug information (cherry picked from commit 09c8337f7e3c55af6af55b9a172dacf6dd817cd4) --- .../package/contents/ui/WallpaperPreview.qml | 20 +- .../potd/package/contents/ui/config.qml | 38 +- wallpapers/potd/package/contents/ui/main.qml | 35 +- wallpapers/potd/plugins/CMakeLists.txt | 2 + wallpapers/potd/plugins/cachedprovider.cpp | 26 +- wallpapers/potd/plugins/cachedprovider.h | 13 +- wallpapers/potd/plugins/potdbackend.cpp | 267 +++++++++++ wallpapers/potd/plugins/potdbackend.h | 127 +++++ wallpapers/potd/plugins/potdengine.cpp | 296 ++++++++++++ wallpapers/potd/plugins/potdengine.h | 106 +++++ wallpapers/potd/plugins/potdplugin.cpp | 20 +- wallpapers/potd/plugins/potdprovidermodel.cpp | 438 +----------------- wallpapers/potd/plugins/potdprovidermodel.h | 136 +----- 13 files changed, 899 insertions(+), 625 deletions(-) create mode 100644 wallpapers/potd/plugins/potdbackend.cpp create mode 100644 wallpapers/potd/plugins/potdbackend.h create mode 100644 wallpapers/potd/plugins/potdengine.cpp create mode 100644 wallpapers/potd/plugins/potdengine.h diff --git a/wallpapers/potd/package/contents/ui/WallpaperPreview.qml b/wallpapers/potd/package/contents/ui/WallpaperPreview.qml index 6e5d98a82..ec662130d 100644 --- a/wallpapers/potd/package/contents/ui/WallpaperPreview.qml +++ b/wallpapers/potd/package/contents/ui/WallpaperPreview.qml @@ -33,31 +33,31 @@ Column { width: Math.round(Screen.width / 10 + Kirigami.Units.smallSpacing * 2) height: Math.round(Screen.height / 10 + Kirigami.Units.smallSpacing * 2) - image: PotdProviderModelInstance.image - localUrl: PotdProviderModelInstance.localUrl - infoUrl: PotdProviderModelInstance.infoUrl - title: PotdProviderModelInstance.title - author: PotdProviderModelInstance.author + image: backend.image + localUrl: backend.localUrl + infoUrl: backend.infoUrl + title: backend.title + author: backend.author thumbnailAvailable: !delegate.isNull - thumbnailLoading: PotdProviderModelInstance.loading + thumbnailLoading: backend.loading actions: [ Kirigami.Action { icon.name: "document-save" - enabled: PotdProviderModelInstance.localUrl.length > 0 + enabled: backend.localUrl.length > 0 visible: enabled tooltip: i18nc("@action:inmenu wallpaper preview menu", "Save Image as…") - onTriggered: PotdProviderModelInstance.saveImage() + onTriggered: backend.saveImage() Accessible.description: i18nc("@info:whatsthis for a button and a menu item", "Save today's picture to local disk") }, Kirigami.Action { icon.name: "internet-services" - enabled: PotdProviderModelInstance.infoUrl.toString().length > 0 + enabled: backend.infoUrl.toString().length > 0 visible: false tooltip: i18nc("@action:inmenu wallpaper preview menu, will open the website of the wallpaper", "Open Link in Browser…") - onTriggered: Qt.openUrlExternally(PotdProviderModelInstance.infoUrl) + onTriggered: Qt.openUrlExternally(backend.infoUrl) Accessible.description: i18nc("@info:whatsthis for a menu item", "Open the website of today's picture in the default browser") } diff --git a/wallpapers/potd/package/contents/ui/config.qml b/wallpapers/potd/package/contents/ui/config.qml index 1010f501d..0f0bfa89f 100644 --- a/wallpapers/potd/package/contents/ui/config.qml +++ b/wallpapers/potd/package/contents/ui/config.qml @@ -6,6 +6,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.8 as QQC2 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 import org.kde.kquickcontrols 2.0 as KQC2 import org.kde.kirigami 2.5 as Kirigami @@ -22,12 +24,26 @@ Kirigami.FormLayout { property alias cfg_Color: colorButton.color property alias formLayout: root + PotdBackend { + id: backend + identifier: cfg_Provider + arguments: { + if (identifier === "unsplash") { + // Needs to specify category for unsplash provider + return [cfg_Category]; + } else if (identifier === "bing") { + // Bing supports 1366/1920/UHD resolutions + return [Screen.width, Screen.height, Screen.devicePixelRatio]; + } + return []; + } + } QQC2.ComboBox { id: providerComboBox Kirigami.FormData.label: i18ndc("plasma_wallpaper_org.kde.potd", "@label:listbox", "Provider:") - model: PotdProviderModelInstance - currentIndex: model.currentIndex + model: PotdProviderModel { } + currentIndex: model.indexOf(cfg_Provider) textRole: "display" valueRole: "id" onCurrentValueChanged: { @@ -246,7 +262,7 @@ Kirigami.FormLayout { Kirigami.FormData.label: i18nc("@label", "Title:") contentWidth: wallpaperPreview.implicitWidth * 1.5 visible: wallpaperPreview.visible && text.length > 0 - text: PotdProviderModelInstance.title + text: backend.title bold: true } @@ -259,7 +275,7 @@ Kirigami.FormLayout { Kirigami.FormData.label: i18nc("@label", "Author:") contentWidth: titleLabel.contentWidth visible: wallpaperPreview.visible && text.length > 0 - text: PotdProviderModelInstance.author + text: backend.author bold: false } @@ -276,26 +292,26 @@ Kirigami.FormLayout { Kirigami.Action { icon.name: "document-open-folder" text: i18nc("@action:button", "Open Containing Folder") - visible: PotdProviderModelInstance.saveStatus === Global.Successful - onTriggered: Qt.openUrlExternally(PotdProviderModelInstance.savedFolder) + visible: backend.saveStatus === Global.Successful + onTriggered: Qt.openUrlExternally(backend.savedFolder) Accessible.description: i18nc("@info:whatsthis for a button", "Open the destination folder where the wallpaper image was saved.") } ] - onLinkActivated: Qt.openUrlExternally(PotdProviderModelInstance.savedUrl) + onLinkActivated: Qt.openUrlExternally(backend.savedUrl) Connections { - target: PotdProviderModelInstance + target: backend function onSaveStatusChanged() { - switch (PotdProviderModelInstance.saveStatus) { + switch (backend.saveStatus) { case Global.Successful: - saveMessage.text = PotdProviderModelInstance.saveStatusMessage; + saveMessage.text = backend.saveStatusMessage; saveMessage.type = Kirigami.MessageType.Positive; break; case Global.Failed: - saveMessage.text = PotdProviderModelInstance.saveStatusMessage; + saveMessage.text = backend.saveStatusMessage; saveMessage.type = Kirigami.MessageType.Error; break; default: diff --git a/wallpapers/potd/package/contents/ui/main.qml b/wallpapers/potd/package/contents/ui/main.qml index 1bb267951..67f0c5ab2 100644 --- a/wallpapers/potd/package/contents/ui/main.qml +++ b/wallpapers/potd/package/contents/ui/main.qml @@ -15,6 +15,21 @@ import org.kde.plasma.wallpapers.potd 1.0 Rectangle { id: root + PotdBackend { + id: backend + identifier: wallpaper.configuration.Provider + arguments: { + if (identifier === "unsplash") { + // Needs to specify category for unsplash provider + return [wallpaper.configuration.Category]; + } else if (identifier === "bing") { + // Bing supports 1366/1920/UHD resolutions + return [Screen.width, Screen.height, Screen.devicePixelRatio]; + } + return []; + } + } + Rectangle { id: backgroundColor anchors.fill: parent @@ -26,7 +41,7 @@ Rectangle { QImageItem { anchors.fill: parent - image: PotdProviderModelInstance.image + image: backend.image fillMode: wallpaper.configuration.FillMode smooth: true @@ -35,22 +50,4 @@ Rectangle { wallpaper.repaintNeeded(); } } - - Component.onCompleted: { - PotdProviderModelInstance.identifier = Qt.binding(() => wallpaper.configuration.Provider); - // Needs to specify category for unsplash provider - PotdProviderModelInstance.arguments = Qt.binding(() => { - const identifier = PotdProviderModelInstance.identifier; - - if (identifier === "unsplash") { - // Needs to specify category for unsplash provider - return [wallpaper.configuration.Category]; - } else if (identifier === "bing") { - // Bing supports 1366/1920/UHD resolutions - return [Screen.width, Screen.height, Screen.devicePixelRatio]; - } - return []; - }); - PotdProviderModelInstance.running = true; - } } diff --git a/wallpapers/potd/plugins/CMakeLists.txt b/wallpapers/potd/plugins/CMakeLists.txt index 052f120a0..07de12fdd 100644 --- a/wallpapers/potd/plugins/CMakeLists.txt +++ b/wallpapers/potd/plugins/CMakeLists.txt @@ -1,5 +1,7 @@ set(potd_engine_SRCS cachedprovider.cpp + potdbackend.cpp + potdengine.cpp potdprovidermodel.cpp potdplugin.cpp ) diff --git a/wallpapers/potd/plugins/cachedprovider.cpp b/wallpapers/potd/plugins/cachedprovider.cpp index 16eeb8578..3f9ffe63f 100644 --- a/wallpapers/potd/plugins/cachedprovider.cpp +++ b/wallpapers/potd/plugins/cachedprovider.cpp @@ -56,15 +56,16 @@ void LoadImageThread::run() Q_EMIT done(data); } -SaveImageThread::SaveImageThread(const QString &identifier, const PotdProviderData &data) +SaveImageThread::SaveImageThread(const QString &identifier, const QVariantList &args, const PotdProviderData &data) : m_identifier(identifier) + , m_args(args) , m_data(data) { } void SaveImageThread::run() { - m_data.wallpaperLocalUrl = CachedProvider::identifierToPath(m_identifier); + m_data.wallpaperLocalUrl = CachedProvider::identifierToPath(m_identifier, m_args); m_data.wallpaperImage.save(m_data.wallpaperLocalUrl, "JPEG"); const QString infoPath = m_data.wallpaperLocalUrl + ".json"; @@ -86,19 +87,28 @@ void SaveImageThread::run() Q_EMIT done(m_identifier, m_data); } -QString CachedProvider::identifierToPath(const QString &identifier) +QString CachedProvider::identifierToPath(const QString &identifier, const QVariantList &args) { + const QString argString = std::accumulate(args.cbegin(), args.cend(), QString(), [](const QString &s, const QVariant &arg) { + if (arg.canConvert(QMetaType::QString)) { + return s + QStringLiteral(":%1").arg(arg.toString()); + } + + return s; + }); + const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/plasma_engine_potd/"); QDir d; d.mkpath(dataDir); - return dataDir + identifier; + return QStringLiteral("%1%2%3").arg(dataDir, identifier, argString); } -CachedProvider::CachedProvider(const QString &identifier, QObject *parent) +CachedProvider::CachedProvider(const QString &identifier, const QVariantList &args, QObject *parent) : PotdProvider(parent, KPluginMetaData(), QVariantList()) , mIdentifier(identifier) + , m_args(args) { - LoadImageThread *thread = new LoadImageThread(identifierToPath(mIdentifier)); + LoadImageThread *thread = new LoadImageThread(identifierToPath(mIdentifier, m_args)); connect(thread, &LoadImageThread::done, this, &CachedProvider::triggerFinished); QThreadPool::globalInstance()->start(thread); } @@ -120,9 +130,9 @@ void CachedProvider::triggerFinished(const PotdProviderData &data) Q_EMIT finished(this); } -bool CachedProvider::isCached(const QString &identifier, bool ignoreAge) +bool CachedProvider::isCached(const QString &identifier, const QVariantList &args, bool ignoreAge) { - const QString path = identifierToPath(identifier); + const QString path = identifierToPath(identifier, args); if (!QFile::exists(path)) { return false; } diff --git a/wallpapers/potd/plugins/cachedprovider.h b/wallpapers/potd/plugins/cachedprovider.h index 3c40bfe73..79f568cb5 100644 --- a/wallpapers/potd/plugins/cachedprovider.h +++ b/wallpapers/potd/plugins/cachedprovider.h @@ -23,9 +23,10 @@ public: * Creates a new cached provider. * * @param identifier The identifier of the cached picture. + * @param args The arguments of the identifier. * @param parent The parent object. */ - CachedProvider(const QString &identifier, QObject *parent); + CachedProvider(const QString &identifier, const QVariantList &args, QObject *parent); /** * Returns the identifier of the picture request (name + date). @@ -33,20 +34,21 @@ public: QString identifier() const override; /** - * Returns whether a picture with the given @p identifier is cached. + * Returns whether a picture with the given @p identifier and @p args is cached. */ - static bool isCached(const QString &identifier, bool ignoreAge = false); + static bool isCached(const QString &identifier, const QVariantList &args, bool ignoreAge = false); /** * Returns a path for the given identifier */ - static QString identifierToPath(const QString &identifier); + static QString identifierToPath(const QString &identifier, const QVariantList &args); private Q_SLOTS: void triggerFinished(const PotdProviderData &data); private: QString mIdentifier; + QVariantList m_args; }; class LoadImageThread : public QObject, public QRunnable @@ -69,7 +71,7 @@ class SaveImageThread : public QObject, public QRunnable Q_OBJECT public: - SaveImageThread(const QString &identifier, const PotdProviderData &data); + SaveImageThread(const QString &identifier, const QVariantList &args, const PotdProviderData &data); void run() override; Q_SIGNALS: @@ -77,5 +79,6 @@ Q_SIGNALS: private: QString m_identifier; + QVariantList m_args; PotdProviderData m_data; }; diff --git a/wallpapers/potd/plugins/potdbackend.cpp b/wallpapers/potd/plugins/potdbackend.cpp new file mode 100644 index 000000000..efdb96bb7 --- /dev/null +++ b/wallpapers/potd/plugins/potdbackend.cpp @@ -0,0 +1,267 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "potdbackend.h" + +#include +#include +#include // For "Pictures" folder + +#include // For "Save Image" +#include + +#include "potdengine.h" + +namespace +{ +static PotdEngine *s_engine = nullptr; +static int s_instanceCount = 0; +} + +PotdBackend::PotdBackend(QObject *parent) + : QObject(parent) +{ + if (!s_engine) { + Q_ASSERT(s_instanceCount == 0); + s_engine = new PotdEngine(); + } + s_instanceCount++; +} + +PotdBackend::~PotdBackend() +{ + s_engine->unregisterClient(m_identifier, m_args); + s_instanceCount--; + + if (!s_instanceCount) { + delete s_engine; + s_engine = nullptr; + } +} + +void PotdBackend::classBegin() +{ +} + +void PotdBackend::componentComplete() +{ + // don't bother loading single image until all properties have settled + m_ready = true; + + // Register the identifier in the data engine + registerClient(); +} + +QString PotdBackend::identifier() const +{ + return m_identifier; +} + +void PotdBackend::setIdentifier(const QString &identifier) +{ + if (m_identifier == identifier) { + return; + } + + if (m_ready) { + s_engine->unregisterClient(m_identifier, m_args); + } + m_identifier = identifier; + registerClient(); + + Q_EMIT identifierChanged(); +} + +QVariantList PotdBackend::arguments() const +{ + return m_args; +} + +void PotdBackend::setArguments(const QVariantList &args) +{ + if (m_args == args) { + return; + } + + if (m_ready) { + s_engine->unregisterClient(m_identifier, m_args); + } + m_args = args; + registerClient(); + + Q_EMIT argumentsChanged(); +} + +QImage PotdBackend::image() const +{ + if (!m_client) { + return {}; + } + + return m_client->m_data.wallpaperImage; +} + +bool PotdBackend::loading() const +{ + if (!m_client) { + return false; + } + + return m_client->m_loading; +} + +QString PotdBackend::localUrl() const +{ + if (!m_client) { + return {}; + } + + return m_client->m_data.wallpaperLocalUrl; +} + +QUrl PotdBackend::infoUrl() const +{ + if (!m_client) { + return {}; + } + + return m_client->m_data.wallpaperInfoUrl; +} + +QUrl PotdBackend::remoteUrl() const +{ + if (!m_client) { + return {}; + } + + return m_client->m_data.wallpaperRemoteUrl; +} + +QString PotdBackend::title() const +{ + if (!m_client) { + return {}; + } + + return m_client->m_data.wallpaperTitle; +} + +QString PotdBackend::author() const +{ + if (!m_client) { + return {}; + } + + return m_client->m_data.wallpaperAuthor; +} + +void PotdBackend::saveImage() +{ + if (m_client->m_data.wallpaperLocalUrl.isEmpty()) { + return; + } + + auto sanitizeFileName = [](const QString &name) { + if (name.isEmpty()) { + return name; + } + + const char notAllowedChars[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`'/\\"; + QString sanitizedName(name); + + for (const char *c = notAllowedChars; *c; c++) { + sanitizedName.replace(QLatin1Char(*c), QLatin1Char('-')); + } + + return sanitizedName; + }; + + const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + const QString path = locations.isEmpty() ? QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0) : locations.at(0); + + QString defaultFileName = m_client->m_metadata.name().trimmed(); + + if (!m_client->m_data.wallpaperTitle.isEmpty()) { + defaultFileName += QLatin1Char('-') + m_client->m_data.wallpaperTitle.trimmed(); + if (!m_client->m_data.wallpaperAuthor.isEmpty()) { + defaultFileName += QLatin1Char('-') + m_client->m_data.wallpaperAuthor.trimmed(); + } + } else { + // Use current date + if (!defaultFileName.isEmpty()) { + defaultFileName += QLatin1Char('-'); + } + defaultFileName += QDate::currentDate().toString(); + } + + m_savedUrl = QUrl::fromLocalFile( // + QFileDialog::getSaveFileName( // + nullptr, // + i18nc("@title:window", "Save Today's Picture"), // + path + "/" + sanitizeFileName(defaultFileName) + ".jpg", // + i18nc("@label:listbox Template for file dialog", "JPEG image (*.jpeg *.jpg *.jpe)"), // + nullptr, // + QFileDialog::DontConfirmOverwrite // KIO::CopyJob will show the confirmation dialog. + ) // + ); + + if (m_savedUrl.isEmpty() || !m_savedUrl.isValid()) { + return; + } + + m_savedFolder = QUrl::fromLocalFile(QFileInfo(m_savedUrl.toLocalFile()).absolutePath()); + + KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(m_client->m_data.wallpaperLocalUrl), m_savedUrl, KIO::HideProgressInfo); + connect(copyJob, &KJob::finished, this, [this](KJob *job) { + if (job->error()) { + m_saveStatusMessage = job->errorText(); + if (m_saveStatusMessage.isEmpty()) { + m_saveStatusMessage = i18nc("@info:status after a save action", "The image was not saved."); + } + m_saveStatus = FileOperationStatus::Failed; + Q_EMIT saveStatusChanged(); + } else { + m_saveStatusMessage = i18nc("@info:status after a save action %1 file path %2 basename", + "The image was saved as %2", + m_savedUrl.toString(), + m_savedUrl.fileName()); + m_saveStatus = FileOperationStatus::Successful; + Q_EMIT saveStatusChanged(); + } + }); + copyJob->start(); +} + +void PotdBackend::registerClient() +{ + if (!m_ready) { + return; + } + + m_client = s_engine->registerClient(m_identifier, m_args); + + if (!m_client) { + // Invalid identifier + return; + } + + connect(m_client, &PotdClient::imageChanged, this, &PotdBackend::imageChanged); + connect(m_client, &PotdClient::loadingChanged, this, &PotdBackend::loadingChanged); + connect(m_client, &PotdClient::localUrlChanged, this, &PotdBackend::localUrlChanged); + connect(m_client, &PotdClient::infoUrlChanged, this, &PotdBackend::infoUrlChanged); + connect(m_client, &PotdClient::remoteUrlChanged, this, &PotdBackend::remoteUrlChanged); + connect(m_client, &PotdClient::titleChanged, this, &PotdBackend::titleChanged); + connect(m_client, &PotdClient::authorChanged, this, &PotdBackend::authorChanged); + + // Refresh the desktop wallpaper and the information in config dialog + Q_EMIT imageChanged(); + Q_EMIT loadingChanged(); + Q_EMIT localUrlChanged(); + Q_EMIT infoUrlChanged(); + Q_EMIT remoteUrlChanged(); + Q_EMIT titleChanged(); + Q_EMIT authorChanged(); +} diff --git a/wallpapers/potd/plugins/potdbackend.h b/wallpapers/potd/plugins/potdbackend.h new file mode 100644 index 000000000..0cf9f831a --- /dev/null +++ b/wallpapers/potd/plugins/potdbackend.h @@ -0,0 +1,127 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include +#include +#include + +#include "potdengine.h" + +/** + * This class provides the backend of Pictures of The Day from various online + * websites. + */ +class PotdBackend : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) + Q_PROPERTY(QVariantList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) + + /** + * Read-only properties that expose data from the provider. + */ + Q_PROPERTY(QImage image READ image NOTIFY imageChanged) + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(QString localUrl READ localUrl NOTIFY localUrlChanged) + /** + * @return The website URL of the image + */ + Q_PROPERTY(QUrl infoUrl READ infoUrl NOTIFY infoUrlChanged) + /** + * @return The remote image URL + */ + Q_PROPERTY(QUrl remoteUrl READ remoteUrl NOTIFY remoteUrlChanged) + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(QString author READ author NOTIFY authorChanged) + + /** + * @return the result of the file operation. + */ + Q_PROPERTY(FileOperationStatus saveStatus MEMBER m_saveStatus NOTIFY saveStatusChanged) + + /** + * @return the status message after a save operation. + */ + Q_PROPERTY(QString saveStatusMessage MEMBER m_saveStatusMessage CONSTANT) + + /** + * @return the folder path of the saved image file. + */ + Q_PROPERTY(QUrl savedFolder MEMBER m_savedFolder CONSTANT) + + /** + * @return the path of the saved image file. + */ + Q_PROPERTY(QUrl savedUrl MEMBER m_savedUrl CONSTANT) + +public: + enum class FileOperationStatus { + None, + Successful, + Failed, + }; + Q_ENUM(FileOperationStatus) + + explicit PotdBackend(QObject *parent = nullptr); + ~PotdBackend() override; + + void classBegin() override; + void componentComplete() override; + + QString identifier() const; + void setIdentifier(const QString &identifier); + + QVariantList arguments() const; + void setArguments(const QVariantList &args); + + QImage image() const; + bool loading() const; + QString localUrl() const; + QUrl infoUrl() const; + QUrl remoteUrl() const; + QString title() const; + QString author() const; + + /** + * Opens a Save dialog to choose the save location, and copies the source file to the + * selected destination. + */ + Q_INVOKABLE void saveImage(); + +Q_SIGNALS: + void identifierChanged(); + void argumentsChanged(); + + void imageChanged(); + void loadingChanged(); + void localUrlChanged(); + void infoUrlChanged(); + void remoteUrlChanged(); + void titleChanged(); + void authorChanged(); + + void saveStatusChanged(); + +private: + void registerClient(); + + bool m_ready = false; + + QString m_identifier; + QVariantList m_args; + + QUrl m_savedFolder; + QUrl m_savedUrl; + FileOperationStatus m_saveStatus = FileOperationStatus::None; + QString m_saveStatusMessage; + + PotdClient *m_client = nullptr; +}; diff --git a/wallpapers/potd/plugins/potdengine.cpp b/wallpapers/potd/plugins/potdengine.cpp new file mode 100644 index 000000000..0b0fb01bd --- /dev/null +++ b/wallpapers/potd/plugins/potdengine.cpp @@ -0,0 +1,296 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "potdengine.h" + +#include + +#include +#include + +#include + +#include "cachedprovider.h" +#include "debug.h" + +using namespace std::chrono_literals; + +PotdClient::PotdClient(const KPluginMetaData &metadata, const QVariantList &args, QObject *parent) + : QObject(parent) + , m_metadata(metadata) + , m_identifier(metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier"))) + , m_args(args) +{ + updateSource(); +} + +void PotdClient::updateSource(bool refresh) +{ + setLoading(true); + + // Check whether it is cached already... + if (!refresh && CachedProvider::isCached(m_identifier, m_args, false)) { + qCDebug(WALLPAPERPOTD) << "A local cache is available for" << m_identifier << "with arguments" << m_args; + + CachedProvider *provider = new CachedProvider(m_identifier, m_args, this); + connect(provider, &PotdProvider::finished, this, &PotdClient::slotFinished); + connect(provider, &PotdProvider::error, this, &PotdClient::slotError); + return; + } + + const auto pluginResult = KPluginFactory::instantiatePlugin(m_metadata, this, m_args); + + if (pluginResult) { + connect(pluginResult.plugin, &PotdProvider::finished, this, &PotdClient::slotFinished); + connect(pluginResult.plugin, &PotdProvider::error, this, &PotdClient::slotError); + } else { + qCWarning(WALLPAPERPOTD) << "Error loading PoTD plugin:" << pluginResult.errorString; + } +} + +void PotdClient::slotFinished(PotdProvider *provider) +{ + setImage(provider->image()); + setInfoUrl(provider->infoUrl()); + setRemoteUrl(provider->remoteUrl()); + setTitle(provider->title()); + setAuthor(provider->author()); + + // Store in cache if it's not the response of a CachedProvider + if (qobject_cast(provider) == nullptr) { + SaveImageThread *thread = new SaveImageThread(m_identifier, m_args, m_data); + connect(thread, &SaveImageThread::done, this, &PotdClient::slotCachingFinished); + QThreadPool::globalInstance()->start(thread); + } else { + // Is cache provider + setLocalUrl(CachedProvider::identifierToPath(m_identifier, m_args)); + } + + provider->deleteLater(); + setLoading(false); + Q_EMIT done(this); +} + +void PotdClient::slotError(PotdProvider *provider) +{ + qCWarning(WALLPAPERPOTD) << m_identifier << "with arguments" << m_args + << "failed to fetch the remote wallpaper. Please check your Internet connection or system date."; + provider->deleteLater(); + setLoading(false); + Q_EMIT done(this); +} + +void PotdClient::slotCachingFinished(const QString &, const PotdProviderData &data) +{ + setLocalUrl(data.wallpaperLocalUrl); +} + +void PotdClient::setImage(const QImage &image) +{ + m_data.wallpaperImage = image; + Q_EMIT imageChanged(); +} + +void PotdClient::setLoading(bool status) +{ + if (status == m_loading) { + return; + } + + m_loading = status; + Q_EMIT loadingChanged(); +} + +void PotdClient::setLocalUrl(const QString &urlString) +{ + if (m_data.wallpaperLocalUrl == urlString) { + return; + } + + m_data.wallpaperLocalUrl = urlString; + Q_EMIT localUrlChanged(); +} + +void PotdClient::setInfoUrl(const QUrl &url) +{ + if (m_data.wallpaperInfoUrl == url) { + return; + } + + m_data.wallpaperInfoUrl = url; + Q_EMIT infoUrlChanged(); +} + +void PotdClient::setRemoteUrl(const QUrl &url) +{ + if (m_data.wallpaperRemoteUrl == url) { + return; + } + + m_data.wallpaperRemoteUrl = url; + Q_EMIT remoteUrlChanged(); +} + +void PotdClient::setTitle(const QString &title) +{ + if (m_data.wallpaperTitle == title) { + return; + } + + m_data.wallpaperTitle = title; + Q_EMIT titleChanged(); +} + +void PotdClient::setAuthor(const QString &author) +{ + if (m_data.wallpaperAuthor == author) { + return; + } + + m_data.wallpaperAuthor = author; + Q_EMIT authorChanged(); +} + +PotdEngine::PotdEngine(QObject *parent) + : QObject(parent) + , m_lastUpdateDate(QDate::currentDate().addDays(-1)) +{ + loadPluginMetaData(); + + connect(&m_checkDatesTimer, &QTimer::timeout, this, &PotdEngine::forceUpdateSource); + + int interval = QDateTime::currentDateTime().msecsTo(QDate::currentDate().addDays(1).startOfDay()) + 1000; + m_checkDatesTimer.setInterval(std::max(interval, 60 * 1000)); + m_checkDatesTimer.start(); + + // Sleep checker + QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.login1"), + QStringLiteral("/org/freedesktop/login1"), + QStringLiteral("org.freedesktop.login1.Manager"), + QStringLiteral("PrepareForSleep"), + this, + SLOT(slotPrepareForSleep(bool))); +} + +PotdClient *PotdEngine::registerClient(const QString &identifier, const QVariantList &args) +{ + auto pr = m_clientMap.equal_range(identifier); + + auto createClient = [this, &identifier, &args]() -> PotdClient * { + auto pluginIt = m_providersMap.find(identifier); + + if (pluginIt == m_providersMap.end()) { + // Not a valid identifier + return nullptr; + } + + qCDebug(WALLPAPERPOTD) << identifier << "is registered with arguments" << args; + auto client = new PotdClient(pluginIt->second, args, this); + m_clientMap.emplace(identifier, ClientPair{client, 1}); + + return client; + }; + + while (pr.first != pr.second) { + // find exact match + if (pr.first->second.client->m_args == args) { + pr.first->second.instanceCount++; + qCDebug(WALLPAPERPOTD) << identifier << "is registered with arguments" << args << "Total client(s):" << pr.first->second.instanceCount; + return pr.first->second.client; + } + + pr.first++; + } + + return createClient(); +} + +void PotdEngine::unregisterClient(const QString &identifier, const QVariantList &args) +{ + auto pr = m_clientMap.equal_range(identifier); + + while (pr.first != pr.second) { + // find exact match + if (pr.first->second.client->m_args == args) { + pr.first->second.instanceCount--; + qCDebug(WALLPAPERPOTD) << identifier << "with arguments" << args << "is unregistered. Remaining client(s):" << pr.first->second.instanceCount; + if (!pr.first->second.instanceCount) { + delete pr.first->second.client; + m_clientMap.erase(pr.first); + qCDebug(WALLPAPERPOTD) << identifier << "with arguments" << args << "is freed."; + break; + } + } + + pr.first++; + } +} + +void PotdEngine::updateSource(bool refresh) +{ + for (const auto &pr : std::as_const(m_clientMap)) { + connect(pr.second.client, &PotdClient::done, this, &PotdEngine::slotDone); + m_updateCount++; + qCDebug(WALLPAPERPOTD) << pr.second.client->m_metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")) << "starts updating wallpaper."; + pr.second.client->updateSource(refresh); + } +} + +void PotdEngine::forceUpdateSource() +{ + updateSource(true); +} + +void PotdEngine::slotDone(PotdClient *client) +{ + disconnect(client, &PotdClient::done, this, &PotdEngine::slotDone); + + qCDebug(WALLPAPERPOTD) << client->m_identifier << "with arguments" << client->m_args + << "finished updating the wallpaper. Remaining clients:" << m_updateCount - 1; + + if (!--m_updateCount) { + // Do not update until next day, and delay 1s to make sure last modified condition is satisfied. + m_lastUpdateDate = QDate::currentDate(); + m_checkDatesTimer.setInterval(QDateTime::currentDateTime().msecsTo(m_lastUpdateDate.startOfDay().addDays(1)) + 1000); + m_checkDatesTimer.start(); + qCDebug(WALLPAPERPOTD) << "Time to next update (ms):" << m_checkDatesTimer.interval(); + } +} + +void PotdEngine::slotPrepareForSleep(bool sleep) +{ + if (sleep) { + return; + } + + // Resume from sleep + if (m_lastUpdateDate != QDate::currentDate()) { + // New day new wallpaper + forceUpdateSource(); + } else { + // Align the update timer's interval, and delay 1s to make sure last modified condition is satisfied. + const int remainingTime = QDateTime::currentDateTime().msecsTo(m_lastUpdateDate.addDays(1).startOfDay()) + 1000; + + // In case the remaining time is too short, set the interval to 1min + m_checkDatesTimer.setInterval(std::max(remainingTime, 60 * 1000)); + m_checkDatesTimer.start(); + } +} + +void PotdEngine::loadPluginMetaData() +{ + const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("potd")); + + m_providersMap.clear(); + m_providersMap.reserve(plugins.size()); + + for (const KPluginMetaData &metadata : plugins) { + const QString identifier = metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")); + if (!identifier.isEmpty()) { + m_providersMap.emplace(identifier, metadata); + } + } +} diff --git a/wallpapers/potd/plugins/potdengine.h b/wallpapers/potd/plugins/potdengine.h new file mode 100644 index 000000000..86c42043d --- /dev/null +++ b/wallpapers/potd/plugins/potdengine.h @@ -0,0 +1,106 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include +#include +#include + +#include + +#include "potdprovider.h" + +/** + * This class provides data for the specific identifier and arguments + */ +class PotdClient : public QObject +{ + Q_OBJECT + +public: + PotdClient(const KPluginMetaData &metadata, const QVariantList &args, QObject *parent = nullptr); + + void updateSource(bool refresh = false); + + KPluginMetaData m_metadata; + PotdProviderData m_data; + bool m_loading = false; + +Q_SIGNALS: + void imageChanged(); + void loadingChanged(); + void localUrlChanged(); + void infoUrlChanged(); + void remoteUrlChanged(); + void titleChanged(); + void authorChanged(); + void done(PotdClient *client); + +private Q_SLOTS: + void slotFinished(PotdProvider *provider); + void slotError(PotdProvider *provider); + void slotCachingFinished(const QString &source, const PotdProviderData &data); + +private: + void setImage(const QImage &image); + void setLoading(bool status); + void setLocalUrl(const QString &urlString); + void setInfoUrl(const QUrl &url); + void setRemoteUrl(const QUrl &url); + void setTitle(const QString &title); + void setAuthor(const QString &author); + + QString m_identifier; + QVariantList m_args; + + friend class PotdEngine; +}; + +/** + * This class manages the clients for the Pictures of The Day plugin + */ +class PotdEngine : public QObject +{ + Q_OBJECT + +public: + explicit PotdEngine(QObject *parent = nullptr); + + /** + * Registers the @p identifier in the engine + * + * A client will be created, and will be automatically destroyed when no backend has + * the identifier. + * + * @return the client that relays signals for the specific identifier + */ + PotdClient *registerClient(const QString &identifier, const QVariantList &args); + void unregisterClient(const QString &identifier, const QVariantList &args); + + void updateSource(bool refresh); + +private Q_SLOTS: + void forceUpdateSource(); + void slotDone(PotdClient *client); + void slotPrepareForSleep(bool sleep); + +private: + void loadPluginMetaData(); + + struct ClientPair { + PotdClient *const client = nullptr; + int instanceCount = 0; + }; + std::unordered_multimap m_clientMap; + std::unordered_map m_providersMap; + + QTimer m_checkDatesTimer; + QDate m_lastUpdateDate; + int m_updateCount = 0; +}; diff --git a/wallpapers/potd/plugins/potdplugin.cpp b/wallpapers/potd/plugins/potdplugin.cpp index cd149a24a..8cf1691dd 100644 --- a/wallpapers/potd/plugins/potdplugin.cpp +++ b/wallpapers/potd/plugins/potdplugin.cpp @@ -4,22 +4,13 @@ SPDX-License-Identifier: GPL-2.0-or-later */ -#include "potdprovidermodel.h" #include #include #include -Q_GLOBAL_STATIC(PotdProviderModel, potdProviderModelSelf) - -PotdProviderModel *self(QQmlEngine *engine, QJSEngine *scriptEngine) -{ - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) - - QQmlEngine::setObjectOwnership(potdProviderModelSelf, QQmlEngine::CppOwnership); - - return potdProviderModelSelf; -} +#include "potdbackend.h" +#include "potdprovider.h" +#include "potdprovidermodel.h" class PotdPlugin : public QQmlExtensionPlugin { @@ -33,9 +24,10 @@ public: qRegisterMetaType(); + qmlRegisterType(uri, 1, 0, "PotdBackend"); qmlRegisterType(uri, 1, 0, "PotdProviderModel"); - qmlRegisterSingletonType(uri, 1, 0, "PotdProviderModelInstance", self); - qmlRegisterUncreatableType(uri, 1, 0, "Global", QStringLiteral("Error: only enums")); + + qmlRegisterUncreatableType(uri, 1, 0, "Global", QStringLiteral("Error: only enums")); } }; diff --git a/wallpapers/potd/plugins/potdprovidermodel.cpp b/wallpapers/potd/plugins/potdprovidermodel.cpp index 89bf97d2e..ec4b9c190 100644 --- a/wallpapers/potd/plugins/potdprovidermodel.cpp +++ b/wallpapers/potd/plugins/potdprovidermodel.cpp @@ -7,34 +7,10 @@ #include "potdprovidermodel.h" -#include - -#include -#include -#include -#include -#include // For "Pictures" folder -#include - -#include // For "Save Image" -#include -#include -#include - -#include "cachedprovider.h" -#include "debug.h" - -using namespace std::chrono_literals; - PotdProviderModel::PotdProviderModel(QObject *parent) : QAbstractListModel(parent) - , m_currentIndex(-1) - , m_lastUpdateDate(QDate::currentDate().addDays(-1)) { loadPluginMetaData(); - - connect(&m_checkDatesTimer, &QTimer::timeout, this, &PotdProviderModel::forceUpdateSource); - m_checkDatesTimer.setInterval(10min); // check every 10 minutes } int PotdProviderModel::rowCount(const QModelIndex &parent) const @@ -75,419 +51,31 @@ QHash PotdProviderModel::roleNames() const }; } -void PotdProviderModel::loadPluginMetaData() +int PotdProviderModel::indexOf(const QString &identifier) { - const QVector plugins = KPluginMetaData::findPlugins(QStringLiteral("potd")); - - beginResetModel(); - - m_providers.clear(); - m_providers.reserve(plugins.size()); - - std::copy_if(plugins.cbegin(), plugins.cend(), std::back_inserter(m_providers), [](const KPluginMetaData &metadata) { - return !metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")).isEmpty(); + auto it = std::find_if(m_providers.cbegin(), m_providers.cend(), [&identifier](const KPluginMetaData &metadata) { + return identifier == metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")); }); - endResetModel(); -} - -int PotdProviderModel::currentIndex() const -{ - return m_currentIndex; -} - -bool PotdProviderModel::running() const -{ - return m_checkDatesTimer.isActive(); -} - -void PotdProviderModel::setRunning(bool flag) -{ - if (m_checkDatesTimer.isActive() == flag) { - return; - } - - if (flag) { - m_checkDatesTimer.start(); - QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.login1"), - QStringLiteral("/org/freedesktop/login1"), - QStringLiteral("org.freedesktop.login1.Manager"), - QStringLiteral("PrepareForSleep"), - this, - SLOT(slotPrepareForSleep(bool))); - } else { - m_checkDatesTimer.stop(); - QDBusConnection::systemBus().disconnect(QStringLiteral("org.freedesktop.login1"), - QStringLiteral("/org/freedesktop/login1"), - QStringLiteral("org.freedesktop.login1.Manager"), - QStringLiteral("PrepareForSleep"), - this, - SLOT(slotPrepareForSleep(bool))); - } - - Q_EMIT runningChanged(); -} - -QString PotdProviderModel::identifier() const -{ - return m_identifier; -} - -void PotdProviderModel::setIdentifier(const QString &identifier) -{ - if (m_identifier == identifier) { - return; - } - - m_identifier = identifier; - - const auto it = std::find_if(m_providers.cbegin(), m_providers.cend(), [&identifier](const KPluginMetaData &metadata) { - return metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")) == identifier; - }); if (it == m_providers.cend()) { - m_currentIndex = -1; - } else { - m_currentIndex = std::distance(m_providers.cbegin(), it); - } - - // Avoid flickering - if (const QString path = CachedProvider::identifierToPath(m_identifier); QFile::exists(path)) { - setImage(QImage(path)); - } else { - resetData(); - } - updateSource(); - - Q_EMIT identifierChanged(); - Q_EMIT currentIndexChanged(); -} - -QVariantList PotdProviderModel::arguments() const -{ - return m_args; -} - -void PotdProviderModel::setArguments(const QVariantList &args) -{ - if (args.isEmpty() || m_args == args) { - return; - } - - m_args = args; - - // Avoid flickering - if (const QString path = CachedProvider::identifierToPath(m_identifier); QFile::exists(path)) { - setImage(QImage(path)); - } else { - resetData(); - } - forceUpdateSource(); - - Q_EMIT argumentsChanged(); -} - -QImage PotdProviderModel::image() const -{ - return m_data.wallpaperImage; -} - -void PotdProviderModel::setImage(const QImage &image) -{ - m_data.wallpaperImage = image; - Q_EMIT imageChanged(); -} - -bool PotdProviderModel::loading() const -{ - return m_loading; -} - -void PotdProviderModel::setLoading(bool status) -{ - if (m_loading == status) { - return; - } - - m_loading = status; - Q_EMIT loadingChanged(); -} - -QString PotdProviderModel::localUrl() const -{ - return m_data.wallpaperLocalUrl; -} - -void PotdProviderModel::setLocalUrl(const QString &urlString) -{ - if (m_data.wallpaperLocalUrl == urlString) { - return; - } - - m_data.wallpaperLocalUrl = urlString; - Q_EMIT localUrlChanged(); -} - -QUrl PotdProviderModel::infoUrl() const -{ - return m_data.wallpaperInfoUrl; -} - -void PotdProviderModel::setInfoUrl(const QUrl &url) -{ - if (m_data.wallpaperInfoUrl == url) { - return; - } - - m_data.wallpaperInfoUrl = url; - Q_EMIT infoUrlChanged(); -} - -QUrl PotdProviderModel::remoteUrl() const -{ - return m_data.wallpaperRemoteUrl; -} - -void PotdProviderModel::setRemoteUrl(const QUrl &url) -{ - if (m_data.wallpaperRemoteUrl == url) { - return; - } - - m_data.wallpaperRemoteUrl = url; - Q_EMIT remoteUrlChanged(); -} - -QString PotdProviderModel::title() const -{ - return m_data.wallpaperTitle; -} - -void PotdProviderModel::setTitle(const QString &title) -{ - if (m_data.wallpaperTitle == title) { - return; - } - - m_data.wallpaperTitle = title; - Q_EMIT titleChanged(); -} - -QString PotdProviderModel::author() const -{ - return m_data.wallpaperAuthor; -} - -void PotdProviderModel::setAuthor(const QString &author) -{ - if (m_data.wallpaperAuthor == author) { - return; + return 0; } - m_data.wallpaperAuthor = author; - Q_EMIT authorChanged(); + return std::distance(m_providers.cbegin(), it); } -void PotdProviderModel::saveImage() +void PotdProviderModel::loadPluginMetaData() { - if (m_data.wallpaperLocalUrl.isEmpty()) { - return; - } - - auto sanitizeFileName = [](const QString &name){ - if (name.isEmpty()) { - return name; - } - - const char notAllowedChars[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`'/\\"; - QString sanitizedName(name); - - for (const char *c = notAllowedChars; *c; c++) { - sanitizedName.replace(QLatin1Char(*c), QLatin1Char('-')); - } - - return sanitizedName; - }; - - const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); - const QString path = locations.isEmpty() ? QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0) : locations.at(0); - - // clang-format off - QString defaultFileName = m_providers.at(m_currentIndex).name().trimmed(); - - if (!m_data.wallpaperTitle.isEmpty()) { - defaultFileName += QLatin1Char('-') + m_data.wallpaperTitle.trimmed(); - if (!m_data.wallpaperAuthor.isEmpty()) { - defaultFileName += QLatin1Char('-') + m_data.wallpaperAuthor.trimmed(); - } - } else { - // Use current date - if (!defaultFileName.isEmpty()) { - defaultFileName += QLatin1Char('-'); - } - defaultFileName += QDate::currentDate().toString(); - } - - m_savedUrl = QUrl::fromLocalFile( - QFileDialog::getSaveFileName( - nullptr, - i18nc("@title:window", "Save Today's Picture"), - path + "/" + sanitizeFileName(defaultFileName) + ".jpg", - i18nc("@label:listbox Template for file dialog", "JPEG image (*.jpeg *.jpg *.jpe)"), - nullptr, - QFileDialog::DontConfirmOverwrite // KIO::CopyJob will show the confirmation dialog. - ) - ); - // clang-format on + const QVector plugins = KPluginMetaData::findPlugins(QStringLiteral("potd")); - if (m_savedUrl.isEmpty() || !m_savedUrl.isValid()) { - return; - } + beginResetModel(); - m_savedFolder = QUrl::fromLocalFile(m_savedUrl.toLocalFile().section(QDir::separator(), 0, -2)); + m_providers.clear(); + m_providers.reserve(plugins.size()); - KIO::CopyJob *copyJob = KIO::copy(QUrl::fromLocalFile(m_data.wallpaperLocalUrl), m_savedUrl, KIO::HideProgressInfo); - connect(copyJob, &KJob::finished, this, [this](KJob *job) { - if (job->error()) { - m_saveStatusMessage = job->errorText(); - if (m_saveStatusMessage.isEmpty()) { - m_saveStatusMessage = i18nc("@info:status after a save action", "The image was not saved."); - } - m_saveStatus = FileOperationStatus::Failed; - Q_EMIT saveStatusChanged(); - } else { - m_saveStatusMessage = i18nc("@info:status after a save action %1 file path %2 basename", - "The image was saved as %2", - m_savedUrl.toString(), - m_savedUrl.fileName()); - m_saveStatus = FileOperationStatus::Successful; - Q_EMIT saveStatusChanged(); - } + std::copy_if(plugins.cbegin(), plugins.cend(), std::back_inserter(m_providers), [](const KPluginMetaData &metadata) { + return !metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")).isEmpty(); }); - copyJob->start(); -} - -void PotdProviderModel::resetData() -{ - setImage(QImage()); - setLocalUrl(QString()); - setInfoUrl(QUrl()); - setRemoteUrl(QUrl()); - setTitle(QString()); - setAuthor(QString()); -} - -bool PotdProviderModel::updateSource(bool refresh) -{ - setLoading(true); - - // Check whether it is cached already... - if (!refresh && CachedProvider::isCached(m_identifier, false)) { - CachedProvider *provider = new CachedProvider(m_identifier, this); - connect(provider, &PotdProvider::finished, this, &PotdProviderModel::slotFinished); - connect(provider, &PotdProvider::error, this, &PotdProviderModel::slotError); - setLoading(false); - return true; - } - - if (m_currentIndex < 0) { - qCWarning(WALLPAPERPOTD) << "Invalid provider: " << m_identifier; - setLoading(false); - return false; - } - - const KPluginMetaData &metadata = m_providers.at(m_currentIndex); - const auto pluginResult = KPluginFactory::instantiatePlugin(metadata, this, m_args); - - if (pluginResult) { - connect(pluginResult.plugin, &PotdProvider::finished, this, &PotdProviderModel::slotFinished); - connect(pluginResult.plugin, &PotdProvider::error, this, &PotdProviderModel::slotError); - return true; - } - qCWarning(WALLPAPERPOTD) << "Error loading PoTD plugin:" << pluginResult.errorString; - return false; -} - -bool PotdProviderModel::forceUpdateSource() -{ - return updateSource(true); -} - -void PotdProviderModel::slotFinished(PotdProvider *provider) -{ - if (provider->identifier() != m_identifier) { - return; - } - - m_lastUpdateDate = QDate::currentDate(); - - setInfoUrl(provider->infoUrl()); - setRemoteUrl(provider->remoteUrl()); - setTitle(provider->title()); - setAuthor(provider->author()); - - // Store in cache if it's not the response of a CachedProvider - if (qobject_cast(provider) == nullptr) { - setImage(provider->image()); - setLoading(false); - SaveImageThread *thread = new SaveImageThread(m_identifier, m_data); - connect(thread, &SaveImageThread::done, this, &PotdProviderModel::slotCachingFinished); - QThreadPool::globalInstance()->start(thread); - } else { - // Image is loaded in setIdentifier or setArguments - setLocalUrl(CachedProvider::identifierToPath(m_identifier)); - } - - // Do not update until next day, and delay 1s to make sure last modified condition is satisfied. - if (running()) { - m_checkDatesTimer.setInterval(QDateTime::currentDateTime().msecsTo(m_lastUpdateDate.startOfDay().addDays(1)) + 1000); - m_checkDatesTimer.start(); - } - - provider->deleteLater(); -} - -void PotdProviderModel::slotCachingFinished(const QString &source, const PotdProviderData &data) -{ - Q_UNUSED(source) - setImage(data.wallpaperImage); - setLocalUrl(data.wallpaperLocalUrl); - setInfoUrl(data.wallpaperInfoUrl); - setRemoteUrl(data.wallpaperRemoteUrl); - setTitle(data.wallpaperTitle); - setAuthor(data.wallpaperAuthor); -} - -void PotdProviderModel::slotError(PotdProvider *provider) -{ - provider->disconnect(this); - provider->deleteLater(); - - setLoading(false); - - // Retry 10min later - if (running()) { - m_checkDatesTimer.setInterval(10min); - m_checkDatesTimer.start(); - } -} - -void PotdProviderModel::slotPrepareForSleep(bool sleep) -{ - if (sleep) { - return; - } - - // Resume from sleep - - if (m_lastUpdateDate != QDate::currentDate()) { - forceUpdateSource(); - } else { - // Align the update timer's interval, and delay 1s to make sure last modified condition is satisfied. - const int remainingTime = QDateTime::currentDateTime().msecsTo(m_lastUpdateDate.addDays(1).startOfDay()) + 1000; - - // In case there is a overflow or the remaining time is too short, set the interval to 1min - m_checkDatesTimer.setInterval(std::max(remainingTime, 60 * 1000)); - m_checkDatesTimer.start(); - } + endResetModel(); } diff --git a/wallpapers/potd/plugins/potdprovidermodel.h b/wallpapers/potd/plugins/potdprovidermodel.h index cba3fca41..ec3283960 100644 --- a/wallpapers/potd/plugins/potdprovidermodel.h +++ b/wallpapers/potd/plugins/potdprovidermodel.h @@ -8,76 +8,17 @@ #pragma once #include -#include -#include -#include #include -#include "potdprovider.h" - /** - * This class provides the backend of Pictures of The Day from various online - * websites. + * This class provides the list of PoTD providers. */ class PotdProviderModel : public QAbstractListModel { Q_OBJECT - /** - * @returns @c true if the update timer is running, @c false otherwise. - */ - Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) - Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) - Q_PROPERTY(QVariantList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) - - Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged) - - /** - * Read-only properties that expose data from the provider. - */ - Q_PROPERTY(QImage image READ image NOTIFY imageChanged) - Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) - Q_PROPERTY(QString localUrl READ localUrl NOTIFY localUrlChanged) - /** - * @return The website URL of the image - */ - Q_PROPERTY(QUrl infoUrl READ infoUrl NOTIFY infoUrlChanged) - /** - * @return The remote image URL - */ - Q_PROPERTY(QUrl remoteUrl READ remoteUrl NOTIFY remoteUrlChanged) - Q_PROPERTY(QString title READ title NOTIFY titleChanged) - Q_PROPERTY(QString author READ author NOTIFY authorChanged) - - /** - * @return the result of the file operation. - */ - Q_PROPERTY(FileOperationStatus saveStatus MEMBER m_saveStatus NOTIFY saveStatusChanged) - - /** - * @return the status message after a save operation. - */ - Q_PROPERTY(QString saveStatusMessage MEMBER m_saveStatusMessage CONSTANT) - - /** - * @return the folder path of the saved image file. - */ - Q_PROPERTY(QUrl savedFolder MEMBER m_savedFolder CONSTANT) - - /** - * @return the path of the saved image file. - */ - Q_PROPERTY(QUrl savedUrl MEMBER m_savedUrl CONSTANT) - public: - enum class FileOperationStatus { - None, - Successful, - Failed, - }; - Q_ENUM(FileOperationStatus) - enum Roles { Id = Qt::UserRole + 1, }; @@ -88,81 +29,10 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; - void loadPluginMetaData(); - int currentIndex() const; - - bool running() const; - void setRunning(bool flag); - - QString identifier() const; - void setIdentifier(const QString &identifier); + Q_INVOKABLE int indexOf(const QString &identifier); - QVariantList arguments() const; - void setArguments(const QVariantList &args); - - QImage image() const; - bool loading() const; - QString localUrl() const; - QUrl infoUrl() const; - QUrl remoteUrl() const; - QString title() const; - QString author() const; - - /** - * Opens a Save dialog to choose the save location, and copies the source file to the - * selected destination. - */ - Q_INVOKABLE void saveImage(); - -Q_SIGNALS: - void currentIndexChanged(); - void runningChanged(); - void identifierChanged(); - void argumentsChanged(); - - void imageChanged(); - void loadingChanged(); - void localUrlChanged(); - void infoUrlChanged(); - void remoteUrlChanged(); - void titleChanged(); - void authorChanged(); - - void saveStatusChanged(); - -private Q_SLOTS: - void slotFinished(PotdProvider *); - void slotCachingFinished(const QString &source, const PotdProviderData &data); - void slotError(PotdProvider *); - void slotPrepareForSleep(bool sleep); - - bool forceUpdateSource(); + void loadPluginMetaData(); private: - void resetData(); - bool updateSource(bool refresh = false); - - void setImage(const QImage &image); - void setLoading(bool status); - void setLocalUrl(const QString &urlString); - void setInfoUrl(const QUrl &url); - void setRemoteUrl(const QUrl &url); - void setTitle(const QString &title); - void setAuthor(const QString &author); - std::vector m_providers; - QString m_identifier; - int m_currentIndex; - QVariantList m_args; - - PotdProviderData m_data; - bool m_loading; - - QTimer m_checkDatesTimer; - QDate m_lastUpdateDate; - - QUrl m_savedFolder; - QUrl m_savedUrl; - FileOperationStatus m_saveStatus; - QString m_saveStatusMessage; }; -- GitLab