Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions v2rayN/ServiceLib/Manager/AppManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,24 @@ public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMa
return items.ToDictionary(it => it.IndexId);
}

public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
{
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
if (idList.Count == 0)
{
Comment on lines +251 to +255
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method signature takes IEnumerable<string> indexIds, but several new call sites pass Select(sp => sp?.IndexId) which is naturally an IEnumerable<string?> and can contain nulls. Since the implementation already filters out null/empty ids, consider changing the parameter type to IEnumerable<string?> (or accepting nullable and guarding) to better reflect actual usage and avoid nullability mismatches.

Copilot uses AI. Check for mistakes.
return [];
}

var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
.Where(it => idList.Contains(it.IndexId))
.ToListAsync();
var itemMap = items.ToDictionary(it => it.IndexId);

return idList.Select(id => itemMap.GetValueOrDefault(id))
.Where(item => item != null)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idList.Select(id => itemMap.GetValueOrDefault(id)).Where(item => item != null).ToList() relies on a null-filter to satisfy the List<ProfileItem> return type. To keep the non-null contract explicit (and resilient if nullable analysis is enabled later), consider using OfType<ProfileItem>() or null-forgiving after the filter so the resulting list is statically non-nullable.

Suggested change
.Where(item => item != null)
.OfType<ProfileItem>()

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个刚开始的写法就是 .OfType<ProfileItem>() 来着,ReSharper 直接冒绿光让我改成 .Where(item => item != null)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

按编辑器的提示吧。 ai 有时候会弄过时的方案

.ToList();
Comment on lines +253 to +266
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetProfileItemsOrderedByIndexIds calls Distinct() on the incoming indexIds, which removes duplicate ids. Previously, call sites that built ordered lists (e.g., group ChildItems ordering) would preserve duplicates by iterating the original id list. If duplicates are meaningful (e.g., weighting / repeated nodes), this changes behavior; consider keeping the original idList for ordering and using a separate distinct set only for the DB query.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这边就不去重了?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

去重应该更合理些

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

但是是否存在一个可能,用户是故意添加的重复的呢?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那就不改了

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

并且这玩意也不是严格对应的,会跳过某些无效项,也不应该直接下标访问,注释没写

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

但是是否存在一个可能,用户是故意添加的重复的呢?

先不管了吧,重复的没什么意义,有人说了再改吧

}

public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
{
if (remarks.IsNullOrEmpty())
Expand Down
18 changes: 4 additions & 14 deletions v2rayN/ServiceLib/Manager/GroupProfileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ private static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? ext
return false;
}

foreach (var child in childIds)
var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds);
foreach (var childItem in childItems)
{
var childItem = await AppManager.Instance.GetProfileItem(child);
if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack))
if (await HasCycle(childItem.IndexId, childItem?.GetProtocolExtra(), visited, stack))
{
return true;
}
Expand Down Expand Up @@ -103,17 +103,7 @@ private static async Task<List<ProfileItem>> GetSelectedChildProfileItems(Protoc
return [];
}

var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(childProfileIds);

var ordered = new List<ProfileItem>(childProfileIds.Count);
foreach (var id in childProfileIds)
{
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
{
ordered.Add(item);
}
}

var ordered = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childProfileIds);
return ordered;
}

Expand Down
11 changes: 2 additions & 9 deletions v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,8 @@ public async Task Init()
Filter = protocolExtra?.Filter;

var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? [];
foreach (var item in childIndexIds)
{
var child = await AppManager.Instance.GetProfileItem(item);
if (child == null)
{
continue;
}
ChildItemsObs.Add(child);
}
var childItemList = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childIndexIds);
ChildItemsObs.AddRange(childItemList);
}

public async Task ChildRemoveAsync()
Expand Down
14 changes: 1 addition & 13 deletions v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,19 +255,7 @@ public async Task RefreshSubscriptions()
{
return null;
}
var lst = new List<ProfileItem>();
foreach (var sp in SelectedProfiles)
{
if (string.IsNullOrEmpty(sp?.IndexId))
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
if (item != null)
{
lst.Add(item);
}
}
var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId));
if (lst.Count == 0)
{
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
Expand Down
9 changes: 1 addition & 8 deletions v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,14 +456,7 @@ from t33 in t3b.DefaultIfEmpty()
var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort);
if (latest)
{
foreach (var profile in orderProfiles)
{
var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
if (item is not null)
{
lstSelected.Add(item);
}
}
lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId)));
}
else
{
Expand Down
9 changes: 1 addition & 8 deletions v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,7 @@ private void AddGroupServerWindow_KeyDown(object? sender, KeyEventArgs e)
private async void MenuAddChild_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
}
else
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
}
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
selectWindow.AllowMultiSelect(true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
Expand Down
4 changes: 2 additions & 2 deletions v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private void Window_Loaded(object? sender, RoutedEventArgs e)
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
Expand All @@ -74,7 +74,7 @@ private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
Expand Down
9 changes: 1 addition & 8 deletions v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,7 @@ private void AddGroupServerWindow_PreviewKeyDown(object sender, KeyEventArgs e)
private async void MenuAddChild_Click(object sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
}
else
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
}
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
selectWindow.AllowMultiSelect(true);
if (selectWindow.ShowDialog() == true)
{
Expand Down
4 changes: 2 additions & 2 deletions v2rayN/v2rayN/Views/SubEditWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
if (selectWindow.ShowDialog() == true)
{
var profile = await selectWindow.ProfileItem;
Expand All @@ -67,7 +67,7 @@ private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e)
private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
if (selectWindow.ShowDialog() == true)
{
var profile = await selectWindow.ProfileItem;
Expand Down