mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-08-16 06:31:42 +02:00
.github
.idea
bld
lib
TweetLib.Browser
TweetLib.Browser.CEF
TweetLib.Communication
TweetLib.Core
TweetLib.Utils
Collections
Data
Dialogs
Globalization
IO
Serialization
Converters
ITypeConverter.cs
SerializationSoftException.cs
SimpleObjectSerializer.cs
TypeConverterRegistry.cs
Startup
Static
Lib.cs
TweetLib.Utils.csproj
TweetTest.Browser.CEF
TweetTest.Core
TweetTest.Utils
linux
resources
windows
.gitattributes
.gitignore
LICENSE.md
README.md
TweetDuck.sln
TweetDuck.sln.DotSettings
Version.cs
global.json
168 lines
4.7 KiB
C#
168 lines
4.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using TweetLib.Utils.Serialization.Converters;
|
|
using TweetLib.Utils.Static;
|
|
|
|
namespace TweetLib.Utils.Serialization {
|
|
public sealed class SimpleObjectSerializer<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[index..nextIndex]);
|
|
|
|
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[index..]).ToString();
|
|
}
|
|
|
|
private readonly TypeConverterRegistry converterRegistry;
|
|
private readonly Dictionary<string, PropertyInfo> props;
|
|
|
|
public SimpleObjectSerializer(TypeConverterRegistry converterRegistry) {
|
|
this.converterRegistry = converterRegistry;
|
|
this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(static prop => prop.CanWrite).ToDictionary(static prop => prop.Name);
|
|
}
|
|
|
|
public void Write(string file, T obj) {
|
|
if (props.Count == 0) {
|
|
File.Delete(file);
|
|
return;
|
|
}
|
|
|
|
var 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) {
|
|
var type = prop.Value.PropertyType;
|
|
var converter = converterRegistry.TryGet(type) ?? ClrTypeConverter.Instance;
|
|
|
|
if (converter.TryWriteType(type, prop.Value.GetValue(obj), 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.");
|
|
}
|
|
|
|
var errors = new LinkedList<string>();
|
|
int currentPos = 0;
|
|
|
|
do {
|
|
string line;
|
|
int nextPos = contents.IndexOf(NewLineReal, currentPos);
|
|
|
|
if (nextPos == -1) {
|
|
line = contents[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[..space];
|
|
string value = UnescapeLine(line[(space + 1)..]);
|
|
|
|
if (props.TryGetValue(property, out var info)) {
|
|
var type = info.PropertyType;
|
|
var converter = converterRegistry.TryGet(type) ?? ClrTypeConverter.Instance;
|
|
|
|
if (converter.TryReadType(type, 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) {
|
|
// ignore
|
|
} catch (DirectoryNotFoundException) {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|