fix: load world size async (#3651)

This commit is contained in:
Alexandru Ionut Tripon 2025-04-18 18:47:05 +03:00 committed by GitHub
commit e9245716f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 98 additions and 65 deletions

View File

@ -198,22 +198,6 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
return f.commit();
}
int64_t calculateWorldSize(const QFileInfo& file)
{
if (file.isFile() && file.suffix() == "zip") {
return file.size();
} else if (file.isDir()) {
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
int64_t total = 0;
while (it.hasNext()) {
it.next();
total += it.fileInfo().size();
}
return total;
}
return -1;
}
World::World(const QFileInfo& file)
{
repath(file);
@ -223,7 +207,6 @@ void World::repath(const QFileInfo& file)
{
m_containerFile = file;
m_folderName = file.fileName();
m_size = calculateWorldSize(file);
if (file.isFile() && file.suffix() == "zip") {
m_iconFile = QString();
readFromZip(file);
@ -252,41 +235,41 @@ void World::readFromFS(const QFileInfo& file)
{
auto bytes = getLevelDatDataFromFS(file);
if (bytes.isEmpty()) {
is_valid = false;
m_isValid = false;
return;
}
loadFromLevelDat(bytes);
levelDatTime = file.lastModified();
m_levelDatTime = file.lastModified();
}
void World::readFromZip(const QFileInfo& file)
{
QuaZip zip(file.absoluteFilePath());
is_valid = zip.open(QuaZip::mdUnzip);
if (!is_valid) {
m_isValid = zip.open(QuaZip::mdUnzip);
if (!m_isValid) {
return;
}
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
is_valid = !location.isEmpty();
if (!is_valid) {
m_isValid = !location.isEmpty();
if (!m_isValid) {
return;
}
m_containerOffsetPath = location;
QuaZipFile zippedFile(&zip);
// read the install profile
is_valid = zip.setCurrentFile(location + "level.dat");
if (!is_valid) {
m_isValid = zip.setCurrentFile(location + "level.dat");
if (!m_isValid) {
return;
}
is_valid = zippedFile.open(QIODevice::ReadOnly);
m_isValid = zippedFile.open(QIODevice::ReadOnly);
QuaZipFileInfo64 levelDatInfo;
zippedFile.getFileInfo(&levelDatInfo);
auto modTime = levelDatInfo.getNTFSmTime();
if (!modTime.isValid()) {
modTime = levelDatInfo.dateTime;
}
levelDatTime = modTime;
if (!is_valid) {
m_levelDatTime = modTime;
if (!m_isValid) {
return;
}
loadFromLevelDat(zippedFile.readAll());
@ -430,7 +413,7 @@ void World::loadFromLevelDat(QByteArray data)
{
auto levelData = parseLevelDat(data);
if (!levelData) {
is_valid = false;
m_isValid = false;
return;
}
@ -439,20 +422,20 @@ void World::loadFromLevelDat(QByteArray data)
valPtr = &levelData->at("Data");
} catch (const std::out_of_range& e) {
qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what();
is_valid = false;
m_isValid = false;
return;
}
nbt::value& val = *valPtr;
is_valid = val.get_type() == nbt::tag_type::Compound;
if (!is_valid)
m_isValid = val.get_type() == nbt::tag_type::Compound;
if (!m_isValid)
return;
auto name = read_string(val, "LevelName");
m_actualName = name ? *name : m_folderName;
auto timestamp = read_long(val, "LastPlayed");
m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime;
m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : m_levelDatTime;
m_gameType = read_gametype(val, "GameType");
@ -490,7 +473,7 @@ bool World::replace(World& with)
bool World::destroy()
{
if (!is_valid)
if (!m_isValid)
return false;
if (FS::trash(m_containerFile.filePath()))
@ -508,7 +491,7 @@ bool World::destroy()
bool World::operator==(const World& other) const
{
return is_valid == other.is_valid && folderName() == other.folderName();
return m_isValid == other.m_isValid && folderName() == other.folderName();
}
bool World::isSymLinkUnder(const QString& instPath) const
@ -531,3 +514,8 @@ bool World::isMoreThanOneHardLink() const
}
return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1;
}
void World::setSize(int64_t size)
{
m_size = size;
}

View File

@ -39,7 +39,7 @@ class World {
QDateTime lastPlayed() const { return m_lastPlayed; }
GameType gameType() const { return m_gameType; }
int64_t seed() const { return m_randomSeed; }
bool isValid() const { return is_valid; }
bool isValid() const { return m_isValid; }
bool isOnFS() const { return m_containerFile.isDir(); }
QFileInfo container() const { return m_containerFile; }
// delete all the files of this world
@ -54,6 +54,8 @@ class World {
bool rename(const QString& to);
bool install(const QString& to, const QString& name = QString());
void setSize(int64_t size);
// WEAK compare operator - used for replacing worlds
bool operator==(const World& other) const;
@ -83,10 +85,10 @@ class World {
QString m_folderName;
QString m_actualName;
QString m_iconFile;
QDateTime levelDatTime;
QDateTime m_levelDatTime;
QDateTime m_lastPlayed;
int64_t m_size;
int64_t m_randomSeed = 0;
GameType m_gameType;
bool is_valid = false;
bool m_isValid = false;
};

View File

@ -37,13 +37,14 @@
#include <FileSystem.h>
#include <QDebug>
#include <QDirIterator>
#include <QFileSystemWatcher>
#include <QMimeData>
#include <QString>
#include <QThreadPool>
#include <QUrl>
#include <QUuid>
#include <Qt>
#include "Application.h"
WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir)
{
@ -51,18 +52,18 @@ WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractList
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
is_watching = false;
m_isWatching = false;
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged);
}
void WorldList::startWatching()
{
if (is_watching) {
if (m_isWatching) {
return;
}
update();
is_watching = m_watcher->addPath(m_dir.absolutePath());
if (is_watching) {
m_isWatching = m_watcher->addPath(m_dir.absolutePath());
if (m_isWatching) {
qDebug() << "Started watching " << m_dir.absolutePath();
} else {
qDebug() << "Failed to start watching " << m_dir.absolutePath();
@ -71,11 +72,11 @@ void WorldList::startWatching()
void WorldList::stopWatching()
{
if (!is_watching) {
if (!m_isWatching) {
return;
}
is_watching = !m_watcher->removePath(m_dir.absolutePath());
if (!is_watching) {
m_isWatching = !m_watcher->removePath(m_dir.absolutePath());
if (!m_isWatching) {
qDebug() << "Stopped watching " << m_dir.absolutePath();
} else {
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
@ -101,12 +102,13 @@ bool WorldList::update()
}
}
beginResetModel();
worlds.swap(newWorlds);
m_worlds.swap(newWorlds);
endResetModel();
loadWorldsAsync();
return true;
}
void WorldList::directoryChanged(QString path)
void WorldList::directoryChanged(QString)
{
update();
}
@ -123,12 +125,12 @@ QString WorldList::instDirPath() const
bool WorldList::deleteWorld(int index)
{
if (index >= worlds.size() || index < 0)
if (index >= m_worlds.size() || index < 0)
return false;
World& m = worlds[index];
World& m = m_worlds[index];
if (m.destroy()) {
beginRemoveRows(QModelIndex(), index, index);
worlds.removeAt(index);
m_worlds.removeAt(index);
endRemoveRows();
emit changed();
return true;
@ -139,11 +141,11 @@ bool WorldList::deleteWorld(int index)
bool WorldList::deleteWorlds(int first, int last)
{
for (int i = first; i <= last; i++) {
World& m = worlds[i];
World& m = m_worlds[i];
m.destroy();
}
beginRemoveRows(QModelIndex(), first, last);
worlds.erase(worlds.begin() + first, worlds.begin() + last + 1);
m_worlds.erase(m_worlds.begin() + first, m_worlds.begin() + last + 1);
endRemoveRows();
emit changed();
return true;
@ -151,9 +153,9 @@ bool WorldList::deleteWorlds(int first, int last)
bool WorldList::resetIcon(int row)
{
if (row >= worlds.size() || row < 0)
if (row >= m_worlds.size() || row < 0)
return false;
World& m = worlds[row];
World& m = m_worlds[row];
if (m.resetIcon()) {
emit dataChanged(index(row), index(row), { WorldList::IconFileRole });
return true;
@ -174,12 +176,12 @@ QVariant WorldList::data(const QModelIndex& index, int role) const
int row = index.row();
int column = index.column();
if (row < 0 || row >= worlds.size())
if (row < 0 || row >= m_worlds.size())
return QVariant();
QLocale locale;
auto& world = worlds[row];
auto& world = m_worlds[row];
switch (role) {
case Qt::DisplayRole:
switch (column) {
@ -339,9 +341,9 @@ QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const
if (idx.column() != 0)
continue;
int row = idx.row();
if (row < 0 || row >= this->worlds.size())
if (row < 0 || row >= this->m_worlds.size())
continue;
worlds_.append(this->worlds[row]);
worlds_.append(this->m_worlds[row]);
}
if (!worlds_.size()) {
return new QMimeData();
@ -393,7 +395,7 @@ bool WorldList::dropMimeData(const QMimeData* data,
return false;
// files dropped from outside?
if (data->hasUrls()) {
bool was_watching = is_watching;
bool was_watching = m_isWatching;
if (was_watching)
stopWatching();
auto urls = data->urls();
@ -416,4 +418,44 @@ bool WorldList::dropMimeData(const QMimeData* data,
return false;
}
int64_t calculateWorldSize(const QFileInfo& file)
{
if (file.isFile() && file.suffix() == "zip") {
return file.size();
} else if (file.isDir()) {
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
int64_t total = 0;
while (it.hasNext()) {
it.next();
total += it.fileInfo().size();
}
return total;
}
return -1;
}
void WorldList::loadWorldsAsync()
{
for (int i = 0; i < m_worlds.size(); ++i) {
auto file = m_worlds.at(i).container();
int row = i;
QThreadPool::globalInstance()->start([this, file, row]() mutable {
auto size = calculateWorldSize(file);
QMetaObject::invokeMethod(
this,
[this, size, row, file]() {
if (row < m_worlds.size() && m_worlds[row].container() == file) {
m_worlds[row].setSize(size);
// Notify views
QModelIndex modelIndex = index(row);
emit dataChanged(modelIndex, modelIndex, { SizeRole });
}
},
Qt::QueuedConnection);
});
}
}
#include "WorldList.moc"

View File

@ -40,9 +40,9 @@ class WorldList : public QAbstractListModel {
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual int columnCount(const QModelIndex& parent) const;
size_t size() const { return worlds.size(); };
size_t size() const { return m_worlds.size(); };
bool empty() const { return size() == 0; }
World& operator[](size_t index) { return worlds[index]; }
World& operator[](size_t index) { return m_worlds[index]; }
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
@ -82,10 +82,11 @@ class WorldList : public QAbstractListModel {
QString instDirPath() const;
const QList<World>& allWorlds() const { return worlds; }
const QList<World>& allWorlds() const { return m_worlds; }
private slots:
void directoryChanged(QString path);
void loadWorldsAsync();
signals:
void changed();
@ -93,7 +94,7 @@ class WorldList : public QAbstractListModel {
protected:
BaseInstance* m_instance;
QFileSystemWatcher* m_watcher;
bool is_watching;
bool m_isWatching;
QDir m_dir;
QList<World> worlds;
QList<World> m_worlds;
};