[PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache.

Took 1 hour 11 minutes

Took 4 seconds
This commit is contained in:
Lukas Brübach 2026-02-22 10:44:34 +01:00
parent 485e4d56aa
commit 0713cd755b
6 changed files with 138 additions and 5 deletions

View File

@ -264,6 +264,9 @@ SettingsCache::SettingsCache()
networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt();
redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
saveCardImagesToLocalStorage = settings->value("personal/saveCardImagesToLocalStorage", true).toBool();
localCardImageStorageNamingScheme =
settings->value("personal/localCardImageStorageNamingScheme", "{set}/{name}_{collector}_{uuid}.png").toString();
picDownload = settings->value("personal/picturedownload", true).toBool();
showStatusBar = settings->value("personal/showStatusBar", false).toBool();
@ -1101,6 +1104,20 @@ void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl)
emit redirectCacheTtlChanged(redirectCacheTtl);
}
void SettingsCache::setSaveCardImagesToLocalStorage(QT_STATE_CHANGED_T _saveCardImagesToLocalStorage)
{
saveCardImagesToLocalStorage = _saveCardImagesToLocalStorage;
settings->setValue("personal/saveCardImagesToLocalStorage", saveCardImagesToLocalStorage);
emit saveCardImagesToLocalStorageChanged(saveCardImagesToLocalStorage);
}
void SettingsCache::setLocalCardImageStorageNamingScheme(const QString _localCardImageStorageNamingScheme)
{
localCardImageStorageNamingScheme = _localCardImageStorageNamingScheme;
settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme);
emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme);
}
void SettingsCache::setClientID(const QString &_clientID)
{
clientID = _clientID;

View File

@ -184,6 +184,8 @@ signals:
void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl);
void saveCardImagesToLocalStorageChanged(bool saveCardImagesToLocalStorage);
void localCardImageStorageNamingSchemeChanged(QString localCardImageStorageNamingScheme);
void masterVolumeChanged(int value);
void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged();
@ -302,6 +304,8 @@ private:
int pixmapCacheSize;
int networkCacheSize;
int redirectCacheTtl;
bool saveCardImagesToLocalStorage;
QString localCardImageStorageNamingScheme;
bool scaleCards;
int verticalCardOverlapPercent;
bool showMessagePopups;
@ -773,6 +777,14 @@ public:
{
return redirectCacheTtl;
}
[[nodiscard]] bool getSaveCardImagesToLocalStorage() const
{
return saveCardImagesToLocalStorage;
}
[[nodiscard]] QString getLocalCardImageStorageNamingScheme() const
{
return localCardImageStorageNamingScheme;
}
[[nodiscard]] bool getScaleCards() const
{
return scaleCards;
@ -1067,6 +1079,8 @@ public slots:
void setPixmapCacheSize(const int _pixmapCacheSize);
void setNetworkCacheSizeInMB(const int _networkCacheSize);
void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
void setSaveCardImagesToLocalStorage(QT_STATE_CHANGED_T _saveCardImagesToLocalStorage);
void setLocalCardImageStorageNamingScheme(const QString _localCardImageStorageNamingScheme);
void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);

View File

@ -149,9 +149,10 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize
void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
{
QPixmap finalPixmap;
if (image.isNull()) {
qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName();
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap());
} else {
if (card.getInfo().getUiAttributes().upsideDownArt) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
@ -159,12 +160,18 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
#else
QImage mirrorImage = image.mirrored(true, true);
#endif
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(mirrorImage));
finalPixmap = QPixmap::fromImage(mirrorImage);
} else {
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(image));
finalPixmap = QPixmap::fromImage(image);
}
}
QPixmapCache::insert(card.getPixmapCacheKey(), finalPixmap);
if (SettingsCache::instance().getSaveCardImagesToLocalStorage()) {
saveCardImageToLocalStorage(card, finalPixmap);
}
// imageLoaded should only be reached if the exactCard isn't already in cache.
// (plus there's a deduplication mechanism in CardPictureLoaderWorker)
// It should be safe to connect the CardInfo here without worrying about redundant connections.
@ -174,6 +181,75 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
card.emitPixmapUpdated();
}
void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap)
{
if (pixmap.isNull() || !card) {
return;
}
const QString picsRoot = SettingsCache::instance().getPicsPath();
const QString scheme = SettingsCache::instance().getLocalCardImageStorageNamingScheme();
if (picsRoot.isEmpty() || scheme.isEmpty()) {
return;
}
// Base directory: <picsPath>/downloadedPics
QDir baseDir(picsRoot);
if (!baseDir.exists("downloadedPics")) {
baseDir.mkpath("downloadedPics");
}
baseDir.cd("downloadedPics");
// Collect card metadata
const QString cardName = card.getInfo().getCorrectedName();
QString setName;
QString collectorNumber;
QString uuid;
PrintingInfo printing = card.getPrinting();
if (printing.getSet()) {
setName = printing.getSet()->getCorrectedShortName();
collectorNumber = printing.getProperty("num");
uuid = printing.getUuid();
}
// Build path from scheme
QString relativePath = scheme;
relativePath.replace("{name}", cardName);
relativePath.replace("{set}", setName);
relativePath.replace("{collector}", collectorNumber);
relativePath.replace("{uuid}", uuid);
relativePath.replace("{ext}", "png");
// Normalize slashes
relativePath = QDir::cleanPath(relativePath);
QFileInfo outInfo(baseDir.filePath(relativePath));
QDir outDir = outInfo.dir();
// Ensure directory exists
if (!outDir.exists()) {
if (!baseDir.mkpath(outDir.path())) {
qCWarning(CardPictureLoaderLog) << "Failed to create directory for downloaded card image:" << outDir.path();
return;
}
}
// Do not overwrite existing files
if (outInfo.exists()) {
return;
}
// Save image
QImage image = pixmap.toImage();
if (!image.save(outInfo.absoluteFilePath(), "PNG")) {
qCWarning(CardPictureLoaderLog) << "Failed to save card image to" << outInfo.absoluteFilePath();
}
}
void CardPictureLoader::clearPixmapCache()
{
QPixmapCache::clear();

View File

@ -117,6 +117,7 @@ public slots:
* @param image Loaded QImage.
*/
void imageLoaded(const ExactCard &card, const QImage &image);
void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap);
private slots:
/**

View File

@ -1063,6 +1063,15 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
networkRedirectCacheTtlEdit.setSingleStep(1);
networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl());
saveCardImagesToLocalStorageCheckBox.setChecked(SettingsCache::instance().getSaveCardImagesToLocalStorage());
connect(&saveCardImagesToLocalStorageCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setSaveCardImagesToLocalStorage);
localCardImageStorageNamingSchemeLineEdit =
new QLineEdit(SettingsCache::instance().getLocalCardImageStorageNamingScheme());
connect(localCardImageStorageNamingSchemeLineEdit, &QLineEdit::textChanged, &SettingsCache::instance(),
&SettingsCache::setLocalCardImageStorageNamingScheme);
auto networkCacheLayout = new QHBoxLayout;
networkCacheLayout->addStretch();
networkCacheLayout->addWidget(&networkCacheLabel);
@ -1078,6 +1087,14 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
pixmapCacheLayout->addWidget(&pixmapCacheLabel);
pixmapCacheLayout->addWidget(&pixmapCacheEdit);
auto saveCardImagesToLocalStorageLayout = new QHBoxLayout;
saveCardImagesToLocalStorageLayout->addWidget(&saveCardImagesToLocalStorageLabel);
saveCardImagesToLocalStorageLayout->addWidget(&saveCardImagesToLocalStorageCheckBox);
auto localCardImageStorageNamingSchemeLayout = new QHBoxLayout;
localCardImageStorageNamingSchemeLayout->addWidget(&localCardImageStorageNamingSchemeLabel);
localCardImageStorageNamingSchemeLayout->addWidget(localCardImageStorageNamingSchemeLineEdit);
// Top Layout
lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0);
lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1);
@ -1085,8 +1102,10 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
lpGeneralGrid->addLayout(networkCacheLayout, 2, 1);
lpGeneralGrid->addLayout(networkRedirectCacheLayout, 3, 0);
lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 1);
lpGeneralGrid->addWidget(&urlLinkLabel, 5, 0);
lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 5, 1);
lpGeneralGrid->addLayout(saveCardImagesToLocalStorageLayout, 4, 0, 1, 2);
lpGeneralGrid->addLayout(localCardImageStorageNamingSchemeLayout, 5, 0, 1, 2);
lpGeneralGrid->addWidget(&urlLinkLabel, 7, 0);
lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 7, 1);
// Spoiler Layout
lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0);
@ -1298,6 +1317,8 @@ void DeckEditorSettingsPage::retranslateUi()
networkRedirectCacheTtlEdit.setToolTip(tr("How long cached redirects for urls are valid for."));
pixmapCacheLabel.setText(tr("Picture Cache Size:"));
pixmapCacheEdit.setToolTip(tr("In-memory cache for pictures not currently on screen"));
saveCardImagesToLocalStorageLabel.setText(tr("Save downloaded images to local storage"));
localCardImageStorageNamingSchemeLabel.setText(tr("Save downloaded images using this naming scheme:"));
updateNowButton->setText(tr("Update Spoilers"));
aAdd->setText(tr("Add New URL"));
aEdit->setText(tr("Edit URL"));

View File

@ -237,6 +237,10 @@ private:
QSpinBox networkRedirectCacheTtlEdit;
QSpinBox pixmapCacheEdit;
QLabel pixmapCacheLabel;
QCheckBox saveCardImagesToLocalStorageCheckBox;
QLabel saveCardImagesToLocalStorageLabel;
QLabel localCardImageStorageNamingSchemeLabel;
QLineEdit *localCardImageStorageNamingSchemeLineEdit;
};
class MessagesSettingsPage : public AbstractSettingsPage