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"