Skip to content

Commit

Permalink
Don't allow working on invisible components
Browse files Browse the repository at this point in the history
Disallow the following operations for mods that are currently not shown
due to a "find" command hiding them:

- Rename mod/download
- Move mod/plugin
- de/activate mod/plugin
- delete mod/download/plugin
- configure

This should help prevent accidental changes.
  • Loading branch information
cyberrumor committed Mar 29, 2024
1 parent 7eee87e commit 43b29c6
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 16 deletions.
68 changes: 52 additions & 16 deletions ammo/mod_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@ def _set_component_state(self, component: ComponentEnum, index: int, state: bool
components = self._get_validated_components(component)
subject = components[index]

if not subject.visible:
raise Warning("You can only de/activate visible components.")

starting_state = subject.enabled
# Handle mods
if isinstance(subject, Mod):
Expand Down Expand Up @@ -520,6 +523,9 @@ def configure(self, index: int) -> None:
# game.directory.

mod = self.mods[index]

if not mod.visible:
raise Warning("You can only configure visible mods.")
if not mod.fomod:
raise Warning("Only fomods can be configured.")

Expand Down Expand Up @@ -649,6 +655,9 @@ def rename(self, component: RenameEnum, index: int, name: str) -> None:
except IndexError as e:
raise Warning(e)

if not download.visible:
raise Warning("You caon only rename visible components.")

if "pytest" not in sys.modules:
# Don't run this during tests because it's slow.
try:
Expand Down Expand Up @@ -676,6 +685,9 @@ def rename(self, component: RenameEnum, index: int, name: str) -> None:
except IndexError as e:
raise Warning(e)

if not mod.visible:
raise Warning("You can only rename visible components.")

new_location = self.game.ammo_mods_dir / name
if new_location.exists():
raise Warning(f"A mod named {str(new_location)} already exists!")
Expand Down Expand Up @@ -719,10 +731,8 @@ def delete(self, component: DeleteEnum, index: Union[int, str]) -> None:
for mod in visible_mods:
if mod.enabled:
raise Warning(
"You must deactivate all visible components of that type before deleting them with all."
"You can only delete all visible components if they are all deactivated."
)
for mod in visible_mods:
self.deactivate(ComponentEnum.MOD, self.mods.index(mod))
for mod in visible_mods:
self.mods.pop(self.mods.index(mod))
try:
Expand All @@ -733,14 +743,17 @@ def delete(self, component: DeleteEnum, index: Union[int, str]) -> None:
self.commit()
else:
try:
self.deactivate(ComponentEnum.MOD, index)
mod = self.mods[index]

except IndexError as e:
# Demote IndexErrors
raise Warning(e)

if not mod.visible:
raise Warning("You can only delete visible components.")

# Remove the mod from the controller then delete it.
mod = self.mods.pop(index)
self.mods.pop(index)
try:
shutil.rmtree(mod.location)
except FileNotFoundError:
Expand Down Expand Up @@ -775,7 +788,7 @@ def get_plugin_files(plugin):
for plugin in visible_plugins:
if plugin.enabled:
raise Warning(
"You must deactivate all visible components of that type before deleting them with all."
"You can only delete all visible components if they are all deactivated."
)

for plugin in visible_plugins:
Expand All @@ -795,14 +808,19 @@ def get_plugin_files(plugin):
self.commit()
else:
try:
plugin = self.plugins.pop(index)
for file in get_plugin_files(plugin):
try:
file.unlink()
except FileNotFoundError:
pass
plugin = self.plugins[index]
except IndexError as e:
# Demote IndexErrors
raise Warning(e)
if not plugin.visible:
raise Warning("You can only delete visible components.")

self.plugins.remove(plugin)
for file in get_plugin_files(plugin):
try:
file.unlink()
except FileNotFoundError:
pass

self.refresh()
self.commit()
Expand All @@ -821,10 +839,16 @@ def get_plugin_files(plugin):
else:
index = int(index)
try:
download = self.downloads.pop(index)
download = self.downloads[index]
except IndexError as e:
# Demote IndexErrors
raise Warning(e)

if not download.visible:
raise Warning("You can only delete visible components")

self.downloads.remove(download)

try:
download.location.unlink()
except FileNotFoundError:
Expand Down Expand Up @@ -916,6 +940,9 @@ def install_download(index, download) -> None:
# Demote IndexErrors
raise Warning(e)

if not download.visible:
raise Warning("You can only install visible downloads.")

install_download(index, download)

self.refresh()
Expand All @@ -931,14 +958,23 @@ def move(self, component: ComponentEnum, index: int, new_index: int) -> None:
components = self._get_validated_components(component)
# Since this operation it not atomic, validation must be performed
# before anything is attempted to ensure nothing can become mangled.
try:
comp = components[index]
except IndexError as e:
# Demote IndexErrors
raise Warning(e)

if not comp.visible:
raise Warning("You can only move visible components.")

if index == new_index:
return

if new_index > len(components) - 1:
# Auto correct astronomical <to index> to max.
new_index = len(components) - 1
if index > len(components) - 1:
raise Warning("Index out of range.")
comp = components.pop(index)

components.pop(index)
components.insert(new_index, comp)
self._stage()
self.changes = True
Expand Down
103 changes: 103 additions & 0 deletions test/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ammo.component import (
ComponentEnum,
DeleteEnum,
RenameEnum,
)
from common import (
AmmoController,
Expand Down Expand Up @@ -224,3 +225,105 @@ def test_no_install_twice():

with pytest.raises(Warning):
install_mod(controller, "normal_mod")


def test_invisible_install():
"""
Don't allow installing hidden downloads.
"""
with AmmoController() as controller:
controller.find("nothing")

with pytest.raises(Warning):
controller.install(0)


def test_invisible_delete_mod():
"""
Don't allow deleting hidden mods.
"""
with AmmoController() as controller:
extract_mod(controller, "normal_mod")

controller.find("nothing")

with pytest.raises(Warning):
controller.delete(DeleteEnum.MOD, 0)


def test_invisible_delete_plugin():
"""
Don't allow deleting hidden plugins.
"""
with AmmoController() as controller:
install_mod(controller, "normal_mod")

controller.find("nothing")

with pytest.raises(Warning):
controller.delete(DeleteEnum.PLUGIN, 0)


def test_invisible_delete_download():
"""
Don't allow deleting hidden downloads.
"""
with AmmoController() as controller:
controller.find("nothing")

with pytest.raises(Warning):
controller.delete(DeleteEnum.DOWNLOAD, 0)


def test_invisible_move_mod():
"""
Don't allow moving hidden mods.
"""
with AmmoController() as controller:
install_mod(controller, "conflict_1")
install_mod(controller, "conflict_2")

controller.find("nothing")

with pytest.raises(Warning):
controller.move(ComponentEnum.MOD, 0, 1)


def test_invisible_move_plugin():
"""
Don't allow moving hidden plugins.
"""
with AmmoController() as controller:
install_mod(controller, "normal_mod")
install_mod(controller, "conflict_1")

controller.find("nothing")

with pytest.raises(Warning):
controller.move(ComponentEnum.PLUGIN, 0, 1)


def test_invisible_rename_mod():
"""
Don't allow renaming hidden mods.
"""
with AmmoController() as controller:
extract_mod(controller, "normal_mod")

controller.find("nothing")

with pytest.raises(Warning):
controller.rename(RenameEnum.MOD, 0, "new_name")


def test_invisible_configure():
"""
Don't allow configuring invisible mods.
"""
with AmmoController() as controller:
extract_mod(controller, "mock_relighting_skyrim")

controller.find("nothing")

with pytest.raises(Warning):
controller.configure(0)

0 comments on commit 43b29c6

Please sign in to comment.