From 5c4f485471b3072dac3461f2c99c0bacb49dbd3c Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 6 Sep 2025 20:12:04 +0800 Subject: [PATCH] Multi Profile --- v2rayN/ServiceLib/Enums/EConfigType.cs | 6 +- v2rayN/ServiceLib/Enums/EMultipleLoad.cs | 2 +- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 38 ++++- v2rayN/ServiceLib/Manager/AppManager.cs | 11 ++ .../Manager/ProfileGroupItemManager.cs | 156 ++++++++++++++++++ v2rayN/ServiceLib/Manager/TaskManager.cs | 1 + v2rayN/ServiceLib/Models/ProfileGroupItem.cs | 13 ++ v2rayN/ServiceLib/Models/ProfileItem.cs | 11 +- .../ViewModels/AddGroupServerViewModel.cs | 17 ++ .../ViewModels/MainWindowViewModel.cs | 1 + 10 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs create mode 100644 v2rayN/ServiceLib/Models/ProfileGroupItem.cs create mode 100644 v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index 6698f962..5ddb8451 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -12,5 +12,9 @@ public enum EConfigType TUIC = 8, WireGuard = 9, HTTP = 10, - Anytls = 11 + Anytls = 11, + + Group = 1000, + PolicyGroup = 1001, + ProxyChain = 1002, } diff --git a/v2rayN/ServiceLib/Enums/EMultipleLoad.cs b/v2rayN/ServiceLib/Enums/EMultipleLoad.cs index 42be7a5b..af42fb60 100644 --- a/v2rayN/ServiceLib/Enums/EMultipleLoad.cs +++ b/v2rayN/ServiceLib/Enums/EMultipleLoad.cs @@ -2,8 +2,8 @@ namespace ServiceLib.Enums; public enum EMultipleLoad { + LeastPing, Random, RoundRobin, - LeastPing, LeastLoad } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 65f2ee53..1ad3b1ca 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -357,6 +357,11 @@ public static class ConfigHandler { } } + else if (profileItem.ConfigType > EConfigType.Group) + { + var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId); + await AddGroupServerCommon(config, profileItem, profileGroupItem, true); + } else { await AddServerCommon(config, profileItem, true); @@ -1074,6 +1079,35 @@ public static class ConfigHandler return 0; } + public static async Task AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true) + { + var maxSort = -1; + if (profileItem.IndexId.IsNullOrEmpty()) + { + profileItem.IndexId = Utils.GetGuid(false); + maxSort = ProfileExManager.Instance.GetMaxSort(); + } + if (maxSort > 0) + { + ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1); + } + if (toFile) + { + await SQLiteHelper.Instance.ReplaceAsync(profileItem); + if (profileGroupItem != null) + { + profileGroupItem.ParentIndexId = profileItem.IndexId; + await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem); + } + else + { + ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId); + await ProfileGroupItemManager.Instance.SaveTo(); + } + } + return 0; + } + /// /// Compare two profile items to determine if they represent the same server /// Used for deduplication and server matching @@ -1207,7 +1241,7 @@ public static class ConfigHandler public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) { ProfileItem? itemSocks = null; - if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) + if (node.ConfigType != EConfigType.Custom && node.ConfigType < EConfigType.Group && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) { itemSocks = new ProfileItem() { @@ -1218,7 +1252,7 @@ public static class ConfigHandler Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks) }; } - else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0) + else if (node.ConfigType == EConfigType.Custom && node.ConfigType < EConfigType.Group && node.PreSocksPort > 0) { var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; itemSocks = new ProfileItem() diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 33125289..8a86133f 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -67,6 +67,7 @@ public sealed class AppManager SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); return true; } @@ -101,6 +102,7 @@ public sealed class AppManager await ConfigHandler.SaveConfig(_config); await ProfileExManager.Instance.SaveTo(); + await ProfileGroupItemManager.Instance.SaveTo(); await StatisticsManager.Instance.SaveTo(); await CoreManager.Instance.CoreStop(); StatisticsManager.Instance.Close(); @@ -219,6 +221,15 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Remarks == remarks); } + public async Task GetProfileGroupItem(string parentIndexId) + { + if (parentIndexId.IsNullOrEmpty()) + { + return null; + } + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.ParentIndexId == parentIndexId); + } + public async Task?> RoutingItems() { return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); diff --git a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs new file mode 100644 index 00000000..7a35b671 --- /dev/null +++ b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs @@ -0,0 +1,156 @@ +using System.Collections.Concurrent; + +namespace ServiceLib.Manager; + +public class ProfileGroupItemManager +{ + private static readonly Lazy _instance = new(() => new()); + private ConcurrentDictionary _items = new(); + + public static ProfileGroupItemManager Instance => _instance.Value; + private static readonly string _tag = "ProfileGroupItemManager"; + + private ProfileGroupItemManager() + { + } + + public async Task Init() + { + await InitData(); + } + + public Task> GetProfileGroupItemList() + { + var bag = new ConcurrentBag(_items.Values); + return Task.FromResult(bag); + } + + private async Task InitData() + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where parentIndexId not in ( select indexId from ProfileItem )"); + + var list = await SQLiteHelper.Instance.TableAsync().ToListAsync(); + _items = new ConcurrentDictionary(list.Where(t => !string.IsNullOrEmpty(t.ParentIndexId)).ToDictionary(t => t.ParentIndexId!)); + } + + private ProfileGroupItem AddProfileGroupItem(string indexId) + { + var profileGroupItem = new ProfileGroupItem() + { + ParentIndexId = indexId, + ChildItems = string.Empty, + MultipleLoad = EMultipleLoad.LeastPing + }; + + _items[indexId] = profileGroupItem; + return profileGroupItem; + } + + private ProfileGroupItem GetProfileGroupItem(string indexId) + { + if (string.IsNullOrEmpty(indexId)) + { + throw new ArgumentNullException(nameof(indexId)); + } + + return _items.GetOrAdd(indexId, AddProfileGroupItem); + } + + public async Task ClearAll() + { + await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem "); + _items.Clear(); + } + + public async Task SaveTo() + { + try + { + var lstExists = await SQLiteHelper.Instance.TableAsync().ToListAsync(); + var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.ParentIndexId)).ToDictionary(t => t.ParentIndexId!); + + var lstInserts = new List(); + var lstUpdates = new List(); + + foreach (var item in _items.Values) + { + if (string.IsNullOrEmpty(item.ParentIndexId)) + { + continue; + } + + if (existsMap.ContainsKey(item.ParentIndexId)) + { + lstUpdates.Add(item); + } + else + { + lstInserts.Add(item); + } + } + + try + { + if (lstInserts.Count > 0) + { + await SQLiteHelper.Instance.InsertAllAsync(lstInserts); + } + + if (lstUpdates.Count > 0) + { + await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId) + { + return GetProfileGroupItem(indexId); + } + + public async ValueTask DisposeAsync() + { + await SaveTo(); + } + + public async Task SaveItemAsync(ProfileGroupItem item) + { + if (item is null) + { + throw new ArgumentNullException(nameof(item)); + } + + if (string.IsNullOrWhiteSpace(item.ParentIndexId)) + { + throw new ArgumentException("ParentIndexId required", nameof(item)); + } + + _items[item.ParentIndexId] = item; + + try + { + var lst = await SQLiteHelper.Instance.TableAsync().Where(t => t.ParentIndexId == item.ParentIndexId).ToListAsync(); + if (lst != null && lst.Count > 0) + { + await SQLiteHelper.Instance.UpdateAllAsync(new List { item }); + } + else + { + await SQLiteHelper.Instance.InsertAllAsync(new List { item }); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } +} diff --git a/v2rayN/ServiceLib/Manager/TaskManager.cs b/v2rayN/ServiceLib/Manager/TaskManager.cs index dfbd242d..c626e1b7 100644 --- a/v2rayN/ServiceLib/Manager/TaskManager.cs +++ b/v2rayN/ServiceLib/Manager/TaskManager.cs @@ -35,6 +35,7 @@ public class TaskManager await ConfigHandler.SaveConfig(_config); await ProfileExManager.Instance.SaveTo(); + await ProfileGroupItemManager.Instance.SaveTo(); } //Execute once 1 hour diff --git a/v2rayN/ServiceLib/Models/ProfileGroupItem.cs b/v2rayN/ServiceLib/Models/ProfileGroupItem.cs new file mode 100644 index 00000000..eb6ac49f --- /dev/null +++ b/v2rayN/ServiceLib/Models/ProfileGroupItem.cs @@ -0,0 +1,13 @@ +using SQLite; +namespace ServiceLib.Models; + +[Serializable] +public class ProfileGroupItem +{ + [PrimaryKey] + public string ParentIndexId { get; set; } + + public string ChildItems { get; set; } + + public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing; +} diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index 998aa120..45c8df8d 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -39,11 +39,14 @@ public class ProfileItem : ReactiveObject > 1 => $"***{arrAddr.Last()}", _ => Address }; - summary += ConfigType switch + if (ConfigType is EConfigType.Custom or > EConfigType.Group) { - EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}", - _ => $"{Remarks}({addr}:{Port})" - }; + summary += $"[{CoreType.ToString()}]{Remarks}"; + } + else + { + summary += $"{Remarks}({addr}:{Port})"; + } return summary; } diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs new file mode 100644 index 00000000..5d10160c --- /dev/null +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -0,0 +1,17 @@ +using System.Reactive; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; + +namespace ServiceLib.ViewModels; + +public class AddGroupServerViewModel : MyReactiveObject +{ + [Reactive] + public ProfileItem SelectedSource { get; set; } + + [Reactive] + public IList SelectedChildren { get; set; } + + //[Reactive] + //public +} diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 4e9fc9fc..83a91ce5 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -228,6 +228,7 @@ public class MainWindowViewModel : MyReactiveObject await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinFullConfigTemplate(_config); await ProfileExManager.Instance.Init(); + await ProfileGroupItemManager.Instance.Init(); await CoreManager.Instance.Init(_config, UpdateHandler); TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);