mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-15 11:38:49 -07:00
[PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache (#6620)
* [PictureLoader] Allow saving downloaded images to local storage and not just the QNetworkManager cache. Took 1 hour 11 minutes Took 4 seconds Took 25 seconds * Give people options from a dropdown. Took 1 hour 6 minutes Took 3 seconds * Simplify directory removal code. Took 5 minutes Took 8 seconds * Merge pull request #8 * Create new category for new settings * Split off storage settings Took 47 minutes Took 4 seconds * Allow toggling between caching methods. Took 1 hour 30 minutes Took 9 seconds * Adjust settings dialog. Took 5 minutes Took 59 seconds Took 22 seconds Took 6 seconds * tr() strings Took 1 minute Took 6 seconds * Readjust layout, default naming scheme. Took 5 minutes * Add stretch. Took 9 minutes * Make scrollable. Took 2 minutes * Add icon. Took 7 minutes * Change naming to be uniform. Took 3 minutes Took 3 seconds --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
This commit is contained in:
parent
20cd7ce73d
commit
f8ce5c2e39
12 changed files with 1382 additions and 132 deletions
|
|
@ -150,9 +150,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))
|
||||
|
|
@ -160,12 +161,19 @@ 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().getCardPictureLoaderCacheMethod() ==
|
||||
CardPictureLoaderCacheMethod::CacheMethod::FILESYSTEM_CACHE) {
|
||||
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.
|
||||
|
|
@ -175,6 +183,88 @@ 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();
|
||||
CardPictureLoaderLocalSchemes::NamingScheme scheme =
|
||||
SettingsCache::instance().getLocalCardImageStorageNamingScheme();
|
||||
|
||||
QString pattern;
|
||||
|
||||
for (const auto &s : CardPictureLoaderLocalSchemes::exportSchemes()) {
|
||||
if (s.id == scheme) {
|
||||
pattern = s.pattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (picsRoot.isEmpty() || pattern.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 =
|
||||
CardPictureLoaderLocalSchemes::expandPattern(pattern, cardName, setName, collectorNumber, uuid);
|
||||
|
||||
if (relativePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// append extension
|
||||
relativePath += ".png";
|
||||
|
||||
// Normalize slashes
|
||||
relativePath = QDir::cleanPath(relativePath);
|
||||
|
||||
QFileInfo outInfo(baseDir.filePath(relativePath));
|
||||
|
||||
// Do not overwrite existing files
|
||||
if (outInfo.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
|
||||
#define COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace CardPictureLoaderCacheMethod
|
||||
{
|
||||
enum class CacheMethod
|
||||
{
|
||||
NETWORK_CACHE,
|
||||
FILESYSTEM_CACHE
|
||||
};
|
||||
|
||||
struct CacheMethodInfo
|
||||
{
|
||||
CacheMethod id;
|
||||
QString displayName;
|
||||
};
|
||||
|
||||
static inline const QList<CacheMethodInfo> methods()
|
||||
{
|
||||
static QList<CacheMethodInfo> all = {
|
||||
{CacheMethod::NETWORK_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Network Cache")},
|
||||
{CacheMethod::FILESYSTEM_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Filesystem")},
|
||||
};
|
||||
return all;
|
||||
}
|
||||
} // namespace CardPictureLoaderCacheMethod
|
||||
|
||||
#endif // COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "card_picture_loader_local.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "card_picture_loader_local_schemes.h"
|
||||
#include "card_picture_to_load.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
|
|
@ -77,26 +78,8 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName,
|
|||
imgReader.setDecideFormatFromContent(true);
|
||||
|
||||
// Most-to-least specific, these will fall through in order.
|
||||
QStringList nameVariants;
|
||||
|
||||
// cardName_providerId
|
||||
if (!providerId.isEmpty()) {
|
||||
nameVariants << QString("%1-%2").arg(correctedCardName, providerId)
|
||||
<< QString("%1_%2").arg(correctedCardName, providerId);
|
||||
}
|
||||
// cardName_setName_collectorNumber & setName-collectorNumber-cardName
|
||||
if (!setName.isEmpty() && !collectorNumber.isEmpty()) {
|
||||
nameVariants << QString("%1_%2_%3").arg(correctedCardName, setName, collectorNumber)
|
||||
<< QString("%1-%2-%3").arg(setName, collectorNumber, correctedCardName);
|
||||
}
|
||||
// cardName_setName
|
||||
if (!setName.isEmpty()) {
|
||||
nameVariants << QString("%1_%2").arg(correctedCardName, setName)
|
||||
<< QString("%1-%2").arg(setName, correctedCardName);
|
||||
}
|
||||
|
||||
// cardName
|
||||
nameVariants << correctedCardName;
|
||||
QStringList nameVariants =
|
||||
CardPictureLoaderLocalSchemes::generateImportVariants(correctedCardName, setName, collectorNumber, providerId);
|
||||
|
||||
for (const QString &nameVariant : nameVariants) {
|
||||
if (nameVariant.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
#ifndef COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
|
||||
#define COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
|
||||
|
||||
#include <QList>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace CardPictureLoaderLocalSchemes
|
||||
{
|
||||
|
||||
enum class NamingScheme
|
||||
{
|
||||
NameOnly,
|
||||
Name_Set,
|
||||
Name_Set_Collector,
|
||||
Set_Collector_Name,
|
||||
Name_ProviderId,
|
||||
Set_Folder_Name_ProviderId,
|
||||
Set_Folder_Name_Set_Collector
|
||||
};
|
||||
|
||||
struct NamingSchemeInfo
|
||||
{
|
||||
NamingScheme id;
|
||||
QString displayName;
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
inline const QList<NamingSchemeInfo> &importSchemes()
|
||||
{
|
||||
static QList<NamingSchemeInfo> list = {
|
||||
{NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"},
|
||||
{NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"},
|
||||
{NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"},
|
||||
{NamingScheme::Name_Set, "Card Name + Set", "{name}_{set}"},
|
||||
{NamingScheme::NameOnly, "Card Name", "{name}"},
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
inline const QList<NamingSchemeInfo> &exportSchemes()
|
||||
{
|
||||
static QList<NamingSchemeInfo> list = {
|
||||
{NamingScheme::Set_Folder_Name_ProviderId, "Set Folder / Name + Provider ID", "{set}/{name}_{providerId}"},
|
||||
{NamingScheme::Set_Folder_Name_Set_Collector, "Set Folder / Name + Set Name + Collector",
|
||||
"{set}/{name}_{set}_{collector}"},
|
||||
{NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"},
|
||||
{NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"},
|
||||
{NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"},
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
inline QString expandPattern(const QString &pattern,
|
||||
const QString &name,
|
||||
const QString &set,
|
||||
const QString &collector,
|
||||
const QString &providerId)
|
||||
{
|
||||
QString result = pattern;
|
||||
|
||||
auto replaceIfPresent = [&](const QString &token, const QString &value) -> bool {
|
||||
if (!result.contains(token))
|
||||
return true;
|
||||
|
||||
if (value.isEmpty())
|
||||
return false;
|
||||
|
||||
result.replace(token, value);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!replaceIfPresent("{name}", name))
|
||||
return {};
|
||||
if (!replaceIfPresent("{set}", set))
|
||||
return {};
|
||||
if (!replaceIfPresent("{collector}", collector))
|
||||
return {};
|
||||
if (!replaceIfPresent("{providerId}", providerId))
|
||||
return {};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline QStringList
|
||||
generateImportVariants(const QString &name, const QString &set, const QString &collector, const QString &providerId)
|
||||
{
|
||||
QStringList variants;
|
||||
const QStringList separators = {"_", "-"};
|
||||
|
||||
for (const auto &scheme : importSchemes()) {
|
||||
for (const QString &sep : separators) {
|
||||
|
||||
QString pattern = scheme.pattern;
|
||||
pattern.replace("_", sep);
|
||||
|
||||
QString v = expandPattern(pattern, name, set, collector, providerId);
|
||||
if (!v.isEmpty())
|
||||
variants << v;
|
||||
}
|
||||
}
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
} // namespace CardPictureLoaderLocalSchemes
|
||||
|
||||
#endif // COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
|
||||
|
|
@ -26,10 +26,14 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
|
|||
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
||||
cache->setMaximumCacheSize(1024L * 1024L *
|
||||
static_cast<qint64>(SettingsCache::instance().getNetworkCacheSizeInMB()));
|
||||
// Note: the settings is in MB, but QNetworkDiskCache uses bytes
|
||||
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, this,
|
||||
[this](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB)); });
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, [this](int newSizeInMB) {
|
||||
if (cache)
|
||||
cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB));
|
||||
});
|
||||
|
||||
networkManager->setCache(cache);
|
||||
|
||||
// Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished
|
||||
// We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache
|
||||
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
|
|
@ -65,14 +69,19 @@ void CardPictureLoaderWorker::queueRequest(const QUrl &url, CardPictureLoaderWor
|
|||
QUrl cachedRedirect = getCachedRedirect(url);
|
||||
if (!cachedRedirect.isEmpty()) {
|
||||
queueRequest(cachedRedirect, worker);
|
||||
} else if (cache->metaData(url).isValid()) {
|
||||
// If we hit a cached url, we get to make the request for free, since it won't contribute towards the rate-limit
|
||||
makeRequest(url, worker);
|
||||
} else {
|
||||
requestLoadQueue.append(qMakePair(url, worker));
|
||||
emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName());
|
||||
processQueuedRequests();
|
||||
return;
|
||||
}
|
||||
if (SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
|
||||
CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE &&
|
||||
cache->metaData(url).isValid()) {
|
||||
// If we hit a cached url, we get to make the request for free, since it won't contribute towards the
|
||||
// rate-limit
|
||||
makeRequest(url, worker);
|
||||
return;
|
||||
}
|
||||
requestLoadQueue.append(qMakePair(url, worker));
|
||||
emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName());
|
||||
processQueuedRequests();
|
||||
}
|
||||
|
||||
QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPictureLoaderWorkerWork *worker)
|
||||
|
|
@ -87,9 +96,12 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
|
|||
QNetworkRequest req(url);
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8");
|
||||
if (!picDownload) {
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
|
||||
}
|
||||
|
||||
bool useNetworkCache = !picDownload && SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
|
||||
CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE;
|
||||
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
|
||||
useNetworkCache ? QNetworkRequest::AlwaysCache : QNetworkRequest::AlwaysNetwork);
|
||||
|
||||
QNetworkReply *reply = networkManager->get(req);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue