using IPA.Config.Data;
|
|
using IPA.Logging;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using Boolean = IPA.Config.Data.Boolean;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace IPA.Config.Providers
|
|
{
|
|
internal class JsonConfigProvider : IConfigProvider
|
|
{
|
|
public static void RegisterConfig()
|
|
{
|
|
Config.Register<JsonConfigProvider>();
|
|
}
|
|
|
|
public string Extension => "json";
|
|
|
|
public Value Load(FileInfo file)
|
|
{
|
|
if (!file.Exists) return Value.Null();
|
|
|
|
try
|
|
{
|
|
JToken jtok;
|
|
using (var sreader = new StreamReader(file.OpenRead()))
|
|
{
|
|
using var jreader = new JsonTextReader(sreader);
|
|
jtok = JToken.ReadFrom(jreader);
|
|
}
|
|
|
|
return VisitToValue(jtok);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Config.Error($"Error reading JSON file {file.FullName}; ignoring");
|
|
Logger.Config.Error(e);
|
|
return Value.Null();
|
|
}
|
|
}
|
|
|
|
private Value VisitToValue(JToken tok)
|
|
{
|
|
if (tok == null) return Value.Null();
|
|
|
|
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:
|
|
Logger.Config.Warn("Found JTokenType.Undefined");
|
|
goto case JTokenType.Null;
|
|
case JTokenType.Bytes: // never used by Newtonsoft
|
|
Logger.Config.Warn("Found JTokenType.Bytes");
|
|
goto case JTokenType.Null;
|
|
case JTokenType.Comment: // never used by Newtonsoft
|
|
Logger.Config.Warn("Found JTokenType.Comment");
|
|
goto case JTokenType.Null;
|
|
case JTokenType.Constructor: // never used by Newtonsoft
|
|
Logger.Config.Warn("Found JTokenType.Constructor");
|
|
goto case JTokenType.Null;
|
|
case JTokenType.Property: // never used by Newtonsoft
|
|
Logger.Config.Warn("Found JTokenType.Property");
|
|
goto case JTokenType.Null;
|
|
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(dec);
|
|
else if (val is double dou) return Value.Float((decimal)dou);
|
|
else if (val is float flo) return Value.Float((decimal)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 void Store(Value value, FileInfo file)
|
|
{
|
|
if (!file.Directory.Exists)
|
|
file.Directory.Create();
|
|
|
|
try
|
|
{
|
|
var tok = VisitToToken(value);
|
|
|
|
using var swriter = new StreamWriter(file.Open(FileMode.Create, FileAccess.Write));
|
|
using var jwriter = new JsonTextWriter(swriter)
|
|
{
|
|
Formatting = Formatting.Indented
|
|
};
|
|
tok.WriteTo(jwriter);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Config.Error($"Error serializing value for {file.FullName}");
|
|
Logger.Config.Error(e);
|
|
}
|
|
}
|
|
|
|
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)}");
|
|
}
|
|
}
|
|
}
|
|
}
|