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