[cli/core/utils] Only remove files in manifest during uninstall

Some games are using the installation directory to store savegames.
To avoid deleting those, only remove files that are actually in the
manifest and only delete the directory if it is empty.
This commit is contained in:
derrod 2020-06-10 18:21:47 +02:00
parent 2647fa4915
commit 0a5b53ab6f
3 changed files with 60 additions and 12 deletions

View file

@ -697,17 +697,15 @@ class LegendaryCLI:
exit(0)
try:
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
self.core.uninstall_game(igame)
# DLCs are already removed once we delete the main game, so this just removes them from the list
# Remove DLC first so directory is empty when game uninstall runs
dlcs = self.core.get_dlc_for_game(igame.app_name)
for dlc in dlcs:
idlc = self.core.get_installed_game(dlc.app_name)
if self.core.is_installed(dlc.app_name):
if (idlc := self.core.get_installed_game(dlc.app_name)) is not None:
logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
self.core.uninstall_game(idlc, delete_files=False)
self.core.uninstall_game(idlc)
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
self.core.uninstall_game(igame, delete_root_directory=True)
logger.info('Game has been uninstalled.')
except Exception as e:
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')

View file

@ -21,7 +21,7 @@ from legendary.api.egs import EPCAPI
from legendary.downloader.manager import DLManager
from legendary.lfs.egl import EPCLFS
from legendary.lfs.lgndry import LGDLFS
from legendary.utils.lfs import clean_filename, delete_folder
from legendary.utils.lfs import clean_filename, delete_folder, delete_filelist
from legendary.models.downloading import AnalysisResult, ConditionCheckResult
from legendary.models.egl import EGLManifest
from legendary.models.exceptions import *
@ -828,14 +828,20 @@ class LegendaryCore:
return dict()
def uninstall_game(self, installed_game: InstalledGame, delete_files=True):
self.lgd.remove_installed_game(installed_game.app_name)
def uninstall_game(self, installed_game: InstalledGame, delete_files=True, delete_root_directory=False):
if installed_game.egl_guid:
self.egl_uninstall(installed_game, delete_files=delete_files)
if delete_files:
if not delete_folder(installed_game.install_path, recursive=True):
self.log.error(f'Unable to delete "{installed_game.install_path}" from disk, please remove manually.')
try:
manifest = self.load_manifest(self.get_installed_manifest(installed_game.app_name)[0])
filelist = [fm.filename for fm in manifest.file_manifest_list.elements]
if not delete_filelist(installed_game.install_path, filelist, delete_root_directory):
self.log.error(f'Deleting "{installed_game.install_path}" failed, please remove manually.')
except Exception as e:
self.log.error(f'Deleting failed with {e!r}, please remove {installed_game.install_path} manually.')
self.lgd.remove_installed_game(installed_game.app_name)
def prereq_installed(self, app_name):
igame = self.lgd.get_installed_game(app_name)

View file

@ -26,6 +26,50 @@ def delete_folder(path: str, recursive=True) -> bool:
return True
def delete_filelist(path: str, filenames: List[str],
delete_root_directory: bool = False) -> bool:
dirs = set()
no_error = True
# delete all files that were installed
for filename in filenames:
_dir, _fn = os.path.split(filename)
if _dir:
dirs.add(_dir)
try:
os.remove(os.path.join(path, _dir, _fn))
except Exception as e:
logger.error(f'Failed deleting file {filename} with {e!r}')
no_error = False
# add intermediate directories that would have been missed otherwise
for _dir in sorted(dirs):
head, _ = os.path.split(_dir)
while head:
dirs.add(head)
head, _ = os.path.split(head)
# remove all directories
for _dir in sorted(dirs, key=len, reverse=True):
try:
os.rmdir(os.path.join(path, _dir))
except FileNotFoundError:
# directory has already been deleted, ignore that
continue
except Exception as e:
logger.error(f'Failed removing directory "{_dir}" with {e!r}')
no_error = False
if delete_root_directory:
try:
os.rmdir(path)
except Exception as e:
logger.error(f'Removing game directory failed with {e!r}')
return no_error
def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1') -> Iterator[tuple]:
"""
Validates the files in filelist in path against the provided hashes