S7Explorer/Services/S7ParserService.cs

258 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Newtonsoft.Json;
using S7Explorer.Models;
using S7Explorer.Parsers;
namespace S7Explorer.Services
{
public class S7ParserService
{
// Evento para notificar mensajes de log
public event EventHandler<string> LogEvent;
// Evento para notificar actualizaciones del árbol
public event EventHandler<S7Object> StructureUpdatedEvent;
// Cancelation support
private CancellationTokenSource _cancellationTokenSource;
// Ruta y nombre del archivo de caché JSON
private static string GetCacheFilePath(string projectFilePath)
{
return Path.ChangeExtension(projectFilePath, ".s7cache.json");
}
// Configuración de JSON para serialización/deserialización
private static JsonSerializerSettings JsonSettings => new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.Auto,
Formatting = Formatting.Indented
};
public async Task<S7Project> ParseProjectAsync(string projectFilePath, bool useCache = true)
{
_cancellationTokenSource = new CancellationTokenSource();
try
{
// Intentar cargar desde caché si useCache es true
if (useCache)
{
var cachedProject = TryLoadFromCache(projectFilePath);
if (cachedProject != null)
{
LogInfo($"Proyecto cargado desde caché: {projectFilePath}");
return cachedProject;
}
}
// Crear nueva instancia del proyecto
var project = new S7Project(projectFilePath);
// Notificar al UI de la estructura base
LogInfo($"Iniciando parseo del proyecto: {projectFilePath}");
// Estructura básica del proyecto
var devicesFolder = new S7Object
{
Name = "Dispositivos",
ObjectType = S7ObjectType.Folder,
Parent = project
};
project.Children.Add(devicesFolder);
// Notificar al UI sobre la estructura inicial
NotifyStructureUpdated(project);
// Iniciar parseo en segundo plano
await Task.Run(() => ParseProjectInBackground(project, devicesFolder, projectFilePath),
_cancellationTokenSource.Token);
return project;
}
catch (OperationCanceledException)
{
LogWarning("Operación de parseo cancelada por el usuario");
throw;
}
catch (Exception ex)
{
LogError($"Error general en parseo: {ex.Message}");
throw;
}
}
public void CancelParsing()
{
_cancellationTokenSource?.Cancel();
}
private void ParseProjectInBackground(S7Project project, S7Object devicesFolder, string projectFilePath)
{
// Crear instancia del parser
var parser = new S7ProjectParser(projectFilePath);
// Conectar eventos de log
parser.LogEvent += (sender, message) => LogEvent?.Invoke(this, message);
parser.StructureUpdatedEvent += (sender, obj) => NotifyStructureUpdated(obj);
try
{
// Parseo de dispositivos
parser.ParseDevices(devicesFolder, _cancellationTokenSource.Token);
// Guardar en caché para futuro uso
SaveToCache(project, projectFilePath);
LogInfo("Parseo del proyecto completado correctamente");
}
catch (OperationCanceledException)
{
LogWarning("Parseo cancelado por el usuario");
throw;
}
catch (Exception ex)
{
LogError($"Error en parseo: {ex.Message}");
// Añadir nodo de error
var errorNode = new S7Object
{
Name = "Error al parsear proyecto",
Description = ex.Message,
ObjectType = S7ObjectType.Folder,
Parent = devicesFolder
};
devicesFolder.Children.Add(errorNode);
// Notificar al UI
NotifyStructureUpdated(devicesFolder);
}
}
private S7Project TryLoadFromCache(string projectFilePath)
{
string cacheFilePath = GetCacheFilePath(projectFilePath);
try
{
if (!File.Exists(cacheFilePath))
{
LogInfo($"No existe archivo de caché: {cacheFilePath}");
return null;
}
// Verificar si el archivo de caché es más reciente que el proyecto
var projectFileInfo = new FileInfo(projectFilePath);
var cacheFileInfo = new FileInfo(cacheFilePath);
if (cacheFileInfo.LastWriteTime < projectFileInfo.LastWriteTime)
{
LogInfo("El archivo de caché es más antiguo que el proyecto. Recargando...");
return null;
}
LogInfo($"Cargando proyecto desde caché: {cacheFilePath}");
string json = File.ReadAllText(cacheFilePath);
var project = JsonConvert.DeserializeObject<S7Project>(json, JsonSettings);
// Reconstruir referencias padre/hijo
RebuildParentChildReferences(project);
LogInfo("Proyecto cargado correctamente desde caché");
return project;
}
catch (Exception ex)
{
LogWarning($"Error al cargar desde caché: {ex.Message}");
// Si hay un error al cargar la caché, simplemente retornar null para forzar el reparseo
try
{
// Intentar eliminar el archivo de caché corrupto
if (File.Exists(cacheFilePath))
{
File.Delete(cacheFilePath);
LogInfo("Archivo de caché corrupto eliminado");
}
}
catch
{
// Ignorar errores al eliminar
}
return null;
}
}
private void SaveToCache(S7Project project, string projectFilePath)
{
string cacheFilePath = GetCacheFilePath(projectFilePath);
try
{
LogInfo($"Guardando proyecto en caché: {cacheFilePath}");
string json = JsonConvert.SerializeObject(project, JsonSettings);
File.WriteAllText(cacheFilePath, json);
LogInfo("Proyecto guardado correctamente en caché");
}
catch (Exception ex)
{
LogWarning($"Error al guardar proyecto en caché: {ex.Message}");
// Continuar a pesar del error, solo es caché
}
}
// Reconstruir referencias padre/hijo después de deserializar JSON
private void RebuildParentChildReferences(S7Object root)
{
foreach (var child in root.Children)
{
child.Parent = root;
RebuildParentChildReferences(child);
}
}
private void NotifyStructureUpdated(S7Object obj)
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
StructureUpdatedEvent?.Invoke(this, obj);
}, DispatcherPriority.Background);
}
#region Logging Methods
private void LogInfo(string message)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
string logMessage = $"[{timestamp}] {message}";
LogEvent?.Invoke(this, logMessage);
}
private void LogWarning(string message)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
string logMessage = $"[{timestamp}] ADVERTENCIA: {message}";
LogEvent?.Invoke(this, logMessage);
}
private void LogError(string message)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
string logMessage = $"[{timestamp}] ERROR: {message}";
LogEvent?.Invoke(this, logMessage);
}
#endregion
}
}