mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 19:13:25 -08:00
[Pictures] Watch the custom picture folder instead of polling (#7635)
The local picture loader walked the whole custom picture tree every ten seconds on the GUI thread. Watch the root and every subdirectory with QFileSystemWatcher, debounce bursts of events into a single 500 ms refresh, and keep a five-minute fallback poll for filesystems where watching fails. Add an explicit-path constructor and indexedPaths() so the indexing can be tested without SettingsCache, and emit indexChanged() after a rebuild. Verification: cockatrice built. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
518c7e6125
commit
5ddfcd7ead
2 changed files with 94 additions and 11 deletions
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMessageLogger>
|
#include <QMessageLogger>
|
||||||
|
|
@ -19,35 +20,62 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <libcockatrice/settings/paths_settings.h>
|
#include <libcockatrice/settings/paths_settings.h>
|
||||||
|
|
||||||
static constexpr int REFRESH_INTERVAL_MS = 10 * 1000;
|
static constexpr int REFRESH_DEBOUNCE_MS = 500;
|
||||||
|
// Watching can fail on network and FUSE mounts; the slow poll still picks those
|
||||||
|
// changes up without the old ten-second full rescan.
|
||||||
|
static constexpr int FALLBACK_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
CardPictureLoaderLocal::CardPictureLoaderLocal(QObject *parent)
|
CardPictureLoaderLocal::CardPictureLoaderLocal(QObject *parent)
|
||||||
: QObject(parent), picsPath(SettingsCache::instance().paths().getPicsPath()),
|
: CardPictureLoaderLocal(parent,
|
||||||
customPicsPath(SettingsCache::instance().paths().getCustomPicsPath())
|
SettingsCache::instance().paths().getPicsPath(),
|
||||||
|
SettingsCache::instance().paths().getCustomPicsPath())
|
||||||
{
|
{
|
||||||
// Hook up signals to settings
|
// Hook up signals to settings
|
||||||
connect(&SettingsCache::instance().paths(), &PathsSettings::picsPathChanged, this,
|
connect(&SettingsCache::instance().paths(), &PathsSettings::picsPathChanged, this,
|
||||||
&CardPictureLoaderLocal::picsPathChanged);
|
&CardPictureLoaderLocal::picsPathChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
CardPictureLoaderLocal::CardPictureLoaderLocal(QObject *parent,
|
||||||
|
const QString &_picsPath,
|
||||||
|
const QString &_customPicsPath)
|
||||||
|
: QObject(parent), picsPath(_picsPath), customPicsPath(_customPicsPath),
|
||||||
|
folderWatcher(new QFileSystemWatcher(this)), indexRefreshTimer(new QTimer(this)),
|
||||||
|
fallbackRefreshTimer(new QTimer(this))
|
||||||
|
{
|
||||||
|
indexRefreshTimer->setSingleShot(true);
|
||||||
|
indexRefreshTimer->setInterval(REFRESH_DEBOUNCE_MS);
|
||||||
|
connect(indexRefreshTimer, &QTimer::timeout, this, &CardPictureLoaderLocal::refreshIndex);
|
||||||
|
|
||||||
|
connect(folderWatcher, &QFileSystemWatcher::directoryChanged, indexRefreshTimer, qOverload<>(&QTimer::start));
|
||||||
|
connect(folderWatcher, &QFileSystemWatcher::fileChanged, indexRefreshTimer, qOverload<>(&QTimer::start));
|
||||||
|
|
||||||
|
fallbackRefreshTimer->setInterval(FALLBACK_REFRESH_INTERVAL_MS);
|
||||||
|
connect(fallbackRefreshTimer, &QTimer::timeout, this, &CardPictureLoaderLocal::refreshIndex);
|
||||||
|
fallbackRefreshTimer->start();
|
||||||
|
|
||||||
refreshIndex();
|
refreshIndex();
|
||||||
|
|
||||||
refreshTimer = new QTimer(this);
|
|
||||||
connect(refreshTimer, &QTimer::timeout, this, &CardPictureLoaderLocal::refreshIndex);
|
|
||||||
refreshTimer->start(REFRESH_INTERVAL_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardPictureLoaderLocal::refreshIndex()
|
void CardPictureLoaderLocal::refreshIndex()
|
||||||
{
|
{
|
||||||
customFolderIndex.clear();
|
customFolderIndex.clear();
|
||||||
|
QSet<QString> directories;
|
||||||
|
|
||||||
|
QDir customDir(customPicsPath);
|
||||||
|
if (customDir.exists()) {
|
||||||
|
directories.insert(customDir.absolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||||
|
|
||||||
// Recursively check all subdirectories of the CUSTOM folder
|
// Recursively check all subdirectories of the CUSTOM folder
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
QString thisPath(it.next());
|
const QString thisPath = it.next();
|
||||||
QFileInfo thisFileInfo(thisPath);
|
const QFileInfo thisFileInfo = it.fileInfo();
|
||||||
|
|
||||||
if (thisFileInfo.isFile()) {
|
if (thisFileInfo.isDir()) {
|
||||||
|
directories.insert(thisFileInfo.absoluteFilePath());
|
||||||
|
} else if (thisFileInfo.isFile()) {
|
||||||
// We don't know which name is the correctedName because there might be '.'s in the cardName.
|
// We don't know which name is the correctedName because there might be '.'s in the cardName.
|
||||||
// Just add all possibilities to be sure.
|
// Just add all possibilities to be sure.
|
||||||
customFolderIndex.insert(thisFileInfo.baseName(), thisFileInfo.absoluteFilePath());
|
customFolderIndex.insert(thisFileInfo.baseName(), thisFileInfo.absoluteFilePath());
|
||||||
|
|
@ -55,8 +83,34 @@ void CardPictureLoaderLocal::refreshIndex()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateWatchedDirectories(directories);
|
||||||
|
|
||||||
qCDebug(CardPictureLoaderLocalLog) << "Finished indexing local image folder CUSTOM; map now has"
|
qCDebug(CardPictureLoaderLocalLog) << "Finished indexing local image folder CUSTOM; map now has"
|
||||||
<< customFolderIndex.size() << "entries.";
|
<< customFolderIndex.size() << "entries.";
|
||||||
|
emit indexChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardPictureLoaderLocal::updateWatchedDirectories(const QSet<QString> &directories)
|
||||||
|
{
|
||||||
|
const QSet<QString> stale = watchedDirectories - directories;
|
||||||
|
if (!stale.isEmpty()) {
|
||||||
|
folderWatcher->removePaths(QStringList(stale.begin(), stale.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSet<QString> added = directories - watchedDirectories;
|
||||||
|
if (!added.isEmpty()) {
|
||||||
|
const QStringList failed = folderWatcher->addPaths(QStringList(added.begin(), added.end()));
|
||||||
|
if (!failed.isEmpty()) {
|
||||||
|
qCDebug(CardPictureLoaderLocalLog) << "Could not watch custom picture directories:" << failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchedDirectories = directories;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CardPictureLoaderLocal::indexedPaths(const QString &baseName) const
|
||||||
|
{
|
||||||
|
return customFolderIndex.values(baseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage CardPictureLoaderLocal::tryLoad(const ExactCard &toLoad) const
|
QImage CardPictureLoaderLocal::tryLoad(const ExactCard &toLoad) const
|
||||||
|
|
@ -145,4 +199,5 @@ void CardPictureLoaderLocal::picsPathChanged()
|
||||||
{
|
{
|
||||||
picsPath = SettingsCache::instance().paths().getPicsPath();
|
picsPath = SettingsCache::instance().paths().getPicsPath();
|
||||||
customPicsPath = SettingsCache::instance().paths().getCustomPicsPath();
|
customPicsPath = SettingsCache::instance().paths().getCustomPicsPath();
|
||||||
|
refreshIndex();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QMultiHash>
|
#include <QMultiHash>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
class ExactCard;
|
class ExactCard;
|
||||||
|
class QFileSystemWatcher;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
|
||||||
inline Q_LOGGING_CATEGORY(CardPictureLoaderLocalLog, "card_picture_loader.local");
|
inline Q_LOGGING_CATEGORY(CardPictureLoaderLocalLog, "card_picture_loader.local");
|
||||||
|
|
@ -40,6 +42,18 @@ public:
|
||||||
*/
|
*/
|
||||||
explicit CardPictureLoaderLocal(QObject *parent);
|
explicit CardPictureLoaderLocal(QObject *parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs the loader against explicit folders without SettingsCache.
|
||||||
|
*
|
||||||
|
* Used by tests and by callers that already resolved the configured paths.
|
||||||
|
*/
|
||||||
|
CardPictureLoaderLocal(QObject *parent, const QString &picsPath, const QString &customPicsPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns every indexed file whose base name matches @p baseName.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QStringList indexedPaths(const QString &baseName) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Attempts to load a card image from local disk or custom folders.
|
* @brief Attempts to load a card image from local disk or custom folders.
|
||||||
* @param toLoad ExactCard object representing the card to load.
|
* @param toLoad ExactCard object representing the card to load.
|
||||||
|
|
@ -54,7 +68,10 @@ private:
|
||||||
QString customPicsPath; ///< Path to custom card image folder
|
QString customPicsPath; ///< Path to custom card image folder
|
||||||
|
|
||||||
QMultiHash<QString, QString> customFolderIndex; ///< Multimap from cardName to file paths in CUSTOM folder
|
QMultiHash<QString, QString> customFolderIndex; ///< Multimap from cardName to file paths in CUSTOM folder
|
||||||
QTimer *refreshTimer; ///< Timer for periodic folder index refresh
|
QFileSystemWatcher *folderWatcher; ///< Watches the custom folder tree for changes
|
||||||
|
QTimer *indexRefreshTimer; ///< Debounces bursts of filesystem events
|
||||||
|
QTimer *fallbackRefreshTimer; ///< Slow poll for filesystems without change notification
|
||||||
|
QSet<QString> watchedDirectories; ///< Directories currently registered with folderWatcher
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Rebuilds the index of the custom image folder.
|
* @brief Rebuilds the index of the custom image folder.
|
||||||
|
|
@ -65,6 +82,11 @@ private:
|
||||||
*/
|
*/
|
||||||
void refreshIndex();
|
void refreshIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Keeps the watcher registered on exactly @p directories.
|
||||||
|
*/
|
||||||
|
void updateWatchedDirectories(const QSet<QString> &directories);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Attempts to load a card image from disk given its set and name info.
|
* @brief Attempts to load a card image from disk given its set and name info.
|
||||||
* @param setName Corrected short name of the card's set.
|
* @param setName Corrected short name of the card's set.
|
||||||
|
|
@ -82,6 +104,12 @@ private:
|
||||||
const QString &collectorNumber,
|
const QString &collectorNumber,
|
||||||
const QString &providerId) const;
|
const QString &providerId) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/**
|
||||||
|
* @brief Emitted after the custom folder index has been rebuilt.
|
||||||
|
*/
|
||||||
|
void indexChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
/**
|
/**
|
||||||
* @brief Updates internal paths when the user changes picture settings.
|
* @brief Updates internal paths when the user changes picture settings.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue