Añade ConfigManager que maneja automáticamente: - Carga/creación de archivo config (loadOrCreate) - Auto-guardado en deinit si hay cambios pendientes - Sistema de observers con contexto para sincronización externa Cambios: - ConfigManager(variables, ConfigType, app_name) type - Observer con contexto: fn(change, config, ctx) void - addObserver(callback, context) para registrar listeners - Métodos: get, set, getConfig, getConfigMut, markDirty, isDirty 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
11 KiB
ConfigManager - Diseño e Implementación
Fecha: 2025-12-17 Versión: v0.2.0 Estado: En implementación
Objetivo
Crear un gestor de configuración completo y autónomo que:
- Gestione automáticamente el archivo de configuración
- Mantenga las variables en memoria
- Notifique cambios via observers (para sincronizar con BD u otros sistemas)
- Minimice el trabajo requerido en proyectos futuros
Filosofía
"Invertir trabajo ahora para ahorrar problemas y trabajo en el futuro"
La librería debe hacer todo el trabajo pesado. El programa solo debe:
- Definir las variables (array de ConfigVariable)
- Definir el struct Config
- Crear el ConfigManager
- Opcionalmente registrar observers
Arquitectura
┌─────────────────────────────────────────────────────────────────────┐
│ ZCATCONFIG │
│ (Librería independiente, reutilizable) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ConfigManager(comptime variables, comptime ConfigType) │
│ ├── allocator: Allocator │
│ ├── config: ConfigType ← Datos en memoria │
│ ├── file_path: []const u8 ← Ruta archivo │
│ ├── app_name: []const u8 ← Nombre app (para headers) │
│ ├── dirty: bool ← Cambios pendientes │
│ ├── observers: []Observer ← Lista de callbacks │
│ │ │
│ ├── init(allocator, path, app_name) │
│ │ └── Llama loadOrCreate() automáticamente │
│ │ │
│ ├── deinit() │
│ │ └── Auto-save si dirty, libera recursos │
│ │ │
│ ├── get(key) → ConfigResult │
│ ├── set(key, value) → !void │
│ │ └── Marca dirty=true, notifica observers │
│ │ │
│ ├── getConfig() → *ConfigType │
│ │ └── Acceso directo al struct (para campos tipados) │
│ │ │
│ ├── save() → !void │
│ ├── load() → !void │
│ ├── loadOrCreate() → !void │
│ │ └── Si archivo no existe, guarda defaults │
│ │ │
│ ├── addObserver(callback) → void │
│ ├── removeObserver(callback) → void │
│ └── notifyObservers(change) → void (interno) │
│ │
└─────────────────────────────────────────────────────────────────────┘
Tipos
ConfigChange
Información sobre un cambio de configuración:
pub const ConfigChange = struct {
key: []const u8, // Clave que cambió ("@auto_guardar")
variable: *const ConfigVariable, // Definición de la variable
old_value: ConfigResult, // Valor anterior
new_value: ConfigResult, // Valor nuevo
};
Observer
Callback que recibe notificaciones de cambios:
pub const Observer = *const fn (change: ConfigChange, config: *const ConfigType) void;
Flujo de Uso
Uso Básico (sin BD)
const zcatconfig = @import("zcatconfig");
// Definir variables (en variables.zig)
const config_variables = [_]zcatconfig.ConfigVariable{
.{ .name = "timeout", .config_key = "@timeout", ... },
// ...
};
// Definir struct (en structures.zig)
const Config = struct {
timeout: u32 = 30,
// ...
};
// En main o donde sea necesario:
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// Crear manager - carga o crea archivo automáticamente
var config_manager = try zcatconfig.ConfigManager(
&config_variables,
Config
).init(allocator, "mi_app_config.txt", "MiApp");
defer config_manager.deinit(); // Auto-save si hay cambios
// Usar configuración
const timeout = config_manager.getConfig().timeout;
// Cambiar valor (notifica observers, marca dirty)
try config_manager.set("@timeout", "60");
}
Uso con Sincronización a BD
// En DataManager:
pub const DataManager = struct {
config_manager: *zcatconfig.ConfigManager(&config_variables, Config),
db: *Database,
pub fn init(allocator: Allocator, db: *Database) !*Self {
var self = try allocator.create(Self);
// Crear config manager
self.config_manager = try zcatconfig.ConfigManager(
&config_variables,
Config
).init(allocator, "config.txt", "zsimifactu");
// Registrar observer para sincronizar con BD
self.config_manager.addObserver(syncToDatabase);
return self;
}
fn syncToDatabase(change: ConfigChange, config: *const Config) void {
// INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)
// Usar change.key y change.new_value
}
};
Comportamiento Automático
En init()
- Inicializa Config con valores por defecto
- Llama
loadOrCreate():- Si archivo existe → carga valores
- Si no existe → guarda archivo con defaults
dirty = false
En set()
- Obtiene valor actual (para ConfigChange)
- Aplica nuevo valor al struct
dirty = true- Notifica a todos los observers
En deinit()
- Si
dirty == true→ llamasave() - Libera recursos del Config (strings alocados)
- Libera lista de observers
Integración con zsimifactu
Antes (actual)
DataManager
├── config: Config ← Struct directo
├── config_file_path: []const u8
├── config_dirty: bool
├── loadConfig() / saveConfig() ← Métodos manuales
└── getConfigValue() / setConfigValue()
Después (con ConfigManager)
DataManager
├── config_manager: *ConfigManager ← Gestor completo
└── (todo lo demás delegado al manager)
// Acceso a config:
dm.config_manager.getConfig().mi_campo
// Cambiar valor:
try dm.config_manager.set("@mi_campo", "valor");
Sincronización con BD
Estrategia
- ConfigManager notifica cambios via observer
- DataManager registra un observer que escribe a BD
- Al iniciar, se puede cargar desde BD y aplicar al ConfigManager
Tabla BD (ya existe en zsimifactu)
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Flujo de Sincronización
Inicio:
1. ConfigManager.init() → carga archivo (o crea con defaults)
2. DataManager carga valores de BD
3. Aplica valores BD al ConfigManager (si BD tiene prioridad)
Durante ejecución:
1. Usuario cambia valor
2. ConfigManager.set() → actualiza memoria + notifica
3. Observer → escribe a BD
4. ConfigManager marca dirty
Al cerrar:
1. ConfigManager.deinit() → guarda archivo (si dirty)
Prioridad de Valores
Orden de prioridad (mayor a menor):
- Cambios en runtime - Siempre prevalecen
- Base de datos - Persistencia principal
- Archivo config - Backup/portabilidad
- Defaults en código - Fallback
Archivos Afectados
zcatconfig (crear/modificar)
src/zcatconfig.zig- Añadir ConfigManagerdocs/CONFIGMANAGER_DESIGN.md- Este documento
zsimifactu (modificar)
src/data_manager/data_manager.zig- Usar ConfigManagersrc/config/config.zig- Simplificar (delegar a zcatconfig)
Plan de Implementación
-
Implementar ConfigManager en zcatconfig
- Struct con todos los campos
- init/deinit con loadOrCreate automático
- get/set con notificación
- Sistema de observers
-
Actualizar zsimifactu
- DataManager usa ConfigManager
- Registrar observer para BD
- Eliminar código duplicado
-
Probar
- Borrar archivo config → debe regenerarse
- Cambiar valor → debe notificar y guardar
- Cerrar app → debe auto-guardar
Notas de Implementación Zig
Observers como ArrayList
observers: std.ArrayList(Observer),
pub fn addObserver(self: *Self, observer: Observer) void {
self.observers.append(self.allocator, observer) catch {};
}
fn notifyObservers(self: *Self, change: ConfigChange) void {
for (self.observers.items) |observer| {
observer(change, &self.config);
}
}
ConfigManager como tipo genérico
pub fn ConfigManager(
comptime variables: []const ConfigVariable,
comptime ConfigType: type,
) type {
return struct {
const Self = @This();
allocator: std.mem.Allocator,
config: ConfigType,
file_path: []const u8,
app_name: []const u8,
dirty: bool,
observers: std.ArrayList(Observer),
pub const Observer = *const fn (ConfigChange, *const ConfigType) void;
pub fn init(...) !Self { ... }
pub fn deinit(self: *Self) void { ... }
// ...
};
}