Marble

FileStorageWatcher.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2009 Bastian Holst <bastianholst@gmx.de>
4//
5
6// Own
7#include "FileStorageWatcher.h"
8
9// Qt
10#include <QDir>
11#include <QDirIterator>
12#include <QFileInfo>
13#include <QTimer>
14
15// Marble
16#include "MarbleDebug.h"
17#include "MarbleDirs.h"
18#include "MarbleGlobal.h"
19
20using namespace Marble;
21
22// Only remove 20 files without checking
23// changed cacheLimits and changed themes etc.
24static const int maxFilesDelete = 20;
25static const int softLimitPercent = 5;
26
27// Methods of FileStorageWatcherThread
28FileStorageWatcherThread::FileStorageWatcherThread(const QString &dataDirectory, QObject *parent)
29 : QObject(parent)
30 , m_dataDirectory(dataDirectory)
31 , m_deleting(false)
32 , m_willQuit(false)
33{
34 // For now setting cache limit to 0. This won't delete anything
35 setCacheLimit(0);
36
37 connect(this, &FileStorageWatcherThread::variableChanged, this, &FileStorageWatcherThread::ensureCacheSize, Qt::QueuedConnection);
38}
39
40FileStorageWatcherThread::~FileStorageWatcherThread() = default;
41
42quint64 FileStorageWatcherThread::cacheLimit()
43{
44 return m_cacheLimit;
45}
46
47void FileStorageWatcherThread::setCacheLimit(quint64 bytes)
48{
49 m_limitMutex.lock();
50 m_cacheLimit = bytes;
51 m_cacheSoftLimit = bytes * (100 - softLimitPercent) / 100;
52 m_limitMutex.unlock();
53 Q_EMIT variableChanged();
54}
55
56void FileStorageWatcherThread::addToCurrentSize(qint64 bytes)
57{
58 // mDebug() << "Current cache size changed by " << bytes;
59 qint64 changedSize = bytes + m_currentCacheSize;
60 if (changedSize >= 0)
61 m_currentCacheSize = changedSize;
62 else
63 m_currentCacheSize = 0;
64 Q_EMIT variableChanged();
65}
66
67void FileStorageWatcherThread::resetCurrentSize()
68{
69 m_currentCacheSize = 0;
70 Q_EMIT variableChanged();
71}
72
73void FileStorageWatcherThread::prepareQuit()
74{
75 m_willQuit = true;
76}
77
78void FileStorageWatcherThread::getCurrentCacheSize()
79{
80 mDebug() << "FileStorageWatcher: Creating cache size";
81 quint64 dataSize = 0;
82 const QString basePath = m_dataDirectory + QLatin1StringView("/maps");
84
85 const int basePathDepth = basePath.split(QLatin1Char('/')).size();
86 while (it.hasNext() && !m_willQuit) {
87 it.next();
88 QFileInfo file = it.fileInfo();
89 // We try to be very careful and just delete images
90 QString suffix = file.suffix().toLower();
91 const QStringList path = file.path().split(QLatin1Char('/'));
92
93 // planet/theme/tilelevel should be deeper than 4
94 if (path.size() > basePathDepth + 3) {
95 bool ok = false;
96 int tileLevel = path[basePathDepth + 2].toInt(&ok);
97 // internal theme layer case
98 // (e.g. "earth/openseamap/seamarks/4")
99 if (!ok)
100 tileLevel = path[basePathDepth + 3].toInt(&ok);
101 if ((ok && tileLevel >= maxBaseTileLevel)
102 && (suffix == QLatin1StringView("jpg") || suffix == QLatin1StringView("png") || suffix == QLatin1StringView("gif")
103 || suffix == QLatin1StringView("svg") || suffix == QLatin1StringView("o5m"))) {
104 dataSize += file.size();
105 m_filesCache.insert(file.lastModified(), file.absoluteFilePath());
106 }
107 }
108 }
109 m_currentCacheSize = dataSize;
110}
111
112void FileStorageWatcherThread::ensureCacheSize()
113{
114 // mDebug() << "Size of tile cache: " << m_currentCacheSize;
115 // We start deleting files if m_currentCacheSize is larger than
116 // the hard cache limit. Then we delete files until our cache size
117 // is smaller than the cache (soft) limit.
118 // m_cacheLimit = 0 means no limit.
119 if (((m_currentCacheSize > m_cacheLimit) || (m_deleting && (m_currentCacheSize > m_cacheSoftLimit))) && (m_cacheLimit != 0) && (m_cacheSoftLimit != 0)
120 && !m_willQuit) {
121 mDebug() << "Deleting extra cached tiles";
122 // The counter for deleted files
123 m_filesDeleted = 0;
124 // We have not reached our soft limit, yet.
125 m_deleting = true;
126
127 // We iterate over the m_filesCache which is sorted by lastModified
128 // and remove a chunk of the oldest 20 (maxFilesDelete) files.
130 while (it != m_filesCache.end() && keepDeleting()) {
131 QString filePath = it.value();
132 QFileInfo info(filePath);
133
134 ++m_filesDeleted;
135 m_currentCacheSize -= info.size();
136 it = m_filesCache.erase(it);
137 bool success = QFile::remove(filePath);
138 if (!success) {
139 mDebug() << "Failed to remove:" << filePath;
140 }
141 }
142 // There might be more chunks left for deletion which we
143 // process with a delay to account for for load-reduction.
144 if (m_filesDeleted >= maxFilesDelete) {
145 QTimer::singleShot(1000, this, SLOT(ensureCacheSize()));
146 return;
147 } else {
148 // A partial chunk is reached at the end of m_filesCache.
149 // At this point deletion is done.
150 m_deleting = false;
151 }
152
153 // If the current Cache Size is still larger than the cacheSoftLimit
154 // then our requested cacheSoftLimit is unreachable.
155 if (m_currentCacheSize > m_cacheSoftLimit) {
156 mDebug() << "FileStorageWatcher: Requested Cache Limit could not be reached!";
157 mDebug() << "Increasing Cache Limit to prevent further futile attempts.";
158 // Softlimit is now exactly on the current cache size.
159 setCacheLimit(m_currentCacheSize / (100 - softLimitPercent) * 100);
160 }
161 }
162}
163
164bool FileStorageWatcherThread::keepDeleting() const
165{
166 return ((m_currentCacheSize > m_cacheSoftLimit) && (m_filesDeleted < maxFilesDelete) && !m_willQuit);
167}
168// End of methods of our Thread
169
170// Beginning of Methods of the main class
171FileStorageWatcher::FileStorageWatcher(const QString &dataDirectory, QObject *parent)
172 : QThread(parent)
173 , m_dataDirectory(dataDirectory)
174{
175 if (m_dataDirectory.isEmpty())
176 m_dataDirectory = MarbleDirs::localPath() + QLatin1StringView("/cache/");
177
178 if (!QDir(m_dataDirectory).exists())
179 QDir::root().mkpath(m_dataDirectory);
180
181 m_started = false;
182 m_limitMutex = new QMutex();
183
184 m_thread = nullptr;
185 m_quitting = false;
186}
187
188FileStorageWatcher::~FileStorageWatcher()
189{
190 mDebug();
191
192 // Making sure that Thread is stopped.
193 m_quitting = true;
194
195 if (m_thread)
196 m_thread->prepareQuit();
197 quit();
198 if (!wait(5000)) {
199 mDebug() << "Failed to stop FileStorageWatcher-Thread, terminating!";
200 terminate();
201 }
202
203 delete m_thread;
204
205 delete m_limitMutex;
206}
207
208void FileStorageWatcher::setCacheLimit(quint64 bytes)
209{
210 QMutexLocker locker(m_limitMutex);
211 if (m_started)
212 // This is done directly to ensure that a running ensureCacheSize()
213 // recognizes the new size.
214 m_thread->setCacheLimit(bytes);
215 // Save the limit, thread has to be initialized with the right one.
216 m_limit = bytes;
217}
218
219quint64 FileStorageWatcher::cacheLimit()
220{
221 if (m_started)
222 return m_thread->cacheLimit();
223 else
224 return m_limit;
225}
226
227void FileStorageWatcher::addToCurrentSize(qint64 bytes)
228{
229 Q_EMIT sizeChanged(bytes);
230}
231
232void FileStorageWatcher::resetCurrentSize()
233{
234 Q_EMIT cleared();
235}
236
237void FileStorageWatcher::run()
238{
239 m_thread = new FileStorageWatcherThread(m_dataDirectory);
240 if (!m_quitting) {
241 m_limitMutex->lock();
242 m_thread->setCacheLimit(m_limit);
243 m_started = true;
244 m_limitMutex->unlock();
245
246 m_thread->getCurrentCacheSize();
247
248 connect(this, &FileStorageWatcher::sizeChanged, m_thread, &FileStorageWatcherThread::addToCurrentSize);
249 connect(this, &FileStorageWatcher::cleared, m_thread, &FileStorageWatcherThread::resetCurrentSize);
250
251 // Make sure that we don't want to stop process.
252 // The thread wouldn't exit from event loop.
253 if (!m_quitting)
254 exec();
255
256 m_started = false;
257 }
258 delete m_thread;
259 m_thread = nullptr;
260}
261// End of all methods
262
263#include "moc_FileStorageWatcher.cpp"
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
Binds a QML item to a specific geodetic location in screen coordinates.
QDir root()
bool remove()
QString absoluteFilePath() const const
QDateTime lastModified() const const
QString path() const const
qint64 size() const const
QString suffix() const const
qsizetype size() const const
iterator begin()
iterator end()
iterator erase(const_iterator first, const_iterator last)
iterator insert(const Key &key, const T &value)
void lock()
void unlock()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString toLower() const const
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
int exec()
void quit()
void terminate()
bool wait(QDeadlineTimer deadline)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.