IniFileCfgSource.cs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. using System.Text;
  2. using Apq.Cfg.Sources;
  3. using Apq.Cfg.Sources.File;
  4. using Microsoft.Extensions.Configuration;
  5. using Microsoft.Extensions.Configuration.Ini;
  6. using Microsoft.Extensions.FileProviders;
  7. namespace Apq.Cfg.Ini;
  8. internal sealed class IniFileCfgSource : FileCfgSourceBase, IWritableCfgSource
  9. {
  10. public IniFileCfgSource(string path, int level, bool writeable, bool optional, bool reloadOnChange,
  11. bool isPrimaryWriter)
  12. : base(path, level, writeable, optional, reloadOnChange, isPrimaryWriter)
  13. {
  14. }
  15. public override IConfigurationSource BuildSource()
  16. {
  17. var (fp, file) = CreatePhysicalFileProvider(_path);
  18. var src = new IniConfigurationSource
  19. {
  20. FileProvider = fp,
  21. Path = file,
  22. Optional = _optional,
  23. ReloadOnChange = _reloadOnChange
  24. };
  25. src.ResolveFileProvider();
  26. return src;
  27. }
  28. public async Task ApplyChangesAsync(IReadOnlyDictionary<string, string?> changes, CancellationToken cancellationToken)
  29. {
  30. if (!IsWriteable)
  31. throw new InvalidOperationException($"配置源 (层级 {Level}) 不可写");
  32. EnsureDirectoryFor(_path);
  33. var sections = new Dictionary<string, Dictionary<string, string?>>(StringComparer.OrdinalIgnoreCase);
  34. string? currentSection = null;
  35. if (File.Exists(_path))
  36. {
  37. var readEncoding = DetectEncoding(_path) ?? Encoding.UTF8;
  38. var lines = await File.ReadAllLinesAsync(_path, readEncoding, cancellationToken).ConfigureAwait(false);
  39. foreach (var line in lines)
  40. {
  41. var trimmed = line.Trim();
  42. if (string.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith(";")) continue;
  43. if (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
  44. {
  45. currentSection = trimmed.Substring(1, trimmed.Length - 2);
  46. if (!sections.ContainsKey(currentSection))
  47. sections[currentSection] = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
  48. continue;
  49. }
  50. var idx = trimmed.IndexOf('=');
  51. if (idx > 0)
  52. {
  53. var key = trimmed.Substring(0, idx).Trim();
  54. var value = trimmed.Substring(idx + 1);
  55. var section = currentSection ?? "";
  56. if (!sections.ContainsKey(section))
  57. sections[section] = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
  58. sections[section][key] = value;
  59. }
  60. }
  61. }
  62. foreach (var (configKey, value) in changes)
  63. {
  64. var colonIdx = configKey.IndexOf(':');
  65. string section, key;
  66. if (colonIdx > 0)
  67. {
  68. section = configKey.Substring(0, colonIdx);
  69. key = configKey.Substring(colonIdx + 1);
  70. }
  71. else
  72. {
  73. section = "";
  74. key = configKey;
  75. }
  76. if (!sections.ContainsKey(section))
  77. sections[section] = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
  78. if (value == null)
  79. sections[section].Remove(key);
  80. else
  81. sections[section][key] = value;
  82. }
  83. var sb = new StringBuilder();
  84. if (sections.TryGetValue("", out var rootKeys) && rootKeys.Count > 0)
  85. {
  86. foreach (var kv in rootKeys)
  87. sb.Append(kv.Key).Append('=').Append(kv.Value).AppendLine();
  88. }
  89. foreach (var (sectionName, keys) in sections)
  90. {
  91. if (string.IsNullOrEmpty(sectionName) || keys.Count == 0) continue;
  92. if (sb.Length > 0) sb.AppendLine();
  93. sb.Append('[').Append(sectionName).Append(']').AppendLine();
  94. foreach (var kv in keys)
  95. sb.Append(kv.Key).Append('=').Append(kv.Value).AppendLine();
  96. }
  97. await File.WriteAllTextAsync(_path, sb.ToString(), WriteEncoding, cancellationToken).ConfigureAwait(false);
  98. }
  99. }