"""Persistent application settings (recent files, defaults, window state).""" # This file is part of ISO Scalpel. # Copyright (C) 2025 Jeremy Anderson # # 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; either version 2 of the License, or # (at your option) any later version. # # 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 # or write to the Free Software Foundation, Inc., 51 Franklin Street, # Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import annotations import json import os import sys from dataclasses import asdict, dataclass, field def _config_dir() -> str: base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config") return os.path.join(base, "iso-scalpel") def _config_path() -> str: return os.path.join(_config_dir(), "settings.json") @dataclass class Settings: recent_files: list[str] = field(default_factory=list) last_dir: str = os.path.expanduser("~") show_hidden: bool = False confirm_delete: bool = True default_volume_label: str = "CDROM" default_interchange_level: int = 1 default_joliet: int = 3 default_rock_ridge: str = "1.09" default_udf: str = "2.60" default_block_size: int = 2048 window_width: int = 1100 window_height: int = 720 splitter_sizes: list[int] = field(default_factory=lambda: [500, 500]) # ------------------------------------------------------------------ @classmethod def load(cls) -> Settings: """Load settings from disk, merging onto a fresh instance. A missing file, a permission error, or a corrupt JSON payload yields the default settings rather than aborting startup. """ try: with open(_config_path(), encoding="utf-8") as fh: data = json.load(fh) except (OSError, json.JSONDecodeError): return cls() if not isinstance(data, dict): return cls() # Merge onto defaults so new fields added in a later release do not # break a settings file written by an older one. merged = cls() for key, value in data.items(): if hasattr(merged, key): setattr(merged, key, value) return merged def save(self) -> None: """Persist settings to disk. Failures are reported on stderr but never raised: a read-only home directory must not crash the app.""" try: os.makedirs(_config_dir(), exist_ok=True) with open(_config_path(), "w", encoding="utf-8") as fh: json.dump(asdict(self), fh, indent=2) except OSError as exc: sys.stderr.write(f"warning: could not save settings: {exc}\n") # ------------------------------------------------------------------ helpers def add_recent(self, path: str) -> None: path = os.path.abspath(path) if path in self.recent_files: self.recent_files.remove(path) self.recent_files.insert(0, path) self.recent_files = self.recent_files[:10]