IniFileCfgSource.cs 4.1 KB

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