Skip to content

Commit 734245e

Browse files
committed
feat: Implement natural sorting for IP addresses. #close 861
1 parent 7191eb6 commit 734245e

File tree

3 files changed

+134
-3
lines changed

3 files changed

+134
-3
lines changed

Ui/Utils/SubTitleSortByNaturalIp.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using _1RM.View;
9+
using IPAddress = System.Net.IPAddress;
10+
11+
namespace _1RM.Utils
12+
{
13+
public class SubTitleSortByNaturalIp : IComparer
14+
{
15+
private bool _orderIsAsc = true;
16+
private int return1 => _orderIsAsc ? 1 : -1;
17+
private int return_1 => _orderIsAsc ? -1 : 1;
18+
19+
public SubTitleSortByNaturalIp(bool orderIsAsc)
20+
{
21+
_orderIsAsc = orderIsAsc;
22+
}
23+
24+
25+
public int Compare(object x, object y)
26+
{
27+
if (x is not ProtocolBaseViewModel px || y is not ProtocolBaseViewModel py)
28+
{
29+
return return_1;
30+
}
31+
if (x == null || y == null)
32+
{
33+
throw new ArgumentNullException("Neither x nor y can be null");
34+
}
35+
36+
string strX = px.SubTitle;
37+
string strY = py.SubTitle;
38+
39+
// 根据 : 分割拆分
40+
var (ipX, portX) = SplitIpAndPort(strX);
41+
var (ipY, portY) = SplitIpAndPort(strY);
42+
43+
// 尝试解析 IP 地址
44+
{
45+
bool isXIpV4 = IsValidIPv4(ipX, out var xa4);
46+
bool isYIpV4 = IsValidIPv4(ipY, out var ya4);
47+
if (isXIpV4 && isYIpV4)
48+
{
49+
var keyX = GetIpV4Key(xa4, portX);
50+
var keyY = GetIpV4Key(ya4, portY);
51+
return string.Compare(keyX, keyY, StringComparison.Ordinal);
52+
}
53+
// 优先级排序:IPv4 > IPv6 > 其他
54+
if (isXIpV4 && !isYIpV4) return return_1; // x 是 IPv4,y 不是
55+
if (!isXIpV4 && isYIpV4) return return1; // y 是 IPv4,x 不是
56+
}
57+
58+
{
59+
bool isXIpV6 = IsValidIPv6(ipX, out var xa6);
60+
bool isYIpV6 = IsValidIPv6(ipY, out var ya6);
61+
if (isXIpV6 && isYIpV6)
62+
{
63+
var xkey = GetIpV6Key(xa6, portX);
64+
var ykey = GetIpV6Key(ya6, portY);
65+
return string.Compare(xkey, ykey, StringComparison.Ordinal);
66+
}
67+
if (isXIpV6 && !isYIpV6) return return_1; // x 是 IPv6,y 不是
68+
if (!isXIpV6 && isYIpV6) return return1; // y 是 IPv6,x 不是
69+
}
70+
71+
// 进行字面值比较
72+
var ret = string.Compare(strX, strY, StringComparison.Ordinal);
73+
if (!_orderIsAsc)
74+
{
75+
ret = -ret;
76+
}
77+
return ret;
78+
}
79+
80+
private (string, string) SplitIpAndPort(string str)
81+
{
82+
int i = str.IndexOf(":", StringComparison.Ordinal);
83+
if (i <= 0)
84+
return (str, "");
85+
var ip = str.Substring(0, i);
86+
var port = str.Substring(i + 1);
87+
return (ip, port);
88+
}
89+
90+
private bool IsValidIPv4(string ip, out IPAddress? address)
91+
{
92+
address = null;
93+
if (ip.Split('.').Length != 4)
94+
{
95+
return false;
96+
}
97+
return IPAddress.TryParse(ip, out address) && address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork;
98+
}
99+
100+
private bool IsValidIPv6(string ip, out IPAddress? address)
101+
{
102+
address = null;
103+
if (ip.Split(':').Length != 8)
104+
{
105+
return false;
106+
}
107+
return IPAddress.TryParse(ip, out address) && address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
108+
}
109+
110+
private string GetIpV4Key(IPAddress address, string port)
111+
{
112+
var parts = address.GetAddressBytes();
113+
return string.Join(".", parts.Select(p => p.ToString("D3"))) + ":" + port;
114+
}
115+
116+
private string GetIpV6Key(IPAddress address, string port)
117+
{
118+
var parts = address.GetAddressBytes();
119+
return string.Join(":", parts.Select(p => p.ToString("D5"))) + ":" + port;
120+
}
121+
}
122+
}

Ui/View/ServerList/ServerListPageViewModel.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ public void ApplySort()
375375
{
376376
Execute.OnUIThreadSync(() =>
377377
{
378-
var cvs = CollectionViewSource.GetDefaultView(v.LvServerCards.ItemsSource);
378+
var cvs = (ListCollectionView)CollectionViewSource.GetDefaultView(v.LvServerCards.ItemsSource);
379379
if (cvs == null) return;
380380

381381
string propertyName = "";
@@ -429,8 +429,15 @@ public void ApplySort()
429429
if (needRefresh)
430430
{
431431
cvs.SortDescriptions.Clear();
432-
cvs.SortDescriptions.Add(new SortDescription(nameof(ProtocolBaseViewModel.GroupedOrder), ListSortDirection.Ascending));
433-
cvs.SortDescriptions.Add(new SortDescription(propertyName, direction));
432+
if (propertyName == nameof(ProtocolBaseViewModel.SubTitle))
433+
{
434+
cvs.CustomSort = new SubTitleSortByNaturalIp(direction == ListSortDirection.Ascending);
435+
}
436+
else
437+
{
438+
cvs.SortDescriptions.Add(new SortDescription(nameof(ProtocolBaseViewModel.GroupedOrder), ListSortDirection.Ascending));
439+
cvs.SortDescriptions.Add(new SortDescription(propertyName, direction));
440+
}
434441
}
435442
//cvs.Refresh();
436443

Ui/View/Utils/MaskAndPop/PopupBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ public abstract class PopupBase : MaskLayerContainerScreenBase
1616

1717
public bool? ShowDialog(IViewAware? ownerViewModel = null)
1818
{
19+
IsShowWithDialog = true;
1920
return IoC.Get<IWindowManager>().ShowDialog(this, ownerViewModel);
2021
}
2122

2223
public void ShowWindow(IViewAware? ownerViewModel = null)
2324
{
25+
IsShowWithDialog = false;
2426
IoC.Get<IWindowManager>().ShowWindow(this, ownerViewModel);
2527
}
2628

0 commit comments

Comments
 (0)