258 lines
8.9 KiB
C#
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
|
|
}
|
|
} |