change: add enable/disable to resources

TIL that zip resource packs, when disabled, actually have the effect of
not showing up in the game at all. Since this can be useful to someone,
I moved the logic for it to the resources.

Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
flow 2022-08-13 11:58:39 -03:00
parent c3ceefbafb
commit e2ab2aea32
No known key found for this signature in database
GPG Key ID: 8D0F221F0A59F469
13 changed files with 231 additions and 168 deletions

View File

@ -56,37 +56,6 @@ Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
}
auto Mod::enable(bool value) -> bool
{
if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER)
return false;
if (m_enabled == value)
return false;
QString path = m_file_info.absoluteFilePath();
QFile file(path);
if (value) {
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!file.rename(path))
return false;
} else {
path += ".disabled";
if (!file.rename(path))
return false;
}
if (status() == ModStatus::NoMetadata)
setFile(QFileInfo(path));
m_enabled = value;
return true;
}
void Mod::setStatus(ModStatus status)
{
m_local_details.status = status;
@ -108,10 +77,6 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
switch (type) {
default:
case SortType::ENABLED:
if (enabled() && !cast_other->enabled())
return { 1, type == SortType::ENABLED };
if (!enabled() && cast_other->enabled())
return { -1, type == SortType::ENABLED };
case SortType::NAME:
case SortType::DATE: {
auto res = Resource::compare(other, type);

View File

@ -54,8 +54,6 @@ public:
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
auto enabled() const -> bool { return m_enabled; }
auto details() const -> const ModDetails&;
auto name() const -> QString override;
auto version() const -> QString;
@ -71,8 +69,6 @@ public:
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
auto enable(bool value) -> bool;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
@ -83,6 +79,4 @@ public:
protected:
ModDetails m_local_details;
bool m_enabled = true;
};

View File

@ -104,20 +104,6 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
}
bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (role == Qt::CheckStateRole)
{
return setModStatus(index.row(), Toggle);
}
return false;
}
QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
@ -305,65 +291,3 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
{
if(!m_can_interact) {
return false;
}
if(indexes.isEmpty())
return true;
for (auto index: indexes)
{
if(index.column() != 0) {
continue;
}
setModStatus(index.row(), enable);
}
return true;
}
bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action)
{
if(row < 0 || row >= m_resources.size()) {
return false;
}
auto mod = at(row);
bool desiredStatus;
switch(action) {
case Enable:
desiredStatus = true;
break;
case Disable:
desiredStatus = false;
break;
case Toggle:
default:
desiredStatus = !mod->enabled();
break;
}
if(desiredStatus == mod->enabled()) {
return true;
}
// preserve the row, but change its ID
auto oldId = mod->internal_id();
if(!mod->enable(!mod->enabled())) {
return false;
}
auto newId = mod->internal_id();
if(m_resources_index.contains(newId)) {
// NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
// But is it necessary?
}
m_resources_index.remove(oldId);
m_resources_index[newId] = row;
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
return true;
}

View File

@ -77,7 +77,6 @@ public:
ModFolderModel(const QString &dir, bool is_indexed = false);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex &parent) const override;
@ -91,9 +90,6 @@ public:
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList &indexes);
/// Enable or disable listed mods
bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
bool isValid();
bool startWatching() override;
@ -111,9 +107,6 @@ slots:
void onUpdateSucceeded() override;
void onParseSucceeded(int ticket, QString resource_id) override;
private:
bool setModStatus(int index, ModStatusAction action);
protected:
bool m_is_indexed;
bool m_first_folder_load = true;

View File

@ -29,8 +29,10 @@ void Resource::parseFile()
m_type = ResourceType::FOLDER;
m_name = file_name;
} else if (m_file_info.isFile()) {
if (file_name.endsWith(".disabled"))
if (file_name.endsWith(".disabled")) {
file_name.chop(9);
m_enabled = false;
}
if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) {
m_type = ResourceType::ZIPFILE;
@ -59,6 +61,11 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
{
switch (type) {
default:
case SortType::ENABLED:
if (enabled() && !other.enabled())
return { 1, type == SortType::ENABLED };
if (!enabled() && other.enabled())
return { -1, type == SortType::ENABLED };
case SortType::NAME: {
QString this_name{ name() };
QString other_name{ other.name() };
@ -85,6 +92,54 @@ bool Resource::applyFilter(QRegularExpression filter) const
return filter.match(name()).hasMatch();
}
bool Resource::enable(EnableAction action)
{
if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER)
return false;
QString path = m_file_info.absoluteFilePath();
QFile file(path);
bool enable = true;
switch (action) {
case EnableAction::ENABLE:
enable = true;
break;
case EnableAction::DISABLE:
enable = false;
break;
case EnableAction::TOGGLE:
default:
enable = !enabled();
break;
}
if (m_enabled == enable)
return false;
if (enable) {
// m_enabled is false, but there's no '.disabled' suffix.
// TODO: Report error?
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!file.rename(path))
return false;
} else {
path += ".disabled";
if (!file.rename(path))
return false;
}
setFile(QFileInfo(path));
m_enabled = enable;
return true;
}
bool Resource::destroy()
{
m_type = ResourceType::UNKNOWN;

View File

@ -22,6 +22,12 @@ enum class SortType {
ENABLED,
};
enum class EnableAction {
ENABLE,
DISABLE,
TOGGLE
};
/** General class for managed resources. It mirrors a file in disk, with some more info
* for display and house-keeping purposes.
*
@ -47,6 +53,7 @@ class Resource : public QObject {
[[nodiscard]] auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; }
[[nodiscard]] auto internal_id() const -> QString { return m_internal_id; }
[[nodiscard]] auto type() const -> ResourceType { return m_type; }
[[nodiscard]] bool enabled() const { return m_enabled; }
[[nodiscard]] virtual auto name() const -> QString { return m_name; }
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
@ -65,6 +72,12 @@ class Resource : public QObject {
*/
[[nodiscard]] virtual bool applyFilter(QRegularExpression filter) const;
/** Changes the enabled property, according to 'action'.
*
* Returns whether a change was applied to the Resource's properties.
*/
bool enable(EnableAction action);
[[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; }
[[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; }
[[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; }
@ -92,6 +105,9 @@ class Resource : public QObject {
/* The type of file we're dealing with. */
ResourceType m_type = ResourceType::UNKNOWN;
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
bool m_enabled = true;
/* Used to keep trach of pending / concluded actions on the resource. */
bool m_is_resolving = false;
bool m_is_resolved = false;

View File

@ -172,6 +172,44 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
}
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action)
{
if (!m_can_interact)
return false;
if (indexes.isEmpty())
return true;
bool succeeded = true;
for (auto const& idx : indexes) {
if (!validateIndex(idx) || idx.column() != 0)
continue;
int row = idx.row();
auto& resource = m_resources[row];
// Preserve the row, but change its ID
auto old_id = resource->internal_id();
if (!resource->enable(action)) {
succeeded = false;
continue;
}
auto new_id = resource->internal_id();
if (m_resources_index.contains(new_id)) {
// FIXME: https://github.com/PolyMC/PolyMC/issues/550
}
m_resources_index.remove(old_id);
m_resources_index[new_id] = row;
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
}
return succeeded;
}
static QMutex s_update_task_mutex;
bool ResourceFolderModel::update()
{
@ -271,7 +309,6 @@ Task* ResourceFolderModel::createUpdateTask()
return new BasicFolderLoadTask(m_dir);
}
bool ResourceFolderModel::hasPendingParseTasks() const
{
return !m_active_parse_tasks.isEmpty();
@ -370,11 +407,30 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
}
case Qt::ToolTipRole:
return m_resources[row]->internal_id();
case Qt::CheckStateRole:
switch (column) {
case ACTIVE_COLUMN:
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return {};
}
default:
return {};
}
}
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
int row = index.row();
if (row < 0 || row >= rowCount(index) || !index.isValid())
return false;
if (role == Qt::CheckStateRole)
return setResourceEnabled({ index }, EnableAction::TOGGLE);
return false;
}
QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role) {
@ -389,9 +445,14 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
}
case Qt::ToolTipRole: {
switch (section) {
case ACTIVE_COLUMN:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the resource enabled?");
case NAME_COLUMN:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The name of the resource.");
case DATE_COLUMN:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The date and time this resource was last changed (or added).");
default:
return {};
@ -459,4 +520,3 @@ void ResourceFolderModel::enableInteraction(bool enabled)
return (compare_result.first < 0);
return (compare_result.first > 0);
}

View File

@ -55,9 +55,14 @@ class ResourceFolderModel : public QAbstractListModel {
* Returns whether the removal was successful.
*/
virtual bool uninstallResource(QString file_name);
virtual bool deleteResources(const QModelIndexList&);
/** Applies the given 'action' to the resources in 'indexes'.
*
* Returns whether the action was successfully applied to all resources.
*/
virtual bool setResourceEnabled(const QModelIndexList& indexes, EnableAction action);
/** Creates a new update task and start it. Returns false if no update was done, like when an update is already underway. */
virtual bool update();
@ -66,6 +71,7 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] size_t size() const { return m_resources.size(); };
[[nodiscard]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
@ -81,7 +87,7 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */
/* Basic columns */
enum Columns { NAME_COLUMN = 0, DATE_COLUMN, NUM_COLUMNS };
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
@ -96,7 +102,7 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] bool validateIndex(const QModelIndex& index) const;
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return false; };
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
@ -174,7 +180,7 @@ class ResourceFolderModel : public QAbstractListModel {
protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important!
QList<SortType> m_column_sort_keys = { SortType::NAME, SortType::DATE };
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
bool m_can_interact = true;

View File

@ -212,6 +212,62 @@ slots:
QCoreApplication::processEvents();
}
}
void test_enable_disable()
{
QString folder_resource = QFINDTESTDATA("testdata/test_folder");
QString file_mod = QFINDTESTDATA("testdata/supercoolmod.jar");
QTemporaryDir tmp;
ResourceFolderModel model(tmp.path());
QCOMPARE(model.size(), 0);
{
EXEC_UPDATE_TASK(model.installResource(folder_resource), QVERIFY)
}
{
EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY)
}
for (auto res : model.all())
qDebug() << res->name();
QCOMPARE(model.size(), 2);
auto& res_1 = model.at(0).type() != ResourceType::FOLDER ? model.at(0) : model.at(1);
auto& res_2 = model.at(0).type() == ResourceType::FOLDER ? model.at(0) : model.at(1);
auto id_1 = res_1.internal_id();
auto id_2 = res_2.internal_id();
bool initial_enabled_res_2 = res_2.enabled();
bool initial_enabled_res_1 = res_1.enabled();
QVERIFY(res_1.type() != ResourceType::FOLDER && res_1.type() != ResourceType::UNKNOWN);
qDebug() << "res_1 is of the correct type.";
QVERIFY(res_1.enabled());
qDebug() << "res_1 is initially enabled.";
QVERIFY(res_1.enable(EnableAction::TOGGLE));
QVERIFY(res_1.enabled() == !initial_enabled_res_1);
qDebug() << "res_1 got successfully toggled.";
QVERIFY(res_1.enable(EnableAction::TOGGLE));
qDebug() << "res_1 got successfully toggled again.";
QVERIFY(res_1.enabled() == initial_enabled_res_1);
QVERIFY(res_1.internal_id() == id_1);
qDebug() << "res_1 got back to its initial state.";
QVERIFY(!res_2.enable(initial_enabled_res_2 ? EnableAction::ENABLE : EnableAction::DISABLE));
QVERIFY(res_2.enabled() == initial_enabled_res_2);
QVERIFY(res_2.internal_id() == id_2);
while (model.hasPendingParseTasks()) {
QTest::qSleep(20);
QCoreApplication::processEvents();
}
}
};
QTEST_GUILESS_MAIN(ResourceFolderModelTest)

View File

@ -40,6 +40,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder);
connect(ui->treeView, &ModListView::customContextMenuRequested, this, &ExternalResourcesPage::ShowContextMenu);
connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated);
auto selection_model = ui->treeView->selectionModel();
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
@ -81,6 +82,15 @@ void ExternalResourcesPage::retranslate()
ui->retranslateUi(this);
}
void ExternalResourcesPage::itemActivated(const QModelIndex&)
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
}
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
{
m_viewFilter = newContents;
@ -157,6 +167,24 @@ void ExternalResourcesPage::removeItem()
m_model->deleteResources(selection.indexes());
}
void ExternalResourcesPage::enableItem()
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setResourceEnabled(selection.indexes(), EnableAction::ENABLE);
}
void ExternalResourcesPage::disableItem()
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE);
}
void ExternalResourcesPage::viewConfigs()
{
DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true);

View File

@ -45,15 +45,15 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
protected slots:
virtual void itemActivated(const QModelIndex& index) {};
void itemActivated(const QModelIndex& index);
void filterTextChanged(const QString& newContents);
virtual void runningStateChanged(bool running);
virtual void addItem();
virtual void removeItem();
virtual void enableItem() {};
virtual void disableItem() {};
virtual void enableItem();
virtual void disableItem();
virtual void viewFolder();
virtual void viewConfigs();

View File

@ -110,8 +110,6 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());
}
connect(ui->treeView, &ModListView::activated, this, &ModFolderPage::itemActivated);
}
void ModFolderPage::runningStateChanged(bool running)
@ -126,33 +124,6 @@ bool ModFolderPage::shouldDisplay() const
return true;
}
void ModFolderPage::itemActivated(const QModelIndex&)
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle);
}
void ModFolderPage::enableItem()
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setModStatus(selection.indexes(), ModFolderModel::Enable);
}
void ModFolderPage::disableItem()
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setModStatus(selection.indexes(), ModFolderModel::Disable);
}
bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);

View File

@ -58,11 +58,6 @@ class ModFolderPage : public ExternalResourcesPage {
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void itemActivated(const QModelIndex& index) override;
void enableItem() override;
void disableItem() override;
private slots:
void installMods();
void updateMods();