src/Services/SettingsService.ps1
|
# SettingsService — Carga y persistencia de las preferencias del usuario. # # Responsabilidad ÚNICA: serializar/deserializar Settings contra un JSON. # No conoce UI, no conoce repos, no aplica defaults de negocio (eso lo hace el model). # # Path por defecto: ~/.repo-nav/preferences.json # Path inyectable por constructor para tests con fixture en TestDrive. class SettingsService { [string] $Path SettingsService() { $this.Path = [SettingsService]::DefaultPath() } SettingsService([string] $path) { if ([string]::IsNullOrWhiteSpace($path)) { throw "SettingsService: path no puede ser vacío." } $this.Path = $path } static [string] DefaultPath() { # Ojo: $HOME es read-only en PS. Usamos var local con otro nombre. $homeDir = if ($env:HOME) { $env:HOME } else { $env:USERPROFILE } return Join-Path $homeDir '.repo-nav/preferences.json' } # Carga desde disco. Si el archivo no existe → defaults. Si está corrupto → defaults + # warning (no tira excepción: las preferencias rotas no deben tirar abajo la app). [Settings] Load() { if (-not (Test-Path -LiteralPath $this.Path)) { return [Settings]::new() } try { $raw = Get-Content -LiteralPath $this.Path -Raw -Encoding UTF8 if ([string]::IsNullOrWhiteSpace($raw)) { return [Settings]::new() } $h = $raw | ConvertFrom-Json -AsHashtable -ErrorAction Stop return [SettingsService]::Hydrate($h) } catch { Write-Warning "SettingsService: settings corruptos en '$($this.Path)' — uso defaults. Detalle: $_" return [Settings]::new() } } # Guarda en disco. Crea el directorio padre si no existe. Devuelve $true si OK, # $false con warning si falló (no tira: caller decide si reintentar). [bool] Save([Settings] $settings) { if ($null -eq $settings) { throw "SettingsService.Save: settings no puede ser null." } try { # .NET puro para evitar quirks de parameter sets de New-Item / Set-Content. $dir = [System.IO.Path]::GetDirectoryName($this.Path) if ($dir -and -not [System.IO.Directory]::Exists($dir)) { [System.IO.Directory]::CreateDirectory($dir) | Out-Null } $payload = [SettingsService]::Serialize($settings) $json = $payload | ConvertTo-Json -Depth 8 $utf8 = New-Object System.Text.UTF8Encoding $false [System.IO.File]::WriteAllText($this.Path, $json, $utf8) return $true } catch { Write-Warning "SettingsService: no pude guardar en '$($this.Path)'. Detalle: $($_.Exception.Message)" return $false } } # Convierte un hashtable (proveniente de ConvertFrom-Json -AsHashtable) en Settings. # Solo asigna las propiedades que existen en el hashtable; las demás quedan con default. # Robust: si una propiedad falta, no es error. hidden static [Settings] Hydrate([hashtable] $h) { $s = [Settings]::new() if ($null -eq $h) { return $s } foreach ($prop in $s.PSObject.Properties) { $name = $prop.Name if (-not $h.ContainsKey($name)) { continue } $value = $h[$name] if ($null -eq $value) { continue } try { $s.$name = $value } catch { Write-Warning "SettingsService.Hydrate: no pude asignar '$name' (tipo incompatible). Skip." } } return $s } # Convierte Settings → hashtable plana lista para ConvertTo-Json. hidden static [hashtable] Serialize([Settings] $s) { $h = @{} foreach ($prop in $s.PSObject.Properties) { $h[$prop.Name] = $prop.Value } return $h } } |