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