You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
6.6 KiB

  1. using IPA.Config.Data;
  2. using IPA.Logging;
  3. using Newtonsoft.Json;
  4. using Newtonsoft.Json.Linq;
  5. using System;
  6. using System.Linq;
  7. using System.Collections.Specialized;
  8. using System.ComponentModel;
  9. using System.IO;
  10. using System.Collections.Generic;
  11. using Boolean = IPA.Config.Data.Boolean;
  12. namespace IPA.Config.Providers
  13. {
  14. internal class JsonConfigProvider : IConfigProvider
  15. {
  16. public static void RegisterConfig()
  17. {
  18. Config.Register<JsonConfigProvider>();
  19. }
  20. public string Extension => "json";
  21. public FileInfo File { get; set; }
  22. public Value Load()
  23. {
  24. if (!File.Exists) return Value.Null();
  25. JToken jtok;
  26. using (var sreader = new StreamReader(File.OpenRead()))
  27. {
  28. using var jreader = new JsonTextReader(sreader);
  29. jtok = JToken.ReadFrom(jreader);
  30. }
  31. try
  32. {
  33. return VisitToValue(jtok);
  34. }
  35. catch (Exception e)
  36. {
  37. Logger.config.Error($"Error reading JSON file {File.FullName}; ignoring");
  38. Logger.config.Error(e);
  39. return Value.Null();
  40. }
  41. }
  42. private Value VisitToValue(JToken tok)
  43. {
  44. if (tok == null) return Value.Null();
  45. switch (tok.Type)
  46. {
  47. case JTokenType.Raw: // idk if the parser will normally emit a Raw type, but just to be safe
  48. return VisitToValue(JToken.Parse((tok as JRaw).Value as string));
  49. case JTokenType.Undefined:
  50. Logger.config.Warn("Found JTokenType.Undefined");
  51. goto case JTokenType.Null;
  52. case JTokenType.Bytes: // never used by Newtonsoft
  53. Logger.config.Warn("Found JTokenType.Bytes");
  54. goto case JTokenType.Null;
  55. case JTokenType.Comment: // never used by Newtonsoft
  56. Logger.config.Warn("Found JTokenType.Comment");
  57. goto case JTokenType.Null;
  58. case JTokenType.Constructor: // never used by Newtonsoft
  59. Logger.config.Warn("Found JTokenType.Constructor");
  60. goto case JTokenType.Null;
  61. case JTokenType.Property: // never used by Newtonsoft
  62. Logger.config.Warn("Found JTokenType.Property");
  63. goto case JTokenType.Null;
  64. case JTokenType.Null:
  65. return Value.Null();
  66. case JTokenType.Boolean:
  67. return Value.Bool(((tok as JValue).Value as bool?) ?? false);
  68. case JTokenType.String:
  69. var val = (tok as JValue).Value;
  70. if (val is string s) return Value.Text(s);
  71. else if (val is char c) return Value.Text("" + c);
  72. else return Value.Text(string.Empty);
  73. case JTokenType.Integer:
  74. val = (tok as JValue).Value;
  75. if (val is long l) return Value.Integer(l);
  76. else if (val is ulong u) return Value.Integer((long)u);
  77. else return Value.Integer(0);
  78. case JTokenType.Float:
  79. val = (tok as JValue).Value;
  80. if (val is decimal dec) return Value.Float((double)dec);
  81. else if (val is double dou) return Value.Float(dou);
  82. else if (val is float flo) return Value.Float(flo);
  83. else return Value.Float(0); // default to 0 if something breaks
  84. case JTokenType.Date:
  85. val = (tok as JValue).Value;
  86. if (val is DateTime dt) return Value.Text(dt.ToString());
  87. else if (val is DateTimeOffset dto) return Value.Text(dto.ToString());
  88. else return Value.Text("Unknown Date-type token");
  89. case JTokenType.TimeSpan:
  90. val = (tok as JValue).Value;
  91. if (val is TimeSpan ts) return Value.Text(ts.ToString());
  92. else return Value.Text("Unknown TimeSpan-type token");
  93. case JTokenType.Guid:
  94. val = (tok as JValue).Value;
  95. if (val is Guid g) return Value.Text(g.ToString());
  96. else return Value.Text("Unknown Guid-type token");
  97. case JTokenType.Uri:
  98. val = (tok as JValue).Value;
  99. if (val is Uri ur) return Value.Text(ur.ToString());
  100. else return Value.Text("Unknown Uri-type token");
  101. case JTokenType.Array:
  102. return Value.From((tok as JArray).Select(VisitToValue));
  103. case JTokenType.Object:
  104. return Value.From((tok as IEnumerable<KeyValuePair<string, JToken>>)
  105. .Select(kvp => new KeyValuePair<string, Value>(kvp.Key, VisitToValue(kvp.Value))));
  106. default:
  107. throw new ArgumentException($"Unknown {nameof(JTokenType)} in parameter");
  108. }
  109. }
  110. public void Store(Value value)
  111. {
  112. if (File.Directory.Exists)
  113. File.Directory.Create();
  114. try
  115. {
  116. var tok = VisitToToken(value);
  117. using var swriter = new StreamWriter(File.OpenWrite());
  118. using var jwriter = new JsonTextWriter(swriter);
  119. tok.WriteTo(jwriter);
  120. }
  121. catch (Exception e)
  122. {
  123. Logger.config.Error($"Error serializing value for {File.FullName}");
  124. Logger.config.Error(e);
  125. }
  126. }
  127. private JToken VisitToToken(Value val)
  128. {
  129. switch (val)
  130. {
  131. case Text t:
  132. return new JValue(t.Value);
  133. case Boolean b:
  134. return new JValue(b.Value);
  135. case Integer i:
  136. return new JValue(i.Value);
  137. case FloatingPoint f:
  138. return new JValue(f.Value);
  139. case List l:
  140. var jarr = new JArray();
  141. foreach (var tok in l.Select(VisitToToken)) jarr.Add(tok);
  142. return jarr;
  143. case Map m:
  144. var jobj = new JObject();
  145. foreach (var kvp in m) jobj.Add(kvp.Key, VisitToToken(kvp.Value));
  146. return jobj;
  147. case null:
  148. return JValue.CreateNull();
  149. default:
  150. throw new ArgumentException($"Unsupported subtype of {nameof(Value)}");
  151. }
  152. }
  153. }
  154. }