File indexing completed on 2025-01-05 03:59:34

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