Files
GFATask/GFATask/Classes/FrontEndRefresh.cs

284 lines
9.6 KiB
C#

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Serialization;
namespace GFATask
{
/// <summary>
/// Stellt eine FrontEnd-Update Klasse dar, welche über eine Datei, Änderungen eines gewissen Typs(<T>) ausgibt
/// </summary>
/// <typeparam name="T">Welcher Klassentyp wird in der Datei gespeichert</typeparam>
public class FrontEndRefresh<T>
{
/// <summary>
/// Löst ein Ereignis mit dem angegebenen Ereignis-Typen (<T>) aus, welches eine Veränderung in der Datei entdeckt hat
/// </summary>
public event EventHandler<FrontEndRefreshEventArgs<T>> Updated;
/// <summary>
/// Löst ein Ereignis aus, sobald ein Fehler im Programmcode erzeugt wird
/// </summary>
public event EventHandler<FrontEndErrorEventArgs> Error;
/// <summary>
/// Threadsichere Ausführung des Update-Ereignisses
/// </summary>
private SynchronizationContext synccontext;
/// <summary>
/// Das FileSystemWatcher-Objekt
/// </summary>
private FileSystemWatcher fsw;
/// <summary>
/// Pfad zur Datei die überwacht werden soll
/// </summary>
private string path;
/// <summary>
/// Verhindert das mehrfache Ausführen einer geänderten Datei (Zugriff und Speichern werden immer als Änderung erkannt)
/// </summary>
private Dictionary<string, bool> acceptchange = new Dictionary<string, bool>();
private bool _selfupdate = false;
/// <summary>
/// Bestimmt, ob das Update auch bei dem Host ausgeführt wird, der die Datei verändert hat
/// </summary>
public bool SelfUpdate
{
get => _selfupdate;
set => _selfupdate = value;
}
private bool _active = true;
/// <summary>
/// Überprüft, ob Updates an FrontEnds gesendet werden sollen
/// </summary>
public bool Active
{
get => _active;
set => _active = value;
}
/// <summary>
/// Stellt eine FrontEnd-Update Klasse dar, welche über eine Datei, Änderungen eines gewissen Typs(<T>) ausgibt
/// </summary>
/// <param name="filename">Gibt den Pfad zur überwachenden Datei an. Ist diese noch nicht vorhanden, wird sie erstellt</param>
public FrontEndRefresh(string filename)
{
try
{
path = filename;
if (!File.Exists(path))
File.Create(path).Close();
using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
{
fsw = new FileSystemWatcher(Path.GetDirectoryName(fs.Name), Path.GetFileName(fs.Name));
fsw.Changed += Fsw_Changed;
}
}
catch (Exception ex)
{
Error?.Invoke(this, new FrontEndErrorEventArgs(ex));
}
}
private void Fsw_Changed(object sender, FileSystemEventArgs e)
{
// Diese Methode überspringt die Änderung beim ersten Mal
if (acceptchange.ContainsKey(path))
acceptchange[path] = true;
else
acceptchange.Add(path, false);
if (acceptchange[path])
{
try
{
if (synccontext != null)
{
synccontext.Post(s =>
{
FrontEndObject o = this.DeserializeObject<FrontEndObject>();
if (_selfupdate || (!_selfupdate && o.HostName != Dns.GetHostName()))
{
acceptchange.Remove(path);
Updated?.Invoke(this, new FrontEndRefreshEventArgs<T>(o.Data, path, o.CurrentAssembly, o.HostName, o.IPv4, o.UpdateTime));
}
}, null);
}
else
{
FrontEndObject o = this.DeserializeObject<FrontEndObject>();
if (_selfupdate || (!_selfupdate && o.HostName != Dns.GetHostName()))
{
acceptchange.Remove(path);
Updated?.Invoke(this, new FrontEndRefreshEventArgs<T>(o.Data, path, o.CurrentAssembly, o.HostName, o.IPv4, o.UpdateTime));
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
/// <summary>
/// Schreibt das Objekt als XML-Model in die Datei
/// </summary>
/// <typeparam name="U"></typeparam>
/// <param name="toSerialize"></param>
private void SerializeObject<U>(U toSerialize)
{
synccontext = SynchronizationContext.Current;
System.Serialization.Xml.SerializeToFile(path, toSerialize);
}
/// <summary>
/// Liest das XML-Model als Objekt, aus der Datei aus
/// </summary>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
private U DeserializeObject<U>()
{
synccontext = SynchronizationContext.Current;
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
{
//if (Verifying.IsFileLocked(new FileInfo(fs.Name)))
// return default(U);
//else
return System.Serialization.Xml.DeserializeFromFile<U>(fs.Name);
}
}
/// <summary>
/// Führt ein Update am FrontEnd und Remote-FrontEnds aus, sofern die Klasse aktiv / gestartet ist
/// </summary>
/// <param name="data"></param>
public void Update(T data)
{
try
{
if (!_active)
return;
FrontEndObject o = new FrontEndObject()
{
Data = data,
CurrentAssembly = Assembly.GetExecutingAssembly().GetName().Name,
HostName = Dns.GetHostName(),
IPv4 = Net.GetIPv4(Dns.GetHostName()).ToString(),
UpdateTime = DateTime.Now
};
this.SerializeObject(o);
if (_selfupdate)
{
if (synccontext != null)
synccontext.Post(s => Updated?.Invoke(this, new FrontEndRefreshEventArgs<T>(o.Data, path, o.CurrentAssembly, o.HostName, o.IPv4, o.UpdateTime)), null);
else
Updated?.Invoke(this, new FrontEndRefreshEventArgs<T>(o.Data, path, o.CurrentAssembly, o.HostName, o.IPv4, o.UpdateTime));
}
}
catch (Exception ex)
{
Error?.Invoke(this, new FrontEndErrorEventArgs(ex));
}
}
/// <summary>
/// Startet die Überwachung
/// </summary>
public void Start()
{
fsw.EnableRaisingEvents = true;
synccontext = SynchronizationContext.Current;
}
/// <summary>
/// Stopt die Überwachung
/// </summary>
public void Stop()
{
fsw.EnableRaisingEvents = false;
}
public class FrontEndObject
{
public T Data { get; set; }
public int Length { get; set; }
public string HostName { get; set; }
public string IPv4 { get; set; }
public DateTime UpdateTime { get; set; }
public string CurrentAssembly { get; set; }
public FrontEndObject() { }
new public string ToString()
{
return string.Join("\n", this.GetType().GetProperties().Select(prop => prop.Name + ": " + prop.GetValue(this)));
}
}
}
public class FrontEndRefreshEventArgs<T>
{
[JsonProperty("assembly")]
public string Assembly { get; set; }
[JsonProperty("host")]
public string Host { get; set; }
[JsonProperty("ipv4")]
public string IPv4 { get; set; }
[JsonProperty("updatetime")]
public DateTime UpdateTime { get; set; } = DateTime.Now;
[JsonProperty("path")]
public string Path { get; set; } = string.Empty;
[JsonProperty("data")]
public T Data { get; set; }
public FrontEndRefreshEventArgs(T data, string path, string assembly, string host, string ipv4, DateTime updatetime)
{
Data = data;
Path = path;
Assembly = assembly;
Host = host;
IPv4 = ipv4;
UpdateTime = updatetime;
}
}
public class FrontEndErrorEventArgs
{
public Exception Ex { get; set; }
public FrontEndErrorEventArgs(Exception ex) =>
Ex = ex;
}
}