File indexing completed on 2024-04-21 03:49:30

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0004 //
0005 
0006 
0007 // Own
0008 #include "DiscCache.h"
0009 
0010 // Qt
0011 #include <QtGlobal>
0012 #include <QFile>
0013 #include <QDirIterator>
0014 #include <QDataStream>
0015 
0016 using namespace Marble;
0017 
0018 static QString indexFileName( const QString &cacheDirectory )
0019 {
0020     return cacheDirectory + QLatin1String("/cache_index.idx");
0021 }
0022 
0023 DiscCache::DiscCache( const QString &cacheDirectory )
0024     : m_CacheDirectory( cacheDirectory ),
0025       m_CacheLimit( 300 * 1024 * 1024 ),
0026       m_CurrentCacheSize( 0 )
0027 {
0028     Q_ASSERT( !m_CacheDirectory.isEmpty() && "Passed empty cache directory!" );
0029 
0030     QFile file( indexFileName( m_CacheDirectory ) );
0031 
0032     if ( file.exists() ) {
0033         if ( file.open( QIODevice::ReadOnly ) ) {
0034             QDataStream s( &file );
0035             s.setVersion( 8 );
0036 
0037             s >> m_CacheLimit;
0038             s >> m_CurrentCacheSize;
0039             s >> m_Entries;
0040 
0041         } else {
0042             qWarning( "Unable to open cache directory %s", qPrintable( m_CacheDirectory ) );
0043         }
0044     }
0045 }
0046 
0047 DiscCache::~DiscCache()
0048 {
0049     QFile file( indexFileName( m_CacheDirectory ) );
0050 
0051     if ( file.open( QIODevice::WriteOnly ) ) {
0052         QDataStream s( &file );
0053         s.setVersion( 8 );
0054 
0055         s << m_CacheLimit;
0056         s << m_CurrentCacheSize;
0057         s << m_Entries;
0058     }
0059 
0060     file.close();
0061 }
0062 
0063 quint64 DiscCache::cacheLimit() const
0064 {
0065     return m_CacheLimit;
0066 }
0067 
0068 void DiscCache::clear()
0069 {
0070     QDirIterator it( m_CacheDirectory );
0071 
0072     // Remove all files from cache directory
0073     while ( it.hasNext() ) {
0074         it.next();
0075 
0076         if ( it.fileName() == indexFileName( m_CacheDirectory ) ) // skip index file
0077             continue;
0078 
0079         QFile::remove( it.fileName() );
0080     }
0081 
0082     // Delete entries
0083     m_Entries.clear();
0084 
0085     // Reset current cache size
0086     m_CurrentCacheSize = 0;
0087 }
0088 
0089 bool DiscCache::exists( const QString &key ) const
0090 {
0091     return m_Entries.contains( key );
0092 }
0093 
0094 bool DiscCache::find( const QString &key, QByteArray &data )
0095 {
0096     // Return error if we don't know this key
0097     if ( !m_Entries.contains( key ) )
0098         return false;
0099 
0100     // If we can open the file, load all data and update access timestamp
0101     QFile file( keyToFileName( key ) );
0102     if ( file.open( QIODevice::ReadOnly ) ) {
0103         data = file.readAll();
0104 
0105         m_Entries[ key ].first = QDateTime::currentDateTime();
0106         return true;
0107     }
0108 
0109     return false;
0110 }
0111 
0112 bool DiscCache::insert( const QString &key, const QByteArray &data )
0113 {
0114     // If we can't open/create a file for this entry signal an error
0115     QFile file( keyToFileName( key ) );
0116     if ( !file.open( QIODevice::WriteOnly ) )
0117         return false;
0118 
0119     // If we overwrite an existing entry, subtract the size first
0120     if ( m_Entries.contains( key ) )
0121         m_CurrentCacheSize -= m_Entries.value( key ).second;
0122 
0123     // Store the data on disc
0124     file.write( data );
0125 
0126     // Create/Overwrite with a new entry
0127     m_Entries.insert( key, QPair<QDateTime, quint64>(QDateTime::currentDateTime(), data.length()) );
0128 
0129     // Add the size of the new entry
0130     m_CurrentCacheSize += data.length();
0131 
0132     cleanup();
0133 
0134     return true;
0135 }
0136 
0137 void DiscCache::remove( const QString &key )
0138 {
0139     // Do nothing if we don't know the key
0140     if ( !m_Entries.contains( key ) )
0141         return;
0142 
0143     // If we can't remove the file we don't remove
0144     // the entry to prevent inconsistency
0145     if ( !QFile::remove( keyToFileName( key ) ) )
0146         return;
0147 
0148     // Subtract from current size
0149     m_CurrentCacheSize -= m_Entries.value( key ).second;
0150 
0151     // Finally remove entry
0152     m_Entries.remove( key );
0153 }
0154 
0155 void DiscCache::setCacheLimit( quint64 n )
0156 {
0157     m_CacheLimit = n;
0158 
0159     cleanup();
0160 }
0161 
0162 QString DiscCache::keyToFileName( const QString &key ) const
0163 {
0164     QString fileName( key );
0165     fileName.replace(QLatin1Char('/'), QLatin1Char('_'));
0166 
0167     return m_CacheDirectory + QLatin1Char('/') + fileName;
0168 }
0169 
0170 void DiscCache::cleanup()
0171 {
0172     // Calculate 5% of our current cache limit
0173     quint64 fivePercent = quint64( m_CacheLimit * 0.05 );
0174 
0175     while ( m_CurrentCacheSize > (m_CacheLimit - fivePercent) ) {
0176         QDateTime oldestDate( QDateTime::currentDateTime() );
0177         QString oldestKey;
0178 
0179         QMapIterator<QString, QPair<QDateTime, quint64> > it( m_Entries );
0180         while ( it.hasNext() ) {
0181             it.next();
0182 
0183             if ( it.value().first < oldestDate ) {
0184                 oldestDate = it.value().first;
0185                 oldestKey = it.key();
0186             }
0187         }
0188 
0189         if ( !oldestKey.isEmpty() ) {
0190             // We found the oldest key, so using remove() to
0191             // remove it from cache
0192             remove( oldestKey );
0193         }
0194     }
0195 }