1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-04-27 15:15:47 +02:00
TweetDuck/lib/TweetLib.Core/Serialization/FileSerializer.cs
Daniel Chýlek 1ccefe853a
Update .NET & begin refactoring code into a core lib ()
* Switch to .NET Framework 4.7.2 & C# 8.0, update libraries

* Add TweetLib.Core project targeting .NET Standard 2.0

* Enable reference nullability checks for TweetLib.Core

* Move a bunch of utility classes into TweetLib.Core & refactor

* Partially move TweetDuck plugin & update system to TweetLib.Core

* Move some constants and CultureInfo setup to TweetLib.Core

* Move some configuration classes to TweetLib.Core

* Minor refactoring and warning suppression

* Add App to TweetLib.Core

* Add IAppErrorHandler w/ implementation

* Continue moving config, plugin, and update classes to TweetLib.Core

* Fix a few nullability checks

* Update installers to check for .NET Framework 4.7.2
2019-05-26 14:55:12 +02:00

169 lines
6.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetLib.Core.Serialization{
public sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n";
private static string EscapeLine(string input) => input.Replace("\\", "\\\\").Replace(Environment.NewLine, "\\\r\n");
private static string UnescapeLine(string input) => input.Replace(NewLineCustom, Environment.NewLine);
private static string UnescapeStream(StreamReader reader){
string data = reader.ReadToEnd();
StringBuilder build = new StringBuilder(data.Length);
int index = 0;
while(true){
int nextIndex = data.IndexOf('\\', index);
if (nextIndex == -1 || nextIndex+1 >= data.Length){
break;
}
else{
build.Append(data.Substring(index, nextIndex-index));
char next = data[nextIndex+1];
if (next == '\\'){ // convert double backslash to single backslash
build.Append('\\');
index = nextIndex+2;
}
else if (next == '\r' && nextIndex+2 < data.Length && data[nextIndex+2] == '\n'){ // convert backslash followed by CRLF to custom new line
build.Append(NewLineCustom);
index = nextIndex+3;
}
else{ // single backslash
build.Append('\\');
index = nextIndex+1;
}
}
}
return build.Append(data.Substring(index)).ToString();
}
private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters;
public FileSerializer(){
this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name);
this.converters = new Dictionary<Type, ITypeConverter>();
}
public void RegisterTypeConverter(Type type, ITypeConverter converter){
converters[type] = converter;
}
public void Write(string file, T obj){
LinkedList<string> errors = new LinkedList<string>();
FileUtils.CreateDirectoryForFile(file);
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){
Type type = prop.Value.PropertyType;
object value = prop.Value.GetValue(obj);
if (!converters.TryGetValue(type, out ITypeConverter serializer)){
serializer = ClrTypeConverter.Instance;
}
if (serializer.TryWriteType(type, value, out string? converted)){
if (converted != null){
writer.Write(prop.Key);
writer.Write(' ');
writer.Write(EscapeLine(converted));
writer.Write(NewLineReal);
}
}
else{
errors.AddLast($"Missing converter for type: {type}");
}
}
}
if (errors.First != null){
throw new SerializationSoftException(errors.ToArray());
}
}
public void Read(string file, T obj){
string contents;
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
contents = UnescapeStream(reader);
}
if (string.IsNullOrWhiteSpace(contents)){
throw new FormatException("File is empty.");
}
else if (contents[0] <= (char)1){
throw new FormatException("Input appears to be a binary file.");
}
LinkedList<string> errors = new LinkedList<string>();
int currentPos = 0;
do{
string line;
int nextPos = contents.IndexOf(NewLineReal, currentPos);
if (nextPos == -1){
line = contents.Substring(currentPos);
currentPos = -1;
if (string.IsNullOrEmpty(line)){
break;
}
}
else{
line = contents.Substring(currentPos, nextPos-currentPos);
currentPos = nextPos+NewLineReal.Length;
}
int space = line.IndexOf(' ');
if (space == -1){
errors.AddLast($"Missing separator on line: {line}");
continue;
}
string property = line.Substring(0, space);
string value = UnescapeLine(line.Substring(space+1));
if (props.TryGetValue(property, out PropertyInfo info)){
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){
serializer = ClrTypeConverter.Instance;
}
if (serializer.TryReadType(info.PropertyType, value, out object? converted)){
info.SetValue(obj, converted);
}
else{
errors.AddLast($"Failed reading property {property} with value: {value}");
}
}
}while(currentPos != -1);
if (errors.First != null){
throw new SerializationSoftException(errors.ToArray());
}
}
public void ReadIfExists(string file, T obj){
try{
Read(file, obj);
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){}
}
}
}