@@ -175,10 +175,16 @@ ImageWriter::ImageWriter(QObject *parent)
175175 _cachingEnabled = !_embeddedMode && _settings.value (" enabled" , IMAGEWRITER_ENABLE_CACHE_DEFAULT ).toBool ();
176176 _cachedFileHash = _settings.value (" lastDownloadSHA256" ).toByteArray ();
177177 _cacheFileName = QStandardPaths::writableLocation (QStandardPaths::CacheLocation)+QDir::separator ()+" lastdownload.cache" ;
178+ QDir ().mkpath (cacheDirectory ());
179+ // Preserve a completed cache from older ImageWriter versions, then use
180+ // hash-addressed files so several images can coexist.
178181 if (!_cachedFileHash.isEmpty ())
179182 {
180183 QFileInfo f (_cacheFileName);
181- if (!f.exists () || !f.isReadable () || !f.size ())
184+ const QString migratedFile = cacheFileNameForHash (_cachedFileHash);
185+ if (f.exists () && f.isReadable () && f.size () && !QFileInfo::exists (migratedFile))
186+ QFile::rename (_cacheFileName, migratedFile);
187+ if (!QFileInfo (migratedFile).isReadable () || !QFileInfo (migratedFile).size ())
182188 {
183189 _cachedFileHash.clear ();
184190 _settings.remove (" lastDownloadSHA256" );
@@ -492,10 +498,12 @@ void ImageWriter::startWrite()
492498 return ;
493499 }
494500
495- if (!_expectedHash.isEmpty () && _cachedFileHash == _expectedHash)
501+ const QString cachedFile = cacheFileNameForHash (_expectedHash);
502+ if (!_expectedHash.isEmpty () && QFileInfo (cachedFile).isReadable () && QFileInfo (cachedFile).size () > 0 )
496503 {
497504 // Use cached file
498- urlstr = QUrl::fromLocalFile (_cacheFileName).toString (_src.FullyEncoded ).toLatin1 ();
505+ QFile (cachedFile).setFileTime (QDateTime::currentDateTime (), QFileDevice::FileModificationTime);
506+ urlstr = QUrl::fromLocalFile (cachedFile).toString (_src.FullyEncoded ).toLatin1 ();
499507 }
500508
501509 if (QUrl (urlstr).isLocalFile ())
@@ -528,25 +536,8 @@ void ImageWriter::startWrite()
528536 _thread->setVerifyEnabled (_verifyEnabled);
529537 _thread->setUserAgent (QString (" Mozilla/5.0 rpi-imager/%1" ).arg (constantVersion ()).toUtf8 ());
530538
531- if (!_expectedHash.isEmpty () && _cachedFileHash != _expectedHash && _cachingEnabled)
539+ if (!_expectedHash.isEmpty () && ! QFileInfo::exists (cachedFile) && _cachingEnabled)
532540 {
533- if (!_cachedFileHash.isEmpty ())
534- {
535- if (_settings.isWritable () && QFile::remove (_cacheFileName))
536- {
537- _settings.remove (" caching/lastDownloadSHA256" );
538- _settings.sync ();
539- _cachedFileHash.clear ();
540- emit cacheChanged ();
541- }
542- else
543- {
544- qDebug () << " Error removing old cache file. Disabling caching" ;
545- _cachingEnabled = false ;
546- }
547- }
548-
549- if (_cachingEnabled)
550541 {
551542 QStorageInfo si (QStandardPaths::writableLocation (QStandardPaths::CacheLocation));
552543 qint64 avail = si.bytesAvailable ();
@@ -558,8 +549,17 @@ void ImageWriter::startWrite()
558549 }
559550 else
560551 {
561- _thread->setCacheFile (_cacheFileName, _downloadLen);
562- connect (_thread, SIGNAL (cacheFileUpdated (QByteArray)), SLOT (onCacheFileUpdated (QByteArray)));
552+ const qint64 remaining = evictCache (_downloadLen);
553+ const qint64 effectiveLimit = qMax (cacheLimitBytes (), qint64 (_downloadLen));
554+ if (remaining + qint64 (_downloadLen) <= effectiveLimit)
555+ {
556+ _thread->setCacheFile (cachedFile, _downloadLen);
557+ connect (_thread, SIGNAL (cacheFileUpdated (QByteArray)), SLOT (onCacheFileUpdated (QByteArray)));
558+ }
559+ else
560+ {
561+ qDebug () << " Unable to free enough cache space; not caching this download." ;
562+ }
563563 }
564564 }
565565 }
@@ -588,6 +588,7 @@ void ImageWriter::onCacheFileUpdated(QByteArray sha256)
588588 _settings.setValue (" caching/lastDownloadSHA256" , sha256);
589589 _settings.sync ();
590590 _cachedFileHash = sha256;
591+ evictCache ();
591592 emit cacheChanged ();
592593 qDebug () << " Done writing cache file" ;
593594}
@@ -649,7 +650,53 @@ void ImageWriter::onCancelled()
649650/* Return true if url is in our local disk cache */
650651bool ImageWriter::isCached (const QUrl &, const QByteArray &sha256)
651652{
652- return !sha256.isEmpty () && _cachedFileHash == sha256;
653+ const QFileInfo file (cacheFileNameForHash (sha256));
654+ return !sha256.isEmpty () && file.isReadable () && file.size () > 0 ;
655+ }
656+
657+ QString ImageWriter::cacheDirectory () const
658+ {
659+ return QStandardPaths::writableLocation (QStandardPaths::CacheLocation) + QDir::separator () + " images" ;
660+ }
661+
662+ QString ImageWriter::cacheFileNameForHash (const QByteArray &sha256) const
663+ {
664+ const QString hash = QString::fromLatin1 (sha256);
665+ if (hash.isEmpty () || !QRegularExpression (" ^[a-fA-F0-9]{64}$" ).match (hash).hasMatch ())
666+ return QString ();
667+ return cacheDirectory () + QDir::separator () + hash.toLower () + " .cache" ;
668+ }
669+
670+ qint64 ImageWriter::cacheLimitBytes () const
671+ {
672+ const int limitGb = qBound (1 , _settings.value (" caching/limitGb" , IMAGEWRITER_CACHE_LIMIT_GB_DEFAULT ).toInt (), 1000 );
673+ return qint64 (limitGb) * 1024 * 1024 * 1024 ;
674+ }
675+
676+ qint64 ImageWriter::evictCache (qint64 incomingBytes)
677+ {
678+ QFileInfoList files = QDir (cacheDirectory ()).entryInfoList (QStringList () << " *.cache" , QDir::Files, QDir::Time | QDir::Reversed);
679+ qint64 total = 0 ;
680+ qint64 largestImage = incomingBytes;
681+ for (const QFileInfo &file : files)
682+ {
683+ total += file.size ();
684+ largestImage = qMax (largestImage, file.size ());
685+ }
686+
687+ // The configured value is the normal cache budget, but retaining one
688+ // complete image is more useful than evicting a valid download solely
689+ // because that image is larger than the budget.
690+ const qint64 effectiveLimit = qMax (cacheLimitBytes (), largestImage);
691+
692+ for (const QFileInfo &file : files)
693+ {
694+ if (total + incomingBytes <= effectiveLimit)
695+ break ;
696+ if (QFile::remove (file.absoluteFilePath ()))
697+ total -= file.size ();
698+ }
699+ return total;
653700}
654701
655702/* Utility function to return filename part from URL */
0 commit comments