""" Settings management with XDG Base Directory specification compliance """ import os import configparser from pathlib import Path from typing import Any, Optional class SettingsManager: """Manages application settings with XDG compliance""" def __init__(self): self.config_dir = self._get_config_dir() self.data_dir = self._get_data_dir() self.cache_dir = self._get_cache_dir() # Ensure directories exist self.config_dir.mkdir(parents=True, exist_ok=True) self.data_dir.mkdir(parents=True, exist_ok=True) self.cache_dir.mkdir(parents=True, exist_ok=True) self.config_file = self.config_dir / "bifrost.conf" self.config = configparser.ConfigParser() self.load_settings() def _get_config_dir(self) -> Path: """Get XDG config directory""" xdg_config = os.getenv('XDG_CONFIG_HOME') if xdg_config: return Path(xdg_config) / "bifrost" return Path.home() / ".config" / "bifrost" def _get_data_dir(self) -> Path: """Get XDG data directory""" xdg_data = os.getenv('XDG_DATA_HOME') if xdg_data: return Path(xdg_data) / "bifrost" return Path.home() / ".local" / "share" / "bifrost" def _get_cache_dir(self) -> Path: """Get XDG cache directory""" xdg_cache = os.getenv('XDG_CACHE_HOME') if xdg_cache: return Path(xdg_cache) / "bifrost" return Path.home() / ".cache" / "bifrost" def load_settings(self): """Load settings from config file""" if self.config_file.exists(): self.config.read(self.config_file) else: self.create_default_config() def create_default_config(self): """Create default configuration""" # General settings self.config.add_section('general') self.config.set('general', 'instance_url', '') self.config.set('general', 'username', '') self.config.set('general', 'current_sound_pack', 'default') self.config.set('general', 'timeline_refresh_interval', '60') self.config.set('general', 'auto_refresh_enabled', 'true') # Sound settings self.config.add_section('sounds') self.config.set('sounds', 'enabled', 'true') self.config.set('sounds', 'private_message_enabled', 'true') self.config.set('sounds', 'private_message_volume', '0.8') self.config.set('sounds', 'mention_enabled', 'true') self.config.set('sounds', 'mention_volume', '1.0') self.config.set('sounds', 'boost_enabled', 'true') self.config.set('sounds', 'boost_volume', '0.7') self.config.set('sounds', 'reply_enabled', 'true') self.config.set('sounds', 'reply_volume', '0.8') self.config.set('sounds', 'post_sent_enabled', 'true') self.config.set('sounds', 'post_sent_volume', '0.9') self.config.set('sounds', 'timeline_update_enabled', 'true') self.config.set('sounds', 'timeline_update_volume', '0.5') self.config.set('sounds', 'notification_enabled', 'true') self.config.set('sounds', 'notification_volume', '0.8') # Accessibility settings self.config.add_section('accessibility') self.config.set('accessibility', 'announce_thread_state', 'true') self.config.set('accessibility', 'auto_expand_mentions', 'false') self.config.set('accessibility', 'keyboard_navigation_wrap', 'true') self.config.set('accessibility', 'focus_follows_mouse', 'false') # Interface settings self.config.add_section('interface') self.config.set('interface', 'default_timeline', 'home') self.config.set('interface', 'show_timestamps', 'true') self.config.set('interface', 'compact_mode', 'false') self.save_settings() def save_settings(self): """Save settings to config file""" with open(self.config_file, 'w') as f: self.config.write(f) def get(self, section: str, key: str, fallback: Any = None) -> Optional[str]: """Get a setting value""" try: return self.config.get(section, key) except (configparser.NoSectionError, configparser.NoOptionError): return fallback def get_bool(self, section: str, key: str, fallback: bool = False) -> bool: """Get a boolean setting value""" try: return self.config.getboolean(section, key) except (configparser.NoSectionError, configparser.NoOptionError, ValueError): return fallback def get_int(self, section: str, key: str, fallback: int = 0) -> int: """Get an integer setting value""" try: return self.config.getint(section, key) except (configparser.NoSectionError, configparser.NoOptionError, ValueError): return fallback def get_float(self, section: str, key: str, fallback: float = 0.0) -> float: """Get a float setting value""" try: return self.config.getfloat(section, key) except (configparser.NoSectionError, configparser.NoOptionError, ValueError): return fallback def set(self, section: str, key: str, value: Any): """Set a setting value""" if not self.config.has_section(section): self.config.add_section(section) self.config.set(section, key, str(value)) def get_sounds_dir(self) -> Path: """Get the sounds directory path""" return self.data_dir / "sounds" def get_current_sound_pack_dir(self) -> Path: """Get the current sound pack directory""" pack_name = self.get('general', 'current_sound_pack', 'default') return self.get_sounds_dir() / pack_name