408 lines
18 KiB
Python
408 lines
18 KiB
Python
# --- START OF FILE config_manager.py ---
|
|
|
|
# config_manager.py
|
|
import configparser
|
|
import os
|
|
|
|
# Rimosso import logging
|
|
import sys # Per fallback print
|
|
|
|
# Importa il nuovo gestore della coda log
|
|
import log_handler
|
|
|
|
# Constants
|
|
CONFIG_FILE = "git_svn_sync.ini"
|
|
DEFAULT_PROFILE = "default"
|
|
# Single source of truth for the default backup directory path
|
|
DEFAULT_BACKUP_DIR = os.path.join(os.path.expanduser("~"), "git_svn_backup")
|
|
|
|
|
|
class ConfigManager:
|
|
"""
|
|
Manages the configuration file (git_svn_sync.ini) for the tool.
|
|
Handles loading, saving, and accessing profile settings using log_handler.
|
|
Ensures default profile and all expected keys exist across profiles.
|
|
"""
|
|
|
|
# Rimosso logger da __init__
|
|
def __init__(self, logger_ignored=None): # Accetta argomento ma lo ignora
|
|
"""Initializes the ConfigManager."""
|
|
# Non c'è più self.logger
|
|
# Inizializza il parser
|
|
self.config = configparser.ConfigParser()
|
|
# Carica la configurazione all'inizializzazione
|
|
# Usa try-except per gestire errori critici durante il caricamento iniziale
|
|
try:
|
|
self.load_config() # Ensure config is loaded and validated
|
|
log_handler.log_debug(
|
|
"ConfigManager initialized and config loaded.", func_name="__init__"
|
|
)
|
|
except Exception as e:
|
|
# Log critico se il caricamento iniziale fallisce completamente
|
|
# Usa print perché il logging potrebbe non essere ancora pronto
|
|
print(
|
|
f"CRITICAL ERROR: Failed to load initial configuration: {e}",
|
|
file=sys.stderr,
|
|
)
|
|
log_handler.log_critical(
|
|
f"Failed to load initial configuration: {e}", func_name="__init__"
|
|
)
|
|
# Inizializza con un config vuoto per permettere all'app di partire (forse)
|
|
self.config = configparser.ConfigParser()
|
|
# Potrebbe essere necessario sollevare l'eccezione qui per fermare l'app
|
|
# raise RuntimeError("Failed to initialize configuration manager") from e
|
|
|
|
def _get_expected_keys_with_defaults(self):
|
|
"""
|
|
Returns a dictionary of expected config keys and their default values.
|
|
This is the single source of truth for profile keys.
|
|
"""
|
|
return {
|
|
"svn_working_copy_path": "/path/to/your/repo", # Aggiornato default
|
|
"usb_drive_path": "/path/to/your/usb", # Aggiornato default
|
|
"bundle_name": "my_repo.bundle",
|
|
"bundle_name_updated": "my_repo_update.bundle",
|
|
"autobackup": "False",
|
|
"backup_dir": DEFAULT_BACKUP_DIR, # Usa costante definita sopra
|
|
"autocommit": "False",
|
|
"commit_message": "",
|
|
"backup_exclude_extensions": ".log,.tmp,.bak,~*", # Esempio default
|
|
"backup_exclude_dirs": "__pycache__,.venv,.vscode,build,dist,node_modules,.git,.svn", # Default esteso
|
|
}
|
|
|
|
def load_config(self):
|
|
"""
|
|
Loads config from CONFIG_FILE, creates/validates defaults and keys.
|
|
Uses log_handler for logging.
|
|
"""
|
|
func_name = "load_config"
|
|
log_handler.log_debug(
|
|
f"Attempting to load configuration from '{CONFIG_FILE}'...",
|
|
func_name=func_name,
|
|
)
|
|
needs_save = False # Flag to track if config needs saving after load/check
|
|
try:
|
|
config_exists = os.path.exists(CONFIG_FILE)
|
|
files_read = []
|
|
if config_exists:
|
|
# Try reading the existing file
|
|
try:
|
|
# Specify UTF-8 encoding for consistency
|
|
files_read = self.config.read(CONFIG_FILE, encoding="utf-8")
|
|
if not files_read and config_exists:
|
|
# File exists but configparser couldn't read it (e.g., empty, corrupted format)
|
|
log_handler.log_warning(
|
|
f"Config file '{CONFIG_FILE}' exists but is empty or unreadable. Will recreate.",
|
|
func_name=func_name,
|
|
)
|
|
self.config = configparser.ConfigParser() # Reset parser
|
|
config_exists = False # Force default creation logic
|
|
except configparser.Error as read_err:
|
|
log_handler.log_error(
|
|
f"Error parsing config file '{CONFIG_FILE}': {read_err}. Recreating with defaults.",
|
|
func_name=func_name,
|
|
)
|
|
self.config = configparser.ConfigParser() # Reset parser
|
|
config_exists = False # Force default creation logic
|
|
|
|
# --- Create or Validate Configuration ---
|
|
if not config_exists:
|
|
# File doesn't exist or failed to parse
|
|
log_handler.log_info(
|
|
f"Config file '{CONFIG_FILE}' not found or invalid. Creating with default profile.",
|
|
func_name=func_name,
|
|
)
|
|
# Ensure clean parser state if recreating
|
|
self.config = configparser.ConfigParser()
|
|
self._create_default_profile() # Create the default section
|
|
needs_save = True # Mark for saving the new default profile
|
|
else:
|
|
# File exists and was read successfully
|
|
log_handler.log_debug(
|
|
f"Successfully read from '{CONFIG_FILE}'. Validating content...",
|
|
func_name=func_name,
|
|
)
|
|
|
|
# --- Ensure Default Profile Exists and Has All Keys ---
|
|
if DEFAULT_PROFILE not in self.config.sections():
|
|
log_handler.log_warning(
|
|
f"Default profile '{DEFAULT_PROFILE}' missing. Adding with defaults.",
|
|
func_name=func_name,
|
|
)
|
|
self._create_default_profile() # Creates section and adds all keys
|
|
needs_save = True
|
|
else:
|
|
# Default profile exists, check/add missing keys
|
|
expected_keys = self._get_expected_keys_with_defaults()
|
|
for key, default_value in expected_keys.items():
|
|
if not self.config.has_option(DEFAULT_PROFILE, key):
|
|
log_handler.log_warning(
|
|
f"Key '{key}' missing in profile '{DEFAULT_PROFILE}'. Adding default: '{default_value}'.",
|
|
func_name=func_name,
|
|
)
|
|
self.config.set(DEFAULT_PROFILE, key, default_value)
|
|
needs_save = True
|
|
|
|
# --- Ensure ALL Profiles Have ALL Expected Keys ---
|
|
expected_keys = self._get_expected_keys_with_defaults()
|
|
all_current_profiles = (
|
|
self.config.sections()
|
|
) # Get potentially updated list
|
|
for profile_name in all_current_profiles:
|
|
for key, default_value in expected_keys.items():
|
|
if not self.config.has_option(profile_name, key):
|
|
log_handler.log_warning(
|
|
f"Key '{key}' missing in profile '{profile_name}'. Adding default: '{default_value}'.",
|
|
func_name=func_name,
|
|
)
|
|
self.config.set(profile_name, key, default_value)
|
|
needs_save = True # Mark for saving
|
|
|
|
# Save configuration only if changes were made during loading/validation
|
|
if needs_save:
|
|
log_handler.log_info(
|
|
f"Configuration updated. Saving changes to '{CONFIG_FILE}'.",
|
|
func_name=func_name,
|
|
)
|
|
self.save_config() # Call save method
|
|
|
|
except Exception as e:
|
|
# Catch unexpected errors during the load/validation process
|
|
log_handler.log_exception(
|
|
f"Unexpected error loading/validating config: {e}", func_name=func_name
|
|
)
|
|
# Reset to a safe state (empty config) in case of severe issues
|
|
self.config = configparser.ConfigParser()
|
|
# Optionally re-raise or handle this critical failure
|
|
|
|
def _create_default_profile(self):
|
|
"""Internal helper: Creates the default profile section with default keys/values."""
|
|
func_name = "_create_default_profile"
|
|
if DEFAULT_PROFILE not in self.config.sections():
|
|
log_handler.log_debug(
|
|
f"Adding default profile section '{DEFAULT_PROFILE}'.",
|
|
func_name=func_name,
|
|
)
|
|
self.config.add_section(DEFAULT_PROFILE)
|
|
|
|
# Get expected keys and set them for the default profile
|
|
expected_keys = self._get_expected_keys_with_defaults()
|
|
log_handler.log_debug(
|
|
f"Setting default keys for profile '{DEFAULT_PROFILE}'.",
|
|
func_name=func_name,
|
|
)
|
|
for key, default_value in expected_keys.items():
|
|
# configparser's set() handles both adding and updating options
|
|
self.config.set(DEFAULT_PROFILE, key, default_value)
|
|
|
|
log_handler.log_info(
|
|
f"Ensured default profile '{DEFAULT_PROFILE}' exists with default keys.",
|
|
func_name=func_name,
|
|
)
|
|
|
|
def save_config(self):
|
|
"""Saves the current configuration state to the CONFIG_FILE."""
|
|
func_name = "save_config"
|
|
log_handler.log_debug(
|
|
f"Attempting to save configuration to '{CONFIG_FILE}'...",
|
|
func_name=func_name,
|
|
)
|
|
# Check/Create directory if config file is in a subdirectory (optional)
|
|
config_dir = os.path.dirname(CONFIG_FILE)
|
|
# Ensure directory exists only if CONFIG_FILE specifies one
|
|
if config_dir and not os.path.exists(config_dir):
|
|
try:
|
|
os.makedirs(config_dir)
|
|
log_handler.log_info(
|
|
f"Created directory for config file: {config_dir}",
|
|
func_name=func_name,
|
|
)
|
|
except OSError as e:
|
|
# Log error but proceed with saving attempt (might work in current dir)
|
|
log_handler.log_error(
|
|
f"Could not create directory '{config_dir}': {e}. Save might fail.",
|
|
func_name=func_name,
|
|
)
|
|
try:
|
|
# Write the config object to the file using UTF-8 encoding
|
|
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
|
self.config.write(configfile)
|
|
log_handler.log_info(
|
|
f"Configuration saved successfully to '{CONFIG_FILE}'.",
|
|
func_name=func_name,
|
|
)
|
|
except IOError as e:
|
|
# Log specific IO errors during save
|
|
log_handler.log_error(
|
|
f"IOError writing config file '{CONFIG_FILE}': {e}", func_name=func_name
|
|
)
|
|
# Optionally re-raise depending on desired application behavior
|
|
# raise
|
|
except Exception as e:
|
|
# Log any other unexpected errors during save
|
|
log_handler.log_exception(
|
|
f"Unexpected error saving configuration: {e}", func_name=func_name
|
|
)
|
|
# Optionally re-raise
|
|
# raise
|
|
|
|
def get_profile_sections(self):
|
|
"""Returns a sorted list of all profile section names."""
|
|
# Sort alphabetically for consistent display order in UI
|
|
return sorted(self.config.sections())
|
|
|
|
def get_profile_option(self, profile, option, fallback=None):
|
|
"""
|
|
Retrieves a specific option from a profile section, handling errors.
|
|
|
|
Args:
|
|
profile (str): Name of the profile section.
|
|
option (str): Name of the option key.
|
|
fallback (any, optional): Value to return if option/section not found.
|
|
|
|
Returns:
|
|
str | any: The option value as a string if found, otherwise the original fallback.
|
|
"""
|
|
func_name = "get_profile_option"
|
|
try:
|
|
# Check section existence first for clearer logging
|
|
if not self.config.has_section(profile):
|
|
log_handler.log_warning(
|
|
f"Profile '{profile}' not found. Cannot get option '{option}'. Using fallback: {fallback}",
|
|
func_name=func_name,
|
|
)
|
|
return fallback # Return original fallback type
|
|
|
|
# Use configparser's built-in fallback mechanism if option missing
|
|
# Convert fallback to string for configparser, handling None
|
|
str_fallback = str(fallback) if fallback is not None else None
|
|
value_str = self.config.get(profile, option, fallback=str_fallback)
|
|
|
|
# Check if the fallback was actually used because the option was missing
|
|
option_was_missing = not self.config.has_option(profile, option)
|
|
# Careful comparison needed if fallback could be None or empty string
|
|
if value_str == str_fallback and option_was_missing:
|
|
log_handler.log_debug(
|
|
f"Option '{option}' not found in profile '{profile}'. Used fallback value.",
|
|
func_name=func_name,
|
|
)
|
|
return fallback # Return original fallback type if option was truly missing
|
|
|
|
# Return the value obtained (always a string from configparser)
|
|
return value_str
|
|
|
|
except Exception as e:
|
|
# Log unexpected errors during option retrieval
|
|
log_handler.log_exception(
|
|
f"Error getting option '{option}' for profile '{profile}': {e}",
|
|
func_name=func_name,
|
|
)
|
|
# Return the fallback value on any error
|
|
return fallback
|
|
|
|
def set_profile_option(self, profile, option, value):
|
|
"""
|
|
Sets an option in a profile section, creating the section if needed.
|
|
Value is converted to string for storage. Uses log_handler.
|
|
|
|
Args:
|
|
profile (str): Name of the profile section.
|
|
option (str): Name of the option key.
|
|
value (any): Value to set (will be converted to string).
|
|
"""
|
|
func_name = "set_profile_option"
|
|
try:
|
|
# Create section if it doesn't exist
|
|
if not self.config.has_section(profile):
|
|
self.config.add_section(profile)
|
|
log_handler.log_info(
|
|
f"Created new profile section during set: '{profile}'",
|
|
func_name=func_name,
|
|
)
|
|
|
|
# Convert value to string for storage (handle None)
|
|
value_str = str(value) if value is not None else ""
|
|
# Set the option using configparser
|
|
self.config.set(profile, option, value_str)
|
|
log_handler.log_debug(
|
|
f"Set option '{option}' = '{value_str}' in profile '{profile}'.",
|
|
func_name=func_name,
|
|
)
|
|
except configparser.Error as e:
|
|
# Log specific configparser errors
|
|
log_handler.log_error(
|
|
f"ConfigParser error setting option '{option}' in '{profile}': {e}",
|
|
func_name=func_name,
|
|
)
|
|
except Exception as e:
|
|
# Log other unexpected errors
|
|
log_handler.log_exception(
|
|
f"Unexpected error setting option '{option}' in '{profile}': {e}",
|
|
func_name=func_name,
|
|
)
|
|
|
|
def remove_profile_section(self, profile):
|
|
"""
|
|
Removes a profile section (cannot remove the default profile). Uses log_handler.
|
|
|
|
Args:
|
|
profile (str): Name of the profile section to remove.
|
|
|
|
Returns:
|
|
bool: True if removed successfully, False otherwise.
|
|
"""
|
|
func_name = "remove_profile_section"
|
|
# Prevent removal of the default profile
|
|
if profile == DEFAULT_PROFILE:
|
|
log_handler.log_warning(
|
|
f"Attempt to remove default profile '{DEFAULT_PROFILE}' denied.",
|
|
func_name=func_name,
|
|
)
|
|
return False
|
|
|
|
# Check if the profile section exists
|
|
if self.config.has_section(profile):
|
|
try:
|
|
# Attempt to remove the section
|
|
removed = self.config.remove_section(profile)
|
|
if removed:
|
|
log_handler.log_info(
|
|
f"Removed profile section: '{profile}'", func_name=func_name
|
|
)
|
|
return True
|
|
else:
|
|
# Should not happen if has_section was True, but log anyway
|
|
log_handler.log_warning(
|
|
f"ConfigParser failed to remove existing section '{profile}'.",
|
|
func_name=func_name,
|
|
)
|
|
return False
|
|
except Exception as e:
|
|
# Log errors during removal
|
|
log_handler.log_exception(
|
|
f"Error removing profile section '{profile}': {e}",
|
|
func_name=func_name,
|
|
)
|
|
return False
|
|
else:
|
|
# Profile section not found
|
|
log_handler.log_warning(
|
|
f"Profile section '{profile}' not found, cannot remove.",
|
|
func_name=func_name,
|
|
)
|
|
return False
|
|
|
|
def add_section(self, section_name):
|
|
"""Adds a new section if it doesn't exist."""
|
|
# Helper function, might be useful for add_profile logic
|
|
if not self.config.has_section(section_name):
|
|
self.config.add_section(section_name)
|
|
log_handler.log_info(
|
|
f"Added new config section: '{section_name}'", func_name="add_section"
|
|
)
|
|
return True
|
|
return False # Already existed
|
|
|
|
|
|
# --- END OF FILE config_manager.py ---
|