From d0c482eaefa6a8ca1e8bad049df0f4a8c80c8b7c Mon Sep 17 00:00:00 2001 From: Hari Rana Date: Wed, 22 Jan 2025 22:52:51 -0500 Subject: [PATCH] backend: Remove DependencyManager --- bottles/backend/managers/dependency.py | 508 ------------------------- bottles/backend/managers/manager.py | 49 --- bottles/backend/managers/meson.build | 1 - 3 files changed, 558 deletions(-) delete mode 100644 bottles/backend/managers/dependency.py diff --git a/bottles/backend/managers/dependency.py b/bottles/backend/managers/dependency.py deleted file mode 100644 index 73b9b19e9d..0000000000 --- a/bottles/backend/managers/dependency.py +++ /dev/null @@ -1,508 +0,0 @@ -# dependency.py -# -# Copyright 2022 brombinmirko -# -# 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, in version 3 of the License. -# -# 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 . -# - -import os -import shutil -from functools import lru_cache -from glob import glob - -import patoolib # type: ignore [import-untyped] - -from bottles.backend.cabextract import CabExtract -from bottles.backend.globals import Paths -from bottles.backend.logger import Logger -from bottles.backend.models.config import BottleConfig -from bottles.backend.models.enum import Arch -from bottles.backend.models.result import Result -from bottles.backend.state import TaskManager, Task -from bottles.backend.utils.manager import ManagerUtils -from bottles.backend.wine.executor import WineExecutor -from bottles.backend.wine.reg import Reg, RegItem -from bottles.backend.wine.regkeys import RegKeys -from bottles.backend.wine.regsvr32 import Regsvr32 -from bottles.backend.wine.uninstaller import Uninstaller -from bottles.backend.wine.winedbg import WineDbg -from bottles.backend.repos.dependency import DependencyRepo - -logging = Logger() - - -class DependencyManager: - def __init__(self, manager): - self.__manager = manager - - url = "https://proxy.usebottles.com/repo/dependencies/" - self.__repo = DependencyRepo(url, "") - - @lru_cache - def get_dependency(self, name: str, plain: bool = False) -> str | dict | bool: - return self.__repo.get(name, plain) - - @lru_cache - def fetch_catalog(self) -> dict: - """ - Fetch all dependencies from the Bottles repository - and return these as a dictionary. It also returns an empty dictionary - if there are no dependencies or fails to fetch them. - """ - - catalog = {} - index = self.__repo.catalog - - for dependency in index.items(): - catalog[dependency[0]] = dependency[1] - - catalog = dict(sorted(catalog.items())) - return catalog - - def install(self, config: BottleConfig, dependency: list) -> Result: - """ - Install a given dependency in a bottle. It will - return True if the installation was successful. - """ - uninstaller = True - - task_id = TaskManager.add(Task(title=dependency[0])) - - logging.info( - "Installing dependency [{}] in bottle [{}].".format( - dependency[0], config.Name - ), - ) - manifest = self.get_dependency(dependency[0]) - if not manifest: - """ - If the manifest is not found, return a Result - object with the error. - """ - TaskManager.remove(task_id) - return Result( - status=False, message=f"Cannot find manifest for {dependency[0]}." - ) - - if manifest.get("Dependencies"): - """ - If the manifest has dependencies, we need to install them - before installing the current one. - """ - for _ext_dep in manifest.get("Dependencies"): - if _ext_dep in config.Installed_Dependencies: - continue - if _ext_dep in self.__manager.supported_dependencies: - _dep = self.__manager.supported_dependencies[_ext_dep] - _res = self.install(config, [_ext_dep, _dep]) - if not _res.status: - return _res - - for step in manifest.get("Steps"): - """ - Here we execute all steps in the manifest. - Steps are the actions performed to install the dependency. - """ - arch = step.get("for", "win64_win32") - if config.Arch not in arch: - continue - - res = self.__perform_steps(config, step) - if not res.ok: - TaskManager.remove(task_id) - return Result( - status=False, - message=f"One or more steps failed for {dependency[0]}.", - ) - if not res.data.get("uninstaller"): - uninstaller = False - - if dependency[0] not in config.Installed_Dependencies: - """ - If the dependency is not already listed in the installed - dependencies list of the bottle, add it. - """ - dependencies = [dependency[0]] - - if config.Installed_Dependencies: - dependencies = config.Installed_Dependencies + [dependency[0]] - - self.__manager.update_config( - config=config, key="Installed_Dependencies", value=dependencies - ) - - if manifest.get("Uninstaller"): - """ - If the manifest has an uninstaller, add it to the - uninstaller list in the bottle config. - Set it to NO_UNINSTALLER if the dependency cannot be uninstalled. - """ - uninstaller = manifest.get("Uninstaller") - - if dependency[0] not in config.Installed_Dependencies: - self.__manager.update_config( - config, dependency[0], uninstaller, "Uninstallers" - ) - - # Remove entry from task manager - TaskManager.remove(task_id) - - # Hide installation button and show remove button - logging.info(f"Dependency installed: {dependency[0]} in {config.Name}", jn=True) - if not uninstaller: - return Result(status=True, data={"uninstaller": False}) - return Result(status=True, data={"uninstaller": True}) - - def __perform_steps(self, config: BottleConfig, step: dict) -> Result: - """ - This method execute a step in the bottle (e.g. changing the Windows - version, installing fonts, etc.) - --- - Returns True if the dependency cannot be uninstalled. - """ - uninstaller = True - - if step["action"] == "delete_dlls": - self.__step_delete_dlls(config, step) - - if step["action"] == "uninstall": - self.__step_uninstall(config=config, file_name=step["file_name"]) - - if step["action"] == "cab_extract": - uninstaller = False - if not self.__step_cab_extract(step=step): - return Result(status=False) - - if step["action"] == "get_from_cab": - uninstaller = False - if not self.__step_get_from_cab(config=config, step=step): - return Result(status=False) - - if step["action"] in ["install_cab_fonts", "install_fonts"]: - uninstaller = False - if not self.__step_install_fonts(config=config, step=step): - return Result(status=False) - - if step["action"] in ["copy_dll", "copy_file"]: - uninstaller = False - if not self.__step_copy_dll(config=config, step=step): - return Result(status=False) - - if step["action"] == "register_dll": - self.__step_register_dll(config=config, step=step) - - if step["action"] == "override_dll": - self.__step_override_dll(config=config, step=step) - - if step["action"] == "set_register_key": - self.__step_set_register_key(config=config, step=step) - - if step["action"] == "register_font": - self.__step_register_font(config=config, step=step) - - if step["action"] == "replace_font": - self.__step_replace_font(config=config, step=step) - - if step["action"] == "set_windows": - self.__step_set_windows(config=config, step=step) - - if step["action"] == "use_windows": - self.__step_use_windows(config=config, step=step) - - return Result(status=True, data={"uninstaller": uninstaller}) - - @staticmethod - def __get_real_dest(config: BottleConfig, dest: str) -> str | bool: - """This function return the real destination path.""" - bottle = ManagerUtils.get_bottle_path(config) - _dest = dest - - if dest.startswith("temp/"): - dest = dest.replace("temp/", f"{Paths.temp}/") - elif dest.startswith("windows/"): - dest = f"{bottle}/drive_c/{dest}" - elif dest.startswith("win32"): - dest = f"{bottle}/drive_c/windows/system32/" - if config.Arch == Arch.WIN64: - dest = f"{bottle}/drive_c/windows/syswow64/" - dest = _dest.replace("win32", dest) - elif dest.startswith("win64"): - if config.Arch == Arch.WIN64: - dest = f"{bottle}/drive_c/windows/system32/" - dest = _dest.replace("win64", dest) - else: - return True - else: - logging.error("Destination path not supported!") - return False - - return dest - - @staticmethod - def __step_uninstall(config: BottleConfig, file_name: str) -> bool: - """ - This function find an uninstaller in the bottle by the given - file name and execute it. - """ - Uninstaller(config).from_name(file_name) - return True - - def __step_cab_extract(self, step: dict): - """ - This function download and extract a Windows Cabinet to the - temp folder. - """ - dest = step.get("dest") - if dest.startswith("temp/"): - dest = dest.replace("temp/", f"{Paths.temp}/") - else: - logging.error("Destination path not supported!") - return False - - if step["url"].startswith("temp/"): - path = step["url"] - path = path.replace("temp/", f"{Paths.temp}/") - - if step.get("rename"): - file_path = os.path.splitext(f"{step.get('rename')}")[0] - else: - file_path = os.path.splitext(f"{step.get('file_name')}")[0] - - if not CabExtract().run( - f"{path}/{step.get('file_name')}", file_path, destination=dest - ): - return False - - return True - - def __step_delete_dlls(self, config: BottleConfig, step: dict): - """Deletes the given dlls from the system32 or syswow64 paths""" - dest = self.__get_real_dest(config, step.get("dest")) - - for d in step.get("dlls", []): - _d = os.path.join(dest, d) - if os.path.exists(_d): - os.remove(_d) - - return True - - def __step_get_from_cab(self, config: BottleConfig, step: dict): - """Take a file from a cabiner and extract to a path.""" - source = step.get("source") - file_name = step.get("file_name") - rename = step.get("rename") - dest = self.__get_real_dest(config, step.get("dest")) - - if isinstance(dest, bool): - return dest - - res = CabExtract().run( - path=os.path.join(Paths.temp, source), files=[file_name], destination=dest - ) - - if rename: - _file_name = file_name.split("/")[-1] - - if os.path.exists(os.path.join(dest, rename)): - os.remove(os.path.join(dest, rename)) - - shutil.move(os.path.join(dest, _file_name), os.path.join(dest, rename)) - - if not res: - return False - return True - - @staticmethod - def __step_install_fonts(config: BottleConfig, step: dict): - """Move fonts to the drive_c/windows/Fonts path.""" - path = step["url"] - path = path.replace("temp/", f"{Paths.temp}/") - bottle_path = ManagerUtils.get_bottle_path(config) - - for font in step.get("fonts"): - font_path = f"{bottle_path}/drive_c/windows/Fonts/" - if not os.path.exists(font_path): - os.makedirs(font_path) - - try: - shutil.copyfile(f"{path}/{font}", f"{font_path}/{font}") - except (FileNotFoundError, FileExistsError): - logging.warning(f"Font {font} already exists or is not found.") - - # print(f"Copying {font} to {bottle_path}/drive_c/windows/Fonts/") - - return True - - # noinspection PyTypeChecker - def __step_copy_dll(self, config: BottleConfig, step: dict): - """ - This function copy dlls from temp folder to a directory - declared in the step. The bottle drive_c path will be used as - root path. - """ - path = step["url"] - path = path.replace("temp/", f"{Paths.temp}/") - dest = self.__get_real_dest(config, step.get("dest")) - - if isinstance(dest, bool): - return dest - - if not os.path.exists(dest): - os.makedirs(dest) - - try: - if "*" in step.get("file_name"): - files = glob(f"{path}/{step.get('file_name')}") - if not files: - logging.info(f"File(s) not found in {path}") - return False - for fg in files: - _name = fg.split("/")[-1] - _path = os.path.join(path, _name) - _dest = os.path.join(dest, _name) - logging.info(f"Copying {_name} to {_dest}") - - if os.path.exists(_dest) and os.path.islink(_dest): - os.unlink(_dest) - - try: - shutil.copyfile(_path, _dest) - except shutil.SameFileError: - logging.info( - f"{_name} already exists at the same version, skipping." - ) - else: - _name = step.get("file_name") - _dest = os.path.join(dest, _name) - logging.info(f"Copying {_name} to {_dest}") - - if os.path.exists(_dest) and os.path.islink(_dest): - os.unlink(_dest) - - try: - shutil.copyfile(os.path.join(path, _name), _dest) - except shutil.SameFileError: - logging.info( - f"{_name} already exists at the same version, skipping." - ) - - except Exception as e: - print(e) - logging.warning("An error occurred while copying dlls.") - return False - - return True - - @staticmethod - def __step_register_dll(config: BottleConfig, step: dict): - """Register one or more dll and ActiveX control""" - regsvr32 = Regsvr32(config) - - for dll in step.get("dlls", []): - regsvr32.register(dll) - - return True - - @staticmethod - def __step_override_dll(config: BottleConfig, step: dict): - """Register a new override for each dll.""" - reg = Reg(config) - - if step.get("url") and step.get("url").startswith("temp/"): - path = step["url"].replace("temp/", f"{Paths.temp}/") - dlls = glob(os.path.join(path, step.get("dll"))) - - bundle = {"HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": []} - - for dll in dlls: - dll_name = os.path.splitext(os.path.basename(dll))[0] - bundle["HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides"].append( - {"value": dll_name, "data": step.get("type")} - ) - - reg.import_bundle(bundle) - return True - - if step.get("bundle"): - _bundle = { - "HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides": step.get("bundle") - } - reg.import_bundle(_bundle) - return True - - reg.add( - key="HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides", - value=step.get("dll"), - data=step.get("type"), - ) - return True - - @staticmethod - def __step_set_register_key(config: BottleConfig, step: dict): - """Set a registry key.""" - reg = Reg(config) - reg.add( - key=step.get("key"), - value=step.get("value"), - data=step.get("data"), - value_type=step.get("type"), - ) - return True - - @staticmethod - def __step_register_font(config: BottleConfig, step: dict): - """Register a font in the registry.""" - reg = Reg(config) - reg.add( - key="HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", - value=step.get("name"), - data=step.get("file"), - ) - return True - - @staticmethod - def __step_replace_font(config: BottleConfig, step: dict): - """Register a font replacement in the registry.""" - reg = Reg(config) - target_font = step.get("font") - replaces = step.get("replace") - - if not isinstance(replaces, list): - logging.warning("Invalid replace_font, 'replace' field should be list.") - return False - - regs = [ - RegItem( - key="HKEY_CURRENT_USER\\Software\\Wine\\Fonts\\Replacements", - value=r, - value_type="", - data=target_font, - ) - for r in replaces - ] - reg.bulk_add(regs) - return True - - @staticmethod - def __step_set_windows(config: BottleConfig, step: dict): - """Set the Windows version.""" - rk = RegKeys(config) - rk.lg_set_windows(step.get("version")) - return True - - @staticmethod - def __step_use_windows(config: BottleConfig, step: dict): - """Set a Windows version per program.""" - rk = RegKeys(config) - rk.set_app_default(step.get("version"), step.get("executable")) - return True diff --git a/bottles/backend/managers/manager.py b/bottles/backend/managers/manager.py index 174c2b3336..7ac90047ba 100644 --- a/bottles/backend/managers/manager.py +++ b/bottles/backend/managers/manager.py @@ -36,7 +36,6 @@ from bottles.backend.dlls.vkd3d import VKD3DComponent from bottles.backend.globals import Paths from bottles.backend.logger import Logger -from bottles.backend.managers.dependency import DependencyManager from bottles.backend.managers.epicgamesstore import EpicGamesStoreManager from bottles.backend.managers.importer import ImportManager from bottles.backend.managers.library import LibraryManager @@ -110,7 +109,6 @@ def __init__( _offline = False # sub-managers - self.dependency_manager = DependencyManager(self) self.import_manager = ImportManager(self) times["ImportManager"] = time.time() @@ -157,8 +155,6 @@ def checks(self, install_latest=False, first_run=False) -> Result: self.check_runners(install_latest) or rv.set_status(False) rv.data["check_runners"] = time.time() - self.organize_dependencies() - self.check_bottles() rv.data["check_bottles"] = time.time() @@ -176,19 +172,6 @@ def check_app_dirs(self): """ map(lambda path: os.makedirs(path, exist_ok=True), Paths.get_components_paths()) - @RunAsync.run_async - def organize_dependencies(self): - """Organizes dependencies into supported_dependencies.""" - EventManager.wait(Events.DependenciesFetching) - catalog = self.dependency_manager.fetch_catalog() - if len(catalog) == 0: - EventManager.done(Events.DependenciesOrganizing) - logging.info("No dependencies found!") - return - - self.supported_dependencies = catalog - EventManager.done(Events.DependenciesOrganizing) - def remove_dependency(self, config: BottleConfig, dependency: list): """Uninstall a dependency and remove it from the bottle config.""" dependency = dependency[0] @@ -893,19 +876,6 @@ def create_bottle_from_config(self, config: BottleConfig) -> bool: """ self.install_dll_component(config, "vkd3d") - for dependency in config.Installed_Dependencies: - """ - Install each declared dependency in the new bottle. - """ - if dependency in self.supported_dependencies.keys(): - dep = [dependency, self.supported_dependencies[dependency]] - res = self.dependency_manager.install(config, dep) - if not res.ok: - logging.error( - _("Failed to install dependency: %s") % dependency, - jn=True, - ) - return False logging.info(f"New bottle from config created: {config.Path}") self.update_bottles(silent=True) return True @@ -1231,25 +1201,6 @@ def components_check(): for dep in env.get("Installed_Dependencies", []): if template and dep in template["config"]["Installed_Dependencies"]: continue - if dep in self.supported_dependencies: - _dep = self.supported_dependencies[dep] - log_update( - _("Installing dependency: %s …") - % _dep.get("Description", "n/a") - ) - res = self.dependency_manager.install(config, [dep, _dep]) - if not res.ok: - logging.error( - _("Failed to install dependency: %s") - % _dep.get("Description", "n/a"), - jn=True, - ) - log_update( - _("Failed to install dependency: %s") - % _dep.get("Description", "n/a") - ) - return Result(False) - template_updated = True # save bottle config config.dump(f"{bottle_complete_path}/bottle.yml") diff --git a/bottles/backend/managers/meson.build b/bottles/backend/managers/meson.build index 7fd361cece..0da12ec253 100644 --- a/bottles/backend/managers/meson.build +++ b/bottles/backend/managers/meson.build @@ -4,7 +4,6 @@ managersdir = join_paths(pkgdatadir, 'bottles/backend/managers') bottles_sources = [ '__init__.py', 'backup.py', - 'dependency.py', 'library.py', 'manager.py', 'runtime.py',