Skip to content

Commit

Permalink
rough draft of YamlDotNet-based config backend
Browse files Browse the repository at this point in the history
The problem seems to be with nullable value type deserialization,
due to aaubry/YamlDotNet#360.

See yaml-nullable-scratch.linq.
  • Loading branch information
EliahKagan committed May 3, 2021
1 parent 8a3a379 commit cc3f3d1
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 7 deletions.
37 changes: 30 additions & 7 deletions Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Threading;
using YamlDotNet.Serialization;

namespace VidDraw {
/// <summary>Partial or complete YAML-backed configuration data.</summary>
internal record Config(Codec? Codec, Color? Color) {
public Config() : this(null, null) { }

Expand Down Expand Up @@ -34,7 +37,6 @@ private static string ConfigDir
private static Mutex Mutex { get; } =
Sync.CreateMutex($"APPDATA::{ConfigFilename}");


private static string GetConfigPath()
=> Path.Combine(ConfigDir, ConfigFilename);

Expand All @@ -45,27 +47,48 @@ private static string GetConfigPath()
try {
// If it already exists, this does nothing and succeeds.
Directory.CreateDirectory(dir);
} catch (SystemException ex)
when (ex is IOException or UnauthorizedAccessException) {
} catch (SystemException ex) when (IsNonFatal(ex)) {
Debug.Print($"Can't create config dir: {ex.Message}");
return null;
}

return Path.Combine(dir, ConfigFilename);
}

private static string TrySlurpConfig()
{
try {
return File.ReadAllText(GetConfigPath(), Encoding.UTF8);
} catch (SystemException ex) when (IsNonFatal(ex)) {
Debug.Print($"Can't read config file: {ex.Message}");
return string.Empty;
}
}

private static bool IsNonFatal(SystemException ex)
=> ex is IOException or UnauthorizedAccessException;

private Config PatchedBy(Config delta)
=> new(delta.Codec ?? Codec,
delta.Color ?? Color);

private static Config TryRead()
{
// FIXME: Cache Serializer and Deserializer instances thread-locally
// instead of constructing them every time in TryRead and TryWrite.

}
private static Config TryRead()
=> new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.Build()
.Deserialize<Config>(TrySlurpConfig())
?? new();

private void TryWrite()
{

if (TryCreateConfigPath() is string path) {
File.WriteAllText(path: path,
contents: new Serializer().Serialize(this),
encoding: Encoding.UTF8);
}
}
}
}
113 changes: 113 additions & 0 deletions yaml-nullable-scratch.linq
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<Query Kind="Statements">
<NuGetReference Version="11.1.1">YamlDotNet</NuGetReference>
</Query>

using System.Drawing;
using YamlDotNet.Serialization;

//new Config { Color = Color.Red }.TrySave();
//Config.TryLoad().Dump();
//new Config { Codec = Codec.H264 }.TrySave();


// After running this:
new Config {
Color = Color.Red,
Codec = Codec.H264,
}.TrySave();

// This fails due to https://github.com/aaubry/YamlDotNet/issues/360:
Config.TryLoad().Dump();

/// <summary>Video stream encoding selections.</summary>
internal enum Codec : uint {
Raw,
Uncompressed,
MotionJpeg,
H264,
}

/// <summary>Partial or complete YAML-backed configuration data.</summary>
internal record Config(Codec? Codec, Color? Color) {
public Config() : this(null, null) { }

internal static Config TryLoad()
{
//using var @lock = new Lock(Mutex);
return TryRead();
}

internal void TrySave()
{
//using var @lock = new Lock(Mutex);
TryRead().PatchedBy(this).TryWrite();
}

private const string ProgramName = "VidDraw-scratch";

private const string ConfigFilename = ProgramName + ".yml";

private static string AppDataDir
=> Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData);

private static string ConfigDir
=> Path.Combine(AppDataDir, ProgramName);

//private static Mutex Mutex { get; } =
// Sync.CreateMutex($"APPDATA::{ConfigFilename}");

private static string GetConfigPath()
=> Path.Combine(ConfigDir, ConfigFilename);

private static string? TryCreateConfigPath()
{
var dir = ConfigDir;

try {
// If it already exists, this does nothing and succeeds.
Directory.CreateDirectory(dir);
} catch (SystemException ex) when (IsNonFatal(ex)) {
Debug.Print($"Can't create config dir: {ex.Message}");
return null;
}

return Path.Combine(dir, ConfigFilename);
}

private static string TrySlurpConfig()
{
try {
return File.ReadAllText(GetConfigPath(), Encoding.UTF8);
} catch (SystemException ex) when (IsNonFatal(ex)) {
Debug.Print($"Can't read config file: {ex.Message}");
return string.Empty;
}
}

private static bool IsNonFatal(SystemException ex)
=> ex is IOException or UnauthorizedAccessException;

private Config PatchedBy(Config delta)
=> new(delta.Codec ?? Codec,
delta.Color ?? Color);

// FIXME: Cache Serializer and Deserializer instances thread-locally
// instead of constructing them every time in TryRead and TryWrite.

private static Config TryRead()
=> new DeserializerBuilder()
//.IgnoreUnmatchedProperties() // Commented to reveal bug 360 (see above).
.Build()
.Deserialize<Config>(TrySlurpConfig())
?? new();

private void TryWrite()
{
if (TryCreateConfigPath() is string path) {
File.WriteAllText(path: path,
contents: new Serializer().Serialize(this),
encoding: Encoding.UTF8);
}
}
}

0 comments on commit cc3f3d1

Please sign in to comment.