From 1e97e741ae89270801526234e3caaf5c7aacdca7 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 10 Sep 2026 14:45:27 -0400 Subject: fix: clean Flatpak state from uninstall API --- py_modules/lsfg_vk/plugin.py | 38 ++++++++++++++++-- src/api/lsfgApi.ts | 9 +++++ src/hooks/useLsfgHooks.ts | 2 +- tests/test_plugin_flatpak_lifecycle.py | 71 ++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 282ed96..aedb99b 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -37,7 +37,20 @@ class Plugin: return self.installation_service.check_installation() async def uninstall_lsfg_vk(self): - return self.installation_service.uninstall() + result = self.installation_service.uninstall() + flatpak = self._cleanup_plugin_flatpak_state() + result = dict(result) + result["flatpak_cleanup"] = flatpak + + if not result.get("success") or not flatpak.get("success"): + result["success"] = False + errors = [ + str(error) + for error in (result.get("error"), flatpak.get("error")) + if error + ] + result["error"] = "; ".join(errors) or "Uninstallation did not complete" + return result async def get_game_configs(self): return self.configuration_service.get_game_configs() @@ -166,13 +179,30 @@ class Plugin: async def _uninstall(self): decky.logger.info("decky-lsfg-vk plugin being uninstalled") self.installation_service.cleanup_on_uninstall() + result = self._cleanup_plugin_flatpak_state() + if not result.get("success"): + decky.logger.warning(result.get("error")) + decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed") + + def _cleanup_plugin_flatpak_state(self) -> Dict[str, Any]: + """Run ownership-safe Flatpak cleanup and fail closed on unexpected errors.""" try: result = self.flatpak_service.remove_plugin_owned_extensions() - if not result.get("success"): - decky.logger.warning(result.get("error")) + if not isinstance(result, dict): + raise RuntimeError("Flatpak cleanup returned an invalid result") + return result except Exception as error: decky.logger.error(f"Error during Flatpak cleanup: {error}") - decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed") + return { + "success": False, + "message": "", + "error": f"Error during Flatpak cleanup: {error}", + "removed_branches": [], + "preserved_branches": [], + "removed_filesystem_grants": [], + "preserved_filesystem_grants": [], + "ownership_uncertain": True, + } async def _migration(self): decky.logger.info("Running decky-lsfg-vk plugin migrations") diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index cf09ed0..12e284e 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -7,8 +7,17 @@ interface ApiResult { error?: string | null; } +export interface FlatpakCleanupResult extends ApiResult { + removed_branches?: string[]; + preserved_branches?: string[]; + removed_filesystem_grants?: string[]; + preserved_filesystem_grants?: string[]; + ownership_uncertain?: boolean; +} + export interface InstallationResult extends ApiResult { removed_files?: string[]; + flatpak_cleanup?: FlatpakCleanupResult; } export interface InstallationStatus { diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts index 16f8962..5a82b61 100644 --- a/src/hooks/useLsfgHooks.ts +++ b/src/hooks/useLsfgHooks.ts @@ -96,7 +96,7 @@ export function useInstallation(reloadConfig?: () => Promise) { return; } setIsInstalled(false); - setInstallationStatus("lsfg-vk uninstalled successfully!"); + setInstallationStatus("lsfg-vk and Flatpak support uninstalled successfully!"); await checkInstallation(); showUninstallSuccessToast(); } catch (error) { diff --git a/tests/test_plugin_flatpak_lifecycle.py b/tests/test_plugin_flatpak_lifecycle.py index 25f09b6..613a807 100644 --- a/tests/test_plugin_flatpak_lifecycle.py +++ b/tests/test_plugin_flatpak_lifecycle.py @@ -57,6 +57,77 @@ class PluginFlatpakLifecycleTests(unittest.TestCase): plugin.installation_service.cleanup_on_uninstall.assert_called_once_with() plugin.flatpak_service.remove_plugin_owned_extensions.assert_called_once_with() + def test_uninstall_button_also_cleans_flatpak_support(self): + plugin = Plugin.__new__(Plugin) + plugin.installation_service = Mock() + plugin.installation_service.uninstall.return_value = { + "success": True, + "message": "lsfg-vk uninstalled successfully", + "error": None, + "removed_files": ["/home/deck/.local/lib/liblsfg-vk.so"], + } + plugin.flatpak_service = Mock() + plugin.flatpak_service.remove_plugin_owned_extensions.return_value = { + "success": True, + "message": "Plugin-owned Flatpak state removed", + "error": None, + "removed_branches": ["23.08", "24.08", "25.08"], + "preserved_branches": [], + "removed_filesystem_grants": ["/home/deck/.config/lsfg-vk"], + "preserved_filesystem_grants": [], + "ownership_uncertain": False, + } + + result = asyncio.run(plugin.uninstall_lsfg_vk()) + + plugin.installation_service.uninstall.assert_called_once_with() + plugin.flatpak_service.remove_plugin_owned_extensions.assert_called_once_with() + self.assertTrue(result["success"]) + self.assertEqual(result["flatpak_cleanup"]["removed_branches"], ["23.08", "24.08", "25.08"]) + + def test_uninstall_button_reports_flatpak_cleanup_failure(self): + plugin = Plugin.__new__(Plugin) + plugin.installation_service = Mock() + plugin.installation_service.uninstall.return_value = { + "success": True, + "message": "lsfg-vk uninstalled successfully", + "error": None, + "removed_files": [], + } + plugin.flatpak_service = Mock() + plugin.flatpak_service.remove_plugin_owned_extensions.return_value = { + "success": False, + "message": "", + "error": "Flatpak ownership metadata was not trusted", + "ownership_uncertain": True, + } + + result = asyncio.run(plugin.uninstall_lsfg_vk()) + + self.assertFalse(result["success"]) + self.assertIn("Flatpak ownership metadata was not trusted", result["error"]) + self.assertTrue(result["flatpak_cleanup"]["ownership_uncertain"]) + + def test_uninstall_button_fails_closed_if_flatpak_cleanup_raises(self): + plugin = Plugin.__new__(Plugin) + plugin.installation_service = Mock() + plugin.installation_service.uninstall.return_value = { + "success": True, + "message": "lsfg-vk uninstalled successfully", + "error": None, + "removed_files": [], + } + plugin.flatpak_service = Mock() + plugin.flatpak_service.remove_plugin_owned_extensions.side_effect = RuntimeError( + "ownership could not be established" + ) + + result = asyncio.run(plugin.uninstall_lsfg_vk()) + + self.assertFalse(result["success"]) + self.assertIn("ownership could not be established", result["error"]) + self.assertTrue(result["flatpak_cleanup"]["ownership_uncertain"]) + if __name__ == "__main__": unittest.main() -- cgit v1.2.3