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.

155 lines
6.1 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. case JTokenType.Bytes: // never used by Newtonsoft
  51. case JTokenType.Comment: // never used by Newtonsoft
  52. case JTokenType.Constructor: // never used by Newtonsoft
  53. case JTokenType.Property: // never used by Newtonsoft
  54. case JTokenType.Null:
  55. return Value.Null();
  56. case JTokenType.Boolean:
  57. return Value.Bool(((tok as JValue).Value as bool?) ?? false);
  58. case JTokenType.String:
  59. var val = (tok as JValue).Value;
  60. if (val is string s) return Value.Text(s);
  61. else if (val is char c) return Value.Text("" + c);
  62. else return Value.Text(string.Empty);
  63. case JTokenType.Integer:
  64. val = (tok as JValue).Value;
  65. if (val is long l) return Value.Integer(l);
  66. else if (val is ulong u) return Value.Integer((long)u);
  67. else return Value.Integer(0);
  68. case JTokenType.Float:
  69. val = (tok as JValue).Value;
  70. if (val is decimal dec) return Value.Float((double)dec);
  71. else if (val is double dou) return Value.Float(dou);
  72. else if (val is float flo) return Value.Float(flo);
  73. else return Value.Float(0); // default to 0 if something breaks
  74. case JTokenType.Date:
  75. val = (tok as JValue).Value;
  76. if (val is DateTime dt) return Value.Text(dt.ToString());
  77. else if (val is DateTimeOffset dto) return Value.Text(dto.ToString());
  78. else return Value.Text("Unknown Date-type token");
  79. case JTokenType.TimeSpan:
  80. val = (tok as JValue).Value;
  81. if (val is TimeSpan ts) return Value.Text(ts.ToString());
  82. else return Value.Text("Unknown TimeSpan-type token");
  83. case JTokenType.Guid:
  84. val = (tok as JValue).Value;
  85. if (val is Guid g) return Value.Text(g.ToString());
  86. else return Value.Text("Unknown Guid-type token");
  87. case JTokenType.Uri:
  88. val = (tok as JValue).Value;
  89. if (val is Uri ur) return Value.Text(ur.ToString());
  90. else return Value.Text("Unknown Uri-type token");
  91. case JTokenType.Array:
  92. return Value.From((tok as JArray).Select(VisitToValue));
  93. case JTokenType.Object:
  94. return Value.From((tok as IEnumerable<KeyValuePair<string, JToken>>)
  95. .Select(kvp => new KeyValuePair<string, Value>(kvp.Key, VisitToValue(kvp.Value))));
  96. default:
  97. throw new ArgumentException($"Unknown {nameof(JTokenType)} in parameter");
  98. }
  99. }
  100. public void Store(Value value)
  101. {
  102. if (File.Directory.Exists)
  103. File.Directory.Create();
  104. try
  105. {
  106. var tok = VisitToToken(value);
  107. using var swriter = new StreamWriter(File.OpenWrite());
  108. using var jwriter = new JsonTextWriter(swriter);
  109. tok.WriteTo(jwriter);
  110. }
  111. catch (Exception e)
  112. {
  113. Logger.config.Error($"Error serializing value for {File.FullName}");
  114. Logger.config.Error(e);
  115. }
  116. }
  117. private JToken VisitToToken(Value val)
  118. {
  119. switch (val)
  120. {
  121. case Text t:
  122. return new JValue(t.Value);
  123. case Boolean b:
  124. return new JValue(b.Value);
  125. case Integer i:
  126. return new JValue(i.Value);
  127. case FloatingPoint f:
  128. return new JValue(f.Value);
  129. case List l:
  130. var jarr = new JArray();
  131. foreach (var tok in l.Select(VisitToToken)) jarr.Add(tok);
  132. return jarr;
  133. case Map m:
  134. var jobj = new JObject();
  135. foreach (var kvp in m) jobj.Add(kvp.Key, VisitToToken(kvp.Value));
  136. return jobj;
  137. case null:
  138. return JValue.CreateNull();
  139. default:
  140. throw new ArgumentException($"Unsupported subtype of {nameof(Value)}");
  141. }
  142. }
  143. }
  144. }