mirror of
https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
synced 2025-10-30 01:35:32 +00:00
Setup Git Repository Parsing
This commit is contained in:
85
HaWeb/FileHelpers/ConfigurationMonitor.cs
Normal file
85
HaWeb/FileHelpers/ConfigurationMonitor.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Timers;
|
||||
|
||||
namespace HaWeb.FileHelpers;
|
||||
|
||||
public class ConfigurationMonitor {
|
||||
private System.Timers.Timer? _timer;
|
||||
private (string, byte[])[]? _h;
|
||||
private IServiceProvider _serviceProvider;
|
||||
|
||||
public ConfigurationMonitor(string[] paths, IServiceProvider services) {
|
||||
_h = _getHash(paths);
|
||||
_serviceProvider = services;
|
||||
}
|
||||
|
||||
private static (string, byte[])[]? _getHash(string[] paths) {
|
||||
if (paths == null || !paths.Any()) return null;
|
||||
var ret = new List<(string, byte[])>();
|
||||
foreach(var c in paths)
|
||||
ret.Add((c, _computeHash(c)));
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
private bool isEqual((string, byte[])[]? _h1, (string, byte[])[]? _h2) {
|
||||
if (_h1 == null && _h2 == null) return true;
|
||||
if (_h1 == null && _h2 != null) return false;
|
||||
if (_h2 == null && _h1 != null) return false;
|
||||
if (_h1!.Count() != _h2!.Count()) return false;
|
||||
foreach (var h1 in _h1!) {
|
||||
foreach (var h2 in _h2!) {
|
||||
if (h1.Item1 == h2.Item1 && !Enumerable.SequenceEqual(h1.Item2, h2.Item2)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void InvokeChanged(string[] paths) {
|
||||
var h = _getHash(paths);
|
||||
if (_timer == null && !isEqual(h, _h)) {
|
||||
_h = h;
|
||||
_timer = new(5000) { AutoReset = false };
|
||||
_timer.Enabled = true;
|
||||
_timer.Elapsed += Action;
|
||||
}
|
||||
}
|
||||
|
||||
private void Action(Object source, System.Timers.ElapsedEventArgs e) {
|
||||
Console.WriteLine("Configuration changed (ConfigurationMonitor Class)");
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
IServiceProvider provider = serviceScope.ServiceProvider;
|
||||
|
||||
var cP = provider.GetRequiredService<IConfiguration>();
|
||||
var hP = provider.GetRequiredService<IHaDocumentWrappper>();
|
||||
hP.ParseConfiguration(cP);
|
||||
var fP = provider.GetRequiredService<IXMLFileProvider>();
|
||||
fP.Reload(cP);
|
||||
|
||||
// _lifetime.StopApplication();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private static byte[] _computeHash(string filePath) {
|
||||
var runCount = 1;
|
||||
|
||||
while(runCount < 4) {
|
||||
try {
|
||||
if (File.Exists(filePath))
|
||||
using (var fs = File.OpenRead(filePath)) {
|
||||
return System.Security.Cryptography.SHA1
|
||||
.Create().ComputeHash(fs);
|
||||
}
|
||||
else {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (runCount == 3)
|
||||
throw;
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
|
||||
runCount++;
|
||||
}
|
||||
}
|
||||
return new byte[20];
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,13 @@ using HaXMLReader.Interfaces;
|
||||
using HaWeb.SearchHelpers;
|
||||
using HaWeb.XMLParser;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using System.Diagnostics;
|
||||
|
||||
public class HaDocumentWrapper : IHaDocumentWrappper {
|
||||
private ILibrary Library;
|
||||
private IXMLProvider _xmlProvider;
|
||||
private IXMLService _xmlService;
|
||||
private string _filepath;
|
||||
private IFileInfo _ActiveFile;
|
||||
private ILibrary? Library;
|
||||
private IXMLInteractionService _xmlService;
|
||||
private int _startYear;
|
||||
private int _endYear;
|
||||
private List<Person>? _availablePersons;
|
||||
@@ -22,23 +23,14 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
||||
|
||||
// public List<SearchHelpers.CollectedItem>? SearchableLetters { get; private set; }
|
||||
|
||||
public HaDocumentWrapper(IXMLProvider xmlProvider, IXMLService service, IConfiguration configuration) {
|
||||
_xmlProvider = xmlProvider;
|
||||
public HaDocumentWrapper(IXMLInteractionService service, IConfiguration configuration) {
|
||||
_xmlService = service;
|
||||
ParseConfiguration(configuration);
|
||||
}
|
||||
|
||||
public void ParseConfiguration(IConfiguration configuration) {
|
||||
_startYear = configuration.GetValue<int>("AvailableStartYear");
|
||||
_endYear = configuration.GetValue<int>("AvailableEndYear");
|
||||
var filelist = xmlProvider.GetHamannFiles();
|
||||
if (filelist != null && filelist.Any()) {
|
||||
_AutoLoad(filelist);
|
||||
}
|
||||
|
||||
// Use Fallback library
|
||||
if (Library == null) {
|
||||
var options = new HaWeb.Settings.HaDocumentOptions();
|
||||
if (SetLibrary(options.HamannXMLFilePath) == null) {
|
||||
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Person>? GetAvailablePersons() => _availablePersons;
|
||||
@@ -49,26 +41,29 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
||||
|
||||
public int GetEndYear() => _endYear;
|
||||
|
||||
public void SetEndYear(int end) {
|
||||
this._endYear = end;
|
||||
SetLibrary(_filepath);
|
||||
}
|
||||
public IFileInfo GetActiveFile() => _ActiveFile;
|
||||
|
||||
public ILibrary? SetLibrary(string filepath, ModelStateDictionary? ModelState = null) {
|
||||
// 1. Set ILibrary
|
||||
public ILibrary? SetLibrary(IFileInfo? file, XDocument? doc, ModelStateDictionary? ModelState = null) {
|
||||
// Handle null on file & doc
|
||||
var path = file == null ? new HaWeb.Settings.HaDocumentOptions().HamannXMLFilePath : file.PhysicalPath;
|
||||
if (doc == null) doc = XDocument.Load(path, LoadOptions.PreserveWhitespace);
|
||||
|
||||
// 1. Parse the Document, create search Index
|
||||
if (_xmlService != null)
|
||||
_xmlService.CreateSearchables(doc);
|
||||
// 2. Set ILibrary
|
||||
try {
|
||||
Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions() { HamannXMLFilePath = filepath, AvailableYearRange = (_startYear, _endYear) });
|
||||
Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions() { HamannXMLFilePath = path, AvailableYearRange = (_startYear, _endYear) }, doc.Root);
|
||||
} catch (Exception ex) {
|
||||
if (ModelState != null) ModelState.AddModelError("Error", "Das Dokument konnte nicht geparst werden: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1a. Set Available Persons
|
||||
// 3a. Set Available Persons
|
||||
var persons = Library.Metas.SelectMany(x => x.Value.Senders.Union(x.Value.Receivers)).Distinct();
|
||||
_availablePersons = persons.Select(x => Library.Persons[x]).OrderBy(x => x.Surname).ThenBy(x => x.Prename).ToList();
|
||||
|
||||
// 1b. Setup a Dictionary with available Person ovierview Pages
|
||||
|
||||
// 3b. Setup a Dictionary with available Person ovierview Pages
|
||||
_personsWithLetters = new Dictionary<string, Person>();
|
||||
var availablePersonPages = Library.Persons.Where(x => !String.IsNullOrWhiteSpace(x.Value.Komm));
|
||||
foreach (var p in availablePersonPages) {
|
||||
@@ -77,30 +72,12 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Set Library in Production, collect some Objects
|
||||
if (_xmlService != null)
|
||||
_xmlService.SetInProduction(System.Xml.Linq.XDocument.Load(filepath, System.Xml.Linq.LoadOptions.PreserveWhitespace));
|
||||
|
||||
// 3. Set Filepath
|
||||
_filepath = filepath;
|
||||
// 4. Set info on loaded file
|
||||
_ActiveFile = file;
|
||||
return Library;
|
||||
}
|
||||
|
||||
public ILibrary GetLibrary() {
|
||||
public ILibrary? GetLibrary() {
|
||||
return Library;
|
||||
}
|
||||
|
||||
private void _AutoLoad(List<IFileInfo> files) {
|
||||
var orderdlist = files.OrderByDescending(x => x.LastModified);
|
||||
foreach (var item in orderdlist) {
|
||||
if (SetLibrary(item.PhysicalPath) != null) {
|
||||
_xmlProvider.SetInProduction(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _prepareSearch(HaDocument.Interfaces.ISearchable objecttoseach) {
|
||||
return SearchHelpers.StringHelpers.NormalizeWhiteSpace(objecttoseach.Element, ' ', false);
|
||||
}
|
||||
}
|
||||
6
HaWeb/FileHelpers/IConfigurationMonitor.cs
Normal file
6
HaWeb/FileHelpers/IConfigurationMonitor.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace HaWeb.FileHelpers;
|
||||
|
||||
public interface IConfigurationMonitor
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,14 +3,16 @@ using HaDocument.Interfaces;
|
||||
using HaDocument.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using HaXMLReader.Interfaces;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Xml.Linq;
|
||||
|
||||
public interface IHaDocumentWrappper {
|
||||
public ILibrary? SetLibrary(string filepath, ModelStateDictionary ModelState);
|
||||
public ILibrary GetLibrary();
|
||||
|
||||
public ILibrary? SetLibrary(IFileInfo? file, XDocument? doc, ModelStateDictionary? ModelState);
|
||||
public ILibrary? GetLibrary();
|
||||
public void ParseConfiguration(IConfiguration configuration);
|
||||
public int GetStartYear();
|
||||
public int GetEndYear();
|
||||
public IFileInfo GetActiveFile();
|
||||
public List<Person>? GetAvailablePersons();
|
||||
public Dictionary<string, Person>? GetPersonsWithLetters();
|
||||
public void SetEndYear(int end);
|
||||
}
|
||||
17
HaWeb/FileHelpers/IXMLFileProvider.cs
Normal file
17
HaWeb/FileHelpers/IXMLFileProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace HaWeb.FileHelpers;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Xml.Linq;
|
||||
using HaWeb.Models;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
public interface IXMLFileProvider {
|
||||
public List<IFileInfo>? GetWorkingTreeFiles();
|
||||
public IFileInfo? SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState);
|
||||
public List<IFileInfo>? GetHamannFiles();
|
||||
public (DateTime PullTime, string Hash)? GetGitData();
|
||||
public void Reload(IConfiguration config);
|
||||
public bool HasChanged();
|
||||
public void DeleteHamannFile(string filename);
|
||||
public void Scan();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace HaWeb.FileHelpers;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Xml.Linq;
|
||||
using HaWeb.Models;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
public interface IXMLProvider {
|
||||
public FileList? GetFiles(string prefix);
|
||||
public Task Save(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState);
|
||||
public Task<IFileInfo?> SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState);
|
||||
public List<IFileInfo>? GetHamannFiles();
|
||||
public IFileInfo? GetInProduction();
|
||||
public void SetInProduction(IFileInfo info);
|
||||
public void DeleteHamannFile(string filename);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace HaWeb.FileHelpers;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
public static class XDocumentFileHelper {
|
||||
|
||||
private readonly static XmlReaderSettings _Settings = new XmlReaderSettings() {
|
||||
CloseInput = true,
|
||||
CheckCharacters = false,
|
||||
ConformanceLevel = ConformanceLevel.Fragment,
|
||||
IgnoreComments = true,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreWhitespace = false
|
||||
};
|
||||
|
||||
public static async Task<XDocument?> ProcessStreamedFile(byte[] bytes, ModelStateDictionary modelState) {
|
||||
try {
|
||||
using (var stream = new MemoryStream(bytes)) {
|
||||
using (var xmlreader = XmlReader.Create(stream, _Settings)) {
|
||||
return XDocument.Load(xmlreader, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
modelState.AddModelError("Error", $"Kein gültiges XML-Dokument geladen. Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -54,152 +54,54 @@ public static class XMLFileHelpers {
|
||||
}
|
||||
};
|
||||
|
||||
// Unused as of rn, used to take a file and do the same sanity checks as below
|
||||
// public static async Task<byte[]> ProcessFormFile<T>(IFormFile formFile, ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit)
|
||||
// {
|
||||
// var fieldDisplayName = string.Empty;
|
||||
|
||||
// // Use reflection to obtain the display name for the model
|
||||
// // property associated with this IFormFile. If a display
|
||||
// // name isn't found, error messages simply won't show
|
||||
// // a display name.
|
||||
// MemberInfo property =
|
||||
// typeof(T).GetProperty(
|
||||
// formFile.Name.Substring(formFile.Name.IndexOf(".",
|
||||
// StringComparison.Ordinal) + 1));
|
||||
|
||||
// if (property != null)
|
||||
// {
|
||||
// if (property.GetCustomAttribute(typeof(DisplayAttribute)) is
|
||||
// DisplayAttribute displayAttribute)
|
||||
// {
|
||||
// fieldDisplayName = $"{displayAttribute.Name} ";
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Don't trust the file name sent by the client. To display
|
||||
// // the file name, HTML-encode the value.
|
||||
// var trustedFileNameForDisplay = WebUtility.HtmlEncode(
|
||||
// formFile.FileName);
|
||||
|
||||
// // Check the file length. This check doesn't catch files that only have
|
||||
// // a BOM as their content.
|
||||
// if (formFile.Length == 0)
|
||||
// {
|
||||
// modelState.AddModelError(formFile.Name,
|
||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
|
||||
|
||||
// return Array.Empty<byte>();
|
||||
// }
|
||||
|
||||
// if (formFile.Length > sizeLimit)
|
||||
// {
|
||||
// var megabyteSizeLimit = sizeLimit / 1048576;
|
||||
// modelState.AddModelError(formFile.Name,
|
||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
|
||||
// $"{megabyteSizeLimit:N1} MB.");
|
||||
|
||||
// return Array.Empty<byte>();
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// using (var memoryStream = new MemoryStream())
|
||||
// {
|
||||
// await formFile.CopyToAsync(memoryStream);
|
||||
|
||||
// // Check the content length in case the file's only
|
||||
// // content was a BOM and the content is actually
|
||||
// // empty after removing the BOM.
|
||||
// if (memoryStream.Length == 0)
|
||||
// {
|
||||
// modelState.AddModelError(formFile.Name,
|
||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
|
||||
// }
|
||||
|
||||
// if (!IsValidFileExtensionAndSignature(
|
||||
// formFile.FileName, memoryStream, permittedExtensions))
|
||||
// {
|
||||
// modelState.AddModelError(formFile.Name,
|
||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) file " +
|
||||
// "type isn't permitted or the file's signature " +
|
||||
// "doesn't match the file's extension.");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return memoryStream.ToArray();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// modelState.AddModelError(formFile.Name,
|
||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " +
|
||||
// $"Please contact the Help Desk for support. Error: {ex.HResult}");
|
||||
// }
|
||||
|
||||
// return Array.Empty<byte>();
|
||||
// public static List<FileModel>? ToFileModel(FileList? fileList) {
|
||||
// if (fileList == null) return null;
|
||||
// var fL = fileList.GetFileList();
|
||||
// if (fL == null) return null;
|
||||
// var ret = new List<FileModel>();
|
||||
// foreach (var f in fL) {
|
||||
// if (f.File == null) continue;
|
||||
// ret.Add(ToFileModel(f));
|
||||
// };
|
||||
// return ret.OrderBy(x => x.LastModified).ToList();
|
||||
// }
|
||||
|
||||
public static List<FileModel>? ToFileModel(FileList? fileList, Dictionary<string, FileList?>? productionFiles = null, Dictionary<string, FileList?>? usedFiles = null) {
|
||||
if (fileList == null) return null;
|
||||
var fL = fileList.GetFileList();
|
||||
if (fL == null) return null;
|
||||
var ret = new List<FileModel>();
|
||||
foreach (var f in fL) {
|
||||
if (f.File == null) continue;
|
||||
ret.Add(ToFileModel(f, productionFiles, usedFiles));
|
||||
};
|
||||
return ret.OrderBy(x => x.LastModified).ToList();
|
||||
}
|
||||
// // TODO: File State IsValid
|
||||
// public static FileModel ToFileModel(XMLRootDocument document) {
|
||||
// string id = document.Prefix;
|
||||
// var model = new FileModel(document.FileName, document.File.LastModified.LocalDateTime, true) {
|
||||
// Fields = document.Fields,
|
||||
// Messages = document.GetLog(),
|
||||
// Prefix = id
|
||||
// };
|
||||
// return model;
|
||||
// }
|
||||
|
||||
public static FileModel ToFileModel(XMLRootDocument document, Dictionary<string, FileList?>? productionFiles = null, Dictionary<string, FileList?>? usedFiles = null) {
|
||||
string id = document.Prefix;
|
||||
|
||||
bool inProduction = false;
|
||||
if (productionFiles != null && productionFiles.ContainsKey(id)) {
|
||||
inProduction = productionFiles[id]!.Contains(document);
|
||||
}
|
||||
|
||||
bool isUsed = false;
|
||||
if (usedFiles != null && usedFiles.ContainsKey(id)) {
|
||||
isUsed = usedFiles[id]!.Contains(document);
|
||||
}
|
||||
|
||||
var model = new FileModel(document.FileName, document.Prefix, document.File.LastModified.LocalDateTime, isUsed, inProduction) { Fields = document.Fields };
|
||||
model.Messages = document.GetLog();
|
||||
return model;
|
||||
}
|
||||
|
||||
public static async Task<byte[]?> ProcessStreamedFile(
|
||||
MultipartSection section, ContentDispositionHeaderValue contentDisposition,
|
||||
ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) {
|
||||
public static bool ProcessFile(
|
||||
Stream file,
|
||||
string fileName,
|
||||
StringBuilder errorMessages,
|
||||
string[] permittedExtensions,
|
||||
long sizeLimit) {
|
||||
try {
|
||||
using (var memoryStream = new MemoryStream()) {
|
||||
await section.Body.CopyToAsync(memoryStream);
|
||||
|
||||
// Check if the file is empty or exceeds the size limit.
|
||||
if (memoryStream.Length == 0)
|
||||
modelState.AddModelError("Error", "The file is empty.");
|
||||
else if (memoryStream.Length > sizeLimit) {
|
||||
var megabyteSizeLimit = sizeLimit / 1048576;
|
||||
modelState.AddModelError("Error", $"The file exceeds {megabyteSizeLimit:N1} MB.");
|
||||
}
|
||||
|
||||
// Check file extension and first bytes
|
||||
else if (!IsValidFileExtensionAndSignature(contentDisposition.FileName.Value, memoryStream, permittedExtensions))
|
||||
modelState.AddModelError("Error", "The file must be of the following specs:<br>" +
|
||||
"1. The file must hava a .xml File-Extension<br>" +
|
||||
"2. To make sure the file isn't executable the file must start with: <?xml version=\"1.0\" encoding=\"utf-8\"?> or <?xml version=\"1.0\"?>");
|
||||
|
||||
// Return the File as a byte array
|
||||
else return memoryStream.ToArray();
|
||||
// Check if the file is empty or exceeds the size limit.
|
||||
if (file.Length == 0) {
|
||||
errorMessages.AppendLine("Die Datei ist leer.");
|
||||
return false;
|
||||
}
|
||||
else if (file.Length > sizeLimit) {
|
||||
var megabyteSizeLimit = sizeLimit / 1048576;
|
||||
errorMessages.AppendLine($"Die Datei überschreitet das Größenlimit {megabyteSizeLimit:N1} MB.");
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
modelState.AddModelError("Error", $"The upload failed. Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
// Return orderly, if signature & extension okay
|
||||
else return IsValidFileExtensionAndSignature(fileName, file, errorMessages, permittedExtensions);
|
||||
|
||||
} catch (Exception ex) {
|
||||
errorMessages.AppendLine($"The upload failed. Error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? StreamToString(System.IO.Stream stream, ModelStateDictionary modelState) {
|
||||
@@ -216,22 +118,26 @@ public static class XMLFileHelpers {
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) {
|
||||
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, StringBuilder errorMessages, string[] permittedExtensions) {
|
||||
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
|
||||
return false;
|
||||
|
||||
var ext = Path.GetExtension(fileName).ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
|
||||
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext)) {
|
||||
errorMessages.AppendLine("Dateiname endet nicht auf .xml");
|
||||
return false;
|
||||
}
|
||||
|
||||
data.Position = 0;
|
||||
|
||||
using (var reader = new BinaryReader(data)) {
|
||||
var signatures = _fileSignature[ext];
|
||||
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
|
||||
return signatures.Any(signature =>
|
||||
headerBytes.Take(signature.Length).SequenceEqual(signature));
|
||||
if (!signatures.Any(signature =>
|
||||
headerBytes.Take(signature.Length).SequenceEqual(signature))) {
|
||||
errorMessages.AppendLine("Datei muss mit <?xml version=\"1.0\" encoding=\"utf-8\"?> oder <?xml version=\"1.0\"?> beginnen.");
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
228
HaWeb/FileHelpers/XMLFileProvider.cs
Normal file
228
HaWeb/FileHelpers/XMLFileProvider.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
namespace HaWeb.FileHelpers;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using HaWeb.Models;
|
||||
using HaWeb.XMLParser;
|
||||
using HaWeb.XMLTests;
|
||||
using System.Xml.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
|
||||
// XMLProvider provides a wrapper around the available XML data on a FILE basis
|
||||
public class XMLFileProvider : IXMLFileProvider {
|
||||
private readonly IHaDocumentWrappper _Lib;
|
||||
private readonly IXMLInteractionService _XMLService;
|
||||
|
||||
private IFileProvider _hamannFileProvider;
|
||||
private IFileProvider _bareRepositoryFileProvider;
|
||||
private IFileProvider _workingTreeFileProvider;
|
||||
|
||||
private string _Branch;
|
||||
|
||||
private List<IFileInfo>? _WorkingTreeFiles;
|
||||
private List<IFileInfo>? _HamannFiles;
|
||||
|
||||
private static (DateTime PullTime, string Hash)? _GitData;
|
||||
|
||||
// Startup (LAST)
|
||||
public XMLFileProvider(IXMLInteractionService xmlservice, IHaDocumentWrappper _lib, IConfiguration config) {
|
||||
// TODO: Test Read / Write Access
|
||||
_Lib = _lib;
|
||||
_XMLService = xmlservice;
|
||||
|
||||
_Branch = config.GetValue<string>("RepositoryBranch");
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreWindows"));
|
||||
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathWindows"));
|
||||
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathWindows"));
|
||||
}
|
||||
else {
|
||||
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreLinux"));
|
||||
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathLinux"));
|
||||
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathLinux"));
|
||||
}
|
||||
|
||||
// Create File Lists; Here and in xmlservice, which does preliminary checking
|
||||
Scan();
|
||||
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||
xmlservice.Collect(_WorkingTreeFiles);
|
||||
}
|
||||
_HamannFiles = _ScanHamannFiles();
|
||||
|
||||
// Check if hamann file already is current working tree status
|
||||
// -> YES: Load up the file via _lib.SetLibrary();
|
||||
if (_IsAlreadyParsed()) {
|
||||
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||
if (_Lib.GetLibrary() != null) return;
|
||||
}
|
||||
|
||||
// -> NO: Try to create a new file
|
||||
var created = xmlservice.TryCreate();
|
||||
if (created != null) {
|
||||
var file = SaveHamannFile(created, _hamannFileProvider.GetFileInfo("./").PhysicalPath, null);
|
||||
if (file != null) {
|
||||
_lib.SetLibrary(file, created.Document, null);
|
||||
if (_Lib.GetLibrary() != null) return;
|
||||
}
|
||||
}
|
||||
|
||||
// It failed, so use the last best File:
|
||||
else if (_HamannFiles != null && _HamannFiles.Any()) {
|
||||
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||
if (_Lib.GetLibrary() != null) return;
|
||||
}
|
||||
|
||||
// -> There is none? Use Fallback:
|
||||
else {
|
||||
var options = new HaWeb.Settings.HaDocumentOptions();
|
||||
if (_lib.SetLibrary(null, null, null) == null) {
|
||||
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reload(IConfiguration config) {
|
||||
_Branch = config.GetValue<string>("RepositoryBranch");
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreWindows"));
|
||||
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathWindows"));
|
||||
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathWindows"));
|
||||
}
|
||||
else {
|
||||
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreLinux"));
|
||||
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathLinux"));
|
||||
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathLinux"));
|
||||
}
|
||||
|
||||
// Create File Lists; Here and in xmlservice, which does preliminary checking
|
||||
Scan();
|
||||
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||
_XMLService.Collect(_WorkingTreeFiles);
|
||||
}
|
||||
_HamannFiles = _ScanHamannFiles();
|
||||
|
||||
// Check if hamann file already is current working tree status
|
||||
// -> YES: Load up the file via _lib.SetLibrary();
|
||||
if (_IsAlreadyParsed()) {
|
||||
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||
if (_Lib.GetLibrary() != null) return;
|
||||
}
|
||||
|
||||
// -> NO: Try to create a new file
|
||||
var created = _XMLService.TryCreate();
|
||||
if (created != null) {
|
||||
var file = SaveHamannFile(created, _hamannFileProvider.GetFileInfo("./").PhysicalPath, null);
|
||||
if (file != null) {
|
||||
_Lib.SetLibrary(file, created.Document, null);
|
||||
if (_Lib.GetLibrary() != null) return;
|
||||
}
|
||||
}
|
||||
|
||||
// It failed, so use the last best File:
|
||||
else if (_HamannFiles != null && _HamannFiles.Any()) {
|
||||
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||
if (_Lib.GetLibrary() != null) return;
|
||||
}
|
||||
|
||||
// -> There is none? Use Fallback:
|
||||
else {
|
||||
var options = new HaWeb.Settings.HaDocumentOptions();
|
||||
if (_Lib.SetLibrary(null, null, null) == null) {
|
||||
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public List<IFileInfo>? GetWorkingTreeFiles() => _WorkingTreeFiles;
|
||||
|
||||
public (DateTime PullTime, string Hash)? GetGitData() => _GitData;
|
||||
|
||||
public List<IFileInfo>? GetHamannFiles() => this._HamannFiles;
|
||||
|
||||
// Functions
|
||||
public void DeleteHamannFile(string filename) {
|
||||
if (_HamannFiles == null) return;
|
||||
var files = _HamannFiles.Where(x => x.Name == filename);
|
||||
foreach (var file in files) {
|
||||
File.Delete(file.PhysicalPath);
|
||||
}
|
||||
_HamannFiles.RemoveAll(x => x.Name == filename);
|
||||
}
|
||||
|
||||
public void Scan() {
|
||||
_WorkingTreeFiles = _ScanWorkingTreeFiles();
|
||||
_GitData = _ScanGitData();
|
||||
}
|
||||
|
||||
public IFileInfo? SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary? ModelState) {
|
||||
if (!_GitData.HasValue) return null;
|
||||
var filename = "hamann_" + _GitData.Value.PullTime.Year + "-" + _GitData.Value.PullTime.Month + "-" + _GitData.Value.PullTime.Day + "_" + _GitData.Value.PullTime.Hour + "-" + _GitData.Value.PullTime.Minute + "." + _GitData.Value.Hash.Substring(0,7) + ".xml";
|
||||
var path = Path.Combine(basefilepath, filename);
|
||||
|
||||
try {
|
||||
if (!Directory.Exists(basefilepath))
|
||||
Directory.CreateDirectory(basefilepath);
|
||||
using (var targetStream = System.IO.File.Create(path))
|
||||
element.Save(targetStream, SaveOptions.DisableFormatting);
|
||||
} catch (Exception ex) {
|
||||
if (ModelState != null) ModelState.AddModelError("Error", "Die Datei konnte nicht gespeichert werden: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
var info = _hamannFileProvider.GetFileInfo(filename);
|
||||
if (info == null) {
|
||||
if (ModelState != null) ModelState.AddModelError("Error", "Auf die neu erstellte Datei konnte nicht zugegriffen werden.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_HamannFiles == null) _HamannFiles = new List<IFileInfo>();
|
||||
_HamannFiles.RemoveAll(x => x.Name == info.Name);
|
||||
_HamannFiles.Add(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
public bool HasChanged() {
|
||||
if (!_GitData.HasValue) return true;
|
||||
var current = _ScanGitData();
|
||||
if (current.Item2 != _GitData.Value.Hash) {
|
||||
_GitData = current;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private (DateTime, string) _ScanGitData() {
|
||||
var head = _bareRepositoryFileProvider.GetFileInfo("refs/heads/" + _Branch);
|
||||
return (head.LastModified.DateTime, File.ReadAllText(head.PhysicalPath));
|
||||
}
|
||||
|
||||
private void _RegisterChangeCallbacks() {
|
||||
var cT = _bareRepositoryFileProvider.Watch("refs/heads/" + _Branch);
|
||||
}
|
||||
|
||||
// Gets all XML Files
|
||||
private List<IFileInfo>? _ScanWorkingTreeFiles() {
|
||||
var files = _workingTreeFileProvider.GetDirectoryContents(string.Empty)!.Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))!.ToList();
|
||||
return files;
|
||||
}
|
||||
|
||||
private List<IFileInfo>? _ScanHamannFiles() {
|
||||
var files = _hamannFileProvider.GetDirectoryContents(string.Empty).Where(x => !x.IsDirectory && x.Name.StartsWith("hamann") && x.Name.EndsWith(".xml"));
|
||||
if (files == null || !files.Any()) return null;
|
||||
return files.OrderByDescending(x => x.LastModified).ToList();
|
||||
}
|
||||
|
||||
private string? _GetHashFromHamannFilename(string filename) {
|
||||
var s = filename.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (s.Length != 3 || s.Last() != "xml" || !s.First().StartsWith("hamann")) return null;
|
||||
return s[1];
|
||||
}
|
||||
|
||||
private bool _IsAlreadyParsed() {
|
||||
if (_HamannFiles == null || !_HamannFiles.Any() || !_GitData.HasValue) return false;
|
||||
var fhash = _GetHashFromHamannFilename(_HamannFiles.First().Name);
|
||||
var ghash = _GitData.Value.Hash.Substring(0,7);
|
||||
return fhash == ghash;
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
namespace HaWeb.FileHelpers;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using HaWeb.Models;
|
||||
using HaWeb.XMLParser;
|
||||
using HaWeb.XMLTests;
|
||||
using System.Xml.Linq;
|
||||
|
||||
// XMLProvider provides a wrapper around the available XML data on a FILE basis
|
||||
public class XMLProvider : IXMLProvider {
|
||||
private IFileProvider _fileProvider;
|
||||
private Dictionary<string, FileList?>? _Files;
|
||||
private Dictionary<string, IXMLRoot>? _Roots;
|
||||
private List<IFileInfo>? _HamannFiles;
|
||||
private Stack<IFileInfo>? _InProduction;
|
||||
|
||||
public XMLProvider(IFileProvider provider, IXMLService xmlservice, IXMLTestService testService) {
|
||||
_fileProvider = provider;
|
||||
_Roots = xmlservice.GetRootsDictionary();
|
||||
_Files = _ScanFiles();
|
||||
_HamannFiles = _ScanHamannFiles();
|
||||
|
||||
if (_Files != null)
|
||||
foreach (var category in _Files)
|
||||
if (category.Value != null)
|
||||
xmlservice.AutoUse(category.Value);
|
||||
|
||||
testService.Test();
|
||||
}
|
||||
|
||||
public List<IFileInfo>? GetHamannFiles() => this._HamannFiles;
|
||||
|
||||
public IFileInfo? GetInProduction() {
|
||||
if (_InProduction == null || !_InProduction.Any()) return null;
|
||||
return this._InProduction.Peek();
|
||||
}
|
||||
|
||||
public void DeleteHamannFile(string filename) {
|
||||
if (_HamannFiles == null) return;
|
||||
var files = _HamannFiles.Where(x => x.Name == filename);
|
||||
foreach (var file in files) {
|
||||
File.Delete(file.PhysicalPath);
|
||||
}
|
||||
_HamannFiles.RemoveAll(x => x.Name == filename);
|
||||
}
|
||||
|
||||
public void SetInProduction(IFileInfo info) {
|
||||
if (_InProduction == null) _InProduction = new Stack<IFileInfo>();
|
||||
_InProduction.Push(info);
|
||||
}
|
||||
|
||||
public FileList? GetFiles(string prefix)
|
||||
=> _Files != null && _Files.ContainsKey(prefix) ? _Files[prefix] : null;
|
||||
|
||||
// Saves a Document as file and adds it to the collection
|
||||
public async Task Save(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState) {
|
||||
var type = doc.Prefix;
|
||||
var directory = Path.Combine(basefilepath, type);
|
||||
var path = Path.Combine(directory, doc.FileName);
|
||||
|
||||
try {
|
||||
if (!Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
using (var targetStream = System.IO.File.Create(path))
|
||||
await doc.Save(targetStream, ModelState);
|
||||
} catch (Exception ex) {
|
||||
ModelState.AddModelError("Error", "Speichern der Datei fehlgeschlagen: " + ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var info = _fileProvider.GetFileInfo(Path.Combine(doc.Prefix, doc.FileName));
|
||||
if (info == null) {
|
||||
ModelState.AddModelError("Error", "Auf die neu erstellte Datei konnte nicht zugegriffen werden.");
|
||||
return;
|
||||
}
|
||||
|
||||
doc.File = info;
|
||||
|
||||
if (_Files == null) _Files = new Dictionary<string, FileList?>();
|
||||
if (!_Files.ContainsKey(doc.Prefix)) _Files.Add(doc.Prefix, new FileList(doc.XMLRoot));
|
||||
_Files[doc.Prefix]!.Add(doc);
|
||||
}
|
||||
|
||||
public async Task<IFileInfo?> SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState) {
|
||||
var date = DateTime.Now;
|
||||
var filename = "hamann_" + date.Year + "-" + date.Month + "-" + date.Day + "." + Path.GetRandomFileName() + ".xml";
|
||||
var directory = Path.Combine(basefilepath, "hamann");
|
||||
var path = Path.Combine(directory, filename);
|
||||
|
||||
try {
|
||||
if (!Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
using (var targetStream = System.IO.File.Create(path))
|
||||
await element.SaveAsync(targetStream, SaveOptions.DisableFormatting, new CancellationToken());
|
||||
} catch (Exception ex) {
|
||||
ModelState.AddModelError("Error", "Die Datei konnte nicht gespeichert werden: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
var info = _fileProvider.GetFileInfo(Path.Combine("hamann", filename));
|
||||
if (info == null) {
|
||||
ModelState.AddModelError("Error", "Auf die neu erstellte Dtaei konnte nicht zugegriffen werden.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_HamannFiles == null) _HamannFiles = new List<IFileInfo>();
|
||||
_HamannFiles.RemoveAll(x => x.Name == info.Name);
|
||||
_HamannFiles.Add(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
private Dictionary<string, FileList?>? _ScanFiles() {
|
||||
if (_Roots == null) return null;
|
||||
Dictionary<string, FileList?>? res = null;
|
||||
var dirs = _fileProvider.GetDirectoryContents(string.Empty).Where(x => x.IsDirectory);
|
||||
foreach (var dir in dirs) {
|
||||
if (_Roots.ContainsKey(dir.Name)) {
|
||||
if (_Files == null) _Files = new Dictionary<string, FileList?>();
|
||||
if (res == null) res = new Dictionary<string, FileList?>();
|
||||
res.Add(dir.Name, _ScanFiles(dir.Name));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private FileList? _ScanFiles(string prefix) {
|
||||
if (_Roots == null) return null;
|
||||
FileList? res = null;
|
||||
var files = _fileProvider.GetDirectoryContents(prefix).Where(x => !x.IsDirectory && x.Name.StartsWith(prefix) && x.Name.EndsWith(".xml"));
|
||||
foreach (var file in files) {
|
||||
if (_Roots == null || !_Roots.ContainsKey(prefix))
|
||||
throw new Exception("Attempting to read a File from an unrecognized Prefix: " + prefix);
|
||||
if (res == null) res = new FileList(_Roots[prefix]);
|
||||
res.Add(new XMLRootDocument(_Roots[prefix], file));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private List<IFileInfo>? _ScanHamannFiles() {
|
||||
var dir = _fileProvider.GetDirectoryContents(string.Empty).Where(x => x.IsDirectory && x.Name == "hamann");
|
||||
if (dir == null || !dir.Any()) return null;
|
||||
var files = _fileProvider.GetDirectoryContents(dir.First().Name).Where(x => !x.IsDirectory && x.Name.StartsWith("hamann") && x.Name.EndsWith(".xml"));
|
||||
if (files == null || !files.Any()) return null;
|
||||
return files.ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user