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.

167 lines
6.8 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. using System.Threading.Tasks;
  13. namespace IPA.Config.Providers
  14. {
  15. internal class JsonConfigProvider : IConfigProvider
  16. {
  17. public static void RegisterConfig()
  18. {
  19. Config.Register<JsonConfigProvider>();
  20. }
  21. public string Extension => "json";
  22. public Value Load(FileInfo file)
  23. {
  24. if (!file.Exists) return Value.Null();
  25. try
  26. {
  27. JToken jtok;
  28. using (var sreader = new StreamReader(file.OpenRead()))
  29. {
  30. using var jreader = new JsonTextReader(sreader);
  31. jtok = JToken.ReadFrom(jreader);
  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(dec);
  81. else if (val is double dou) return Value.Float((decimal)dou);
  82. else if (val is float flo) return Value.Float((decimal)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, FileInfo file)
  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.Open(FileMode.Create, FileAccess.Write));
  118. using var jwriter = new JsonTextWriter(swriter)
  119. {
  120. Formatting = Formatting.Indented
  121. };
  122. tok.WriteTo(jwriter);
  123. }
  124. catch (Exception e)
  125. {
  126. Logger.config.Error($"Error serializing value for {file.FullName}");
  127. Logger.config.Error(e);
  128. }
  129. }
  130. private JToken VisitToToken(Value val)
  131. {
  132. switch (val)
  133. {
  134. case Text t:
  135. return new JValue(t.Value);
  136. case Boolean b:
  137. return new JValue(b.Value);
  138. case Integer i:
  139. return new JValue(i.Value);
  140. case FloatingPoint f:
  141. return new JValue(f.Value);
  142. case List l:
  143. var jarr = new JArray();
  144. foreach (var tok in l.Select(VisitToToken)) jarr.Add(tok);
  145. return jarr;
  146. case Map m:
  147. var jobj = new JObject();
  148. foreach (var kvp in m) jobj.Add(kvp.Key, VisitToToken(kvp.Value));
  149. return jobj;
  150. case null:
  151. return JValue.CreateNull();
  152. default:
  153. throw new ArgumentException($"Unsupported subtype of {nameof(Value)}");
  154. }
  155. }
  156. }
  157. }