refactor+fix: add new tests for Resource models and fix issues
Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
parent
e7cf9932a9
commit
c3ceefbafb
@ -165,7 +165,7 @@ int ModFolderModel::columnCount(const QModelIndex &parent) const
|
|||||||
Task* ModFolderModel::createUpdateTask()
|
Task* ModFolderModel::createUpdateTask()
|
||||||
{
|
{
|
||||||
auto index_dir = indexDir();
|
auto index_dir = indexDir();
|
||||||
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
|
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, this);
|
||||||
m_first_folder_load = false;
|
m_first_folder_load = false;
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
@ -181,6 +181,9 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat
|
|||||||
if(mod->fileinfo().fileName() == filename){
|
if(mod->fileinfo().fileName() == filename){
|
||||||
auto index_dir = indexDir();
|
auto index_dir = indexDir();
|
||||||
mod->destroy(index_dir, preserve_metadata);
|
mod->destroy(index_dir, preserve_metadata);
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,6 +209,9 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
|||||||
auto index_dir = indexDir();
|
auto index_dir = indexDir();
|
||||||
m->destroy(index_dir);
|
m->destroy(index_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,14 +274,13 @@ void ModFolderModel::onUpdateSucceeded()
|
|||||||
|
|
||||||
applyUpdates(current_set, new_set, new_mods);
|
applyUpdates(current_set, new_set, new_mods);
|
||||||
|
|
||||||
update_results.reset();
|
|
||||||
m_current_update_task.reset();
|
m_current_update_task.reset();
|
||||||
|
|
||||||
emit updateFinished();
|
if (m_scheduled_update) {
|
||||||
|
|
||||||
if(m_scheduled_update) {
|
|
||||||
m_scheduled_update = false;
|
m_scheduled_update = false;
|
||||||
update();
|
update();
|
||||||
|
} else {
|
||||||
|
emit updateFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,9 +304,6 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
|||||||
resource->finishResolvingWithDetails(std::move(result->details));
|
resource->finishResolvingWithDetails(std::move(result->details));
|
||||||
|
|
||||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||||
|
|
||||||
parse_task->deleteLater();
|
|
||||||
m_active_parse_tasks.remove(ticket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* PolyMC - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <QTest>
|
|
||||||
#include <QTemporaryDir>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
|
||||||
|
|
||||||
class ModFolderModelTest : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private
|
|
||||||
slots:
|
|
||||||
// test for GH-1178 - install a folder with files to a mod list
|
|
||||||
void test_1178()
|
|
||||||
{
|
|
||||||
// source
|
|
||||||
QString source = QFINDTESTDATA("testdata/test_folder");
|
|
||||||
|
|
||||||
// sanity check
|
|
||||||
QVERIFY(!source.endsWith('/'));
|
|
||||||
|
|
||||||
auto verify = [](QString path)
|
|
||||||
{
|
|
||||||
QDir target_dir(FS::PathCombine(path, "test_folder"));
|
|
||||||
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
|
|
||||||
QVERIFY(target_dir.entryList().contains("assets"));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. test with no trailing /
|
|
||||||
{
|
|
||||||
QString folder = source;
|
|
||||||
QTemporaryDir tempDir;
|
|
||||||
|
|
||||||
QEventLoop loop;
|
|
||||||
|
|
||||||
ModFolderModel m(tempDir.path(), true);
|
|
||||||
|
|
||||||
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
|
|
||||||
|
|
||||||
QTimer expire_timer;
|
|
||||||
expire_timer.callOnTimeout(&loop, &QEventLoop::quit);
|
|
||||||
expire_timer.setSingleShot(true);
|
|
||||||
expire_timer.start(4000);
|
|
||||||
|
|
||||||
m.installMod(folder);
|
|
||||||
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished.");
|
|
||||||
expire_timer.stop();
|
|
||||||
|
|
||||||
verify(tempDir.path());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. test with trailing /
|
|
||||||
{
|
|
||||||
QString folder = source + '/';
|
|
||||||
QTemporaryDir tempDir;
|
|
||||||
QEventLoop loop;
|
|
||||||
ModFolderModel m(tempDir.path(), true);
|
|
||||||
|
|
||||||
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
|
|
||||||
|
|
||||||
QTimer expire_timer;
|
|
||||||
expire_timer.callOnTimeout(&loop, &QEventLoop::quit);
|
|
||||||
expire_timer.setSingleShot(true);
|
|
||||||
expire_timer.start(4000);
|
|
||||||
|
|
||||||
m.installMod(folder);
|
|
||||||
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished.");
|
|
||||||
expire_timer.stop();
|
|
||||||
|
|
||||||
verify(tempDir.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(ModFolderModelTest)
|
|
||||||
|
|
||||||
#include "ModFolderModel_test.moc"
|
|
@ -24,8 +24,6 @@ bool ResourceFolderModel::startWatching(const QStringList paths)
|
|||||||
if (m_is_watching)
|
if (m_is_watching)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
auto couldnt_be_watched = m_watcher.addPaths(paths);
|
auto couldnt_be_watched = m_watcher.addPaths(paths);
|
||||||
for (auto path : paths) {
|
for (auto path : paths) {
|
||||||
if (couldnt_be_watched.contains(path))
|
if (couldnt_be_watched.contains(path))
|
||||||
@ -34,6 +32,8 @@ bool ResourceFolderModel::startWatching(const QStringList paths)
|
|||||||
qDebug() << "Started watching " << path;
|
qDebug() << "Started watching " << path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
m_is_watching = !m_is_watching;
|
m_is_watching = !m_is_watching;
|
||||||
return m_is_watching;
|
return m_is_watching;
|
||||||
}
|
}
|
||||||
@ -105,7 +105,8 @@ bool ResourceFolderModel::installResource(QString original_path)
|
|||||||
QFileInfo new_path_file_info(new_path);
|
QFileInfo new_path_file_info(new_path);
|
||||||
resource.setFile(new_path_file_info);
|
resource.setFile(new_path_file_info);
|
||||||
|
|
||||||
update();
|
if (!m_is_watching)
|
||||||
|
return update();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -123,7 +124,8 @@ bool ResourceFolderModel::installResource(QString original_path)
|
|||||||
QFileInfo newpathInfo(new_path);
|
QFileInfo newpathInfo(new_path);
|
||||||
resource.setFile(newpathInfo);
|
resource.setFile(newpathInfo);
|
||||||
|
|
||||||
update();
|
if (!m_is_watching)
|
||||||
|
return update();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -136,8 +138,13 @@ bool ResourceFolderModel::installResource(QString original_path)
|
|||||||
bool ResourceFolderModel::uninstallResource(QString file_name)
|
bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||||
{
|
{
|
||||||
for (auto& resource : m_resources) {
|
for (auto& resource : m_resources) {
|
||||||
if (resource->fileinfo().fileName() == file_name)
|
if (resource->fileinfo().fileName() == file_name) {
|
||||||
return resource->destroy();
|
auto res = resource->destroy();
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -156,13 +163,21 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& resource = m_resources.at(i.row());
|
auto& resource = m_resources.at(i.row());
|
||||||
|
|
||||||
resource->destroy();
|
resource->destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QMutex s_update_task_mutex;
|
||||||
bool ResourceFolderModel::update()
|
bool ResourceFolderModel::update()
|
||||||
{
|
{
|
||||||
|
// We hold a lock here to prevent race conditions on the m_current_update_task reset.
|
||||||
|
QMutexLocker lock(&s_update_task_mutex);
|
||||||
|
|
||||||
// Already updating, so we schedule a future update and return.
|
// Already updating, so we schedule a future update and return.
|
||||||
if (m_current_update_task) {
|
if (m_current_update_task) {
|
||||||
m_scheduled_update = true;
|
m_scheduled_update = true;
|
||||||
@ -183,7 +198,7 @@ bool ResourceFolderModel::update()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceFolderModel::resolveResource(Resource::WeakPtr res)
|
void ResourceFolderModel::resolveResource(Resource::Ptr res)
|
||||||
{
|
{
|
||||||
if (!res->shouldResolve()) {
|
if (!res->shouldResolve()) {
|
||||||
return;
|
return;
|
||||||
@ -205,6 +220,8 @@ void ResourceFolderModel::resolveResource(Resource::WeakPtr res)
|
|||||||
task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||||
connect(
|
connect(
|
||||||
task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(
|
||||||
|
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
||||||
|
|
||||||
auto* thread_pool = QThreadPool::globalInstance();
|
auto* thread_pool = QThreadPool::globalInstance();
|
||||||
thread_pool->start(task);
|
thread_pool->start(task);
|
||||||
@ -229,15 +246,13 @@ void ResourceFolderModel::onUpdateSucceeded()
|
|||||||
|
|
||||||
applyUpdates(current_set, new_set, new_resources);
|
applyUpdates(current_set, new_set, new_resources);
|
||||||
|
|
||||||
update_results.reset();
|
|
||||||
m_current_update_task->deleteLater();
|
|
||||||
m_current_update_task.reset();
|
m_current_update_task.reset();
|
||||||
|
|
||||||
emit updateFinished();
|
|
||||||
|
|
||||||
if (m_scheduled_update) {
|
if (m_scheduled_update) {
|
||||||
m_scheduled_update = false;
|
m_scheduled_update = false;
|
||||||
update();
|
update();
|
||||||
|
} else {
|
||||||
|
emit updateFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,9 +262,6 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
|||||||
if (iter == m_active_parse_tasks.constEnd())
|
if (iter == m_active_parse_tasks.constEnd())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(*iter)->deleteLater();
|
|
||||||
m_active_parse_tasks.remove(ticket);
|
|
||||||
|
|
||||||
int row = m_resources_index[resource_id];
|
int row = m_resources_index[resource_id];
|
||||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||||
}
|
}
|
||||||
@ -259,6 +271,12 @@ Task* ResourceFolderModel::createUpdateTask()
|
|||||||
return new BasicFolderLoadTask(m_dir);
|
return new BasicFolderLoadTask(m_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ResourceFolderModel::hasPendingParseTasks() const
|
||||||
|
{
|
||||||
|
return !m_active_parse_tasks.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
void ResourceFolderModel::directoryChanged(QString path)
|
void ResourceFolderModel::directoryChanged(QString path)
|
||||||
{
|
{
|
||||||
update();
|
update();
|
||||||
|
@ -62,7 +62,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
virtual bool update();
|
virtual bool update();
|
||||||
|
|
||||||
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
||||||
virtual void resolveResource(Resource::WeakPtr res);
|
virtual void resolveResource(Resource::Ptr res);
|
||||||
|
|
||||||
[[nodiscard]] size_t size() const { return m_resources.size(); };
|
[[nodiscard]] size_t size() const { return m_resources.size(); };
|
||||||
[[nodiscard]] bool empty() const { return size() == 0; }
|
[[nodiscard]] bool empty() const { return size() == 0; }
|
||||||
@ -71,6 +71,13 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
|
|
||||||
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
||||||
|
|
||||||
|
/** Checks whether there's any parse tasks being done.
|
||||||
|
*
|
||||||
|
* Since they can be quite expensive, and are usually done in a separate thread, if we were to destroy the model while having
|
||||||
|
* such tasks would introduce an undefined behavior, most likely resulting in a crash.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool hasPendingParseTasks() const;
|
||||||
|
|
||||||
/* Qt behavior */
|
/* Qt behavior */
|
||||||
|
|
||||||
/* Basic columns */
|
/* Basic columns */
|
||||||
@ -228,10 +235,12 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
|||||||
QSet<QString> kept_set = current_set;
|
QSet<QString> kept_set = current_set;
|
||||||
kept_set.intersect(new_set);
|
kept_set.intersect(new_set);
|
||||||
|
|
||||||
for (auto& kept : kept_set) {
|
for (auto const& kept : kept_set) {
|
||||||
auto row = m_resources_index[kept];
|
auto row_it = m_resources_index.constFind(kept);
|
||||||
|
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||||
|
auto row = row_it.value();
|
||||||
|
|
||||||
auto new_resource = new_resources[kept];
|
auto& new_resource = new_resources[kept];
|
||||||
auto const& current_resource = m_resources[row];
|
auto const& current_resource = m_resources[row];
|
||||||
|
|
||||||
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||||
@ -242,11 +251,12 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
|||||||
// If the resource is resolving, but something about it changed, we don't want to
|
// If the resource is resolving, but something about it changed, we don't want to
|
||||||
// continue the resolving.
|
// continue the resolving.
|
||||||
if (current_resource->isResolving()) {
|
if (current_resource->isResolving()) {
|
||||||
m_active_parse_tasks.remove(current_resource->resolutionTicket());
|
auto task = (*m_active_parse_tasks.find(current_resource->resolutionTicket())).get();
|
||||||
|
task->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_resources[row] = new_resource;
|
m_resources[row].reset(new_resource);
|
||||||
resolveResource(new_resource);
|
resolveResource(m_resources.at(row));
|
||||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,21 +270,21 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
|||||||
for (auto& removed : removed_set)
|
for (auto& removed : removed_set)
|
||||||
removed_rows.append(m_resources_index[removed]);
|
removed_rows.append(m_resources_index[removed]);
|
||||||
|
|
||||||
std::sort(removed_rows.begin(), removed_rows.end());
|
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||||
|
|
||||||
for (int i = 0; i < removed_rows.size(); i++)
|
|
||||||
removed_rows[i] -= i;
|
|
||||||
|
|
||||||
for (auto& removed_index : removed_rows) {
|
for (auto& removed_index : removed_rows) {
|
||||||
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
|
||||||
|
|
||||||
auto removed_it = m_resources.begin() + removed_index;
|
auto removed_it = m_resources.begin() + removed_index;
|
||||||
|
|
||||||
|
Q_ASSERT(removed_it != m_resources.end());
|
||||||
|
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
|
||||||
|
|
||||||
if ((*removed_it)->isResolving()) {
|
if ((*removed_it)->isResolving()) {
|
||||||
m_active_parse_tasks.remove((*removed_it)->resolutionTicket());
|
auto task = (*m_active_parse_tasks.find((*removed_it)->resolutionTicket())).get();
|
||||||
|
task->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||||
m_resources.erase(removed_it);
|
m_resources.erase(removed_it);
|
||||||
|
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
219
launcher/minecraft/mod/ResourceFolderModel_test.cpp
Normal file
219
launcher/minecraft/mod/ResourceFolderModel_test.cpp
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QTest>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
|
#include "minecraft/mod/ResourceFolderModel.h"
|
||||||
|
|
||||||
|
#define EXEC_UPDATE_TASK(EXEC, VERIFY) \
|
||||||
|
QEventLoop loop; \
|
||||||
|
\
|
||||||
|
connect(&model, &ResourceFolderModel::updateFinished, &loop, &QEventLoop::quit); \
|
||||||
|
\
|
||||||
|
QTimer expire_timer; \
|
||||||
|
expire_timer.callOnTimeout(&loop, &QEventLoop::quit); \
|
||||||
|
expire_timer.setSingleShot(true); \
|
||||||
|
expire_timer.start(4000); \
|
||||||
|
\
|
||||||
|
VERIFY(EXEC); \
|
||||||
|
loop.exec(); \
|
||||||
|
\
|
||||||
|
QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); \
|
||||||
|
expire_timer.stop(); \
|
||||||
|
\
|
||||||
|
disconnect(&model, nullptr, nullptr, nullptr);
|
||||||
|
|
||||||
|
class ResourceFolderModelTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private
|
||||||
|
slots:
|
||||||
|
// test for GH-1178 - install a folder with files to a mod list
|
||||||
|
void test_1178()
|
||||||
|
{
|
||||||
|
// source
|
||||||
|
QString source = QFINDTESTDATA("testdata/test_folder");
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
QVERIFY(!source.endsWith('/'));
|
||||||
|
|
||||||
|
auto verify = [](QString path)
|
||||||
|
{
|
||||||
|
QDir target_dir(FS::PathCombine(path, "test_folder"));
|
||||||
|
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
|
||||||
|
QVERIFY(target_dir.entryList().contains("assets"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. test with no trailing /
|
||||||
|
{
|
||||||
|
QString folder = source;
|
||||||
|
QTemporaryDir tempDir;
|
||||||
|
|
||||||
|
QEventLoop loop;
|
||||||
|
|
||||||
|
ModFolderModel m(tempDir.path(), true);
|
||||||
|
|
||||||
|
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QTimer expire_timer;
|
||||||
|
expire_timer.callOnTimeout(&loop, &QEventLoop::quit);
|
||||||
|
expire_timer.setSingleShot(true);
|
||||||
|
expire_timer.start(4000);
|
||||||
|
|
||||||
|
m.installMod(folder);
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished.");
|
||||||
|
expire_timer.stop();
|
||||||
|
|
||||||
|
verify(tempDir.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. test with trailing /
|
||||||
|
{
|
||||||
|
QString folder = source + '/';
|
||||||
|
QTemporaryDir tempDir;
|
||||||
|
QEventLoop loop;
|
||||||
|
ModFolderModel m(tempDir.path(), true);
|
||||||
|
|
||||||
|
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QTimer expire_timer;
|
||||||
|
expire_timer.callOnTimeout(&loop, &QEventLoop::quit);
|
||||||
|
expire_timer.setSingleShot(true);
|
||||||
|
expire_timer.start(4000);
|
||||||
|
|
||||||
|
m.installMod(folder);
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished.");
|
||||||
|
expire_timer.stop();
|
||||||
|
|
||||||
|
verify(tempDir.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_addFromWatch()
|
||||||
|
{
|
||||||
|
QString source = QFINDTESTDATA("testdata");
|
||||||
|
|
||||||
|
ModFolderModel model(source);
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 0);
|
||||||
|
|
||||||
|
EXEC_UPDATE_TASK(model.startWatching(), )
|
||||||
|
|
||||||
|
for (auto mod : model.allMods())
|
||||||
|
qDebug() << mod->name();
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 2);
|
||||||
|
|
||||||
|
model.stopWatching();
|
||||||
|
|
||||||
|
while (model.hasPendingParseTasks()) {
|
||||||
|
QTest::qSleep(20);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_removeResource()
|
||||||
|
{
|
||||||
|
QString folder_resource = QFINDTESTDATA("testdata/test_folder");
|
||||||
|
QString file_mod = QFINDTESTDATA("testdata/supercoolmod.jar");
|
||||||
|
|
||||||
|
QTemporaryDir tmp;
|
||||||
|
|
||||||
|
ResourceFolderModel model(QDir(tmp.path()));
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY)
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 1);
|
||||||
|
qDebug() << "Added first mod.";
|
||||||
|
|
||||||
|
{
|
||||||
|
EXEC_UPDATE_TASK(model.startWatching(), )
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 1);
|
||||||
|
qDebug() << "Started watching the temp folder.";
|
||||||
|
|
||||||
|
{
|
||||||
|
EXEC_UPDATE_TASK(model.installResource(folder_resource), QVERIFY)
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 2);
|
||||||
|
qDebug() << "Added second mod.";
|
||||||
|
|
||||||
|
{
|
||||||
|
EXEC_UPDATE_TASK(model.uninstallResource("supercoolmod.jar"), QVERIFY);
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 1);
|
||||||
|
qDebug() << "Removed first mod.";
|
||||||
|
|
||||||
|
QString mod_file_name {model.at(0).fileinfo().fileName()};
|
||||||
|
QVERIFY(!mod_file_name.isEmpty());
|
||||||
|
|
||||||
|
{
|
||||||
|
EXEC_UPDATE_TASK(model.uninstallResource(mod_file_name), QVERIFY);
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(model.size(), 0);
|
||||||
|
qDebug() << "Removed second mod.";
|
||||||
|
|
||||||
|
model.stopWatching();
|
||||||
|
|
||||||
|
while (model.hasPendingParseTasks()) {
|
||||||
|
QTest::qSleep(20);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(ResourceFolderModelTest)
|
||||||
|
|
||||||
|
#include "ResourceFolderModel_test.moc"
|
@ -17,7 +17,7 @@ class BasicFolderLoadTask : public Task
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
struct Result {
|
struct Result {
|
||||||
QMap<QString, Resource*> resources;
|
QMap<QString, Resource::Ptr> resources;
|
||||||
};
|
};
|
||||||
using ResultPtr = std::shared_ptr<Result>;
|
using ResultPtr = std::shared_ptr<Result>;
|
||||||
|
|
||||||
@ -27,6 +27,10 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) {}
|
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool canAbort() const override { return true; }
|
||||||
|
bool abort() override { m_aborted = true; return true; }
|
||||||
|
|
||||||
void executeTask() override
|
void executeTask() override
|
||||||
{
|
{
|
||||||
m_dir.refresh();
|
m_dir.refresh();
|
||||||
@ -35,10 +39,15 @@ public:
|
|||||||
m_result->resources.insert(resource->internal_id(), resource);
|
m_result->resources.insert(resource->internal_id(), resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSucceeded();
|
if (m_aborted)
|
||||||
|
emitAborted();
|
||||||
|
else
|
||||||
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
ResultPtr m_result;
|
ResultPtr m_result;
|
||||||
|
|
||||||
|
bool m_aborted = false;
|
||||||
};
|
};
|
||||||
|
@ -497,6 +497,12 @@ void LocalModParseTask::processAsLitemod()
|
|||||||
zip.close();
|
zip.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LocalModParseTask::abort()
|
||||||
|
{
|
||||||
|
m_aborted = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void LocalModParseTask::executeTask()
|
void LocalModParseTask::executeTask()
|
||||||
{
|
{
|
||||||
switch(m_type)
|
switch(m_type)
|
||||||
@ -513,5 +519,9 @@ void LocalModParseTask::executeTask()
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
emitSucceeded();
|
|
||||||
|
if (m_aborted)
|
||||||
|
emitAborted();
|
||||||
|
else
|
||||||
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,9 @@ public:
|
|||||||
return m_result;
|
return m_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool canAbort() const override { return true; }
|
||||||
|
bool abort() override;
|
||||||
|
|
||||||
LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile);
|
LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile);
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
@ -35,4 +38,6 @@ private:
|
|||||||
ResourceType m_type;
|
ResourceType m_type;
|
||||||
QFileInfo m_modFile;
|
QFileInfo m_modFile;
|
||||||
ResultPtr m_result;
|
ResultPtr m_result;
|
||||||
|
|
||||||
|
bool m_aborted = false;
|
||||||
};
|
};
|
||||||
|
1
launcher/minecraft/mod/testdata/supercoolmod.jar
vendored
Normal file
1
launcher/minecraft/mod/testdata/supercoolmod.jar
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
the best mod.
|
Loading…
Reference in New Issue
Block a user