File indexing completed on 2025-09-14 05:22:01
0001 /* 0002 SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "potdengine.h" 0008 0009 #include <chrono> 0010 0011 #include <QDBusConnection> 0012 #include <QFileInfo> 0013 #include <QThreadPool> 0014 0015 #include <KPluginFactory> 0016 0017 #include "cachedprovider.h" 0018 #include "debug.h" 0019 0020 using namespace std::chrono_literals; 0021 0022 namespace 0023 { 0024 constinit bool s_networkInformationAvailable = false; 0025 0026 bool isUsingMeteredConnection() 0027 { 0028 if (!s_networkInformationAvailable) { 0029 return false; 0030 } 0031 const auto instance = QNetworkInformation::instance(); 0032 if (instance->supports(QNetworkInformation::Feature::Metered)) { 0033 return instance->isMetered(); 0034 } else if (instance->supports(QNetworkInformation::Feature::TransportMedium)) { 0035 const auto transport = instance->transportMedium(); 0036 return transport == QNetworkInformation::TransportMedium::Cellular // 0037 || transport == QNetworkInformation::TransportMedium::Bluetooth; 0038 } 0039 return false; 0040 } 0041 0042 bool isNetworkConnected() 0043 { 0044 const auto instance = QNetworkInformation::instance(); 0045 if (instance->supports(QNetworkInformation::Feature::Reachability) && instance->reachability() != QNetworkInformation::Reachability::Online) { 0046 return false; 0047 } 0048 return true; 0049 } 0050 } 0051 0052 PotdClient::PotdClient(const KPluginMetaData &metadata, const QVariantList &args, QObject *parent) 0053 : QObject(parent) 0054 , m_metadata(metadata) 0055 , m_identifier(metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier"))) 0056 , m_args(args) 0057 { 0058 // updateSource() will be called in PotdClient::setUpdateOverMeteredConnection(bool) 0059 // or PotdBackend::setUpdateOverMeteredConnection(bool) 0060 } 0061 0062 void PotdClient::updateSource(bool refresh) 0063 { 0064 if (m_loading) { 0065 return; 0066 } 0067 0068 setLoading(true); 0069 0070 // Check whether it is cached already... 0071 // Use cache even if it's outdated when using metered connection 0072 const bool ignoreAge = m_doesUpdateOverMeteredConnection == 0 && isUsingMeteredConnection(); 0073 if ((!refresh || ignoreAge /* Allow force refresh only when no cached image is available */) && CachedProvider::isCached(m_identifier, m_args, ignoreAge)) { 0074 qCDebug(WALLPAPERPOTD) << "A local cache is available for" << m_identifier << "with arguments" << m_args; 0075 0076 CachedProvider *provider = new CachedProvider(m_identifier, m_args, this); 0077 connect(provider, &PotdProvider::finished, this, &PotdClient::slotFinished); 0078 connect(provider, &PotdProvider::error, this, &PotdClient::slotError); 0079 return; 0080 } 0081 0082 if (auto url = CachedProvider::identifierToPath(m_identifier, m_args); QFileInfo::exists(url)) { 0083 setLocalUrl(url); 0084 } 0085 0086 const auto pluginResult = KPluginFactory::instantiatePlugin<PotdProvider>(m_metadata, this, m_args); 0087 0088 if (pluginResult) { 0089 qCDebug(WALLPAPERPOTD) << "Downloading wallpaper from" << m_identifier << m_args; 0090 connect(pluginResult.plugin, &PotdProvider::finished, this, &PotdClient::slotFinished); 0091 connect(pluginResult.plugin, &PotdProvider::error, this, &PotdClient::slotError); 0092 } else { 0093 qCWarning(WALLPAPERPOTD) << "Error loading PoTD plugin:" << pluginResult.errorString; 0094 } 0095 } 0096 0097 void PotdClient::setUpdateOverMeteredConnection(int value) 0098 { 0099 // Don't return if values are the same because there can be multiple 0100 // backends. Instead, let updateSource() decide whether to update 0101 // the wallpaper. 0102 0103 m_doesUpdateOverMeteredConnection = value; 0104 updateSource(); 0105 } 0106 0107 void PotdClient::slotFinished(PotdProvider *provider, const QImage &image) 0108 { 0109 setInfoUrl(provider->infoUrl()); 0110 setRemoteUrl(provider->remoteUrl()); 0111 setTitle(provider->title()); 0112 setAuthor(provider->author()); 0113 0114 // Store in cache if it's not the response of a CachedProvider 0115 if (qobject_cast<CachedProvider *>(provider) == nullptr) { 0116 PotdProviderData data; 0117 data.remoteUrl = provider->remoteUrl(); 0118 data.infoUrl = provider->infoUrl(); 0119 data.title = provider->title(); 0120 data.author = provider->author(); 0121 data.remoteUrl = provider->remoteUrl(); 0122 data.image = image; 0123 0124 SaveImageThread *thread = new SaveImageThread(m_identifier, m_args, data); 0125 connect(thread, &SaveImageThread::done, this, &PotdClient::slotCached); 0126 QThreadPool::globalInstance()->start(thread); 0127 } else { 0128 // Is cache provider 0129 setLocalUrl(provider->localPath()); 0130 setLoading(false); 0131 Q_EMIT done(this, true); 0132 } 0133 0134 provider->deleteLater(); 0135 } 0136 0137 void PotdClient::slotError(PotdProvider *provider) 0138 { 0139 qCWarning(WALLPAPERPOTD) << m_identifier << "with arguments" << m_args 0140 << "failed to fetch the remote wallpaper. Please check your Internet connection or system date."; 0141 provider->deleteLater(); 0142 setLoading(false); 0143 Q_EMIT done(this, false); 0144 } 0145 0146 void PotdClient::slotCached(const QString &localPath) 0147 { 0148 setLocalUrl(localPath); 0149 setLoading(false); 0150 Q_EMIT done(this, true); 0151 } 0152 0153 void PotdClient::setLoading(bool status) 0154 { 0155 if (status == m_loading) { 0156 return; 0157 } 0158 0159 m_loading = status; 0160 Q_EMIT loadingChanged(); 0161 } 0162 0163 void PotdClient::setLocalUrl(const QString &urlString) 0164 { 0165 if (m_localPath == urlString) { 0166 return; 0167 } 0168 0169 m_localPath = urlString; 0170 Q_EMIT localUrlChanged(); 0171 } 0172 0173 void PotdClient::setInfoUrl(const QUrl &url) 0174 { 0175 if (m_infoUrl == url) { 0176 return; 0177 } 0178 0179 m_infoUrl = url; 0180 Q_EMIT infoUrlChanged(); 0181 } 0182 0183 void PotdClient::setRemoteUrl(const QUrl &url) 0184 { 0185 if (m_remoteUrl == url) { 0186 return; 0187 } 0188 0189 m_remoteUrl = url; 0190 Q_EMIT remoteUrlChanged(); 0191 } 0192 0193 void PotdClient::setTitle(const QString &title) 0194 { 0195 if (m_title == title) { 0196 return; 0197 } 0198 0199 m_title = title; 0200 Q_EMIT titleChanged(); 0201 } 0202 0203 void PotdClient::setAuthor(const QString &author) 0204 { 0205 if (m_author == author) { 0206 return; 0207 } 0208 0209 m_author = author; 0210 Q_EMIT authorChanged(); 0211 } 0212 0213 PotdEngine::PotdEngine(QObject *parent) 0214 : QObject(parent) 0215 { 0216 qRegisterMetaType<PotdProviderData>(); 0217 0218 loadPluginMetaData(); 0219 0220 connect(&m_checkDatesTimer, &QTimer::timeout, this, &PotdEngine::forceUpdateSource); 0221 0222 int interval = QDateTime::currentDateTime().msecsTo(QDate::currentDate().addDays(1).startOfDay()) + 60000; 0223 m_checkDatesTimer.setInterval(interval); 0224 m_checkDatesTimer.start(); 0225 qCDebug(WALLPAPERPOTD) << "Time to next update (h):" << m_checkDatesTimer.interval() / 1000.0 / 60.0 / 60.0; 0226 0227 // Sleep checker 0228 QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.login1"), 0229 QStringLiteral("/org/freedesktop/login1"), 0230 QStringLiteral("org.freedesktop.login1.Manager"), 0231 QStringLiteral("PrepareForSleep"), 0232 this, 0233 SLOT(slotPrepareForSleep(bool))); 0234 loadNetworkInformation(); 0235 } 0236 0237 PotdClient *PotdEngine::registerClient(const QString &identifier, const QVariantList &args) 0238 { 0239 auto pr = m_clientMap.equal_range(identifier); 0240 0241 auto createClient = [this, &identifier, &args]() -> PotdClient * { 0242 auto pluginIt = m_providersMap.find(identifier); 0243 0244 if (pluginIt == m_providersMap.end()) { 0245 // Not a valid identifier 0246 return nullptr; 0247 } 0248 0249 qCDebug(WALLPAPERPOTD) << identifier << "is registered with arguments" << args; 0250 auto client = new PotdClient(pluginIt->second, args, this); 0251 m_clientMap.emplace(identifier, ClientPair{client, 1}); 0252 0253 return client; 0254 }; 0255 0256 while (pr.first != pr.second) { 0257 // find exact match 0258 if (pr.first->second.client->m_args == args) { 0259 pr.first->second.instanceCount++; 0260 qCDebug(WALLPAPERPOTD) << identifier << "is registered with arguments" << args << "Total client(s):" << pr.first->second.instanceCount; 0261 return pr.first->second.client; 0262 } 0263 0264 pr.first++; 0265 } 0266 0267 return createClient(); 0268 } 0269 0270 void PotdEngine::unregisterClient(const QString &identifier, const QVariantList &args) 0271 { 0272 auto [beginIt, endIt] = m_clientMap.equal_range(identifier); 0273 0274 while (beginIt != endIt) { 0275 // find exact match 0276 if (beginIt->second.client->m_args == args) { 0277 beginIt->second.instanceCount--; 0278 qCDebug(WALLPAPERPOTD) << identifier << "with arguments" << args << "is unregistered. Remaining client(s):" << beginIt->second.instanceCount; 0279 if (!beginIt->second.instanceCount) { 0280 delete beginIt->second.client; 0281 m_clientMap.erase(beginIt); 0282 qCDebug(WALLPAPERPOTD) << identifier << "with arguments" << args << "is freed."; 0283 break; 0284 } 0285 } 0286 0287 beginIt++; 0288 } 0289 } 0290 0291 void PotdEngine::updateSource(bool refresh) 0292 { 0293 if (!isNetworkConnected()) { 0294 qCDebug(WALLPAPERPOTD) << "Network is not connected, so the backend will not update wallpapers."; 0295 return; 0296 } 0297 0298 m_lastUpdateSuccess = true; 0299 0300 for (const auto &[_, clientPair] : std::as_const(m_clientMap)) { 0301 if (clientPair.client->m_loading) { 0302 continue; 0303 } 0304 0305 connect(clientPair.client, &PotdClient::done, this, &PotdEngine::slotDone); 0306 m_updateCount++; 0307 qCDebug(WALLPAPERPOTD) << clientPair.client->m_metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")) << "starts updating wallpaper."; 0308 clientPair.client->updateSource(refresh); 0309 } 0310 } 0311 0312 void PotdEngine::forceUpdateSource() 0313 { 0314 updateSource(true); 0315 } 0316 0317 void PotdEngine::slotDone(PotdClient *client, bool success) 0318 { 0319 disconnect(client, &PotdClient::done, this, &PotdEngine::slotDone); 0320 0321 qCDebug(WALLPAPERPOTD) << client->m_identifier << "with arguments" << client->m_args << (success ? "finished" : "failed") 0322 << "updating the wallpaper. Remaining clients:" << m_updateCount - 1; 0323 0324 if (!success) { 0325 m_lastUpdateSuccess = false; 0326 } 0327 0328 if (!--m_updateCount) { 0329 // Do not update until next day, and delay 1minute to make sure last modified condition is satisfied. 0330 if (m_lastUpdateSuccess) { 0331 m_checkDatesTimer.setInterval(QDateTime::currentDateTime().msecsTo(QDate::currentDate().startOfDay().addDays(1)) + 60000); 0332 } else { 0333 m_checkDatesTimer.setInterval(10min); 0334 } 0335 m_checkDatesTimer.start(); 0336 qCDebug(WALLPAPERPOTD) << "Time to next update (h):" << m_checkDatesTimer.interval() / 1000.0 / 60.0 / 60.0; 0337 } 0338 } 0339 0340 void PotdEngine::slotPrepareForSleep(bool sleep) 0341 { 0342 if (sleep) { 0343 return; 0344 } 0345 0346 // Resume from sleep 0347 // Always force update to work around the current date not being updated 0348 forceUpdateSource(); 0349 } 0350 0351 void PotdEngine::slotReachabilityChanged(QNetworkInformation::Reachability newReachability) 0352 { 0353 if (newReachability == QNetworkInformation::Reachability::Online) { 0354 qCDebug(WALLPAPERPOTD) << "Network is connected."; 0355 updateSource(false); 0356 } 0357 } 0358 0359 void PotdEngine::slotIsMeteredChanged(bool isMetered) 0360 { 0361 if (isMetered) { 0362 return; 0363 } 0364 0365 updateSource(false); 0366 } 0367 0368 void PotdEngine::loadNetworkInformation() 0369 { 0370 if (!QNetworkInformation::loadDefaultBackend()) { 0371 return; 0372 } 0373 0374 s_networkInformationAvailable = true; 0375 const auto instance = QNetworkInformation::instance(); 0376 0377 if (instance->supports(QNetworkInformation::Feature::Metered)) { 0378 connect(instance, &QNetworkInformation::isMeteredChanged, this, &PotdEngine::slotIsMeteredChanged); 0379 } 0380 if (instance->supports(QNetworkInformation::Feature::Reachability)) { 0381 connect(instance, &QNetworkInformation::reachabilityChanged, this, &PotdEngine::slotReachabilityChanged); 0382 } 0383 } 0384 0385 void PotdEngine::loadPluginMetaData() 0386 { 0387 const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("potd")); 0388 0389 m_providersMap.clear(); 0390 m_providersMap.reserve(plugins.size()); 0391 0392 for (const KPluginMetaData &metadata : plugins) { 0393 const QString identifier = metadata.value(QStringLiteral("X-KDE-PlasmaPoTDProvider-Identifier")); 0394 if (!identifier.isEmpty()) { 0395 m_providersMap.emplace(identifier, metadata); 0396 } 0397 } 0398 }