|
|
|
|
@ -0,0 +1,168 @@ |
|
|
|
|
// Author: Gockner, Simon |
|
|
|
|
// Created: 2020-02-12 |
|
|
|
|
// Copyright(c) 2020 SimonG. All Rights Reserved. |
|
|
|
|
|
|
|
|
|
using System; |
|
|
|
|
using System.IO; |
|
|
|
|
using System.Linq; |
|
|
|
|
using System.Threading; |
|
|
|
|
using System.Threading.Tasks; |
|
|
|
|
using System.Xml.Linq; |
|
|
|
|
using GBase.Interfaces.DataHandling; |
|
|
|
|
using GBase.Interfaces.DataHandling.Xml; |
|
|
|
|
|
|
|
|
|
namespace GBase.DataHandling |
|
|
|
|
{ |
|
|
|
|
/// <summary> |
|
|
|
|
/// A <see cref="IDataWriter"/> that writes to a xml file |
|
|
|
|
/// </summary> |
|
|
|
|
public class XmlDataWriter : IXmlDataWriter |
|
|
|
|
{ |
|
|
|
|
private readonly string _rootElementName; |
|
|
|
|
private readonly FileStream _file; |
|
|
|
|
|
|
|
|
|
private XDocument _xmlDocument; |
|
|
|
|
private XElement _rootElement; |
|
|
|
|
|
|
|
|
|
private bool _isInitialized; |
|
|
|
|
private CancellationToken _cancellationToken; |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// A <see cref="IDataWriter"/> that writes to an xml file |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="path">The path to the xml file</param> |
|
|
|
|
/// <param name="rootElementName">The root element name of the xml file</param> |
|
|
|
|
public XmlDataWriter(string path, string rootElementName) |
|
|
|
|
{ |
|
|
|
|
_rootElementName = rootElementName; |
|
|
|
|
_file = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Initialize the <see cref="XmlDataWriter"/> |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the async operation</param> |
|
|
|
|
/// <returns>Returns true if successful, false if not</returns> |
|
|
|
|
public async Task<bool> Init(CancellationToken cancellationToken) |
|
|
|
|
{ |
|
|
|
|
if (_isInitialized) |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
_cancellationToken = cancellationToken; |
|
|
|
|
//if the xml file is empty, write the root element |
|
|
|
|
if (_file.Length <= 3) //<= 3 because of BOM |
|
|
|
|
{ |
|
|
|
|
_xmlDocument = new XDocument(); |
|
|
|
|
_xmlDocument.Add(new XElement(_rootElementName)); |
|
|
|
|
_file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it |
|
|
|
|
await _xmlDocument.SaveAsync(_file, SaveOptions.OmitDuplicateNamespaces, cancellationToken); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
_xmlDocument = await XDocument.LoadAsync(_file, LoadOptions.None, _cancellationToken); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_rootElement = _xmlDocument.Root;//?.Element(_rootElementName); |
|
|
|
|
if (_rootElement == null) |
|
|
|
|
throw new Exception("No root element found."); |
|
|
|
|
|
|
|
|
|
_isInitialized = true; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Write the data of a property |
|
|
|
|
/// </summary> |
|
|
|
|
/// <typeparam name="T">The <see cref="Type"/> that implements the property</typeparam> |
|
|
|
|
/// <typeparam name="TProperty">The <see cref="Type"/> of the property</typeparam> |
|
|
|
|
/// <param name="propertyName">The name of the property</param> |
|
|
|
|
/// <param name="value">The value of the property</param> |
|
|
|
|
/// <param name="overwrite">If true an existing value is overwritten, if false an additional value is added</param> |
|
|
|
|
/// <returns>A <see cref="Task"/> to await</returns> |
|
|
|
|
public async Task Write<T, TProperty>(string propertyName, string value, bool overwrite) |
|
|
|
|
{ |
|
|
|
|
string typeName = typeof(T).FullName; |
|
|
|
|
if (typeName == null) |
|
|
|
|
throw new ArgumentNullException(nameof(typeName)); |
|
|
|
|
|
|
|
|
|
_file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it |
|
|
|
|
|
|
|
|
|
XElement typeElement = _rootElement.Element(typeName); |
|
|
|
|
if (typeElement != null) //type element already exists |
|
|
|
|
{ |
|
|
|
|
XElement propertyElement = typeElement.Element(propertyName); |
|
|
|
|
if (propertyElement != null) //property element already exists |
|
|
|
|
{ |
|
|
|
|
XElement valueElement = propertyElement.Element(XmlDataHandler.VALUE_ELEMENT_NAME); |
|
|
|
|
if (valueElement != null && overwrite) //value element exists and overwrite is true |
|
|
|
|
{ |
|
|
|
|
valueElement.Value = value; //overwrite existing value |
|
|
|
|
} |
|
|
|
|
else if (!overwrite && propertyElement.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).Any(v => v.Value.Equals(value))) //no overwrite and same value exists already |
|
|
|
|
{ |
|
|
|
|
XElement sameValueElement = propertyElement.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).First(v => v.Value.Equals(value)); |
|
|
|
|
sameValueElement.Remove(); //remove the already existing value from its current position |
|
|
|
|
propertyElement.AddFirst(sameValueElement); //add it as the first element again |
|
|
|
|
} |
|
|
|
|
else //no value element exists or overwrite is false |
|
|
|
|
propertyElement.AddFirst(new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //add a new value element |
|
|
|
|
} |
|
|
|
|
else //property element doesn't exist |
|
|
|
|
{ |
|
|
|
|
propertyElement = new XElement(propertyName, new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //create the new property element with the value element |
|
|
|
|
propertyElement.SetAttributeValue(XmlDataHandler.VALUE_TYPE_ATTRIBUTE_NAME, typeof(TProperty).FullName); //add the property type attribute |
|
|
|
|
|
|
|
|
|
typeElement.Add(propertyElement); //create new property element with the value element |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else //type element doesn't exist |
|
|
|
|
{ |
|
|
|
|
XElement propertyElement = new XElement(propertyName, new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //create the new property element with the value element |
|
|
|
|
propertyElement.SetAttributeValue(XmlDataHandler.VALUE_TYPE_ATTRIBUTE_NAME, typeof(TProperty).FullName); //add the property type attribute |
|
|
|
|
|
|
|
|
|
_rootElement.Add(new XElement(typeName, propertyElement)); //create a new type element with the new property element |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//TODO: check if whole file is overwritten (probably) -> performance issues for large files? |
|
|
|
|
await _xmlDocument.SaveAsync(_file, SaveOptions.OmitDuplicateNamespaces, _cancellationToken); //save the document with the added elements |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Remove the value for the given property |
|
|
|
|
/// </summary> |
|
|
|
|
/// <typeparam name="T">The <see cref="Type"/> of the property</typeparam> |
|
|
|
|
/// <typeparam name="TProperty">The <see cref="Type"/> of the property</typeparam> |
|
|
|
|
/// <param name="propertyName">The name of the property</param> |
|
|
|
|
/// <param name="value">The value to set</param> |
|
|
|
|
/// <returns>A <see cref="Task"/> to await</returns> |
|
|
|
|
public async Task Remove<T, TProperty>(string propertyName, string value) |
|
|
|
|
{ |
|
|
|
|
string typeName = typeof(T).FullName; |
|
|
|
|
if (typeName == null) |
|
|
|
|
throw new ArgumentNullException(nameof(typeName)); |
|
|
|
|
|
|
|
|
|
_file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it |
|
|
|
|
|
|
|
|
|
XElement typeElement = _rootElement.Element(typeName); |
|
|
|
|
XElement propertyElement = typeElement?.Element(propertyName); |
|
|
|
|
XElement valueElement = propertyElement?.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).FirstOrDefault(e => e.Value.Equals(value)); |
|
|
|
|
if (valueElement == null) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
valueElement.Remove(); |
|
|
|
|
|
|
|
|
|
//TODO: check if whole file is overwritten (probably) -> performance issues for large files? |
|
|
|
|
await _xmlDocument.SaveAsync(_file, SaveOptions.OmitDuplicateNamespaces, _cancellationToken); //save the document with the added elements |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Dispose used resources asynchronously |
|
|
|
|
/// </summary> |
|
|
|
|
/// <returns>A <see cref="ValueTask"/> to await</returns> |
|
|
|
|
public async ValueTask DisposeAsync() |
|
|
|
|
{ |
|
|
|
|
await _xmlDocument.SaveAsync(_file, SaveOptions.None, _cancellationToken); |
|
|
|
|
await _file.DisposeAsync(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |