Skip to content

Commit 6e3fa12

Browse files
feat: retain multiple cached images
1 parent 1e28fb5 commit 6e3fa12

3 files changed

Lines changed: 78 additions & 24 deletions

File tree

‎src/config.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
/* Enable caching */
3838
#define IMAGEWRITER_ENABLE_CACHE_DEFAULT true
3939

40+
/* Default total size for the persistent, multi-image download cache. */
41+
#define IMAGEWRITER_CACHE_LIMIT_GB_DEFAULT 5
42+
4043
/* Do not cache if it would bring free disk space under 5 GB */
4144
#define IMAGEWRITER_MINIMAL_SPACE_FOR_CACHING 5*1024*1024*1024ll
4245

‎src/imagewriter.cpp‎

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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 */
650651
bool 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 */

‎src/imagewriter.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ protected slots:
266266
void startOrqaSshOperation(bool rebootToBootloader);
267267
void setOrqaSshMessage(const QString &message);
268268
void startNxpFlashThread();
269+
QString cacheDirectory() const;
270+
QString cacheFileNameForHash(const QByteArray &sha256) const;
271+
qint64 cacheLimitBytes() const;
272+
qint64 evictCache(qint64 incomingBytes = 0);
269273
};
270274

271275
#endif // IMAGEWRITER_H

0 commit comments

Comments
 (0)