using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.VisualStudio.Threading; using Netch.JsonConverter; using Netch.Models; namespace Netch.Utils; public static class Configuration { /// /// 数据目录 /// public static string DataDirectoryFullName => Path.Combine(Global.NetchDir, "data"); public static string FileFullName => Path.Combine(DataDirectoryFullName, FileName); private static string BackupFileFullName => Path.Combine(DataDirectoryFullName, BackupFileName); private const string FileName = "settings.json"; private const string BackupFileName = "settings.json.bak"; private static readonly AsyncReaderWriterLock _lock = new(null); private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); static Configuration() { JsonSerializerOptions.Converters.Add(new ServerConverterWithTypeDiscriminator()); JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); } public static async Task LoadAsync() { try { if (!File.Exists(FileFullName)) { await SaveAsync(); return; } await using var _ = await _lock.ReadLockAsync(); if (await LoadCoreAsync(FileFullName)) return; Log.Information("Load backup configuration \"{FileName}\"", BackupFileFullName); await LoadCoreAsync(BackupFileFullName); } catch (Exception e) { Log.Error(e, "Load configuration failed"); Environment.Exit(-1); } } private static async ValueTask LoadCoreAsync(string filename) { try { Setting settings; await using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) { settings = (await JsonSerializer.DeserializeAsync(fs, JsonSerializerOptions))!; } CheckSetting(settings); Global.Settings = settings; return true; } catch (Exception e) { Log.Error(e, "Load configuration file \"{FileName}\" error ", filename); return false; } } private static void CheckSetting(Setting settings) { settings.Profiles.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty); if (settings.Profiles.Any(p => settings.Profiles.Any(p1 => p1 != p && p1.Index == p.Index))) for (var i = 0; i < settings.Profiles.Count; i++) settings.Profiles[i].Index = i; settings.AioDNS.ChinaDNS = DnsUtils.AppendPort(settings.AioDNS.ChinaDNS); settings.AioDNS.OtherDNS = DnsUtils.AppendPort(settings.AioDNS.OtherDNS); } /// /// 保存配置 /// public static async Task SaveAsync() { if (_lock.IsWriteLockHeld) return; try { await using var _ = await _lock.WriteLockAsync(); Log.Verbose("Save Configuration"); if (!Directory.Exists(DataDirectoryFullName)) Directory.CreateDirectory(DataDirectoryFullName); var tempFile = Path.Combine(DataDirectoryFullName, FileFullName + ".tmp"); await using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) { await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions); } await EnsureConfigFileExistsAsync(); File.Replace(tempFile, FileFullName, BackupFileFullName); } catch (Exception e) { Log.Error(e, "Save Configuration error"); } } private static async ValueTask EnsureConfigFileExistsAsync() { if (!File.Exists(FileFullName)) { await using var fs = new FileStream(FileFullName, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true); } } }