Marble

FileStorageWatcher.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2009 Bastian Holst <[email protected]>
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 
20 using namespace Marble;
21 
22 // Only remove 20 files without checking
23 // changed cacheLimits and changed themes etc.
24 static const int maxFilesDelete = 20;
25 static const int softLimitPercent = 5;
26 
27 
28 // Methods of FileStorageWatcherThread
29 FileStorageWatcherThread::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 
43 FileStorageWatcherThread::~FileStorageWatcherThread()
44 {
45 }
46 
47 quint64 FileStorageWatcherThread::cacheLimit()
48 {
49  return m_cacheLimit;
50 }
51 
52 void 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 
61 void 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 
72 void FileStorageWatcherThread::resetCurrentSize()
73 {
74  m_currentCacheSize = 0;
75  emit variableChanged();
76 }
77 
78 void FileStorageWatcherThread::prepareQuit()
79 {
80  m_willQuit = true;
81 }
82 
83 void 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 
121 void 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 
179 bool 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
189 FileStorageWatcher::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 
206 FileStorageWatcher::~FileStorageWatcher()
207 {
208  mDebug() << "Deleting FileStorageWatcher";
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 
226 void 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 
237 quint64 FileStorageWatcher::cacheLimit()
238 {
239  if( m_started )
240  return m_thread->cacheLimit();
241  else
242  return m_limit;
243 }
244 
245 void FileStorageWatcher::addToCurrentSize( qint64 bytes )
246 {
247  emit sizeChanged( bytes );
248 }
249 
250 void FileStorageWatcher::resetCurrentSize()
251 {
252  emit cleared();
253 }
254 
255 void 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 const
int size() const const
bool remove()
const T value(const Key &key, const T &defaultValue) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QMap::iterator begin()
QDir root()
QString suffix() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int size() const const
QString absoluteFilePath() const const
QAction * quit(const QObject *recvr, const char *slot, QObject *parent)
QMap::iterator erase(QMap::iterator pos)
bool mkpath(const QString &dirPath) const const
int toInt(bool *ok, int base) const const
QueuedConnection
qint64 size() const const
Binds a QML item to a specific geodetic location in screen coordinates.
QString toLower() const const
QString path(const QString &relativePath)
QDateTime lastModified() const const
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Sep 21 2023 04:12:26 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.