1 /*
2 * SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "potd.h"
8
9 #include <QDate>
10 #include <QDebug>
11 #include <QFile>
12 #include <QFileInfo>
13 #include <QRegularExpression>
14 #include <QThreadPool>
15 #include <QTimer>
16
17 #include <KPluginLoader>
18 #include <KPluginMetaData>
19 #include <Plasma/DataContainer>
20
21 #include "cachedprovider.h"
22
23 namespace
24 {
25 namespace DataKeys
26 {
image()27 inline QString image()
28 {
29 return QStringLiteral("Image");
30 }
url()31 inline QString url()
32 {
33 return QStringLiteral("Url");
34 }
35 }
36 }
37
PotdEngine(QObject * parent,const QVariantList & args)38 PotdEngine::PotdEngine(QObject *parent, const QVariantList &args)
39 : Plasma::DataEngine(parent, args)
40 {
41 // set polling to every 5 minutes
42 setMinimumPollingInterval(5 * 60 * 1000);
43 m_checkDatesTimer = new QTimer(this); // change picture after 24 hours
44 connect(m_checkDatesTimer, &QTimer::timeout, this, &PotdEngine::checkDayChanged);
45 // FIXME: would be nice to stop and start this timer ONLY as needed, e.g. only when there are
46 // time insensitive sources to serve; still, this is better than how i found it, checking
47 // every 2 seconds (!)
48 m_checkDatesTimer->setInterval(10 * 60 * 1000); // check every 10 minutes
49 m_checkDatesTimer->start();
50
51 const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("potd"));
52
53 for (const auto &metadata : plugins) {
54 QString provider = metadata.value(QLatin1String("X-KDE-PlasmaPoTDProvider-Identifier"));
55 if (provider.isEmpty()) {
56 continue;
57 }
58 mFactories.insert(provider, metadata);
59 setData(QLatin1String("Providers"), provider, metadata.name());
60 }
61 }
62
~PotdEngine()63 PotdEngine::~PotdEngine()
64 {
65 }
66
updateSourceEvent(const QString & identifier)67 bool PotdEngine::updateSourceEvent(const QString &identifier)
68 {
69 return updateSource(identifier, false);
70 }
71
updateSource(const QString & identifier,bool loadCachedAlways)72 bool PotdEngine::updateSource(const QString &identifier, bool loadCachedAlways)
73 {
74 // check whether it is cached already...
75 if (CachedProvider::isCached(identifier, loadCachedAlways)) {
76 QVariantList args;
77 args << QLatin1String("String") << identifier;
78
79 CachedProvider *provider = new CachedProvider(identifier, this);
80 connect(provider, &PotdProvider::finished, this, &PotdEngine::finished);
81 connect(provider, &PotdProvider::error, this, &PotdEngine::error);
82
83 m_canDiscardCache = loadCachedAlways;
84 if (!loadCachedAlways) {
85 return true;
86 }
87 }
88
89 const QStringList parts = identifier.split(QLatin1Char(':'), Qt::SkipEmptyParts);
90 if (parts.empty()) {
91 qDebug() << "invalid identifier";
92 return false;
93 }
94 const QString providerName = parts[0];
95 if (!mFactories.contains(providerName)) {
96 qDebug() << "invalid provider: " << parts[0];
97 return false;
98 }
99
100 QVariantList args;
101
102 for (int i = 0; i < parts.count(); i++) {
103 args << parts[i];
104 }
105
106 auto factory = KPluginLoader(mFactories[providerName].fileName()).factory();
107 PotdProvider *provider = nullptr;
108 if (factory) {
109 provider = factory->create<PotdProvider>(this, args);
110 }
111 if (provider) {
112 connect(provider, &PotdProvider::finished, this, &PotdEngine::finished);
113 connect(provider, &PotdProvider::error, this, &PotdEngine::error);
114 return true;
115 }
116
117 return false;
118 }
119
sourceRequestEvent(const QString & identifier)120 bool PotdEngine::sourceRequestEvent(const QString &identifier)
121 {
122 if (updateSource(identifier, true)) {
123 setData(identifier, DataKeys::image(), QImage());
124 return true;
125 }
126
127 return false;
128 }
129
finished(PotdProvider * provider)130 void PotdEngine::finished(PotdProvider *provider)
131 {
132 if (m_canDiscardCache && qobject_cast<CachedProvider *>(provider)) {
133 Plasma::DataContainer *source = containerForSource(provider->identifier());
134 if (source && !source->data().value(DataKeys::image()).value<QImage>().isNull()) {
135 provider->deleteLater();
136 return;
137 }
138 }
139
140 QImage img(provider->image());
141 // store in cache if it's not the response of a CachedProvider
142 if (qobject_cast<CachedProvider *>(provider) == nullptr && !img.isNull()) {
143 SaveImageThread *thread = new SaveImageThread(provider->identifier(), img);
144 connect(thread, &SaveImageThread::done, this, &PotdEngine::cachingFinished);
145 QThreadPool::globalInstance()->start(thread);
146 } else {
147 setData(provider->identifier(), DataKeys::image(), img);
148 setData(provider->identifier(), DataKeys::url(), CachedProvider::identifierToPath(provider->identifier()));
149 }
150
151 provider->deleteLater();
152 }
153
cachingFinished(const QString & source,const QString & path,const QImage & img)154 void PotdEngine::cachingFinished(const QString &source, const QString &path, const QImage &img)
155 {
156 setData(source, DataKeys::image(), img);
157 setData(source, DataKeys::url(), path);
158 }
159
error(PotdProvider * provider)160 void PotdEngine::error(PotdProvider *provider)
161 {
162 provider->disconnect(this);
163 provider->deleteLater();
164 }
165
checkDayChanged()166 void PotdEngine::checkDayChanged()
167 {
168 SourceDict dict = containerDict();
169 QHashIterator<QString, Plasma::DataContainer *> it(dict);
170 QRegularExpression re(QLatin1String(":\\d{4}-\\d{2}-\\d{2}"));
171
172 while (it.hasNext()) {
173 it.next();
174
175 if (it.key() == QLatin1String("Providers")) {
176 continue;
177 }
178
179 // Check if the identifier contains ISO date string, like 2019-01-09.
180 // If so, don't update the picture. Otherwise, update the picture.
181 if (!re.match(it.key()).hasMatch()) {
182 const QString path = CachedProvider::identifierToPath(it.key());
183 if (!QFile::exists(path)) {
184 updateSourceEvent(it.key());
185 } else {
186 QFileInfo info(path);
187 if (info.lastModified().daysTo(QDateTime::currentDateTime()) >= 1) {
188 updateSourceEvent(it.key());
189 }
190 }
191 }
192 }
193 }
194
195 K_PLUGIN_CLASS_WITH_JSON(PotdEngine, "plasma-dataengine-potd.json")
196
197 #include "potd.moc"
198