CtrEditor/DataStates/StateManager.cs

383 lines
13 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Linq;
using System.Windows.Controls;
using System.Windows;
using CtrEditor.ObjetosSim;
using LibS7Adv;
namespace CtrEditor
{
/// <summary>
/// Metadata de una página, incluyendo información sobre la imagen y configuración
/// </summary>
public class PageMetadata
{
public string PageId { get; set; }
public string PageName { get; set; }
public string ImagePath { get; set; }
public DateTime LastModified { get; set; }
public Dictionary<string, object> CustomMetadata { get; set; } = new();
}
/// <summary>
/// Estado de un objeto dentro de una página específica
/// </summary>
public class PageObjectState
{
public string GlobalObjectId { get; set; }
public bool IsVisible { get; set; } = true;
public float Left { get; set; }
public float Top { get; set; }
public float Rotation { get; set; }
public Dictionary<string, object> CustomProperties { get; set; } = new();
}
/// <summary>
/// Estado de una página específica
/// </summary>
public class PageState
{
public string PageId { get; set; }
public string PageName { get; set; }
public List<osBase> LocalObjects { get; set; } = new();
public Dictionary<string, PageObjectState> GlobalObjectStates { get; set; } = new();
public Dictionary<string, object> PageMetadata { get; set; } = new();
}
/// <summary>
/// Estado global de la aplicación
/// </summary>
public class GlobalState
{
public List<osBase> SharedObjects { get; set; } = new();
public Dictionary<string, PageMetadata> PagesMetadata { get; set; } = new();
public PLCViewModel PLCConfiguration { get; set; }
public UnitConverter UnitConverter { get; set; }
public int LastUsedId { get; set; }
}
/// <summary>
/// Gestor principal de estados de la aplicación
/// </summary>
public class StateManager : IDisposable
{
private readonly string _basePath;
private GlobalState _globalState;
private Dictionary<string, PageState> _loadedPages;
private string _currentPageId;
private readonly object _lockObject = new();
private bool _hasUnsavedChanges;
private MainViewModel _mainViewModel;
public event EventHandler<string> PageStateChanged;
public event EventHandler GlobalStateChanged;
public StateManager(string basePath, MainViewModel mainViewModel)
{
_basePath = basePath;
_mainViewModel = mainViewModel;
_loadedPages = new Dictionary<string, PageState>();
Directory.CreateDirectory(basePath);
}
public async Task InitializeAsync()
{
await LoadGlobalStateAsync();
if (_mainViewModel.SelectedImage != null)
{
await LoadPageStateAsync(_mainViewModel.SelectedImage);
}
}
private JsonSerializerSettings GetSerializerSettings()
{
return new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
}
public async Task LoadGlobalStateAsync()
{
var path = Path.Combine(_basePath, "global.json");
if (File.Exists(path))
{
try
{
var json = await File.ReadAllTextAsync(path);
_globalState = JsonConvert.DeserializeObject<GlobalState>(json, GetSerializerSettings());
// Restaurar configuración global
_mainViewModel.PLCViewModel = _globalState.PLCConfiguration ?? new PLCViewModel();
if (_globalState.UnitConverter != null)
PixelToMeter.Instance.calc = _globalState.UnitConverter;
else
PixelToMeter.Instance.calc = new UnitConverter(1.0f); // Valor por defecto
// Restaurar objetos globales
foreach (var obj in _globalState.SharedObjects)
{
await RestoreObjectAsync(obj);
}
}
catch (Exception ex)
{
// Log error y crear nuevo estado global
_globalState = new GlobalState();
}
}
else
{
_globalState = new GlobalState();
}
}
public async Task SaveGlobalStateAsync()
{
var path = Path.Combine(_basePath, "global.json");
var backupPath = Path.ChangeExtension(path, ".bak");
// Actualizar estado global
_globalState.PLCConfiguration = _mainViewModel.PLCViewModel;
_globalState.UnitConverter = PixelToMeter.Instance.calc;
// Crear backup
if (File.Exists(path))
{
File.Copy(path, backupPath, true);
}
try
{
// Preparar objetos para serialización
foreach (var obj in _globalState.SharedObjects)
{
obj.SalvarDatosNoSerializables();
}
var json = JsonConvert.SerializeObject(_globalState, GetSerializerSettings());
await File.WriteAllTextAsync(path, json);
_hasUnsavedChanges = false;
GlobalStateChanged?.Invoke(this, EventArgs.Empty);
}
finally
{
// Restaurar estado de objetos
foreach (var obj in _globalState.SharedObjects)
{
obj.RestaurarDatosNoSerializables();
}
}
}
public async Task<PageState> LoadPageStateAsync(string pageId)
{
// Retornar página cacheada si existe
if (_loadedPages.TryGetValue(pageId, out var cachedState))
{
return cachedState;
}
var path = Path.Combine(_basePath, $"page_{pageId}.json");
try
{
PageState pageState;
if (File.Exists(path))
{
var json = await File.ReadAllTextAsync(path);
pageState = JsonConvert.DeserializeObject<PageState>(json, GetSerializerSettings());
}
else
{
pageState = new PageState
{
PageId = pageId,
PageName = _globalState.PagesMetadata.GetValueOrDefault(pageId)?.PageName ?? pageId
};
}
_loadedPages[pageId] = pageState;
// Restaurar objetos locales
foreach (var obj in pageState.LocalObjects)
{
await RestoreObjectAsync(obj);
}
// Aplicar estados de objetos globales
ApplyGlobalObjectStates(pageState);
return pageState;
}
catch (Exception ex)
{
// Log error
throw;
}
}
private void ApplyGlobalObjectStates(PageState pageState)
{
foreach (var kvp in pageState.GlobalObjectStates)
{
var globalObj = _globalState.SharedObjects.FirstOrDefault(o => o.Id.Value.ToString() == kvp.Key);
if (globalObj != null)
{
var state = kvp.Value;
globalObj.Left = state.Left;
globalObj.Top = state.Top;
globalObj.Angulo = state.Rotation;
// Actualizar Show_On_This_Page que ahora maneja internamente showOnThisPagesList
if (state.IsVisible && !globalObj.Show_On_This_Page)
globalObj.Show_On_This_Page = true;
else if (!state.IsVisible && globalObj.Show_On_This_Page)
globalObj.Show_On_This_Page = false;
// Aplicar propiedades personalizadas
foreach (var prop in state.CustomProperties)
{
var property = globalObj.GetType().GetProperty(prop.Key);
if (property != null && property.CanWrite)
{
property.SetValue(globalObj, prop.Value);
}
}
}
}
}
private async Task RestoreObjectAsync(osBase obj)
{
if (obj != null)
{
obj.CheckData();
await Task.Run(() =>
{
Application.Current.Dispatcher.Invoke(() =>
{
CrearUserControlDesdeObjetoSimulable(obj);
});
});
}
}
private bool CrearUserControlDesdeObjetoSimulable(osBase osObjeto)
{
Type tipoObjeto = osObjeto.GetType();
UserControl userControl = UserControlFactory.GetControlForType(tipoObjeto);
if (userControl != null)
{
UserControlFactory.AssignDatos(userControl, osObjeto, _mainViewModel.simulationManager);
osObjeto._mainViewModel = _mainViewModel;
if (osObjeto.Id == null)
{
osObjeto.Id = new UniqueId().ObtenerNuevaID();
}
_mainViewModel.MainWindow.AgregarRegistrarUserControlCanvas(userControl);
return true;
}
return false;
}
public async Task SavePageStateAsync(string pageId)
{
if (!_loadedPages.TryGetValue(pageId, out var pageState))
return;
var path = Path.Combine(_basePath, $"page_{pageId}.json");
var backupPath = Path.ChangeExtension(path, ".bak");
// Crear backup
if (File.Exists(path))
{
File.Copy(path, backupPath, true);
}
// Actualizar estado de objetos globales
pageState.GlobalObjectStates.Clear();
foreach (var obj in _mainViewModel.ObjetosSimulables.Where(o => o.Enable_On_All_Pages))
{
var currentPageId = _mainViewModel.SelectedImage;
pageState.GlobalObjectStates[obj.Id.Value.ToString()] = new PageObjectState
{
GlobalObjectId = obj.Id.Value.ToString(),
IsVisible = obj.Show_On_This_Page,
Left = obj.Left,
Top = obj.Top,
Rotation = obj.Angulo,
CustomProperties = CaptureCustomProperties(obj)
};
}
try
{
// Preparar objetos para serialización
foreach (var obj in pageState.LocalObjects)
{
obj.SalvarDatosNoSerializables();
}
var json = JsonConvert.SerializeObject(pageState, GetSerializerSettings());
await File.WriteAllTextAsync(path, json);
_hasUnsavedChanges = false;
PageStateChanged?.Invoke(this, pageId);
}
finally
{
// Restaurar estado de objetos
foreach (var obj in pageState.LocalObjects)
{
obj.RestaurarDatosNoSerializables();
}
}
}
private Dictionary<string, object> CaptureCustomProperties(osBase obj)
{
var customProps = new Dictionary<string, object>();
var properties = obj.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(typeof(SerializeAttribute), true).Any());
foreach (var prop in properties)
{
customProps[prop.Name] = prop.GetValue(obj);
}
return customProps;
}
public async Task SaveAllAsync()
{
foreach (var pageId in _loadedPages.Keys)
{
await SavePageStateAsync(pageId);
}
await SaveGlobalStateAsync();
}
public void Dispose()
{
if (_hasUnsavedChanges)
{
SaveAllAsync().Wait();
}
}
}
/// <summary>
/// Atributo para marcar propiedades que deben ser serializadas como propiedades personalizadas
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class SerializeAttribute : Attribute { }
}