diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-08-16 19:33:47 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-08-16 19:33:47 -0400 |
| commit | 1baac4d3bb0a036e4cc19319ca881ca39ce09250 (patch) | |
| tree | 89f79dc0f9f8a71b4c26f05e9ee481b63f99b7a7 | |
| parent | b01f9510fa5c0885c320d5231a163cb70c1df515 (diff) | |
| download | decky-lsfg-vk-1baac4d3bb0a036e4cc19319ca881ca39ce09250.tar.gz decky-lsfg-vk-1baac4d3bb0a036e4cc19319ca881ca39ce09250.zip | |
add conf preservation logic, edge case handling if we add params down the line
| -rw-r--r-- | py_modules/lsfg_vk/config_schema.py | 3 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/installation.py | 149 | ||||
| -rw-r--r-- | src/components/ConfigurationSection.tsx | 2 |
3 files changed, 127 insertions, 27 deletions
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 8027bc1..66aeb69 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -205,7 +205,8 @@ class ConfigurationManager: if dll_path: lines.append(f"# specify where Lossless.dll is stored") lines.append(f'dll = "{dll_path}"') - + lines.append("") + # Add no_fp16 field no_fp16 = profile_data["global_config"].get("no_fp16", False) lines.append(f"# force-disable fp16 (use on older nvidia cards)") diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index b0e05ad..04ffb33 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -141,25 +141,55 @@ class InstallationService(BaseService): shutil.copy2(src_file, dst_file) def _create_config_file(self) -> None: - """Create the TOML config file in ~/.config/lsfg-vk with default configuration and detected DLL path""" + """Create or update the TOML config file in ~/.config/lsfg-vk with default configuration and detected DLL path + + If a config file already exists, preserve existing profiles and only update global settings like DLL path. + """ # Import here to avoid circular imports from .dll_detection import DllDetectionService # Try to detect DLL path dll_service = DllDetectionService(self.log) - config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) - # Generate TOML content using centralized manager - toml_content = ConfigurationManager.generate_toml_content(config) + # Check if config file already exists + if self.config_file_path.exists(): + try: + # Read existing config to preserve user profiles + content = self.config_file_path.read_text(encoding='utf-8') + existing_profile_data = ConfigurationManager.parse_toml_content_multi_profile(content) + self.log.info(f"Found existing config file, preserving user profiles") + + # Create merged profile data that preserves user settings but adds any new fields + merged_profile_data = self._merge_config_with_defaults(existing_profile_data, dll_service) + + # Generate TOML content with merged profiles + toml_content = ConfigurationManager.generate_toml_content_multi_profile(merged_profile_data) + + except Exception as e: + self.log.warning(f"Failed to parse existing config file: {str(e)}, creating new one") + # Fall back to creating a new config file + config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + toml_content = ConfigurationManager.generate_toml_content(config) + else: + # No existing config file, create a new one with defaults + config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + toml_content = ConfigurationManager.generate_toml_content(config) + self.log.info(f"Creating new config file") - # Write initial config file + # Write config file self._write_file(self.config_file_path, toml_content, 0o644) self.log.info(f"Created config file at {self.config_file_path}") # Log detected DLL path if found - USE GENERATED CONSTANTS from .config_schema_generated import DLL - if config[DLL]: - self.log.info(f"Configured DLL path: {config[DLL]}") + try: + # Try to parse the written content to get the DLL path + final_content = self.config_file_path.read_text(encoding='utf-8') + final_config = ConfigurationManager.parse_toml_content(final_content) + if final_config.get(DLL): + self.log.info(f"Configured DLL path: {final_config[DLL]}") + except Exception: + pass # Don't fail installation if we can't log the DLL path def _create_lsfg_launch_script(self) -> None: """Create the ~/lsfg launch script for easier game setup""" @@ -221,12 +251,15 @@ class InstallationService(BaseService): def uninstall(self) -> UninstallationResponse: """Uninstall lsfg-vk by removing the installed files + Note: The config file (conf.toml) is preserved to maintain user's custom profiles + Returns: UninstallationResponse with success status and removed files list """ try: removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_launch_script_path] + # Remove core lsfg-vk files, but preserve config file to maintain user's custom profiles + files_to_remove = [self.lib_file, self.json_file, self.lsfg_launch_script_path] for file_path in files_to_remove: if self._remove_if_exists(file_path): @@ -236,13 +269,7 @@ class InstallationService(BaseService): if self._remove_if_exists(self.lsfg_script_path): removed_files.append(str(self.lsfg_script_path)) - # Remove config directory if it's empty - try: - if self.config_dir.exists() and not any(self.config_dir.iterdir()): - self.config_dir.rmdir() - removed_files.append(str(self.config_dir)) - except OSError: - pass # Directory not empty or other error, ignore + # Don't remove config directory since we're preserving the config file if not removed_files: return self._success_response(UninstallationResponse, @@ -261,17 +288,21 @@ class InstallationService(BaseService): message="", removed_files=None) def cleanup_on_uninstall(self) -> None: - """Clean up lsfg-vk files when the plugin is uninstalled""" + """Clean up lsfg-vk files when the plugin is uninstalled + + Note: The config file (conf.toml) is preserved to maintain user's custom profiles + """ try: self.log.info("Checking for lsfg-vk files to clean up:") self.log.info(f" Library file: {self.lib_file}") self.log.info(f" JSON file: {self.json_file}") - self.log.info(f" Config file: {self.config_file_path}") + self.log.info(f" Config file: {self.config_file_path} (preserved)") self.log.info(f" Launch script: {self.lsfg_launch_script_path}") self.log.info(f" Old script file: {self.lsfg_script_path}") removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_launch_script_path, self.lsfg_script_path] + # Remove core lsfg-vk files, but preserve config file to maintain user's custom profiles + files_to_remove = [self.lib_file, self.json_file, self.lsfg_launch_script_path, self.lsfg_script_path] for file_path in files_to_remove: try: @@ -280,13 +311,7 @@ class InstallationService(BaseService): except OSError as e: self.log.error(f"Failed to remove {file_path}: {e}") - # Try to remove config directory if empty - try: - if self.config_dir.exists() and not any(self.config_dir.iterdir()): - self.config_dir.rmdir() - removed_files.append(str(self.config_dir)) - except OSError: - pass # Directory not empty or other error, ignore + # Don't remove config directory since we're preserving the config file if removed_files: self.log.info(f"Cleaned up {len(removed_files)} lsfg-vk files during plugin uninstall: {removed_files}") @@ -297,3 +322,77 @@ class InstallationService(BaseService): self.log.error(f"Error cleaning up lsfg-vk files during uninstall: {str(e)}") import traceback self.log.error(f"Traceback: {traceback.format_exc()}") + + def _merge_config_with_defaults(self, existing_profile_data, dll_service): + """Merge existing user config with current schema defaults + + This ensures that: + 1. User's custom profiles and values are preserved + 2. Any new fields added to the schema get their default values + 3. Global settings like DLL path are updated as needed + + Args: + existing_profile_data: The user's existing ProfileData + dll_service: DLL detection service for updating DLL path + + Returns: + ProfileData with merged configuration + """ + from .config_schema import ProfileData + + # Get current schema defaults + default_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + default_global_config = { + "dll": default_config.get("dll", ""), + "no_fp16": default_config.get("no_fp16", False) + } + + # Start with existing data + merged_data: ProfileData = { + "current_profile": existing_profile_data.get("current_profile", "decky-lsfg-vk"), + "global_config": existing_profile_data.get("global_config", {}).copy(), + "profiles": {} + } + + # Merge global config: preserve user values, add missing fields, update DLL + for key, default_value in default_global_config.items(): + if key not in merged_data["global_config"]: + merged_data["global_config"][key] = default_value + self.log.info(f"Added missing global field '{key}' with default value: {default_value}") + + # Update DLL path if detected + dll_result = dll_service.check_lossless_scaling_dll() + if dll_result.get("detected") and dll_result.get("path"): + old_dll = merged_data["global_config"].get("dll") + merged_data["global_config"]["dll"] = dll_result["path"] + if old_dll != dll_result["path"]: + self.log.info(f"Updated DLL path from '{old_dll}' to: {dll_result['path']}") + + # Merge each profile: preserve user values, add missing fields + existing_profiles = existing_profile_data.get("profiles", {}) + + for profile_name, existing_profile_config in existing_profiles.items(): + merged_profile_config = existing_profile_config.copy() + + # Add any missing fields from current schema with default values + added_fields = [] + for key, default_value in default_config.items(): + if key not in merged_profile_config and key not in ["dll", "no_fp16"]: # Skip global fields + merged_profile_config[key] = default_value + added_fields.append(key) + + if added_fields: + self.log.info(f"Profile '{profile_name}': Added missing fields {added_fields}") + + merged_data["profiles"][profile_name] = merged_profile_config + + # If no profiles exist, create the default one + if not merged_data["profiles"]: + merged_data["profiles"]["decky-lsfg-vk"] = { + k: v for k, v in default_config.items() + if k not in ["dll", "no_fp16"] # Exclude global fields + } + merged_data["current_profile"] = "decky-lsfg-vk" + self.log.info("No existing profiles found, created default profile") + + return merged_data diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 466a982..92d1867 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -4,7 +4,7 @@ import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { ConfigurationData } from "../config/configSchema"; import { FpsMultiplierControl } from "./FpsMultiplierControl"; import { - NO_FP16, FLOW_SCALE, PERFORMANCE_MODE, HDR_MODE, + FLOW_SCALE, PERFORMANCE_MODE, HDR_MODE, EXPERIMENTAL_PRESENT_MODE, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT, FORCE_ENABLE_VKBASALT, ENABLE_WSI } from "../config/generatedConfigSchema"; |
