Browse Source

Rewrote JsonConfigProvider to use new system

pull/46/head
Anairkoen Schno 5 years ago
parent
commit
c132c68cec
6 changed files with 155 additions and 91 deletions
  1. +1
    -1
      IPA.Loader/Config/Config.cs
  2. +12
    -0
      IPA.Loader/Config/Data/Primitives.cs
  3. +13
    -1
      IPA.Loader/Config/Data/Value.cs
  4. +10
    -0
      IPA.Loader/Config/IConfigProvider.cs
  5. +118
    -89
      IPA.Loader/Config/Providers/JsonConfigProvider.cs
  6. +1
    -0
      IPA.Loader/Config/Stores/GeneratedStore.cs

+ 1
- 1
IPA.Loader/Config/Config.cs View File

@ -21,7 +21,7 @@ namespace IPA.Config
{ {
static Config() static Config()
{ {
//JsonConfigProvider.RegisterConfig();
JsonConfigProvider.RegisterConfig();
} }
/// <summary> /// <summary>


+ 12
- 0
IPA.Loader/Config/Data/Primitives.cs View File

@ -35,6 +35,12 @@ namespace IPA.Config.Data
/// </summary> /// </summary>
public long Value { get; set; } public long Value { get; set; }
/// <summary>
/// Coerces this <see cref="Integer"/> into a <see cref="FloatingPoint"/>.
/// </summary>
/// <returns>a <see cref="FloatingPoint"/> representing the closest approximation of <see cref="Value"/></returns>
public FloatingPoint AsFloat() => Float(Value);
/// <summary> /// <summary>
/// Converts this <see cref="Data.Value"/> into a human-readable format. /// Converts this <see cref="Data.Value"/> into a human-readable format.
/// </summary> /// </summary>
@ -53,6 +59,12 @@ namespace IPA.Config.Data
/// </summary> /// </summary>
public double Value { get; set; } public double Value { get; set; }
/// <summary>
/// Coerces this <see cref="FloatingPoint"/> into an <see cref="Integer"/>.
/// </summary>
/// <returns>a <see cref="Integer"/> representing the closest approximation of <see cref="Value"/></returns>
public Integer AsInteger() => Integer((long)Value);
/// <summary> /// <summary>
/// Converts this <see cref="Data.Value"/> into a human-readable format. /// Converts this <see cref="Data.Value"/> into a human-readable format.
/// </summary> /// </summary>


+ 13
- 1
IPA.Loader/Config/Data/Value.cs View File

@ -38,6 +38,7 @@ namespace IPA.Config.Data
/// </summary> /// </summary>
/// <returns>an empty <see cref="Map"/></returns> /// <returns>an empty <see cref="Map"/></returns>
/// <seealso cref="From(IDictionary{string, Value})"/> /// <seealso cref="From(IDictionary{string, Value})"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map Map() => new Map(); public static Map Map() => new Map();
/// <summary> /// <summary>
@ -122,7 +123,18 @@ namespace IPA.Config.Data
/// <param name="vals">the dictionary of <see cref="Value"/>s to initialize the <see cref="Data.Map"/> wtih</param> /// <param name="vals">the dictionary of <see cref="Value"/>s to initialize the <see cref="Data.Map"/> wtih</param>
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns> /// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/> /// <seealso cref="Map"/>
public static Map From(IDictionary<string, Value> vals)
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map From(IDictionary<string, Value> vals) => From(vals as IEnumerable<KeyValuePair<string, Value>>);
/// <summary>
/// Creates a new <see cref="Data.Map"/> holding the content of an <see cref="IEnumerable{T}"/>
/// of <see cref="KeyValuePair{TKey, TValue}"/> of <see cref="string"/> to <see cref="Value"/>.
/// </summary>
/// <param name="vals">the enumerable of <see cref="KeyValuePair{TKey, TValue}"/> of name to <see cref="Value"/></param>
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IDictionary{string, Value})"/>
public static Map From(IEnumerable<KeyValuePair<string, Value>> vals)
{ {
if (vals == null) return null; if (vals == null) return null;
var m = Map(); var m = Map();


+ 10
- 0
IPA.Loader/Config/IConfigProvider.cs View File

@ -8,7 +8,17 @@ namespace IPA.Config
/// An interface for configuration providers. /// An interface for configuration providers.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// Implementers must provide a default constructor. Do not assume that <see cref="File"/> will ever be set for a given object. /// Implementers must provide a default constructor. Do not assume that <see cref="File"/> will ever be set for a given object.
/// </para>
/// <para>
/// Implementers are expected to preserve the typing of values passed to <see cref="Store"/> when returned from <see cref="Load"/>.
/// The only exceptions to this are the numeric types, <see cref="Integer"/> and <see cref="FloatingPoint"/>, since they can be coerced
/// to each other with <see cref="Integer.AsFloat"/> and <see cref="FloatingPoint.AsInteger"/> respectively. The provider <i>should</i>
/// however store and recover <see cref="Integer"/> with as much precision as is possible. For example, a JSON provider may decide to
/// decode all numbers that have an integral value, even if they were originally <see cref="FloatingPoint"/>, as <see cref="Integer"/>.
/// This is reasonable, as <see cref="Integer"/> is more precise, particularly with larger values, than <see cref="FloatingPoint"/>.
/// </para>
/// </remarks> /// </remarks>
public interface IConfigProvider public interface IConfigProvider
{ {


+ 118
- 89
IPA.Loader/Config/Providers/JsonConfigProvider.cs View File

@ -1,14 +1,17 @@
using IPA.Logging;
using IPA.Config.Data;
using IPA.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Linq;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Collections.Generic;
using Boolean = IPA.Config.Data.Boolean;
namespace IPA.Config.Providers namespace IPA.Config.Providers
{ // TODO: implement this for the new provider system
/*[Config.Type("json")]
{
internal class JsonConfigProvider : IConfigProvider internal class JsonConfigProvider : IConfigProvider
{ {
public static void RegisterConfig() public static void RegisterConfig()
@ -16,112 +19,138 @@ namespace IPA.Config.Providers
Config.Register<JsonConfigProvider>(); Config.Register<JsonConfigProvider>();
} }
private JObject jsonObj;
public string Extension => "json";
#if NET4
// TODO: create a wrapper that allows empty object creation
public dynamic Dynamic => jsonObj;
#endif
public FileInfo File { get; set; }
public bool HasChanged { get; private set; }
public bool InMemoryChanged { get; set; }
public DateTime LastModified => File.GetLastWriteTime(Filename + ".json");
private string _filename;
public string Filename
public Value Load()
{ {
get => _filename;
set
if (!File.Exists) return Value.Null();
JToken jtok;
using (var sreader = new StreamReader(File.OpenRead()))
{ {
if (_filename != null)
throw new InvalidOperationException("Can only assign to Filename once");
_filename = value;
using var jreader = new JsonTextReader(sreader);
jtok = JToken.ReadFrom(jreader);
} }
}
public void Load()
{
Logger.config.Debug($"Loading file {Filename}.json");
var fileInfo = new FileInfo(Filename + ".json");
if (fileInfo.Exists)
try
{ {
string json = File.ReadAllText(fileInfo.FullName);
try
{
jsonObj = JObject.Parse(json);
}
catch (Exception e)
{
Logger.config.Error($"Error parsing JSON in file {Filename}.json; resetting to empty JSON");
Logger.config.Error(e);
jsonObj = new JObject();
File.WriteAllText(fileInfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
}
return VisitToValue(jtok);
} }
else
catch (Exception e)
{ {
jsonObj = new JObject();
Logger.config.Error($"Error reading JSON file {File.FullName}; ignoring");
Logger.config.Error(e);
return Value.Null();
} }
SetupListeners();
InMemoryChanged = true;
} }
private void SetupListeners()
private Value VisitToValue(JToken tok)
{ {
jsonObj.PropertyChanged += JsonObj_PropertyChanged;
jsonObj.ListChanged += JsonObj_ListChanged;
#if NET4
jsonObj.CollectionChanged += JsonObj_CollectionChanged;
#endif
}
if (tok == null) return Value.Null();
#if NET4
private void JsonObj_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HasChanged = true;
InMemoryChanged = true;
}
#endif
private void JsonObj_ListChanged(object sender, ListChangedEventArgs e)
{
HasChanged = true;
InMemoryChanged = true;
}
private void JsonObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
HasChanged = true;
InMemoryChanged = true;
switch (tok.Type)
{
case JTokenType.Raw: // idk if the parser will normally emit a Raw type, but just to be safe
return VisitToValue(JToken.Parse((tok as JRaw).Value as string));
case JTokenType.Undefined:
case JTokenType.Bytes: // never used by Newtonsoft
case JTokenType.Comment: // never used by Newtonsoft
case JTokenType.Constructor: // never used by Newtonsoft
case JTokenType.Property: // never used by Newtonsoft
case JTokenType.Null:
return Value.Null();
case JTokenType.Boolean:
return Value.Bool(((tok as JValue).Value as bool?) ?? false);
case JTokenType.String:
var val = (tok as JValue).Value;
if (val is string s) return Value.Text(s);
else if (val is char c) return Value.Text("" + c);
else return Value.Text(string.Empty);
case JTokenType.Integer:
val = (tok as JValue).Value;
if (val is long l) return Value.Integer(l);
else if (val is ulong u) return Value.Integer((long)u);
else return Value.Integer(0);
case JTokenType.Float:
val = (tok as JValue).Value;
if (val is decimal dec) return Value.Float((double)dec);
else if (val is double dou) return Value.Float(dou);
else if (val is float flo) return Value.Float(flo);
else return Value.Float(0); // default to 0 if something breaks
case JTokenType.Date:
val = (tok as JValue).Value;
if (val is DateTime dt) return Value.Text(dt.ToString());
else if (val is DateTimeOffset dto) return Value.Text(dto.ToString());
else return Value.Text("Unknown Date-type token");
case JTokenType.TimeSpan:
val = (tok as JValue).Value;
if (val is TimeSpan ts) return Value.Text(ts.ToString());
else return Value.Text("Unknown TimeSpan-type token");
case JTokenType.Guid:
val = (tok as JValue).Value;
if (val is Guid g) return Value.Text(g.ToString());
else return Value.Text("Unknown Guid-type token");
case JTokenType.Uri:
val = (tok as JValue).Value;
if (val is Uri ur) return Value.Text(ur.ToString());
else return Value.Text("Unknown Uri-type token");
case JTokenType.Array:
return Value.From((tok as JArray).Select(VisitToValue));
case JTokenType.Object:
return Value.From((tok as IEnumerable<KeyValuePair<string, JToken>>)
.Select(kvp => new KeyValuePair<string, Value>(kvp.Key, VisitToValue(kvp.Value))));
default:
throw new ArgumentException($"Unknown {nameof(JTokenType)} in parameter");
}
} }
public T Parse<T>()
public void Store(Value value)
{ {
if (jsonObj == null)
return default(T);
return jsonObj.ToObject<T>();
}
if (File.Directory.Exists)
File.Directory.Create();
public void Save()
{
Logger.config.Debug($"Saving file {Filename}.json");
if (!Directory.Exists(Path.GetDirectoryName(Filename)))
Directory.CreateDirectory(Path.GetDirectoryName(Filename) ?? throw new InvalidOperationException());
File.WriteAllText(Filename + ".json", JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
try
{
var tok = VisitToToken(value);
HasChanged = false;
using var swriter = new StreamWriter(File.OpenWrite());
using var jwriter = new JsonTextWriter(swriter);
tok.WriteTo(jwriter);
}
catch (Exception e)
{
Logger.config.Error($"Error serializing value for {File.FullName}");
Logger.config.Error(e);
}
} }
public void Store<T>(T obj)
{
jsonObj = JObject.FromObject(obj);
SetupListeners();
HasChanged = true;
InMemoryChanged = true;
private JToken VisitToToken(Value val)
{
switch (val)
{
case Text t:
return new JValue(t.Value);
case Boolean b:
return new JValue(b.Value);
case Integer i:
return new JValue(i.Value);
case FloatingPoint f:
return new JValue(f.Value);
case List l:
var jarr = new JArray();
foreach (var tok in l.Select(VisitToToken)) jarr.Add(tok);
return jarr;
case Map m:
var jobj = new JObject();
foreach (var kvp in m) jobj.Add(kvp.Key, VisitToToken(kvp.Value));
return jobj;
case null:
return JValue.CreateNull();
default:
throw new ArgumentException($"Unsupported subtype of {nameof(Value)}");
}
} }
}*/
}
} }

+ 1
- 0
IPA.Loader/Config/Stores/GeneratedStore.cs View File

@ -106,6 +106,7 @@ namespace IPA.Config.Stores
{ {
// TODO: implement // TODO: implement
Logger.config.Debug("Generated impl ReadFrom"); Logger.config.Debug("Generated impl ReadFrom");
Logger.config.Debug($"Read {provider.Load()}");
} }
internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo)); internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo));


Loading…
Cancel
Save