KNewStuff

cache.cpp
1 /*
2  Copyright (c) 2009 Frederik Gladhorn <[email protected]>
3  Copyright (c) 2010 Matthias Fuchs <[email protected]>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Lesser General Public
7  License as published by the Free Software Foundation; either
8  version 2.1 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "cache.h"
20 
21 #include <QFile>
22 #include <QDir>
23 #include <QFileInfo>
24 #include <QTimer>
25 #include <QXmlStreamReader>
26 #include <qstandardpaths.h>
27 #include <knewstuffcore_debug.h>
28 
29 using namespace KNSCore;
30 
32 Q_GLOBAL_STATIC(CacheHash, s_caches)
33 
34 Cache::Cache(const QString &appName): QObject(nullptr)
35 {
36  m_kns2ComponentName = appName;
37 
39  QDir().mkpath(path);
40  registryFile = path + appName + QStringLiteral(".knsregistry");
41  qCDebug(KNEWSTUFFCORE) << "Using registry file: " << registryFile;
42  setProperty("dirty", false); //KF6 make normal variable
43 }
44 
45 QSharedPointer<Cache> Cache::getCache(const QString &appName)
46 {
47  CacheHash::const_iterator it = s_caches()->constFind(appName);
48  if ((it != s_caches()->constEnd()) && !(*it).isNull()) {
49  return QSharedPointer<Cache>(*it);
50  }
51 
52  QSharedPointer<Cache> p(new Cache(appName));
53  s_caches()->insert(appName, QWeakPointer<Cache>(p));
54  QObject::connect(p.data(), &QObject::destroyed, [appName] {
55  if (auto cache = s_caches()) {
56  cache->remove(appName);
57  }
58  });
59 
60  return p;
61 }
62 
63 Cache::~Cache()
64 {
65 }
66 
67 void Cache::readRegistry()
68 {
69  // read KNS2 registry first to migrate it
70  readKns2MetaFiles();
71 
72  QFile f(registryFile);
73  if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
74  if (QFileInfo::exists(registryFile)) {
75  qWarning() << "The file " << registryFile << " could not be opened.";
76  }
77  return;
78  }
79 
80  QXmlStreamReader reader(&f);
81  if (reader.hasError() || !reader.readNextStartElement()) {
82  qCWarning(KNEWSTUFFCORE) << "The file could not be parsed.";
83  return;
84  }
85 
86  if (reader.name() != QLatin1String("hotnewstuffregistry")) {
87  qCWarning(KNEWSTUFFCORE) << "The file doesn't seem to be of interest.";
88  return;
89  }
90 
91  for (auto token = reader.readNext(); !reader.atEnd(); token = reader.readNext()) {
92  if (token != QXmlStreamReader::StartElement)
93  continue;
94  EntryInternal e;
95  e.setEntryXML(reader);
96  e.setSource(EntryInternal::Cache);
97  cache.insert(e);
98  Q_ASSERT(reader.tokenType() == QXmlStreamReader::EndElement);
99  }
100 
101  qCDebug(KNEWSTUFFCORE) << "Cache read... entries: " << cache.size();
102 }
103 
104 void Cache::readKns2MetaFiles()
105 {
106  qCDebug(KNEWSTUFFCORE) << "Loading KNS2 registry of files for the component: " << m_kns2ComponentName;
107 
108  const auto realAppName = m_kns2ComponentName.splitRef(QLatin1Char(':'))[0];
109 
111  for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
112  qCDebug(KNEWSTUFFCORE) << QStringLiteral(" + Load from directory '") + (*it) + QStringLiteral("'.");
113  QDir dir((*it));
114  const QStringList files = dir.entryList(QDir::Files | QDir::Readable);
115  for (QStringList::const_iterator fit = files.begin(); fit != files.end(); ++fit) {
116  QString filepath = (*it) + QLatin1Char('/') + (*fit);
117 
118  qCDebug(KNEWSTUFFCORE) << QStringLiteral(" Load from file '") + filepath + QStringLiteral("'.");
119 
120  QFileInfo info(filepath);
121  QFile f(filepath);
122 
123  // first see if this file is even for this app
124  // because the registry contains entries for all apps
125  // FIXMEE: should be able to do this with a filter on the entryList above probably
126  QString thisAppName = QString::fromUtf8(QByteArray::fromBase64(info.baseName().toUtf8()));
127 
128  // NOTE: the ":" needs to always coincide with the separator character used in
129  // the id(Entry*) method
130  thisAppName = thisAppName.split(QLatin1Char(':'))[0];
131 
132  if (thisAppName != realAppName) {
133  continue;
134  }
135 
136  if (!f.open(QIODevice::ReadOnly)) {
137  qWarning() << "The file: " << filepath << " could not be opened.";
138  continue;
139  }
140 
141  QDomDocument doc;
142  if (!doc.setContent(&f)) {
143  qWarning() << "The file could not be parsed.";
144  return;
145  }
146  qCDebug(KNEWSTUFFCORE) << "found entry: " << doc.toString();
147 
148  QDomElement root = doc.documentElement();
149  if (root.tagName() != QLatin1String("ghnsinstall")) {
150  qWarning() << "The file doesn't seem to be of interest.";
151  return;
152  }
153 
154  // The .meta files only contain one entry
155  QDomElement stuff = root.firstChildElement(QStringLiteral("stuff"));
156  EntryInternal e;
157  e.setEntryXML(stuff);
158  e.setSource(EntryInternal::Cache);
159 
160  if (e.payload().startsWith(QLatin1String("http://download.kde.org/khotnewstuff"))) {
161  // This is 99% sure a opendesktop file, make it a real one.
162  e.setProviderId(QStringLiteral("https://api.opendesktop.org/v1/"));
163  e.setHomepage(QUrl(QString(QLatin1String("http://opendesktop.org/content/show.php?content=") + e.uniqueId())));
164 
165  } else if (e.payload().startsWith(QLatin1String("http://edu.kde.org/contrib/kvtml/"))) {
166  // kvmtl-1
167  e.setProviderId(QStringLiteral("http://edu.kde.org/contrib/kvtml/kvtml.xml"));
168  } else if (e.payload().startsWith(QLatin1String("http://edu.kde.org/contrib/kvtml2/"))) {
169  // kvmtl-2
170  e.setProviderId(QStringLiteral("http://edu.kde.org/contrib/kvtml2/provider41.xml"));
171  } else {
172  // we failed, skip
173  qWarning() << "Could not load entry: " << filepath;
174  continue;
175  }
176 
177  e.setStatus(KNS3::Entry::Installed);
178 
179  cache.insert(e);
180  QDomDocument tmp(QStringLiteral("yay"));
181  tmp.appendChild(e.entryXML());
182  qCDebug(KNEWSTUFFCORE) << "new entry: " << tmp.toString();
183 
184  f.close();
185 
186  QDir dir;
187  if (!dir.remove(filepath)) {
188  qWarning() << "could not delete old kns2 .meta file: " << filepath;
189  } else {
190  qCDebug(KNEWSTUFFCORE) << "Migrated KNS2 entry to KNS3.";
191  }
192 
193  }
194  }
195  setProperty("dirty", false);
196 }
197 
198 EntryInternal::List Cache::registryForProvider(const QString &providerId)
199 {
200  EntryInternal::List entries;
201  for (const EntryInternal &e : qAsConst(cache)) {
202  if (e.providerId() == providerId) {
203  entries.append(e);
204  }
205  }
206  return entries;
207 }
208 
209 void Cache::writeRegistry()
210 {
211  if (!property("dirty").toBool())
212  return;
213 
214  qCDebug(KNEWSTUFFCORE) << "Write registry";
215 
216  QFile f(registryFile);
217  if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
218  qWarning() << "Cannot write meta information to '" << registryFile << "'.";
219  return;
220  }
221 
222  QDomDocument doc(QStringLiteral("khotnewstuff3"));
223  doc.appendChild(doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"")));
224  QDomElement root = doc.createElement(QStringLiteral("hotnewstuffregistry"));
225  doc.appendChild(root);
226 
227  for (EntryInternal entry : cache) {
228  // This might possibly seem a little naughty, but the cache data only cares about installed
229  // items, and in reality Updateable will be set when checking installed items against
230  // the remote server's information on the load of the cache
231  if (entry.status() == KNS3::Entry::Updating || entry.status() == KNS3::Entry::Installing) {
232  entry.setStatus(KNS3::Entry::Installed);
233  }
234  // Write the entry, unless the policy is CacheNever and the entry is not installed.
235  if (entry.status() == KNS3::Entry::Installed || entry.status() == KNS3::Entry::Updateable) {
236  QDomElement exml = entry.entryXML();
237  root.appendChild(exml);
238  }
239  }
240 
241  QTextStream metastream(&f);
242  metastream << doc.toByteArray();
243 
244  setProperty("dirty", false);
245 }
246 
247 void Cache::registerChangedEntry(const KNSCore::EntryInternal &entry)
248 {
249  setProperty("dirty", true);
250  cache.insert(entry);
251  QTimer::singleShot(1000, this, [this](){ writeRegistry(); });
252 }
253 
254 void Cache::insertRequest(const KNSCore::Provider::SearchRequest &request, const KNSCore::EntryInternal::List &entries)
255 {
256  // append new entries
257  auto &cacheList = requestCache[request.hashForRequest()];
258  for (const auto &entry : entries) {
259  if (!cacheList.contains(entry)) {
260  cacheList.append(entry);
261  }
262  }
263  qCDebug(KNEWSTUFFCORE) << request.hashForRequest() << " add: " << entries.size() << " keys: " << requestCache.keys();
264 }
265 
266 EntryInternal::List Cache::requestFromCache(const KNSCore::Provider::SearchRequest &request)
267 {
268  qCDebug(KNEWSTUFFCORE) << request.hashForRequest();
269  return requestCache.value(request.hashForRequest());
270 }
271 
272 void KNSCore::Cache::removeDeletedEntries()
273 {
275  while (i.hasNext()) {
276  const KNSCore::EntryInternal &entry = i.next();
277  bool installedFileExists{false};
278  for (const auto &installedFile: entry.installedFiles()) {
279  if (QFile::exists(installedFile)) {
280  installedFileExists = true;
281  break;
282  }
283  }
284  if (!installedFileExists) {
285  i.remove();
286  setProperty("dirty", true);
287  }
288  }
289  writeRegistry();
290 }
QDomProcessingInstruction createProcessingInstruction(const QString &target, const QString &data)
QString writableLocation(QStandardPaths::StandardLocation type)
QString uniqueId() const
Get the object&#39;s unique ID.
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
QDomNode appendChild(const QDomNode &newChild)
QString toString(int indent) const const
Contains the core functionality for handling interaction with NewStuff providers. ...
int size() const const
bool remove(const QString &fileName)
QDomElement documentElement() const const
used to keep track of a search
Definition: provider.h:77
bool exists() const const
QDomElement entryXML() const
get the xml string for the entry
QString payload() const
Retrieve the file name of the object.
void append(const T &value)
QString fromUtf8(const char *str, int size)
QStringList installedFiles() const
Retrieve the locally installed files.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool setEntryXML(QXmlStreamReader &reader)
set the xml for the entry parses the xml and sets the private members accordingly used to deserialize...
void setSource(Source source)
The source of this entry can be Cache, Registry or Online -.
void setStatus(KNS3::Entry::Status status)
Sets the entry&#39;s status.
QList::iterator end()
bool exists() const const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
KNewStuff data entry container.
Definition: entryinternal.h:60
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
QDomElement firstChildElement(const QString &tagName) const const
typedef ConstIterator
QString tagName() const const
void setHomepage(const QUrl &page)
Set a link to a website containing information about this entry.
QDomElement createElement(const QString &tagName)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
QString providerId() const
The id of the provider this entry belongs to.
QList::iterator begin()
void destroyed(QObject *obj)
bool mkpath(const QString &dirPath) const const
QByteArray toByteArray(int indent) const const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
KStandardDirs * dirs()
QCA_EXPORT QString appName()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Aug 9 2020 22:43:40 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.