File indexing completed on 2024-04-14 03:47:44
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2009 Bastian Holst <bastianholst@gmx.de> 0004 // 0005 0006 // Own 0007 #include "FileStorageWatcher.h" 0008 0009 // Qt 0010 #include <QDir> 0011 #include <QDirIterator> 0012 #include <QFileInfo> 0013 #include <QTimer> 0014 0015 // Marble 0016 #include "MarbleGlobal.h" 0017 #include "MarbleDebug.h" 0018 #include "MarbleDirs.h" 0019 0020 using namespace Marble; 0021 0022 // Only remove 20 files without checking 0023 // changed cacheLimits and changed themes etc. 0024 static const int maxFilesDelete = 20; 0025 static const int softLimitPercent = 5; 0026 0027 0028 // Methods of FileStorageWatcherThread 0029 FileStorageWatcherThread::FileStorageWatcherThread( const QString &dataDirectory, QObject *parent ) 0030 : QObject( parent ), 0031 m_dataDirectory( dataDirectory ), 0032 m_deleting( false ), 0033 m_willQuit( false ) 0034 { 0035 // For now setting cache limit to 0. This won't delete anything 0036 setCacheLimit( 0 ); 0037 0038 connect( this, SIGNAL(variableChanged()), 0039 this, SLOT(ensureCacheSize()), 0040 Qt::QueuedConnection ); 0041 } 0042 0043 FileStorageWatcherThread::~FileStorageWatcherThread() 0044 { 0045 } 0046 0047 quint64 FileStorageWatcherThread::cacheLimit() 0048 { 0049 return m_cacheLimit; 0050 } 0051 0052 void FileStorageWatcherThread::setCacheLimit( quint64 bytes ) 0053 { 0054 m_limitMutex.lock(); 0055 m_cacheLimit = bytes; 0056 m_cacheSoftLimit = bytes * ( 100 - softLimitPercent ) / 100; 0057 m_limitMutex.unlock(); 0058 emit variableChanged(); 0059 } 0060 0061 void FileStorageWatcherThread::addToCurrentSize( qint64 bytes ) 0062 { 0063 // mDebug() << "Current cache size changed by " << bytes; 0064 qint64 changedSize = bytes + m_currentCacheSize; 0065 if( changedSize >= 0 ) 0066 m_currentCacheSize = changedSize; 0067 else 0068 m_currentCacheSize = 0; 0069 emit variableChanged(); 0070 } 0071 0072 void FileStorageWatcherThread::resetCurrentSize() 0073 { 0074 m_currentCacheSize = 0; 0075 emit variableChanged(); 0076 } 0077 0078 void FileStorageWatcherThread::prepareQuit() 0079 { 0080 m_willQuit = true; 0081 } 0082 0083 void FileStorageWatcherThread::getCurrentCacheSize() 0084 { 0085 mDebug() << "FileStorageWatcher: Creating cache size"; 0086 quint64 dataSize = 0; 0087 const QString basePath = m_dataDirectory + QLatin1String("/maps"); 0088 QDirIterator it( basePath, 0089 QDir::Files | QDir::Writable, 0090 QDirIterator::Subdirectories ); 0091 0092 const int basePathDepth = basePath.split(QLatin1Char('/')).size(); 0093 while( it.hasNext() && !m_willQuit ) { 0094 it.next(); 0095 QFileInfo file = it.fileInfo(); 0096 // We try to be very careful and just delete images 0097 QString suffix = file.suffix().toLower(); 0098 const QStringList path = file.path().split(QLatin1Char('/')); 0099 0100 // planet/theme/tilelevel should be deeper than 4 0101 if ( path.size() > basePathDepth + 3 ) { 0102 bool ok = false; 0103 int tileLevel = path[basePathDepth + 2].toInt(&ok); 0104 // internal theme layer case 0105 // (e.g. "earth/openseamap/seamarks/4") 0106 if (!ok) tileLevel = path[basePathDepth + 3].toInt(&ok); 0107 if ((ok && tileLevel >= maxBaseTileLevel ) && 0108 (suffix == QLatin1String("jpg") || 0109 suffix == QLatin1String("png") || 0110 suffix == QLatin1String("gif") || 0111 suffix == QLatin1String("svg") || 0112 suffix == QLatin1String("o5m"))) { 0113 dataSize += file.size(); 0114 m_filesCache.insert(file.lastModified(), file.absoluteFilePath()); 0115 } 0116 } 0117 } 0118 m_currentCacheSize = dataSize; 0119 } 0120 0121 void FileStorageWatcherThread::ensureCacheSize() 0122 { 0123 // mDebug() << "Size of tile cache: " << m_currentCacheSize; 0124 // We start deleting files if m_currentCacheSize is larger than 0125 // the hard cache limit. Then we delete files until our cache size 0126 // is smaller than the cache (soft) limit. 0127 // m_cacheLimit = 0 means no limit. 0128 if( ( ( m_currentCacheSize > m_cacheLimit ) 0129 || ( m_deleting && ( m_currentCacheSize > m_cacheSoftLimit ) ) ) 0130 && ( m_cacheLimit != 0 ) 0131 && ( m_cacheSoftLimit != 0 ) 0132 && !m_willQuit ) { 0133 0134 mDebug() << "Deleting extra cached tiles"; 0135 // The counter for deleted files 0136 m_filesDeleted = 0; 0137 // We have not reached our soft limit, yet. 0138 m_deleting = true; 0139 0140 // We iterate over the m_filesCache which is sorted by lastModified 0141 // and remove a chunk of the oldest 20 (maxFilesDelete) files. 0142 QMultiMap<QDateTime, QString>::iterator it= m_filesCache.begin(); 0143 while ( it != m_filesCache.end() && 0144 keepDeleting() ) { 0145 QString filePath = it.value(); 0146 QFileInfo info( filePath ); 0147 0148 ++m_filesDeleted; 0149 m_currentCacheSize -= info.size(); 0150 it = m_filesCache.erase(it); 0151 bool success = QFile::remove( filePath ); 0152 if (!success) { 0153 mDebug() << "Failed to remove:" << filePath; 0154 } 0155 } 0156 // There might be more chunks left for deletion which we 0157 // process with a delay to account for for load-reduction. 0158 if( m_filesDeleted >= maxFilesDelete ) { 0159 QTimer::singleShot( 1000, this, SLOT(ensureCacheSize()) ); 0160 return; 0161 } 0162 else { 0163 // A partial chunk is reached at the end of m_filesCache. 0164 // At this point deletion is done. 0165 m_deleting = false; 0166 } 0167 0168 // If the current Cache Size is still larger than the cacheSoftLimit 0169 // then our requested cacheSoftLimit is unreachable. 0170 if( m_currentCacheSize > m_cacheSoftLimit ) { 0171 mDebug() << "FileStorageWatcher: Requested Cache Limit could not be reached!"; 0172 mDebug() << "Increasing Cache Limit to prevent further futile attempts."; 0173 // Softlimit is now exactly on the current cache size. 0174 setCacheLimit( m_currentCacheSize / ( 100 - softLimitPercent ) * 100 ); 0175 } 0176 } 0177 } 0178 0179 bool FileStorageWatcherThread::keepDeleting() const 0180 { 0181 return ( ( m_currentCacheSize > m_cacheSoftLimit ) && 0182 ( m_filesDeleted < maxFilesDelete ) && 0183 !m_willQuit ); 0184 } 0185 // End of methods of our Thread 0186 0187 0188 // Beginning of Methods of the main class 0189 FileStorageWatcher::FileStorageWatcher( const QString &dataDirectory, QObject * parent ) 0190 : QThread( parent ), 0191 m_dataDirectory( dataDirectory ) 0192 { 0193 if ( m_dataDirectory.isEmpty() ) 0194 m_dataDirectory = MarbleDirs::localPath() + QLatin1String("/cache/"); 0195 0196 if ( ! QDir( m_dataDirectory ).exists() ) 0197 QDir::root().mkpath( m_dataDirectory ); 0198 0199 m_started = false; 0200 m_limitMutex = new QMutex(); 0201 0202 m_thread = nullptr; 0203 m_quitting = false; 0204 } 0205 0206 FileStorageWatcher::~FileStorageWatcher() 0207 { 0208 mDebug(); 0209 0210 // Making sure that Thread is stopped. 0211 m_quitting = true; 0212 0213 if( m_thread ) 0214 m_thread->prepareQuit(); 0215 quit(); 0216 if( !wait( 5000 ) ) { 0217 mDebug() << "Failed to stop FileStorageWatcher-Thread, terminating!"; 0218 terminate(); 0219 } 0220 0221 delete m_thread; 0222 0223 delete m_limitMutex; 0224 } 0225 0226 void FileStorageWatcher::setCacheLimit( quint64 bytes ) 0227 { 0228 QMutexLocker locker( m_limitMutex ); 0229 if( m_started ) 0230 // This is done directly to ensure that a running ensureCacheSize() 0231 // recognizes the new size. 0232 m_thread->setCacheLimit( bytes ); 0233 // Save the limit, thread has to be initialized with the right one. 0234 m_limit = bytes; 0235 } 0236 0237 quint64 FileStorageWatcher::cacheLimit() 0238 { 0239 if( m_started ) 0240 return m_thread->cacheLimit(); 0241 else 0242 return m_limit; 0243 } 0244 0245 void FileStorageWatcher::addToCurrentSize( qint64 bytes ) 0246 { 0247 emit sizeChanged( bytes ); 0248 } 0249 0250 void FileStorageWatcher::resetCurrentSize() 0251 { 0252 emit cleared(); 0253 } 0254 0255 void FileStorageWatcher::run() 0256 { 0257 m_thread = new FileStorageWatcherThread( m_dataDirectory ); 0258 if( !m_quitting ) { 0259 m_limitMutex->lock(); 0260 m_thread->setCacheLimit( m_limit ); 0261 m_started = true; 0262 m_limitMutex->unlock(); 0263 0264 m_thread->getCurrentCacheSize(); 0265 0266 connect( this, SIGNAL(sizeChanged(qint64)), 0267 m_thread, SLOT(addToCurrentSize(qint64)) ); 0268 connect( this, SIGNAL(cleared()), 0269 m_thread, SLOT(resetCurrentSize()) ); 0270 0271 // Make sure that we don't want to stop process. 0272 // The thread wouldn't exit from event loop. 0273 if( !m_quitting ) 0274 exec(); 0275 0276 m_started = false; 0277 } 0278 delete m_thread; 0279 m_thread = nullptr; 0280 } 0281 // End of all methods 0282 0283 #include "moc_FileStorageWatcher.cpp"