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

KDE's Doxygen guidelines are available online.