284 lines
9.6 KiB
C#
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;
|
|
}
|
|
}
|