Compare commits

...

37 Commits
6.56 ... 6.59

Author SHA1 Message Date
2dust
a7f3a7b1a7 up 6.59 2024-09-17 18:07:07 +08:00
2dust
71eb5f0813 HyperlinkButton Classes="WithIcon" 2024-09-17 17:26:01 +08:00
2dust
b4f50258a7 Fix style for desktop 2024-09-17 17:20:37 +08:00
2dust
346a9c5fcc Add Connections Host Filter 2024-09-17 16:59:35 +08:00
2dust
61635db5b5 Add IsNotEmpty function 2024-09-17 16:52:41 +08:00
2dust
4c0a59a715 Add Connections Host Filter
https://github.com/2dust/v2rayN/issues/5683
2024-09-17 14:50:31 +08:00
2dust
aa829a66ea Improved Style for Desktop version 2024-09-16 18:21:30 +08:00
2dust
885f193a00 Fix GetVersion 2024-09-16 15:34:35 +08:00
2dust
0a9bbf526d up PackageReference 2024-09-16 15:26:17 +08:00
2dust
ff6716b39d Restore backup file check 2024-09-16 15:23:06 +08:00
2dust
8505f2db96 Optimize backup
https://github.com/2dust/v2rayN/issues/5681
2024-09-16 13:37:38 +08:00
2dust
233d605256 Optimize the information function 2024-09-16 11:09:44 +08:00
2dust
c4e01d20a0 up 6.58 2024-09-08 13:39:13 +08:00
2dust
07156cb55c Bug fix 2024-09-08 10:45:05 +08:00
2dust
8361b4e4a0 Fix 2024-09-07 19:04:19 +08:00
2dust
bb90671979 Fix 2024-09-07 18:08:56 +08:00
2dust
beddc71ed8 Add backup and restore 2024-09-07 14:52:33 +08:00
2dust
d5f1cc99ac Refactor V2rayUpgrade 2024-09-06 18:37:46 +08:00
2dust
d03a86292e Add SaveFileDialog 2024-09-06 15:51:06 +08:00
2dust
c061a24948 Update v2rayUpgrade 2024-09-06 14:07:48 +08:00
2dust
9c7446f820 In-app update alone package 2024-09-06 14:07:26 +08:00
2dust
8be1730719 Bug fix 2024-09-06 09:40:45 +08:00
2dust
7ceaf5c071 Add preprocess of yaml file
https://github.com/2dust/v2rayN/issues/5646
2024-09-06 08:57:42 +08:00
2dust
7901a59aa4 Code clean 2024-09-05 13:29:51 +08:00
2dust
8343a1002f Improvement check updates for Desktop 2024-09-05 13:29:25 +08:00
2dust
c83dce3cc0 Fix
https://github.com/2dust/v2rayN/issues/5644
2024-09-05 10:02:26 +08:00
2dust
14afeab2bb Fix
https://github.com/2dust/v2rayN/issues/5640
2024-09-05 09:55:36 +08:00
2dust
c22b57927c up 6.57 2024-09-04 16:23:10 +08:00
2dust
81b84d235c Code clean 2024-09-04 16:15:31 +08:00
2dust
488e8aab00 Improvement check updates 2024-09-04 15:47:17 +08:00
Wydy
fc43a9a726 Update pac.txt (#5636) 2024-09-04 09:42:54 +08:00
2dust
31947fdcb3 Bug fix
https://github.com/2dust/v2rayN/issues/5635
2024-09-03 19:30:23 +08:00
2dust
34c7963d28 Bug fix
https://github.com/2dust/v2rayN/issues/5627
2024-09-03 16:14:49 +08:00
2dust
d91b0afb9a Improved log message display 2024-09-03 16:03:26 +08:00
2dust
82eb3fd6bd Unified processing version 2024-09-03 10:16:50 +08:00
2dust
3d3d3f83df Bug fix
https://github.com/2dust/v2rayN/issues/5619#issuecomment-2323968966
2024-09-02 15:10:34 +08:00
2dust
6879c75bc8 Using Queues to Improve Message Display 2024-09-02 14:20:09 +08:00
94 changed files with 21276 additions and 1355 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.28.0" />
<PackageReference Include="Google.Protobuf" Version="3.28.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
<PackageReference Include="Grpc.Tools" Version="2.66.0">
<PrivateAssets>all</PrivateAssets>

View File

@@ -18,7 +18,7 @@ namespace ServiceLib.Common
Uri uri = new(url);
//Authorization Header
var headers = new WebHeaderCollection();
if (!Utils.IsNullOrEmpty(uri.UserInfo))
if (Utils.IsNotEmpty(uri.UserInfo))
{
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
}
@@ -176,7 +176,7 @@ namespace ServiceLib.Common
};
using var cts = new CancellationTokenSource();
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token);
downloadOpt = null;
}

View File

@@ -82,7 +82,7 @@ namespace ServiceLib.Common
}
try
{
if (!Utils.IsNullOrEmpty(ignoredName) && entry.Name.Contains(ignoredName))
if (Utils.IsNotEmpty(ignoredName) && entry.Name.Contains(ignoredName))
{
continue;
}
@@ -102,11 +102,34 @@ namespace ServiceLib.Common
return true;
}
public static List<string>? GetFilesFromZip(string fileName)
{
if (!File.Exists(fileName))
{
return null;
}
try
{
using ZipArchive archive = ZipFile.OpenRead(fileName);
return archive.Entries.Select(entry => entry.FullName).ToList();
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return null;
}
}
public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName)
{
try
{
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName);
if (File.Exists(destinationArchiveFileName))
{
File.Delete(destinationArchiveFileName);
}
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true);
}
catch (Exception ex)
{
@@ -115,6 +138,46 @@ namespace ServiceLib.Common
}
return true;
}
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, string ignoredName)
{
// Get information about the source directory
var dir = new DirectoryInfo(sourceDir);
// Check if the source directory exists
if (!dir.Exists)
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
// Cache directories before we start copying
DirectoryInfo[] dirs = dir.GetDirectories();
// Create the destination directory
Directory.CreateDirectory(destinationDir);
// Get the files in the source directory and copy to the destination directory
foreach (FileInfo file in dir.GetFiles())
{
if (Utils.IsNotEmpty(ignoredName) && file.Name.Contains(ignoredName))
{
continue;
}
if (file.Extension == file.Name)
{
continue;
}
string targetFilePath = Path.Combine(destinationDir, file.Name);
file.CopyTo(targetFilePath);
}
// If recursive and copying subdirectories, recursively call this method
if (recursive)
{
foreach (DirectoryInfo subDir in dirs)
{
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
CopyDirectory(subDir.FullName, newDestinationDir, true, ignoredName);
}
}
}
}
}

View File

@@ -22,7 +22,7 @@ namespace ServiceLib.Common
public async Task<string?> TryGetAsync(string url)
{
if (string.IsNullOrEmpty(url))
if (Utils.IsNullOrEmpty(url))
return null;
try

View File

@@ -27,7 +27,7 @@
this.minor = int.Parse(parts[1]);
this.patch = 0;
}
else if (parts.Length == 3)
else if (parts.Length == 3 || parts.Length == 4)
{
this.major = int.Parse(parts[0]);
this.minor = int.Parse(parts[1]);

View File

@@ -14,6 +14,11 @@ namespace ServiceLib.Common
return string.IsNullOrWhiteSpace(value);
}
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
{
return !string.IsNullOrEmpty(value);
}
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
{
if (s.IsNullOrEmpty()) return false;

View File

@@ -417,6 +417,11 @@ namespace ServiceLib.Common
return false;
}
public static bool IsNotEmpty(string? text)
{
return !string.IsNullOrEmpty(text);
}
/// <summary>
/// 验证IP地址是否合法
/// </summary>
@@ -567,13 +572,12 @@ namespace ServiceLib.Common
{
try
{
string location = GetExePath();
if (blFull)
{
return string.Format("{0} - V{1} - {2}",
Global.AppName,
GetVersionInfo(),
File.GetLastWriteTime(location).ToString("yyyy/MM/dd"));
File.GetLastWriteTime(GetExePath()).ToString("yyyy/MM/dd"));
}
else
{
@@ -593,8 +597,7 @@ namespace ServiceLib.Common
{
try
{
string location = GetExePath();
return FileVersionInfo.GetVersionInfo(location)?.FileVersion ?? "0.0";
return Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString(3) ?? "0.0";
}
catch (Exception ex)
{

View File

@@ -1,4 +1,5 @@
using YamlDotNet.Serialization;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace ServiceLib.Common
@@ -35,13 +36,17 @@ namespace ServiceLib.Common
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToYaml(Object obj)
public static string ToYaml(Object? obj)
{
string result = string.Empty;
if (obj == null)
{
return result;
}
var serializer = new SerializerBuilder()
.WithNamingConvention(HyphenatedNamingConvention.Instance)
.Build();
string result = string.Empty;
try
{
result = serializer.Serialize(obj);
@@ -53,6 +58,24 @@ namespace ServiceLib.Common
return result;
}
public static string? PreprocessYaml(string str)
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.Build();
try
{
var mergingParser = new MergingParser(new Parser(new StringReader(str)));
var obj = new DeserializerBuilder().Build().Deserialize(mergingParser);
return ToYaml(obj);
}
catch (Exception ex)
{
Logging.SaveLog("PreprocessYaml", ex);
return null;
}
}
#endregion YAML
}
}

View File

@@ -39,6 +39,7 @@
DispatcherRefreshServersBiz,
DispatcherRefreshIcon,
DispatcherCheckUpdate,
DispatcherCheckUpdateFinished,
DispatcherCheckUpdateFinished,
DispatcherShowMsg,
}
}

View File

@@ -23,7 +23,7 @@ namespace ServiceLib.Handler
{
//载入配置文件
var result = Utils.LoadResource(Utils.GetConfigPath(configRes));
if (!Utils.IsNullOrEmpty(result))
if (Utils.IsNotEmpty(result))
{
//转成Json
config = JsonUtils.Deserialize<Config>(result);
@@ -124,20 +124,16 @@ namespace ServiceLib.Handler
mtu = 9000,
};
}
if (config.guiItem == null)
config.guiItem ??= new()
{
config.guiItem = new()
{
enableStatistics = false,
};
}
if (config.uiItem == null)
enableStatistics = false,
};
config.msgUIItem ??= new();
config.uiItem ??= new UIItem()
{
config.uiItem = new UIItem()
{
enableAutoAdjustMainLvColWidth = true
};
}
enableAutoAdjustMainLvColWidth = true
};
if (config.uiItem.mainColumnItem == null)
{
config.uiItem.mainColumnItem = new();
@@ -174,11 +170,11 @@ namespace ServiceLib.Handler
}
config.mux4RayItem ??= new()
{
concurrency = 8,
xudpConcurrency = 16,
xudpProxyUDP443 = "reject"
};
{
concurrency = 8,
xudpConcurrency = 16,
xudpProxyUDP443 = "reject"
};
if (config.mux4SboxItem == null)
{
@@ -208,6 +204,8 @@ namespace ServiceLib.Handler
};
}
config.webDavItem ??= new();
return 0;
}
@@ -1009,7 +1007,7 @@ namespace ServiceLib.Handler
{
return -1;
}
if (!Utils.IsNullOrEmpty(profileItem.security) && profileItem.security != Global.None)
if (Utils.IsNotEmpty(profileItem.security) && profileItem.security != Global.None)
{
profileItem.security = Global.None;
}
@@ -1047,7 +1045,7 @@ namespace ServiceLib.Handler
{
profileItem.configVersion = 2;
if (!Utils.IsNullOrEmpty(profileItem.streamSecurity))
if (Utils.IsNotEmpty(profileItem.streamSecurity))
{
if (profileItem.streamSecurity != Global.StreamSecurity
&& profileItem.streamSecurity != Global.StreamSecurityReality)
@@ -1067,7 +1065,7 @@ namespace ServiceLib.Handler
}
}
if (!Utils.IsNullOrEmpty(profileItem.network) && !Global.Networks.Contains(profileItem.network))
if (Utils.IsNotEmpty(profileItem.network) && !Global.Networks.Contains(profileItem.network))
{
profileItem.network = Global.DefaultNetwork;
}
@@ -1188,7 +1186,7 @@ namespace ServiceLib.Handler
string subFilter = string.Empty;
//remove sub items
if (isSub && !Utils.IsNullOrEmpty(subid))
if (isSub && Utils.IsNotEmpty(subid))
{
RemoveServerViaSubid(config, subid, isSub);
subFilter = LazyConfig.Instance.GetSubItem(subid)?.filter ?? "";
@@ -1221,7 +1219,7 @@ namespace ServiceLib.Handler
}
//exist sub items
if (isSub && !Utils.IsNullOrEmpty(subid))
if (isSub && Utils.IsNotEmpty(subid))
{
var existItem = lstOriSub?.FirstOrDefault(t => t.isSub == isSub
&& config.uiItem.enableUpdateSubOnlyRemarksExist ? t.remarks == profileItem.remarks : CompareProfileItem(t, profileItem, true));
@@ -1243,7 +1241,7 @@ namespace ServiceLib.Handler
}
}
//filter
if (!Utils.IsNullOrEmpty(subFilter))
if (Utils.IsNotEmpty(subFilter))
{
if (!Regex.IsMatch(profileItem.remarks, subFilter))
{
@@ -1307,7 +1305,7 @@ namespace ServiceLib.Handler
}
if (lstProfiles != null && lstProfiles.Count > 0)
{
if (isSub && !Utils.IsNullOrEmpty(subid))
if (isSub && Utils.IsNotEmpty(subid))
{
RemoveServerViaSubid(config, subid, isSub);
}
@@ -1363,7 +1361,7 @@ namespace ServiceLib.Handler
return -1;
}
if (isSub && !Utils.IsNullOrEmpty(subid))
if (isSub && Utils.IsNotEmpty(subid))
{
RemoveServerViaSubid(config, subid, isSub);
}
@@ -1391,7 +1389,7 @@ namespace ServiceLib.Handler
return -1;
}
if (isSub && !Utils.IsNullOrEmpty(subid))
if (isSub && Utils.IsNotEmpty(subid))
{
RemoveServerViaSubid(config, subid, isSub);
}
@@ -1423,7 +1421,7 @@ namespace ServiceLib.Handler
return -1;
}
List<ProfileItem>? lstOriSub = null;
if (isSub && !Utils.IsNullOrEmpty(subid))
if (isSub && Utils.IsNotEmpty(subid))
{
lstOriSub = LazyConfig.Instance.ProfileItems(subid);
}
@@ -1510,6 +1508,7 @@ namespace ServiceLib.Handler
item.userAgent = subItem.userAgent;
item.sort = subItem.sort;
item.filter = subItem.filter;
item.updateTime = subItem.updateTime;
item.convertTarget = subItem.convertTarget;
item.prevProfile = subItem.prevProfile;
item.nextProfile = subItem.nextProfile;

View File

@@ -43,7 +43,7 @@
}
string addressFileName = node.address;
if (string.IsNullOrEmpty(addressFileName))
if (Utils.IsNullOrEmpty(addressFileName))
{
msg = ResUI.FailedGetDefaultConfiguration;
return -1;
@@ -64,6 +64,12 @@
var txtFile = File.ReadAllText(addressFileName);
txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2);
//YAML anchors
if (txtFile.Contains("<<:") && txtFile.Contains("*") && txtFile.Contains("&"))
{
txtFile = YamlUtils.PreprocessYaml(txtFile);
}
var fileContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
if (fileContent == null)
{
@@ -111,7 +117,7 @@
if (_config.tunModeItem.enableTun)
{
string tun = Utils.GetEmbedText(Global.ClashTunYaml);
if (!string.IsNullOrEmpty(tun))
if (Utils.IsNotEmpty(tun))
{
var tunContent = YamlUtils.FromYaml<Dictionary<string, object>>(tun);
if (tunContent != null)

View File

@@ -370,7 +370,7 @@ namespace ServiceLib.Handler.CoreConfig
}
string addressFileName = node.address;
if (string.IsNullOrEmpty(addressFileName))
if (Utils.IsNullOrEmpty(addressFileName))
{
msg = ResUI.FailedGetDefaultConfiguration;
return -1;
@@ -489,7 +489,7 @@ namespace ServiceLib.Handler.CoreConfig
if (_config.routingBasicItem.enableRoutingAdvanced)
{
var routing = ConfigHandler.GetDefaultRouting(_config);
if (!Utils.IsNullOrEmpty(routing.domainStrategy4Singbox))
if (Utils.IsNotEmpty(routing.domainStrategy4Singbox))
{
inbound.domain_strategy = routing.domainStrategy4Singbox;
}
@@ -512,7 +512,7 @@ namespace ServiceLib.Handler.CoreConfig
singboxConfig.inbounds.Add(inbound4);
//auth
if (!Utils.IsNullOrEmpty(_config.inbound[0].user) && !Utils.IsNullOrEmpty(_config.inbound[0].pass))
if (Utils.IsNotEmpty(_config.inbound[0].user) && Utils.IsNotEmpty(_config.inbound[0].pass))
{
inbound3.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } };
inbound4.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } };
@@ -604,8 +604,8 @@ namespace ServiceLib.Handler.CoreConfig
case EConfigType.Socks:
{
outbound.version = "5";
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
if (Utils.IsNotEmpty(node.security)
&& Utils.IsNotEmpty(node.id))
{
outbound.username = node.security;
outbound.password = node.id;
@@ -614,8 +614,8 @@ namespace ServiceLib.Handler.CoreConfig
}
case EConfigType.Http:
{
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
if (Utils.IsNotEmpty(node.security)
&& Utils.IsNotEmpty(node.id))
{
outbound.username = node.security;
outbound.password = node.id;
@@ -649,7 +649,7 @@ namespace ServiceLib.Handler.CoreConfig
{
outbound.password = node.id;
if (!Utils.IsNullOrEmpty(node.path))
if (Utils.IsNotEmpty(node.path))
{
outbound.obfs = new()
{
@@ -695,7 +695,7 @@ namespace ServiceLib.Handler.CoreConfig
{
try
{
if (_config.coreBasicItem.muxEnabled && !Utils.IsNullOrEmpty(_config.mux4SboxItem.protocol))
if (_config.coreBasicItem.muxEnabled && Utils.IsNotEmpty(_config.mux4SboxItem.protocol))
{
var mux = new Multiplex4Sbox()
{
@@ -721,11 +721,11 @@ namespace ServiceLib.Handler.CoreConfig
if (node.streamSecurity == Global.StreamSecurityReality || node.streamSecurity == Global.StreamSecurity)
{
var server_name = string.Empty;
if (!Utils.IsNullOrEmpty(node.sni))
if (Utils.IsNotEmpty(node.sni))
{
server_name = node.sni;
}
else if (!Utils.IsNullOrEmpty(node.requestHost))
else if (Utils.IsNotEmpty(node.requestHost))
{
server_name = Utils.String2List(node.requestHost)[0];
}
@@ -736,7 +736,7 @@ namespace ServiceLib.Handler.CoreConfig
insecure = Utils.ToBool(node.allowInsecure.IsNullOrEmpty() ? _config.coreBasicItem.defAllowInsecure.ToString().ToLower() : node.allowInsecure),
alpn = node.GetAlpn(),
};
if (!Utils.IsNullOrEmpty(node.fingerprint))
if (Utils.IsNotEmpty(node.fingerprint))
{
tls.utls = new Utls4Sbox()
{
@@ -798,7 +798,7 @@ namespace ServiceLib.Handler.CoreConfig
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path;
if (!Utils.IsNullOrEmpty(node.requestHost))
if (Utils.IsNotEmpty(node.requestHost))
{
transport.headers = new()
{
@@ -1020,7 +1020,7 @@ namespace ServiceLib.Handler.CoreConfig
outbound = item.outboundTag,
};
if (!Utils.IsNullOrEmpty(item.port))
if (Utils.IsNotEmpty(item.port))
{
if (item.port.Contains("-"))
{
@@ -1031,7 +1031,7 @@ namespace ServiceLib.Handler.CoreConfig
rule.port = new List<int> { Utils.ToInt(item.port) };
}
}
if (!Utils.IsNullOrEmpty(item.network))
if (Utils.IsNotEmpty(item.network))
{
rule.network = Utils.String2List(item.network);
}
@@ -1221,7 +1221,7 @@ namespace ServiceLib.Handler.CoreConfig
});
var lstDomain = singboxConfig.outbounds
.Where(t => !Utils.IsNullOrEmpty(t.server) && Utils.IsDomain(t.server))
.Where(t => Utils.IsNotEmpty(t.server) && Utils.IsDomain(t.server))
.Select(t => t.server)
.Distinct()
.ToList();
@@ -1250,7 +1250,7 @@ namespace ServiceLib.Handler.CoreConfig
private int GenExperimental(SingboxConfig singboxConfig)
{
if (_config.guiItem.enableStatistics)
//if (_config.guiItem.enableStatistics)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
@@ -1324,10 +1324,10 @@ namespace ServiceLib.Handler.CoreConfig
if (_config.routingBasicItem.enableRoutingAdvanced)
{
var routing = ConfigHandler.GetDefaultRouting(_config);
if (!Utils.IsNullOrEmpty(routing.customRulesetPath4Singbox))
if (Utils.IsNotEmpty(routing.customRulesetPath4Singbox))
{
var result = Utils.LoadResource(routing.customRulesetPath4Singbox);
if (!Utils.IsNullOrEmpty(result))
if (Utils.IsNotEmpty(result))
{
customRulesets = (JsonUtils.Deserialize<List<Ruleset4Sbox>>(result) ?? [])
.Where(t => t.tag != null)

View File

@@ -392,7 +392,7 @@ namespace ServiceLib.Handler.CoreConfig
v2rayConfig.inbounds.Add(inbound4);
//auth
if (!Utils.IsNullOrEmpty(_config.inbound[0].user) && !Utils.IsNullOrEmpty(_config.inbound[0].pass))
if (Utils.IsNotEmpty(_config.inbound[0].user) && Utils.IsNotEmpty(_config.inbound[0].pass))
{
inbound3.settings.auth = "password";
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.inbound[0].user, pass = _config.inbound[0].pass } };
@@ -453,7 +453,7 @@ namespace ServiceLib.Handler.CoreConfig
var routing = ConfigHandler.GetDefaultRouting(_config);
if (routing != null)
{
if (!Utils.IsNullOrEmpty(routing.domainStrategy))
if (Utils.IsNotEmpty(routing.domainStrategy))
{
v2rayConfig.routing.domainStrategy = routing.domainStrategy;
}
@@ -550,7 +550,7 @@ namespace ServiceLib.Handler.CoreConfig
}
if (!hasDomainIp)
{
if (!Utils.IsNullOrEmpty(rule.port)
if (Utils.IsNotEmpty(rule.port)
|| rule.protocol?.Count > 0
|| rule.inboundTag?.Count > 0
)
@@ -660,8 +660,8 @@ namespace ServiceLib.Handler.CoreConfig
serversItem.method = null;
serversItem.password = null;
if (!Utils.IsNullOrEmpty(node.security)
&& !Utils.IsNullOrEmpty(node.id))
if (Utils.IsNotEmpty(node.security)
&& Utils.IsNotEmpty(node.id))
{
SocksUsersItem4Ray socksUsersItem = new()
{
@@ -712,7 +712,7 @@ namespace ServiceLib.Handler.CoreConfig
if (node.streamSecurity == Global.StreamSecurityReality
|| node.streamSecurity == Global.StreamSecurity)
{
if (!Utils.IsNullOrEmpty(node.flow))
if (Utils.IsNotEmpty(node.flow))
{
usersItem.flow = node.flow;
@@ -818,11 +818,11 @@ namespace ServiceLib.Handler.CoreConfig
alpn = node.GetAlpn(),
fingerprint = node.fingerprint.IsNullOrEmpty() ? _config.coreBasicItem.defFingerprint : node.fingerprint
};
if (!Utils.IsNullOrEmpty(sni))
if (Utils.IsNotEmpty(sni))
{
tlsSettings.serverName = sni;
}
else if (!Utils.IsNullOrEmpty(host))
else if (Utils.IsNotEmpty(host))
{
tlsSettings.serverName = Utils.String2List(host)[0];
}
@@ -867,7 +867,7 @@ namespace ServiceLib.Handler.CoreConfig
{
type = node.headerType
};
if (!Utils.IsNullOrEmpty(node.path))
if (Utils.IsNotEmpty(node.path))
{
kcpSettings.seed = node.path;
}
@@ -878,15 +878,15 @@ namespace ServiceLib.Handler.CoreConfig
WsSettings4Ray wsSettings = new();
wsSettings.headers = new Headers4Ray();
string path = node.path;
if (!Utils.IsNullOrEmpty(host))
if (Utils.IsNotEmpty(host))
{
wsSettings.headers.Host = host;
}
if (!Utils.IsNullOrEmpty(path))
if (Utils.IsNotEmpty(path))
{
wsSettings.path = path;
}
if (!Utils.IsNullOrEmpty(useragent))
if (Utils.IsNotEmpty(useragent))
{
wsSettings.headers.UserAgent = useragent;
}
@@ -897,11 +897,11 @@ namespace ServiceLib.Handler.CoreConfig
case nameof(ETransport.httpupgrade):
HttpupgradeSettings4Ray httpupgradeSettings = new();
if (!Utils.IsNullOrEmpty(node.path))
if (Utils.IsNotEmpty(node.path))
{
httpupgradeSettings.path = node.path;
}
if (!Utils.IsNullOrEmpty(host))
if (Utils.IsNotEmpty(host))
{
httpupgradeSettings.host = host;
}
@@ -916,11 +916,11 @@ namespace ServiceLib.Handler.CoreConfig
maxConcurrentUploads = 10
};
if (!Utils.IsNullOrEmpty(node.path))
if (Utils.IsNotEmpty(node.path))
{
splithttpSettings.path = node.path;
}
if (!Utils.IsNullOrEmpty(host))
if (Utils.IsNotEmpty(host))
{
splithttpSettings.host = host;
}
@@ -931,7 +931,7 @@ namespace ServiceLib.Handler.CoreConfig
case nameof(ETransport.h2):
HttpSettings4Ray httpSettings = new();
if (!Utils.IsNullOrEmpty(host))
if (Utils.IsNotEmpty(host))
{
httpSettings.host = Utils.String2List(host);
}
@@ -954,7 +954,7 @@ namespace ServiceLib.Handler.CoreConfig
streamSettings.quicSettings = quicsettings;
if (node.streamSecurity == Global.StreamSecurity)
{
if (!Utils.IsNullOrEmpty(sni))
if (Utils.IsNotEmpty(sni))
{
streamSettings.tlsSettings.serverName = sni;
}
@@ -1000,7 +1000,7 @@ namespace ServiceLib.Handler.CoreConfig
request = request.Replace("$requestUserAgent$", $"\"{useragent}\"");
//Path
string pathHttp = @"/";
if (!Utils.IsNullOrEmpty(node.path))
if (Utils.IsNotEmpty(node.path))
{
string[] arrPath = node.path.Split(',');
pathHttp = string.Join("\",\"", arrPath);
@@ -1033,7 +1033,7 @@ namespace ServiceLib.Handler.CoreConfig
}
//Outbound Freedom domainStrategy
if (!Utils.IsNullOrEmpty(domainStrategy4Freedom))
if (Utils.IsNotEmpty(domainStrategy4Freedom))
{
var outbound = v2rayConfig.outbounds[1];
outbound.settings.domainStrategy = domainStrategy4Freedom;
@@ -1097,7 +1097,7 @@ namespace ServiceLib.Handler.CoreConfig
address = Utils.IsNullOrEmpty(dNSItem?.domainDNSAddress) ? Global.DomainDNSAddress.FirstOrDefault() : dNSItem?.domainDNSAddress,
domains = [node.address]
};
servers.AsArray().Insert(0, JsonUtils.SerializeToNode(dnsServer));
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
}
return 0;
@@ -1157,7 +1157,7 @@ namespace ServiceLib.Handler.CoreConfig
{
//fragment proxy
if (_config.coreBasicItem.enableFragment
&& !Utils.IsNullOrEmpty(v2rayConfig.outbounds[0].streamSettings?.security))
&& Utils.IsNotEmpty(v2rayConfig.outbounds[0].streamSettings?.security))
{
var fragmentOutbound = new Outbounds4Ray
{

View File

@@ -302,7 +302,7 @@ namespace ServiceLib.Handler
{
proc.OutputDataReceived += (sender, e) =>
{
if (!Utils.IsNullOrEmpty(e.Data))
if (Utils.IsNotEmpty(e.Data))
{
string msg = e.Data + Environment.NewLine;
ShowMsg(false, msg);
@@ -310,7 +310,7 @@ namespace ServiceLib.Handler
};
proc.ErrorDataReceived += (sender, e) =>
{
if (!Utils.IsNullOrEmpty(e.Data))
if (Utils.IsNotEmpty(e.Data))
{
string msg = e.Data + Environment.NewLine;
ShowMsg(false, msg);

View File

@@ -58,7 +58,7 @@ namespace ServiceLib.Handler
return 0;
}
public async Task DownloadFileAsync(string url, bool blProxy, int downloadTimeout)
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
{
try
{
@@ -74,7 +74,7 @@ namespace ServiceLib.Handler
var webProxy = GetWebProxy(blProxy);
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
url,
Utils.GetTempPath(Utils.GetDownloadFileName(url)),
fileName,
progress,
downloadTimeout);
}
@@ -117,7 +117,7 @@ namespace ServiceLib.Handler
try
{
var result1 = await DownloadStringAsync(url, blProxy, userAgent);
if (!Utils.IsNullOrEmpty(result1))
if (Utils.IsNotEmpty(result1))
{
return result1;
}
@@ -135,7 +135,7 @@ namespace ServiceLib.Handler
try
{
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent);
if (!Utils.IsNullOrEmpty(result2))
if (Utils.IsNotEmpty(result2))
{
return result2;
}
@@ -155,7 +155,7 @@ namespace ServiceLib.Handler
using var wc = new WebClient();
wc.Proxy = GetWebProxy(blProxy);
var result3 = await wc.DownloadStringTaskAsync(url);
if (!Utils.IsNullOrEmpty(result3))
if (Utils.IsNotEmpty(result3))
{
return result3;
}
@@ -197,7 +197,7 @@ namespace ServiceLib.Handler
Uri uri = new(url);
//Authorization Header
if (!Utils.IsNullOrEmpty(uri.UserInfo))
if (Utils.IsNotEmpty(uri.UserInfo))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo));
}

View File

@@ -16,12 +16,12 @@ namespace ServiceLib.Handler.Fmt
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{
if (!Utils.IsNullOrEmpty(item.flow))
if (Utils.IsNotEmpty(item.flow))
{
dicQuery.Add("flow", item.flow);
}
if (!Utils.IsNullOrEmpty(item.streamSecurity))
if (Utils.IsNotEmpty(item.streamSecurity))
{
dicQuery.Add("security", item.streamSecurity);
}
@@ -32,27 +32,27 @@ namespace ServiceLib.Handler.Fmt
dicQuery.Add("security", securityDef);
}
}
if (!Utils.IsNullOrEmpty(item.sni))
if (Utils.IsNotEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
if (Utils.IsNotEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}
if (!Utils.IsNullOrEmpty(item.fingerprint))
if (Utils.IsNotEmpty(item.fingerprint))
{
dicQuery.Add("fp", Utils.UrlEncode(item.fingerprint));
}
if (!Utils.IsNullOrEmpty(item.publicKey))
if (Utils.IsNotEmpty(item.publicKey))
{
dicQuery.Add("pbk", Utils.UrlEncode(item.publicKey));
}
if (!Utils.IsNullOrEmpty(item.shortId))
if (Utils.IsNotEmpty(item.shortId))
{
dicQuery.Add("sid", Utils.UrlEncode(item.shortId));
}
if (!Utils.IsNullOrEmpty(item.spiderX))
if (Utils.IsNotEmpty(item.spiderX))
{
dicQuery.Add("spx", Utils.UrlEncode(item.spiderX));
}
@@ -61,21 +61,21 @@ namespace ServiceLib.Handler.Fmt
dicQuery.Add("allowInsecure", "1");
}
dicQuery.Add("type", !Utils.IsNullOrEmpty(item.network) ? item.network : nameof(ETransport.tcp));
dicQuery.Add("type", Utils.IsNotEmpty(item.network) ? item.network : nameof(ETransport.tcp));
switch (item.network)
{
case nameof(ETransport.tcp):
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
if (!Utils.IsNullOrEmpty(item.requestHost))
dicQuery.Add("headerType", Utils.IsNotEmpty(item.headerType) ? item.headerType : Global.None);
if (Utils.IsNotEmpty(item.requestHost))
{
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
}
break;
case nameof(ETransport.kcp):
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
if (!Utils.IsNullOrEmpty(item.path))
dicQuery.Add("headerType", Utils.IsNotEmpty(item.headerType) ? item.headerType : Global.None);
if (Utils.IsNotEmpty(item.path))
{
dicQuery.Add("seed", Utils.UrlEncode(item.path));
}
@@ -84,11 +84,11 @@ namespace ServiceLib.Handler.Fmt
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
case nameof(ETransport.splithttp):
if (!Utils.IsNullOrEmpty(item.requestHost))
if (Utils.IsNotEmpty(item.requestHost))
{
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
}
if (!Utils.IsNullOrEmpty(item.path))
if (Utils.IsNotEmpty(item.path))
{
dicQuery.Add("path", Utils.UrlEncode(item.path));
}
@@ -97,24 +97,24 @@ namespace ServiceLib.Handler.Fmt
case nameof(ETransport.http):
case nameof(ETransport.h2):
dicQuery["type"] = nameof(ETransport.http);
if (!Utils.IsNullOrEmpty(item.requestHost))
if (Utils.IsNotEmpty(item.requestHost))
{
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
}
if (!Utils.IsNullOrEmpty(item.path))
if (Utils.IsNotEmpty(item.path))
{
dicQuery.Add("path", Utils.UrlEncode(item.path));
}
break;
case nameof(ETransport.quic):
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
dicQuery.Add("headerType", Utils.IsNotEmpty(item.headerType) ? item.headerType : Global.None);
dicQuery.Add("quicSecurity", Utils.UrlEncode(item.requestHost));
dicQuery.Add("key", Utils.UrlEncode(item.path));
break;
case nameof(ETransport.grpc):
if (!Utils.IsNullOrEmpty(item.path))
if (Utils.IsNotEmpty(item.path))
{
dicQuery.Add("authority", Utils.UrlEncode(item.requestHost));
dicQuery.Add("serviceName", Utils.UrlEncode(item.path));

View File

@@ -31,20 +31,20 @@
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.sni))
if (Utils.IsNotEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
if (Utils.IsNotEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}
if (!Utils.IsNullOrEmpty(item.path))
if (Utils.IsNotEmpty(item.path))
{
dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.path));

View File

@@ -30,7 +30,7 @@ namespace ServiceLib.Handler.Fmt
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
@@ -59,7 +59,7 @@ namespace ServiceLib.Handler.Fmt
ProfileItem item = new();
var base64 = match.Groups["base64"].Value.TrimEnd('/');
var tag = match.Groups["tag"].Value;
if (!Utils.IsNullOrEmpty(tag))
if (Utils.IsNotEmpty(tag))
{
item.remarks = Utils.UrlDecode(tag);
}
@@ -128,7 +128,7 @@ namespace ServiceLib.Handler.Fmt
{
//obfs-host exists
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
if (queryParameters["plugin"].Contains("obfs=http") && !Utils.IsNullOrEmpty(obfsHost))
if (queryParameters["plugin"].Contains("obfs=http") && Utils.IsNotEmpty(obfsHost))
{
obfsHost = obfsHost?.Replace("obfs-host=", "");
item.network = Global.DefaultNetwork;

View File

@@ -28,7 +28,7 @@
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}

View File

@@ -30,7 +30,7 @@
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}

View File

@@ -36,16 +36,16 @@
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.sni))
if (Utils.IsNotEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
if (Utils.IsNotEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}

View File

@@ -33,12 +33,12 @@
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.security))
if (Utils.IsNotEmpty(item.security))
{
dicQuery.Add("encryption", item.security);
}

View File

@@ -78,12 +78,12 @@
item.alterId = Utils.ToInt(vmessQRCode.aid);
item.security = Utils.ToString(vmessQRCode.scy);
item.security = !Utils.IsNullOrEmpty(vmessQRCode.scy) ? vmessQRCode.scy : Global.DefaultSecurity;
if (!Utils.IsNullOrEmpty(vmessQRCode.net))
item.security = Utils.IsNotEmpty(vmessQRCode.scy) ? vmessQRCode.scy : Global.DefaultSecurity;
if (Utils.IsNotEmpty(vmessQRCode.net))
{
item.network = vmessQRCode.net;
}
if (!Utils.IsNullOrEmpty(vmessQRCode.type))
if (Utils.IsNotEmpty(vmessQRCode.type))
{
item.headerType = vmessQRCode.type;
}

View File

@@ -34,25 +34,25 @@
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
if (Utils.IsNotEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.publicKey))
if (Utils.IsNotEmpty(item.publicKey))
{
dicQuery.Add("publickey", Utils.UrlEncode(item.publicKey));
}
if (!Utils.IsNullOrEmpty(item.path))
if (Utils.IsNotEmpty(item.path))
{
dicQuery.Add("reserved", Utils.UrlEncode(item.path));
}
if (!Utils.IsNullOrEmpty(item.requestHost))
if (Utils.IsNotEmpty(item.requestHost))
{
dicQuery.Add("address", Utils.UrlEncode(item.requestHost));
}
if (!Utils.IsNullOrEmpty(item.shortId))
if (Utils.IsNotEmpty(item.shortId))
{
dicQuery.Add("mtu", Utils.UrlEncode(item.shortId));
}

View File

@@ -1,6 +1,4 @@
using System.Runtime.InteropServices;
namespace ServiceLib.Handler
namespace ServiceLib.Handler
{
public sealed class LazyConfig
{
@@ -106,11 +104,11 @@ namespace ServiceLib.Handler
from ProfileItem a
left join SubItem b on a.subid = b.id
where 1=1 ";
if (!Utils.IsNullOrEmpty(subid))
if (Utils.IsNotEmpty(subid))
{
sql += $" and a.subid = '{subid}'";
}
if (!Utils.IsNullOrEmpty(filter))
if (Utils.IsNotEmpty(filter))
{
if (filter.Contains('\''))
{

View File

@@ -22,13 +22,13 @@ namespace ServiceLib.Handler
MessageBus.Current.SendMessage(content, Global.CommandSendMsgView);
}
public void SendMessage(string? content, bool time)
public void SendMessageEx(string? content)
{
if (content.IsNullOrEmpty())
{
return;
}
content = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {content}";
content = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss} {content}";
SendMessage(content);
}

View File

@@ -35,7 +35,7 @@ namespace ServiceLib.Handler
private void IndexIdEnqueue(string indexId)
{
if (!Utils.IsNullOrEmpty(indexId) && !_queIndexIds.Contains(indexId))
if (Utils.IsNotEmpty(indexId) && !_queIndexIds.Contains(indexId))
{
_queIndexIds.Enqueue(indexId);
}

View File

@@ -88,7 +88,7 @@ namespace ServiceLib.Handler.Statistics
while (!res.CloseStatus.HasValue)
{
var result = Encoding.UTF8.GetString(buffer, 0, res.Count);
if (!Utils.IsNullOrEmpty(result))
if (Utils.IsNotEmpty(result))
{
ParseOutput(result, out ulong up, out ulong down);

View File

@@ -61,7 +61,7 @@
{
if ((dtNow - autoUpdateGeoTime).Hours % config.guiItem.autoUpdateInterval == 0)
{
updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
{
update(false, msg);
});

View File

@@ -9,10 +9,9 @@ namespace ServiceLib.Handler
{
private Action<bool, string> _updateFunc;
private Config _config;
private int _timeout = 30;
public event EventHandler<ResultEventArgs> AbsoluteCompleted;
public class ResultEventArgs : EventArgs
private class ResultEventArgs
{
public bool Success;
public string Msg;
@@ -26,11 +25,12 @@ namespace ServiceLib.Handler
}
}
public void CheckUpdateGuiN(Config config, Action<bool, string> update, bool preRelease)
public async Task CheckUpdateGuiN(Config config, Action<bool, string> update, bool preRelease)
{
_config = config;
_updateFunc = update;
var url = string.Empty;
var fileName = string.Empty;
DownloadHandler downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
@@ -38,9 +38,7 @@ namespace ServiceLib.Handler
if (args.Success)
{
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
fileName = Utils.UrlEncode(fileName);
_updateFunc(true, fileName);
_updateFunc(true, Utils.UrlEncode(fileName));
}
else
{
@@ -50,36 +48,31 @@ namespace ServiceLib.Handler
downloadHandle.Error += (sender2, args) =>
{
_updateFunc(false, args.GetException().Message);
_updateFunc(false, "");
};
AbsoluteCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
_updateFunc(false, args.Msg);
url = args.Url;
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
{
_updateFunc(false, "");
});
}
else
{
_updateFunc(false, args.Msg);
_updateFunc(false, "");
}
};
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
var args = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
_updateFunc(false, args.Msg);
url = args.Url;
fileName = Utils.GetTempPath(Utils.GetGUID());
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
}
else
{
_updateFunc(false, args.Msg);
}
}
public async void CheckUpdateCore(ECoreType type, Config config, Action<bool, string> update, bool preRelease)
public async Task CheckUpdateCore(ECoreType type, Config config, Action<bool, string> update, bool preRelease)
{
_config = config;
_updateFunc = update;
var url = string.Empty;
var fileName = string.Empty;
DownloadHandler downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
@@ -91,7 +84,7 @@ namespace ServiceLib.Handler
try
{
_updateFunc(true, url);
_updateFunc(true, fileName);
}
catch (Exception ex)
{
@@ -105,31 +98,27 @@ namespace ServiceLib.Handler
};
downloadHandle.Error += (sender2, args) =>
{
_updateFunc(false, args.GetException().Message);
_updateFunc(false, "");
_updateFunc(false, args.GetException().Message);
};
AbsoluteCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
_updateFunc(false, args.Msg);
url = args.Url;
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
{
_updateFunc(false, "");
});
}
else
{
_updateFunc(false, args.Msg);
_updateFunc(false, "");
}
};
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, type));
CheckUpdateAsync(downloadHandle, type, preRelease);
var args = await CheckUpdateAsync(downloadHandle, type, preRelease);
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
_updateFunc(false, args.Msg);
url = args.Url;
fileName = Utils.GetTempPath(Utils.GetGUID());
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
}
else
{
if (!args.Msg.IsNullOrEmpty())
{
_updateFunc(false, args.Msg);
}
}
}
public void UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action<bool, string> update)
@@ -154,7 +143,7 @@ namespace ServiceLib.Handler
string url = item.url.TrimEx();
string userAgent = item.userAgent.TrimEx();
string hashCode = $"{item.remarks}->";
if (Utils.IsNullOrEmpty(id) || Utils.IsNullOrEmpty(url) || (!Utils.IsNullOrEmpty(subId) && item.id != subId))
if (Utils.IsNullOrEmpty(id) || Utils.IsNullOrEmpty(url) || (Utils.IsNotEmpty(subId) && item.id != subId))
{
//_updateFunc(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
continue;
@@ -180,7 +169,7 @@ namespace ServiceLib.Handler
//one url
url = Utils.GetPunycode(url);
//convert
if (!Utils.IsNullOrEmpty(item.convertTarget))
if (Utils.IsNotEmpty(item.convertTarget))
{
var subConvertUrl = Utils.IsNullOrEmpty(config.constItem.subConvertUrl) ? Global.SubConvertUrls.FirstOrDefault() : config.constItem.subConvertUrl;
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
@@ -200,9 +189,9 @@ namespace ServiceLib.Handler
}
//more url
if (Utils.IsNullOrEmpty(item.convertTarget) && !Utils.IsNullOrEmpty(item.moreUrl.TrimEx()))
if (Utils.IsNullOrEmpty(item.convertTarget) && Utils.IsNotEmpty(item.moreUrl.TrimEx()))
{
if (!Utils.IsNullOrEmpty(result) && Utils.IsBase64String(result!))
if (Utils.IsNotEmpty(result) && Utils.IsBase64String(result!))
{
result = Utils.Base64Decode(result);
}
@@ -221,7 +210,7 @@ namespace ServiceLib.Handler
{
result2 = await downloadHandle.TryDownloadString(url2, false, userAgent);
}
if (!Utils.IsNullOrEmpty(result2))
if (Utils.IsNotEmpty(result2))
{
if (Utils.IsBase64String(result2!))
{
@@ -265,50 +254,43 @@ namespace ServiceLib.Handler
});
}
public void UpdateGeoFileAll(Config config, Action<bool, string> update)
public async Task UpdateGeoFileAll(Config config, Action<bool, string> update)
{
Task.Run(async () =>
{
await UpdateGeoFile("geosite", _config, update);
await UpdateGeoFile("geoip", _config, update);
_updateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
});
await UpdateGeoFile("geosite", _config, update);
await UpdateGeoFile("geoip", _config, update);
_updateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
}
public void RunAvailabilityCheck(Action<bool, string> update)
public async Task RunAvailabilityCheck(Action<bool, string> update)
{
Task.Run(async () =>
{
var time = await (new DownloadHandler()).RunAvailabilityCheck(null);
update(false, string.Format(ResUI.TestMeOutput, time));
});
var time = await (new DownloadHandler()).RunAvailabilityCheck(null);
update(false, string.Format(ResUI.TestMeOutput, time));
}
#region private
private async void CheckUpdateAsync(DownloadHandler downloadHandle, ECoreType type, bool preRelease)
private async Task<ResultEventArgs> CheckUpdateAsync(DownloadHandler downloadHandle, ECoreType type, bool preRelease)
{
try
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
string url = coreInfo.coreReleaseApiUrl;
var url = coreInfo?.coreReleaseApiUrl;
var result = await downloadHandle.DownloadStringAsync(url, true, Global.AppName);
if (!Utils.IsNullOrEmpty(result))
if (Utils.IsNotEmpty(result))
{
ResponseHandler(type, result, preRelease);
return await ParseDownloadUrl(type, result, preRelease);
}
else
{
Logging.SaveLog("StatusCode error: " + url);
return;
return new ResultEventArgs(false, "");
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
_updateFunc(false, ex.Message);
return new ResultEventArgs(false, ex.Message);
}
}
@@ -380,7 +362,7 @@ namespace ServiceLib.Handler
}
}
private void ResponseHandler(ECoreType type, string gitHubReleaseApi, bool preRelease)
private async Task<ResultEventArgs> ParseDownloadUrl(ECoreType type, string gitHubReleaseApi, bool preRelease)
{
try
{
@@ -434,16 +416,16 @@ namespace ServiceLib.Handler
if (curVersion >= version && version != new SemanticVersion(0, 0, 0))
{
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(false, message));
return;
return new ResultEventArgs(false, message);
}
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(true, body, url));
return new ResultEventArgs(true, body, url);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
_updateFunc(false, ex.Message);
return new ResultEventArgs(false, ex.Message);
}
}
@@ -451,6 +433,15 @@ namespace ServiceLib.Handler
{
if (Utils.IsWindows())
{
//Check for standalone windows .Net version
if (coreInfo?.coreType == ECoreType.v2rayN
&& File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll"))
&& File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll"))
)
{
return coreInfo?.coreDownloadUrl64.Replace("v2rayN.zip", "zz_v2rayN-SelfContained.zip");
}
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.Arm64 => coreInfo?.coreDownloadUrlArm64,
@@ -472,31 +463,12 @@ namespace ServiceLib.Handler
return null;
}
private async Task AskToDownload(DownloadHandler downloadHandle, string url, bool blAsk)
{
//bool blDownload = false;
//if (blAsk)
//{
// if (UI.ShowYesNo(string.Format(ResUI.DownloadYesNo, url)) == MessageBoxResult.Yes)
// {
// blDownload = true;
// }
//}
//else
//{
// blDownload = true;
//}
//if (blDownload)
//{
await downloadHandle.DownloadFileAsync(url, true, 60);
//}
}
private async Task UpdateGeoFile(string geoName, Config config, Action<bool, string> update)
{
_config = config;
_updateFunc = update;
var url = string.Format(Global.GeoUrl, geoName);
var fileName = Utils.GetTempPath(Utils.GetGUID());
DownloadHandler downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
@@ -507,7 +479,6 @@ namespace ServiceLib.Handler
try
{
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
if (File.Exists(fileName))
{
string targetPath = Utils.GetBinPath($"{geoName}.dat");
@@ -531,7 +502,8 @@ namespace ServiceLib.Handler
{
_updateFunc(false, args.GetException().Message);
};
await AskToDownload(downloadHandle, url, false);
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
}
#endregion private

View File

@@ -0,0 +1,171 @@
using System.Net;
using WebDav;
namespace ServiceLib.Handler
{
public sealed class WebDavHandler
{
private static readonly Lazy<WebDavHandler> _instance = new(() => new());
public static WebDavHandler Instance => _instance.Value;
private Config? _config;
private WebDavClient? _client;
private string? _lastDescription;
private string _webDir = Global.AppName + "_backup";
private string _webFileName = "backup.zip";
private string _logTitle = "WebDav--";
public WebDavHandler()
{
_config = LazyConfig.Instance.Config;
}
private async Task<bool> GetClient()
{
try
{
if (_config.webDavItem.url.IsNullOrEmpty()
|| _config.webDavItem.userName.IsNullOrEmpty()
|| _config.webDavItem.password.IsNullOrEmpty())
{
throw new ArgumentException("webdav parameter error or null");
}
if (_client != null)
{
_client?.Dispose();
_client = null;
}
if (_config.webDavItem.dirName.IsNullOrEmpty())
{
_webDir = Global.AppName + "_backup";
}
else
{
_webDir = _config.webDavItem.dirName.TrimEx();
}
var clientParams = new WebDavClientParams
{
BaseAddress = new Uri(_config.webDavItem.url),
Credentials = new NetworkCredential(_config.webDavItem.userName, _config.webDavItem.password)
};
_client = new WebDavClient(clientParams);
}
catch (Exception ex)
{
SaveLog(ex);
return false;
}
return await Task.FromResult(true);
}
private async Task<bool> TryCreateDir()
{
if (_client is null) return false;
try
{
var result2 = await _client.Mkcol(_webDir);
if (result2.IsSuccessful)
{
return true;
}
SaveLog(result2.Description);
}
catch (Exception ex)
{
SaveLog(ex);
}
return false;
}
private void SaveLog(string desc)
{
_lastDescription = desc;
Logging.SaveLog(_logTitle + desc);
}
private void SaveLog(Exception ex)
{
_lastDescription = ex.Message;
Logging.SaveLog(_logTitle, ex);
}
public async Task<bool> CheckConnection()
{
if (await GetClient() == false)
{
return false;
}
await TryCreateDir();
var testName = "readme_test";
var myContent = new StringContent(testName);
var result = await _client.PutFile($"{_webDir}/{testName}", myContent);
if (result.IsSuccessful)
{
await _client.Delete($"{_webDir}/{testName}");
return true;
}
else
{
SaveLog(result.Description);
return false;
}
}
public async Task<bool> PutFile(string fileName)
{
if (await GetClient() == false)
{
return false;
}
await TryCreateDir();
try
{
using var fs = File.OpenRead(fileName);
var result = await _client.PutFile($"{_webDir}/{_webFileName}", fs); // upload a resource
if (result.IsSuccessful)
{
return true;
}
SaveLog(result.Description);
}
catch (Exception ex)
{
SaveLog(ex);
}
return false;
}
public async Task<bool> GetRawFile(string fileName)
{
if (await GetClient() == false)
{
return false;
}
await TryCreateDir();
try
{
var response = await _client.GetRawFile($"{_webDir}/{_webFileName}");
if (!response.IsSuccessful)
{
SaveLog(response.Description);
return false;
}
using var outputFileStream = new FileStream(fileName, FileMode.Create);
response.Stream.CopyTo(outputFileStream);
return true;
}
catch (Exception ex)
{
SaveLog(ex);
}
return false;
}
public string GetLastError() => _lastDescription ?? string.Empty;
}
}

View File

@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServiceLib.Models
namespace ServiceLib.Models
{
public class CheckUpdateItem
{
@@ -14,4 +8,4 @@ namespace ServiceLib.Models
public string? fileName { get; set; }
public bool? isFinished { get; set; }
}
}
}

View File

@@ -38,6 +38,7 @@
public GrpcItem grpcItem { get; set; }
public RoutingBasicItem routingBasicItem { get; set; }
public GUIItem guiItem { get; set; }
public MsgUIItem msgUIItem { get; set; }
public UIItem uiItem { get; set; }
public ConstItem constItem { get; set; }
public SpeedTestItem speedTestItem { get; set; }
@@ -46,6 +47,7 @@
public HysteriaItem hysteriaItem { get; set; }
public ClashUIItem clashUIItem { get; set; }
public SystemProxyItem systemProxyItem { get; set; }
public WebDavItem webDavItem { get; set; }
public List<InItem> inbound { get; set; }
public List<KeyEventItem> globalHotkeys { get; set; }
public List<CoreTypeItem> coreTypeItem { get; set; }

View File

@@ -107,6 +107,13 @@
public bool enableLog { get; set; } = true;
}
[Serializable]
public class MsgUIItem
{
public string? mainMsgFilter { get; set; }
public bool? autoRefresh { get; set; }
}
[Serializable]
public class UIItem
{
@@ -126,7 +133,6 @@
public bool enableDragDropSort { get; set; }
public bool doubleClick2Activate { get; set; }
public bool autoHideStartup { get; set; }
public string mainMsgFilter { get; set; }
public List<ColumnItem> mainColumnItem { get; set; }
public bool showInTaskbar { get; set; }
}
@@ -242,4 +248,13 @@
public bool notProxyLocalAddress { get; set; } = true;
public string systemProxyAdvancedProtocol { get; set; }
}
[Serializable]
public class WebDavItem
{
public string? url { get; set; }
public string? userName { get; set; }
public string? password { get; set; }
public string? dirName { get; set; }
}
}

View File

@@ -105,6 +105,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Host filter 的本地化字符串。
/// </summary>
public static string ConnectionsHostFilterTitle {
get {
return ResourceManager.GetString("ConnectionsHostFilterTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。
/// </summary>
@@ -321,6 +330,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Invalid backup file 的本地化字符串。
/// </summary>
public static string LocalRestoreInvalidZipTips {
get {
return ResourceManager.GetString("LocalRestoreInvalidZipTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Address 的本地化字符串。
/// </summary>
@@ -582,6 +600,51 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 WebDav Check 的本地化字符串。
/// </summary>
public static string LvWebDavCheck {
get {
return ResourceManager.GetString("LvWebDavCheck", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remote folder name (optional) 的本地化字符串。
/// </summary>
public static string LvWebDavDirName {
get {
return ResourceManager.GetString("LvWebDavDirName", resourceCulture);
}
}
/// <summary>
/// 查找类似 WebDav Password 的本地化字符串。
/// </summary>
public static string LvWebDavPassword {
get {
return ResourceManager.GetString("LvWebDavPassword", resourceCulture);
}
}
/// <summary>
/// 查找类似 WebDav Url 的本地化字符串。
/// </summary>
public static string LvWebDavUrl {
get {
return ResourceManager.GetString("LvWebDavUrl", resourceCulture);
}
}
/// <summary>
/// 查找类似 WebDav User Name 的本地化字符串。
/// </summary>
public static string LvWebDavUserName {
get {
return ResourceManager.GetString("LvWebDavUserName", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add a custom configuration server 的本地化字符串。
/// </summary>
@@ -690,6 +753,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Backup and Restore 的本地化字符串。
/// </summary>
public static string menuBackupAndRestore {
get {
return ResourceManager.GetString("menuBackupAndRestore", resourceCulture);
}
}
/// <summary>
/// 查找类似 Check Update 的本地化字符串。
/// </summary>
@@ -861,6 +933,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Backup to local 的本地化字符串。
/// </summary>
public static string menuLocalBackup {
get {
return ResourceManager.GetString("menuLocalBackup", resourceCulture);
}
}
/// <summary>
/// 查找类似 Local 的本地化字符串。
/// </summary>
public static string menuLocalBackupAndRestore {
get {
return ResourceManager.GetString("menuLocalBackupAndRestore", resourceCulture);
}
}
/// <summary>
/// 查找类似 Restore from local 的本地化字符串。
/// </summary>
public static string menuLocalRestore {
get {
return ResourceManager.GetString("menuLocalRestore", resourceCulture);
}
}
/// <summary>
/// 查找类似 One-click multi test Latency and speed (Ctrl+E) 的本地化字符串。
/// </summary>
@@ -1095,6 +1194,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Backup to remote (WebDAV) 的本地化字符串。
/// </summary>
public static string menuRemoteBackup {
get {
return ResourceManager.GetString("menuRemoteBackup", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remote (WebDAV) 的本地化字符串。
/// </summary>
public static string menuRemoteBackupAndRestore {
get {
return ResourceManager.GetString("menuRemoteBackupAndRestore", resourceCulture);
}
}
/// <summary>
/// 查找类似 Restore from remote (WebDAV) 的本地化字符串。
/// </summary>
public static string menuRemoteRestore {
get {
return ResourceManager.GetString("menuRemoteRestore", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remove duplicate servers 的本地化字符串。
/// </summary>

View File

@@ -1279,4 +1279,46 @@
<data name="TbPreSocksPort4Sub" xml:space="preserve">
<value>Custom config socks port</value>
</data>
<data name="menuBackupAndRestore" xml:space="preserve">
<value>Backup and Restore</value>
</data>
<data name="menuLocalBackup" xml:space="preserve">
<value>Backup to local</value>
</data>
<data name="menuLocalRestore" xml:space="preserve">
<value>Restore from local</value>
</data>
<data name="menuRemoteBackup" xml:space="preserve">
<value>Backup to remote (WebDAV)</value>
</data>
<data name="menuRemoteRestore" xml:space="preserve">
<value>Restore from remote (WebDAV)</value>
</data>
<data name="menuLocalBackupAndRestore" xml:space="preserve">
<value>Local</value>
</data>
<data name="menuRemoteBackupAndRestore" xml:space="preserve">
<value>Remote (WebDAV)</value>
</data>
<data name="LvWebDavUrl" xml:space="preserve">
<value>WebDav Url</value>
</data>
<data name="LvWebDavUserName" xml:space="preserve">
<value>WebDav User Name</value>
</data>
<data name="LvWebDavPassword" xml:space="preserve">
<value>WebDav Password</value>
</data>
<data name="LvWebDavCheck" xml:space="preserve">
<value>WebDav Check</value>
</data>
<data name="LvWebDavDirName" xml:space="preserve">
<value>Remote folder name (optional)</value>
</data>
<data name="LocalRestoreInvalidZipTips" xml:space="preserve">
<value>Invalid backup file</value>
</data>
<data name="ConnectionsHostFilterTitle" xml:space="preserve">
<value>Host filter</value>
</data>
</root>

View File

@@ -1276,4 +1276,46 @@
<data name="TbPreSocksPort4Sub" xml:space="preserve">
<value>自定义配置的Socks端口</value>
</data>
<data name="menuBackupAndRestore" xml:space="preserve">
<value>备份和还原</value>
</data>
<data name="menuLocalBackup" xml:space="preserve">
<value>备份到本地</value>
</data>
<data name="menuLocalRestore" xml:space="preserve">
<value>从本地恢复</value>
</data>
<data name="menuRemoteBackup" xml:space="preserve">
<value>备份到远程 (WebDAV)</value>
</data>
<data name="menuRemoteRestore" xml:space="preserve">
<value>从远程恢复 (WebDAV)</value>
</data>
<data name="menuLocalBackupAndRestore" xml:space="preserve">
<value>本地</value>
</data>
<data name="menuRemoteBackupAndRestore" xml:space="preserve">
<value>远程 (WebDAV)</value>
</data>
<data name="LvWebDavUserName" xml:space="preserve">
<value>WebDav 账户</value>
</data>
<data name="LvWebDavCheck" xml:space="preserve">
<value>WebDav 可用检查</value>
</data>
<data name="LvWebDavPassword" xml:space="preserve">
<value>WebDav 密码</value>
</data>
<data name="LvWebDavUrl" xml:space="preserve">
<value>WebDav 服务器地址</value>
</data>
<data name="LvWebDavDirName" xml:space="preserve">
<value>远程文件夹名称(可选)</value>
</data>
<data name="LocalRestoreInvalidZipTips" xml:space="preserve">
<value>无效备份文件</value>
</data>
<data name="ConnectionsHostFilterTitle" xml:space="preserve">
<value>主机过滤器</value>
</data>
</root>

View File

@@ -1156,4 +1156,46 @@
<data name="TbPreSocksPort4Sub" xml:space="preserve">
<value>自訂配置的Socks端口</value>
</data>
<data name="menuBackupAndRestore" xml:space="preserve">
<value>備份和還原</value>
</data>
<data name="menuLocalBackup" xml:space="preserve">
<value>備份到本地</value>
</data>
<data name="menuLocalRestore" xml:space="preserve">
<value>從本地恢復</value>
</data>
<data name="menuRemoteBackup" xml:space="preserve">
<value>備份到遠端 (WebDAV)</value>
</data>
<data name="menuRemoteRestore" xml:space="preserve">
<value>從遠端恢復 (WebDAV)</value>
</data>
<data name="menuLocalBackupAndRestore" xml:space="preserve">
<value>本地</value>
</data>
<data name="menuRemoteBackupAndRestore" xml:space="preserve">
<value>遠端 (WebDAV)</value>
</data>
<data name="LvWebDavUserName" xml:space="preserve">
<value>WebDav 賬戶</value>
</data>
<data name="LvWebDavCheck" xml:space="preserve">
<value>WebDav 可用檢查</value>
</data>
<data name="LvWebDavPassword" xml:space="preserve">
<value>WebDav 密碼</value>
</data>
<data name="LvWebDavUrl" xml:space="preserve">
<value>WebDav 服務器地址</value>
</data>
<data name="LvWebDavDirName" xml:space="preserve">
<value>遠端資料夾名稱(可選)</value>
</data>
<data name="LocalRestoreInvalidZipTips" xml:space="preserve">
<value>無效備份文件</value>
</data>
<data name="ConnectionsHostFilterTitle" xml:space="preserve">
<value>主機過濾器</value>
</data>
</root>

View File

@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>6.59.0</Version>
</PropertyGroup>
<ItemGroup>
@@ -12,7 +13,8 @@
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="Splat.NLog" Version="15.1.1" />
<PackageReference Include="YamlDotNet" Version="16.0.0" />
<PackageReference Include="WebDav.Client" Version="2.8.0" />
<PackageReference Include="YamlDotNet" Version="16.1.2" />
<PackageReference Include="QRCoder" Version="1.6.0" />
</ItemGroup>

View File

@@ -9,6 +9,7 @@ namespace ServiceLib.ViewModels
{
[Reactive]
public ProfileItem SelectedSource { get; set; }
[Reactive]
public string? CoreType { get; set; }
@@ -89,7 +90,7 @@ namespace ServiceLib.ViewModels
if (ConfigHandler.AddCustomServer(_config, item, false) == 0)
{
_noticeHandler?.Enqueue(ResUI.SuccessfullyImportedCustomServer);
if (!Utils.IsNullOrEmpty(item.indexId))
if (Utils.IsNotEmpty(item.indexId))
{
SelectedSource = JsonUtils.DeepCopy(item);
}

View File

@@ -0,0 +1,166 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
using System.Reactive;
namespace ServiceLib.ViewModels
{
public class BackupAndRestoreViewModel : MyReactiveObject
{
private readonly string _guiConfigs = "guiConfigs";
private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip";
public ReactiveCommand<Unit, Unit> RemoteBackupCmd { get; }
public ReactiveCommand<Unit, Unit> RemoteRestoreCmd { get; }
public ReactiveCommand<Unit, Unit> WebDavCheckCmd { get; }
[Reactive]
public WebDavItem SelectedSource { get; set; }
[Reactive]
public string OperationMsg { get; set; }
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{
_config = LazyConfig.Instance.Config;
_updateView = updateView;
_noticeHandler = Locator.Current.GetService<NoticeHandler>();
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
{
await WebDavCheck();
});
RemoteBackupCmd = ReactiveCommand.CreateFromTask(async () =>
{
await RemoteBackup();
});
RemoteRestoreCmd = ReactiveCommand.CreateFromTask(async () =>
{
await RemoteRestore();
});
SelectedSource = JsonUtils.DeepCopy(_config.webDavItem);
}
private void DisplayOperationMsg(string msg = "")
{
OperationMsg = msg;
}
private async Task WebDavCheck()
{
DisplayOperationMsg();
_config.webDavItem = SelectedSource;
ConfigHandler.SaveConfig(_config);
var result = await WebDavHandler.Instance.CheckConnection();
if (result)
{
DisplayOperationMsg(ResUI.OperationSuccess);
}
else
{
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
}
}
private async Task RemoteBackup()
{
DisplayOperationMsg();
var fileName = Utils.GetBackupPath(BackupFileName);
var result = await CreateZipFileFromDirectory(fileName);
if (result)
{
var result2 = await WebDavHandler.Instance.PutFile(fileName);
if (result2)
{
DisplayOperationMsg(ResUI.OperationSuccess);
return;
}
}
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
}
private async Task RemoteRestore()
{
DisplayOperationMsg();
var fileName = Utils.GetTempPath(Utils.GetGUID());
var result = await WebDavHandler.Instance.GetRawFile(fileName);
if (result)
{
await LocalRestore(fileName);
return;
}
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
}
public async Task<bool> LocalBackup(string fileName)
{
DisplayOperationMsg();
var result = await CreateZipFileFromDirectory(fileName);
if (result)
{
DisplayOperationMsg(ResUI.OperationSuccess);
}
else
{
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
}
return result;
}
public async Task LocalRestore(string fileName)
{
DisplayOperationMsg();
if (Utils.IsNullOrEmpty(fileName))
{
return;
}
//exist
if (!File.Exists(fileName))
{
return;
}
//check
var lstFiles = FileManager.GetFilesFromZip(fileName);
if (lstFiles is null || !lstFiles.Where(t => t.Contains(_guiConfigs)).Any())
{
DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips);
return;
}
//backup first
var fileBackup = Utils.GetBackupPath(BackupFileName);
var result = await CreateZipFileFromDirectory(fileBackup);
if (result)
{
Locator.Current.GetService<MainWindowViewModel>()?.V2rayUpgrade(fileName);
}
else
{
DisplayOperationMsg(WebDavHandler.Instance.GetLastError());
}
}
private async Task<bool> CreateZipFileFromDirectory(string fileName)
{
if (Utils.IsNullOrEmpty(fileName))
{
return false;
}
var configDir = Utils.GetConfigPath();
var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}");
var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs);
await Task.Run(() => FileManager.CopyDirectory(configDir, configDirTemp, false, "cache.db"));
var ret = await Task.Run(() => FileManager.CreateFromDirectory(configDirZipTemp, fileName));
await Task.Run(() => Directory.Delete(configDirZipTemp, true));
return ret;
}
}
}

View File

@@ -3,7 +3,6 @@ using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
using System.Diagnostics;
using System.Reactive;
namespace ServiceLib.ViewModels
@@ -11,6 +10,7 @@ namespace ServiceLib.ViewModels
public class CheckUpdateViewModel : MyReactiveObject
{
private const string _geo = "GeoFiles";
private string _v2rayN = ECoreType.v2rayN.ToString();
private List<CheckUpdateItem> _lstUpdated = [];
private IObservableCollection<CheckUpdateItem> _checkUpdateItem = new ObservableCollectionExtended<CheckUpdateItem>();
@@ -28,9 +28,13 @@ namespace ServiceLib.ViewModels
RefreshSubItems();
CheckUpdateCmd = ReactiveCommand.Create(() =>
CheckUpdateCmd = ReactiveCommand.CreateFromTask(async () =>
{
CheckUpdate();
await CheckUpdate()
.ContinueWith(t =>
{
UpdateFinished();
});
});
EnableCheckPreReleaseUpdate = _config.guiItem.checkPreReleaseUpdate;
IsCheckUpdate = true;
@@ -48,7 +52,7 @@ namespace ServiceLib.ViewModels
_checkUpdateItem.Add(new CheckUpdateItem()
{
isSelected = false,
coreType = ECoreType.v2rayN.ToString(),
coreType = _v2rayN,
remarks = ResUI.menuCheckUpdate,
});
_checkUpdateItem.Add(new CheckUpdateItem()
@@ -77,9 +81,11 @@ namespace ServiceLib.ViewModels
});
}
private void CheckUpdate()
private async Task CheckUpdate()
{
_lstUpdated.Clear();
_lstUpdated = _checkUpdateItem.Where(x => x.isSelected == true)
.Select(x => new CheckUpdateItem() { coreType = x.coreType }).ToList();
for (int k = _checkUpdateItem.Count - 1; k >= 0; k--)
{
@@ -87,23 +93,22 @@ namespace ServiceLib.ViewModels
if (item.isSelected == true)
{
IsCheckUpdate = false;
_lstUpdated.Add(new CheckUpdateItem() { coreType = item.coreType });
UpdateView(item.coreType, "...");
if (item.coreType == _geo)
{
CheckUpdateGeo();
await CheckUpdateGeo();
}
else if (item.coreType == ECoreType.v2rayN.ToString())
else if (item.coreType == _v2rayN)
{
CheckUpdateN(EnableCheckPreReleaseUpdate);
await CheckUpdateN(EnableCheckPreReleaseUpdate);
}
else if (item.coreType == ECoreType.mihomo.ToString())
{
CheckUpdateCore(item, false);
await CheckUpdateCore(item, false);
}
else
{
CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
await CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
}
}
}
@@ -123,7 +128,7 @@ namespace ServiceLib.ViewModels
}
}
private void CheckUpdateGeo()
private async Task CheckUpdateGeo()
{
void _updateUI(bool success, string msg)
{
@@ -131,74 +136,61 @@ namespace ServiceLib.ViewModels
if (success)
{
UpdatedPlusPlus(_geo, "");
UpdateFinished();
}
}
(new UpdateHandler()).UpdateGeoFileAll(_config, _updateUI);
await (new UpdateHandler()).UpdateGeoFileAll(_config, _updateUI)
.ContinueWith(t =>
{
UpdatedPlusPlus(_geo, "");
});
}
private void CheckUpdateN(bool preRelease)
private async Task CheckUpdateN(bool preRelease)
{
//Check for standalone windows .Net version
if (Utils.IsWindows()
&& File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll"))
&& File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll"))
)
{
UpdateView(ResUI.UpdateStandalonePackageTip, ResUI.UpdateStandalonePackageTip);
return;
}
////Check for standalone windows .Net version
//if (Utils.IsWindows()
// && File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll"))
// && File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll"))
// )
//{
// UpdateView(_v2rayN, ResUI.UpdateStandalonePackageTip);
// return;
//}
void _updateUI(bool success, string msg)
{
UpdateView(_v2rayN, msg);
if (success)
{
UpdateView(ECoreType.v2rayN.ToString(), ResUI.OperationSuccess);
UpdatedPlusPlus(ECoreType.v2rayN.ToString(), msg);
UpdateFinished();
}
else
{
if (msg.IsNullOrEmpty())
{
UpdatedPlusPlus(ECoreType.v2rayN.ToString(), "");
UpdateFinished();
}
else
{
UpdateView(ECoreType.v2rayN.ToString(), msg);
}
UpdateView(_v2rayN, ResUI.OperationSuccess);
UpdatedPlusPlus(_v2rayN, msg);
}
}
(new UpdateHandler()).CheckUpdateGuiN(_config, _updateUI, preRelease);
await (new UpdateHandler()).CheckUpdateGuiN(_config, _updateUI, preRelease)
.ContinueWith(t =>
{
UpdatedPlusPlus(_v2rayN, "");
});
}
private void CheckUpdateCore(CheckUpdateItem item, bool preRelease)
private async Task CheckUpdateCore(CheckUpdateItem item, bool preRelease)
{
void _updateUI(bool success, string msg)
{
UpdateView(item.coreType, msg);
if (success)
{
UpdateView(item.coreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
UpdatedPlusPlus(item.coreType, msg);
UpdateFinished();
}
else
{
if (msg.IsNullOrEmpty())
{
UpdatedPlusPlus(item.coreType, "");
UpdateFinished();
}
else
{
UpdateView(item.coreType, msg);
}
}
}
var type = (ECoreType)Enum.Parse(typeof(ECoreType), item.coreType);
(new UpdateHandler()).CheckUpdateCore(type, _config, _updateUI, preRelease);
await (new UpdateHandler()).CheckUpdateCore(type, _config, _updateUI, preRelease)
.ContinueWith(t =>
{
UpdatedPlusPlus(item.coreType, "");
});
}
private void UpdateFinished()
@@ -206,13 +198,15 @@ namespace ServiceLib.ViewModels
if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.isFinished == true) == _lstUpdated.Count)
{
_updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, false);
Task.Delay(1000);
UpgradeCore();
if (_lstUpdated.Any(x => x.coreType == ECoreType.v2rayN.ToString() && x.isFinished == true))
if (_lstUpdated.Any(x => x.coreType == _v2rayN && x.isFinished == true))
{
Task.Delay(1000);
UpgradeN();
}
Task.Delay(1000);
_updateView?.Invoke(EViewAction.DispatcherCheckUpdateFinished, true);
}
}
@@ -234,30 +228,16 @@ namespace ServiceLib.ViewModels
{
try
{
var fileName = _lstUpdated.FirstOrDefault(x => x.coreType == ECoreType.v2rayN.ToString())?.fileName;
var fileName = _lstUpdated.FirstOrDefault(x => x.coreType == _v2rayN)?.fileName;
if (fileName.IsNullOrEmpty())
{
return;
}
Process process = new()
{
StartInfo = new ProcessStartInfo
{
FileName = "v2rayUpgrade",
Arguments = fileName.AppendQuotes(),
WorkingDirectory = Utils.StartupPath()
}
};
process.Start();
if (process.Id > 0)
{
Locator.Current.GetService<MainWindowViewModel>()?.MyAppExitAsync(false);
}
Locator.Current.GetService<MainWindowViewModel>()?.V2rayUpgrade(fileName);
}
catch (Exception ex)
{
UpdateView(ECoreType.v2rayN.ToString(), ex.Message);
UpdateView(_v2rayN, ex.Message);
}
}
@@ -270,7 +250,7 @@ namespace ServiceLib.ViewModels
continue;
}
var fileName = Utils.GetTempPath(Utils.GetDownloadFileName(item.fileName));
var fileName = item.fileName;
if (!File.Exists(fileName))
{
continue;

View File

@@ -19,6 +19,9 @@ namespace ServiceLib.ViewModels
public ReactiveCommand<Unit, Unit> ConnectionCloseCmd { get; }
public ReactiveCommand<Unit, Unit> ConnectionCloseAllCmd { get; }
[Reactive]
public string HostFilter { get; set; }
[Reactive]
public int SortingSelected { get; set; }
@@ -34,7 +37,7 @@ namespace ServiceLib.ViewModels
var canEditRemove = this.WhenAnyValue(
x => x.SelectedSource,
selectedSource => selectedSource != null && !string.IsNullOrEmpty(selectedSource.id));
selectedSource => selectedSource != null && Utils.IsNotEmpty(selectedSource.id));
this.WhenAnyValue(
x => x.SortingSelected,
@@ -77,7 +80,7 @@ namespace ServiceLib.ViewModels
{
var lastTime = DateTime.Now;
Observable.Interval(TimeSpan.FromSeconds(10))
Observable.Interval(TimeSpan.FromSeconds(5))
.Subscribe(x =>
{
if (!(AutoRefresh && _config.uiItem.showInTaskbar && _config.IsRunningCore(ECoreType.clash)))
@@ -118,12 +121,18 @@ namespace ServiceLib.ViewModels
var lstModel = new List<ClashConnectionModel>();
foreach (var item in connections ?? [])
{
var host = $"{(Utils.IsNullOrEmpty(item.metadata.host) ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}";
if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter))
{
continue;
}
ClashConnectionModel model = new();
model.id = item.id;
model.network = item.metadata.network;
model.type = item.metadata.type;
model.host = $"{(string.IsNullOrEmpty(item.metadata.host) ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}";
model.host = host;
var sp = (dtNow - item.start);
model.time = sp.TotalSeconds < 0 ? 1 : sp.TotalSeconds;
model.upload = item.upload;

View File

@@ -57,7 +57,7 @@ namespace ServiceLib.ViewModels
this.WhenAnyValue(
x => x.SelectedGroup,
y => y != null && !string.IsNullOrEmpty(y.name))
y => y != null && Utils.IsNotEmpty(y.name))
.Subscribe(c => RefreshProxyDetails(c));
this.WhenAnyValue(
@@ -135,7 +135,7 @@ namespace ServiceLib.ViewModels
private void UpdateHandler(bool notify, string msg)
{
_noticeHandler?.SendMessage(msg, true);
_noticeHandler?.SendMessageEx(msg);
}
public void ProxiesReload()
@@ -194,7 +194,7 @@ namespace ServiceLib.ViewModels
{
foreach (var it in proxyGroups)
{
if (string.IsNullOrEmpty(it.name) || !_proxies.ContainsKey(it.name))
if (Utils.IsNullOrEmpty(it.name) || !_proxies.ContainsKey(it.name))
{
continue;
}
@@ -220,7 +220,7 @@ namespace ServiceLib.ViewModels
continue;
}
var item = _proxyGroups.Where(t => t.name == kv.Key).FirstOrDefault();
if (item != null && !string.IsNullOrEmpty(item.name))
if (item != null && Utils.IsNotEmpty(item.name))
{
continue;
}
@@ -257,7 +257,7 @@ namespace ServiceLib.ViewModels
return;
}
var name = SelectedGroup?.name;
if (string.IsNullOrEmpty(name))
if (Utils.IsNullOrEmpty(name))
{
return;
}
@@ -342,21 +342,21 @@ namespace ServiceLib.ViewModels
public void SetActiveProxy()
{
if (SelectedGroup == null || string.IsNullOrEmpty(SelectedGroup.name))
if (SelectedGroup == null || Utils.IsNullOrEmpty(SelectedGroup.name))
{
return;
}
if (SelectedDetail == null || string.IsNullOrEmpty(SelectedDetail.name))
if (SelectedDetail == null || Utils.IsNullOrEmpty(SelectedDetail.name))
{
return;
}
var name = SelectedGroup.name;
if (string.IsNullOrEmpty(name))
if (Utils.IsNullOrEmpty(name))
{
return;
}
var nameNode = SelectedDetail.name;
if (string.IsNullOrEmpty(nameNode))
if (Utils.IsNullOrEmpty(nameNode))
{
return;
}
@@ -393,7 +393,7 @@ namespace ServiceLib.ViewModels
GetClashProxies(true);
return;
}
if (string.IsNullOrEmpty(result))
if (Utils.IsNullOrEmpty(result))
{
return;
}

View File

@@ -58,7 +58,7 @@ namespace ServiceLib.ViewModels
private async Task SaveSettingAsync()
{
if (!Utils.IsNullOrEmpty(normalDNS))
if (Utils.IsNotEmpty(normalDNS))
{
var obj = JsonUtils.ParseJson(normalDNS);
if (obj != null && obj["servers"] != null)
@@ -73,7 +73,7 @@ namespace ServiceLib.ViewModels
}
}
}
if (!Utils.IsNullOrEmpty(normalDNS2))
if (Utils.IsNotEmpty(normalDNS2))
{
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(normalDNS2);
if (obj2 == null)
@@ -82,7 +82,7 @@ namespace ServiceLib.ViewModels
return;
}
}
if (!Utils.IsNullOrEmpty(tunDNS2))
if (Utils.IsNotEmpty(tunDNS2))
{
var obj2 = JsonUtils.Deserialize<Dns4Sbox>(tunDNS2);
if (obj2 == null)

View File

@@ -71,13 +71,6 @@ namespace ServiceLib.ViewModels
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
public ReactiveCommand<Unit, Unit> OpenTheFileLocationCmd { get; }
//CheckUpdate
public ReactiveCommand<Unit, Unit> CheckUpdateNCmd { get; }
public ReactiveCommand<Unit, Unit> CheckUpdateXrayCoreCmd { get; }
public ReactiveCommand<Unit, Unit> CheckUpdateClashMetaCoreCmd { get; }
public ReactiveCommand<Unit, Unit> CheckUpdateSingBoxCoreCmd { get; }
public ReactiveCommand<Unit, Unit> CheckUpdateGeoCmd { get; }
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
[Reactive]
@@ -295,28 +288,6 @@ namespace ServiceLib.ViewModels
Utils.ProcessStart("Explorer", $"/select,{Utils.GetConfigPath()}");
});
//CheckUpdate
CheckUpdateNCmd = ReactiveCommand.Create(() =>
{
CheckUpdateN();
});
CheckUpdateXrayCoreCmd = ReactiveCommand.Create(() =>
{
CheckUpdateCore(ECoreType.Xray, null);
});
CheckUpdateClashMetaCoreCmd = ReactiveCommand.Create(() =>
{
CheckUpdateCore(ECoreType.mihomo, false);
});
CheckUpdateSingBoxCoreCmd = ReactiveCommand.Create(() =>
{
CheckUpdateCore(ECoreType.sing_box, null);
});
CheckUpdateGeoCmd = ReactiveCommand.Create(() =>
{
CheckUpdateGeo();
});
ReloadCmd = ReactiveCommand.Create(() =>
{
Reload();
@@ -378,10 +349,6 @@ namespace ServiceLib.ViewModels
private void UpdateHandler(bool notify, string msg)
{
if (!_config.uiItem.showInTaskbar)
{
return;
}
_noticeHandler?.SendMessage(msg);
if (notify)
{
@@ -391,7 +358,7 @@ namespace ServiceLib.ViewModels
private void UpdateTaskHandler(bool success, string msg)
{
_noticeHandler?.SendMessage(msg);
_noticeHandler?.SendMessageEx(msg);
if (success)
{
var indexIdOld = _config.indexId;
@@ -457,6 +424,24 @@ namespace ServiceLib.ViewModels
}
}
public async Task V2rayUpgrade(string fileName)
{
Process process = new()
{
StartInfo = new ProcessStartInfo
{
FileName = "v2rayUpgrade",
Arguments = fileName.AppendQuotes(),
WorkingDirectory = Utils.StartupPath()
}
};
process.Start();
if (process.Id > 0)
{
await MyAppExitAsync(false);
}
}
#endregion Actions
#region Servers && Groups
@@ -622,21 +607,16 @@ namespace ServiceLib.ViewModels
SetDefaultServer(SelectedServer.ID);
}
public void TestServerAvailability()
public async Task TestServerAvailability()
{
var item = ConfigHandler.GetDefaultServer(_config);
if (item == null)
{
return;
}
(new UpdateHandler()).RunAvailabilityCheck(async (bool success, string msg) =>
await (new UpdateHandler()).RunAvailabilityCheck(async (bool success, string msg) =>
{
_noticeHandler?.SendMessage(msg, true);
if (!_config.uiItem.showInTaskbar)
{
return;
}
_noticeHandler?.SendMessageEx(msg);
await _updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg);
});
}
@@ -718,99 +698,6 @@ namespace ServiceLib.ViewModels
#endregion Setting
#region CheckUpdate
private void CheckUpdateN()
{
//Check for standalone windows .Net version
if (Utils.IsWindows()
&& File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll"))
&& File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll"))
)
{
_noticeHandler?.SendMessageAndEnqueue(ResUI.UpdateStandalonePackageTip);
return;
}
void _updateUI(bool success, string msg)
{
_noticeHandler?.SendMessage(msg);
if (success)
{
try
{
var fileName = msg;
Process process = new()
{
StartInfo = new ProcessStartInfo
{
FileName = "v2rayUpgrade",
Arguments = fileName.AppendQuotes(),
WorkingDirectory = Utils.StartupPath()
}
};
process.Start();
if (process.Id > 0)
{
MyAppExitAsync(false);
}
}
catch (Exception ex)
{
_noticeHandler?.SendMessage(ex.Message);
}
}
}
(new UpdateHandler()).CheckUpdateGuiN(_config, _updateUI, _config.guiItem.checkPreReleaseUpdate);
}
private void CheckUpdateCore(ECoreType type, bool? preRelease)
{
void _updateUI(bool success, string msg)
{
_noticeHandler?.SendMessage(msg);
if (success)
{
CloseCore();
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(msg));
string toPath = Utils.GetBinPath("", type.ToString());
if (fileName.Contains(".tar.gz"))
{
//It's too complicated to unzip. TODO
}
else if (fileName.Contains(".gz"))
{
FileManager.UncompressedFile(fileName, toPath, type.ToString());
}
else
{
FileManager.ZipExtractToFile(fileName, toPath, _config.guiItem.ignoreGeoUpdateCore ? "geo" : "");
}
_noticeHandler?.SendMessage(ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
Reload();
_noticeHandler?.SendMessage(ResUI.MsgUpdateV2rayCoreSuccessfully);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
}
}
(new UpdateHandler()).CheckUpdateCore(type, _config, _updateUI, preRelease ?? _config.guiItem.checkPreReleaseUpdate);
}
private void CheckUpdateGeo()
{
(new UpdateHandler()).UpdateGeoFileAll(_config, UpdateTaskHandler);
}
#endregion CheckUpdate
#region core job
public void Reload()
@@ -819,7 +706,7 @@ namespace ServiceLib.ViewModels
LoadCore().ContinueWith(async task =>
{
TestServerAvailability();
await TestServerAvailability();
await _updateView?.Invoke(EViewAction.DispatcherReload, null);
});
@@ -881,7 +768,7 @@ namespace ServiceLib.ViewModels
private async Task ChangeSystemProxyStatusAsync(ESysProxyType type, bool blChange)
{
await _updateView?.Invoke(EViewAction.UpdateSysProxy, _config.tunModeItem.enableTun ? true : false);
_noticeHandler?.SendMessage($"{ResUI.TipChangeSystemProxy} - {_config.systemProxyItem.sysProxyType.ToString()}", true);
_noticeHandler?.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.systemProxyItem.sysProxyType.ToString()}");
BlSystemProxyClear = (type == ESysProxyType.ForcedClear);
BlSystemProxySet = (type == ESysProxyType.ForcedChange);
@@ -941,7 +828,7 @@ namespace ServiceLib.ViewModels
if (ConfigHandler.SetDefaultRouting(_config, item) == 0)
{
_noticeHandler?.SendMessage(ResUI.TipChangeRouting, true);
_noticeHandler?.SendMessageEx(ResUI.TipChangeRouting);
Reload();
await _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
}

View File

@@ -0,0 +1,115 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
namespace ServiceLib.ViewModels
{
public class MsgViewModel : MyReactiveObject
{
private ConcurrentQueue<string> _queueMsg = new();
private int _numMaxMsg = 500;
private string _lastMsgFilter = string.Empty;
private bool _lastMsgFilterNotAvailable;
private bool _blLockShow = false;
[Reactive]
public string MsgFilter { get; set; }
[Reactive]
public bool AutoRefresh { get; set; }
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
{
_config = LazyConfig.Instance.Config;
_updateView = updateView;
_noticeHandler = Locator.Current.GetService<NoticeHandler>();
MessageBus.Current.Listen<string>(Global.CommandSendMsgView).Subscribe(async x => await AppendQueueMsg(x));
MsgFilter = _config.msgUIItem.mainMsgFilter ?? string.Empty;
AutoRefresh = _config.msgUIItem.autoRefresh ?? true;
this.WhenAnyValue(
x => x.MsgFilter)
.Subscribe(c => _config.msgUIItem.mainMsgFilter = MsgFilter);
this.WhenAnyValue(
x => x.AutoRefresh,
y => y == true)
.Subscribe(c => { _config.msgUIItem.autoRefresh = AutoRefresh; });
}
private async Task AppendQueueMsg(string msg)
{
//if (msg == Global.CommandClearMsg)
//{
// ClearMsg();
// return;
//}
if (AutoRefresh == false)
{
return;
}
_ = EnqueueQueueMsg(msg);
if (_blLockShow)
{
return;
}
_blLockShow = true;
if (!_config.uiItem.showInTaskbar)
{
await Task.Delay(1000);
}
await Task.Delay(100);
var txt = string.Join("", _queueMsg.ToArray());
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt);
_blLockShow = false;
}
private async Task EnqueueQueueMsg(string msg)
{
//filter msg
if (MsgFilter != _lastMsgFilter) _lastMsgFilterNotAvailable = false;
if (Utils.IsNotEmpty(MsgFilter) && !_lastMsgFilterNotAvailable)
{
try
{
if (!Regex.IsMatch(msg, MsgFilter))
{
return;
}
}
catch (Exception)
{
_lastMsgFilterNotAvailable = true;
}
}
_lastMsgFilter = MsgFilter;
//Enqueue
if (_queueMsg.Count > _numMaxMsg)
{
for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++)
{
_queueMsg.TryDequeue(out _);
}
}
_queueMsg.Enqueue(msg);
if (!msg.EndsWith(Environment.NewLine))
{
_queueMsg.Enqueue(Environment.NewLine);
}
}
public void ClearMsg()
{
_queueMsg.Clear();
}
}
}

View File

@@ -258,20 +258,20 @@ namespace ServiceLib.ViewModels
{
if (Utils.IsNullOrEmpty(result.IndexId))
{
_noticeHandler?.SendMessage(result.Delay, true);
_noticeHandler?.SendMessageEx(result.Delay);
_noticeHandler?.Enqueue(result.Delay);
return;
}
var item = _profileItems.Where(it => it.indexId == result.IndexId).FirstOrDefault();
if (item != null)
{
if (!Utils.IsNullOrEmpty(result.Delay))
if (Utils.IsNotEmpty(result.Delay))
{
int.TryParse(result.Delay, out int temp);
item.delay = temp;
item.delayVal = $"{result.Delay} {Global.DelayUnit}";
}
if (!Utils.IsNullOrEmpty(result.Speed))
if (Utils.IsNotEmpty(result.Speed))
{
item.speedVal = $"{result.Speed} {Global.SpeedUnit}";
}

View File

@@ -80,7 +80,7 @@ namespace ServiceLib.ViewModels
|| SelectedSource.ip?.Count > 0
|| SelectedSource.protocol?.Count > 0
|| SelectedSource.process?.Count > 0
|| !Utils.IsNullOrEmpty(SelectedSource.port);
|| Utils.IsNotEmpty(SelectedSource.port);
if (!hasRule)
{

View File

@@ -13,7 +13,10 @@
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Clicked="TrayIcon_Clicked" Icon="/Assets/NotifyIcon1.ico">
<TrayIcon
Clicked="TrayIcon_Clicked"
Icon="/Assets/NotifyIcon1.ico"
ToolTipText="v2rayN Desktop">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Click="MenuAddServerViaClipboardClick" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />

View File

@@ -5,22 +5,6 @@
</Border>
</Design.PreviewWith>
<Style Selector="TextBlock.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="StackPanel.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="DockPanel.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="WrapPanel.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="Grid.Margin4">
<Setter Property="Margin" Value="8" />
</Style>
<Style Selector="TextBlock.Margin8">
<Setter Property="Margin" Value="8" />
</Style>

View File

@@ -70,7 +70,7 @@ namespace v2rayN.Desktop.ViewModels
y => y != null && !y.IsNullOrEmpty())
.Subscribe(c =>
{
if (!Utils.IsNullOrEmpty(CurrentLanguage) && _config.uiItem.currentLanguage != CurrentLanguage)
if (Utils.IsNotEmpty(CurrentLanguage) && _config.uiItem.currentLanguage != CurrentLanguage)
{
_config.uiItem.currentLanguage = CurrentLanguage;
Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage);

View File

@@ -16,7 +16,7 @@
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
@@ -57,14 +57,14 @@
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.menuServers}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
@@ -74,13 +74,13 @@
Width="400"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4" />
Classes="Margin8" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbAddress}" />
<TextBox
x:Name="txtAddress"
@@ -89,7 +89,7 @@
Width="400"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
IsReadOnly="True" />
<StackPanel
Grid.Row="2"
@@ -110,7 +110,7 @@
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbCoreType}" />
<ComboBox
x:Name="cmbCoreType"
@@ -118,24 +118,24 @@
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin4"
Classes="Margin8"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbDisplayLog}" />
<StackPanel
Grid.Row="4"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
Orientation="Horizontal">
<ToggleSwitch
x:Name="togDisplayLog"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8" />
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
@@ -146,7 +146,7 @@
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbPreSocksPort}" />
<TextBox
x:Name="txtPreSocksPort"
@@ -154,12 +154,12 @@
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8" />
<StackPanel
Grid.Row="6"
Grid.Column="1"
Grid.ColumnSpan="2"
Classes="Margin4">
Classes="Margin8">
<TextBlock
Width="500"
VerticalAlignment="Center"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
<UserControl
x:Class="v2rayN.Desktop.Views.CheckUpdateView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:CheckUpdateViewModel"
mc:Ignorable="d">
<Button Classes="Tertiary">
<Button.Content>
<TextBlock Text="{x:Static resx:ResUI.menuCheckUpdate}" />
</Button.Content>
<Button.Flyout>
<Flyout Placement="RightEdgeAlignedTop">
<DockPanel Margin="16">
<StackPanel
HorizontalAlignment="Right"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<TextBlock
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
<ToggleSwitch
x:Name="togEnableCheckPreReleaseUpdate"
HorizontalAlignment="Left"
Classes="Margin8" />
<Button
x:Name="btnCheckUpdate"
Width="100"
Classes="Margin8"
Content="{x:Static resx:ResUI.menuCheckUpdate}" />
</StackPanel>
<StackPanel>
<ListBox
x:Name="lstCheckUpdates"
BorderThickness="1"
ItemsSource="{Binding CheckUpdateItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Width="500"
Height="80"
Margin="0"
VerticalAlignment="Center"
Theme="{StaticResource CardBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ToggleSwitch
x:Name="togAutoRefresh"
Grid.Column="0"
Margin="8"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding isSelected}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding coreType}" />
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding remarks}"
TextWrapping="WrapWithOverflow" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</StackPanel>
</DockPanel>
</Flyout>
</Button.Flyout>
</Button>
</UserControl>

View File

@@ -0,0 +1,48 @@
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
using System.Reactive.Disposables;
namespace v2rayN.Desktop.Views
{
public partial class CheckUpdateView : ReactiveUserControl<CheckUpdateViewModel>
{
public CheckUpdateView()
{
InitializeComponent();
ViewModel = new CheckUpdateViewModel(UpdateViewHandler);
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.CheckUpdateItems, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCheckPreReleaseUpdate, v => v.togEnableCheckPreReleaseUpdate.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.IsCheckUpdate, v => v.btnCheckUpdate.IsEnabled).DisposeWith(disposables);
});
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.DispatcherCheckUpdate:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.UpdateViewResult((CheckUpdateItem)obj),
DispatcherPriority.Default);
break;
case EViewAction.DispatcherCheckUpdateFinished:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ViewModel?.UpdateFinishedResult((bool)obj),
DispatcherPriority.Default);
break;
}
return await Task.FromResult(true);
}
}
}

View File

@@ -18,6 +18,13 @@
DockPanel.Dock="Top"
Orientation="Horizontal">
<TextBox
x:Name="txtHostFilter"
Width="200"
Margin="8,0"
VerticalContentAlignment="Center"
Watermark="{x:Static resx:ResUI.ConnectionsHostFilterTitle}" />
<TextBlock
Margin="8,0"
VerticalAlignment="Center"

View File

@@ -21,6 +21,7 @@ namespace v2rayN.Desktop.Views
this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);

View File

@@ -92,7 +92,6 @@
<DockPanel>
<ListBox
x:Name="lstProxyGroups"
Margin="0,0,5,0"
DockPanel.Dock="Left"
ItemsSource="{Binding ProxyGroups}">
<ItemsControl.ItemsPanel>
@@ -104,20 +103,21 @@
<DataTemplate>
<Border
Width="160"
Margin="4,0"
Margin="0"
Padding="0"
Theme="{StaticResource CardBorder}">
<DockPanel>
<Grid Grid.Column="0" Classes="Margin4">
<Grid Grid.Column="0" Classes="Margin8">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="1*" />
<RowDefinition Height="8" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<TextBlock DockPanel.Dock="Right" Text="{Binding type}" />
<TextBlock Text="{Binding name}" />
</DockPanel>
<TextBlock Grid.Row="1" Text="{Binding now}" />
<TextBlock Grid.Row="2" Text="{Binding now}" />
</Grid>
</DockPanel>
</Border>
@@ -141,6 +141,7 @@
<DataTemplate>
<Border
Width="160"
Margin="0"
Padding="0"
Theme="{StaticResource CardBorder}">
<DockPanel>
@@ -152,16 +153,17 @@
CornerRadius="4"
DockPanel.Dock="Left"
IsVisible="{Binding isActive}" />
<Grid Classes="Margin4">
<Grid Classes="Margin8">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
<RowDefinition Height="8" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Text="{Binding name}"
TextWrapping="WrapWithOverflow" />
<DockPanel Grid.Row="1">
<DockPanel Grid.Row="2">
<TextBlock
DockPanel.Dock="Right"
Foreground="{Binding Path=delay, Converter={StaticResource DelayColorConverter}}"

View File

@@ -16,7 +16,7 @@
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
@@ -43,9 +43,9 @@
Text="{x:Static resx:ResUI.TbSettingsRemoteDNS}" />
<TextBlock VerticalAlignment="Center" Classes="Margin8">
<Button Click="linkDnsObjectDoc_Click">
<HyperlinkButton Classes="WithIcon" Click="linkDnsObjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbDnsObjectDoc}" />
</Button>
</HyperlinkButton>
</TextBlock>
<Button
x:Name="btnImportDefConfig4V2ray"
@@ -95,7 +95,7 @@
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap"
ToolTip.Tip="Http/Socks" />
Watermark="Http/Socks" />
</DockPanel>
</TabItem>
@@ -103,9 +103,9 @@
<DockPanel Classes="Margin8">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Classes="Margin8">
<Button Click="linkDnsSingboxObjectDoc_Click">
<HyperlinkButton Classes="WithIcon" Click="linkDnsSingboxObjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbDnsSingboxObjectDoc}" />
</Button>
</HyperlinkButton>
</TextBlock>
<Button
x:Name="btnImportDefConfig4Singbox"
@@ -153,7 +153,7 @@
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap"
ToolTip.Tip="Http/Socks" />
Watermark="Http/Socks" />
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
@@ -165,7 +165,7 @@
BorderThickness="1"
Classes="TextArea Margin8"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.TbSettingsTunMode}" />
Watermark="{x:Static resx:ResUI.TbSettingsTunMode}" />
</Grid>
</DockPanel>
</TabItem>

View File

@@ -15,7 +15,7 @@
mc:Ignorable="d">
<DockPanel Classes="Margin8">
<StackPanel
Classes="Margin4"
Classes="Margin8"
HorizontalAlignment="Center"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
@@ -60,13 +60,13 @@
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbGlobalHotkeySetting}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbDisplayGUI}" />
@@ -74,68 +74,68 @@
x:Name="txtGlobalHotkey0"
Grid.Row="1"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbClearSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey1"
Grid.Row="2"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSetSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey2"
Grid.Row="3"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbNotChangeSystemProxy}" />
<TextBox
x:Name="txtGlobalHotkey3"
Grid.Row="4"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
IsReadOnly="True" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSystemProxyPac}" />
<TextBox
x:Name="txtGlobalHotkey4"
Grid.Row="5"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
IsReadOnly="True" />
</Grid>
<TextBlock
Grid.Row="1"
Classes="Margin4"
Classes="Margin8"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbGlobalHotkeySettingTip}" />
</Grid>

View File

@@ -87,16 +87,8 @@
<MenuItem Padding="8,0">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static resx:ResUI.menuCheckUpdate}" />
</StackPanel>
<ContentControl x:Name="conCheckUpdate" />
</MenuItem.Header>
<MenuItem x:Name="menuCheckUpdateN" Header="V2rayN" />
<MenuItem x:Name="menuCheckUpdateXrayCore" Header="Xray Core" />
<MenuItem x:Name="menuCheckUpdateMihomoCore" Header="Mihomo Core" />
<MenuItem x:Name="menuCheckUpdateSingBoxCore" Header="Sing-box Core" />
<Separator />
<MenuItem x:Name="menuCheckUpdateGeo" Header="Geo files" />
</MenuItem>
<MenuItem x:Name="menuHelp" Padding="8,0">
@@ -156,7 +148,8 @@
<ToggleSwitch
x:Name="togEnableTun"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8"
Theme="{StaticResource SimpleToggleSwitch}" />
</StackPanel>
<StackPanel

View File

@@ -72,13 +72,7 @@ namespace v2rayN.Desktop.Views
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.OpenTheFileLocationCmd, v => v.menuOpenTheFileLocation).DisposeWith(disposables);
//check update
this.BindCommand(ViewModel, vm => vm.CheckUpdateNCmd, v => v.menuCheckUpdateN).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateXrayCoreCmd, v => v.menuCheckUpdateXrayCore).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateClashMetaCoreCmd, v => v.menuCheckUpdateMihomoCore).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateSingBoxCoreCmd, v => v.menuCheckUpdateSingBoxCore).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateGeoCmd, v => v.menuCheckUpdateGeo).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
@@ -172,6 +166,7 @@ namespace v2rayN.Desktop.Views
tabClashConnections2.Content ??= new ClashConnectionsView();
}
conTheme.Content ??= new ThemeSettingView();
conCheckUpdate.Content ??= new CheckUpdateView();
RestoreUI();
AddHelpMenuItem();
@@ -279,6 +274,7 @@ namespace v2rayN.Desktop.Views
var clipboardData = await AvaUtils.GetClipboardData(this);
ViewModel?.AddServerViaClipboardAsync(clipboardData);
break;
case EViewAction.AdjustMainLvColWidth:
Dispatcher.UIThread.Post(() =>
Locator.Current.GetService<ProfilesViewModel>()?.AutofitColumnWidthAsync(),

View File

@@ -20,7 +20,7 @@
Width="200"
Margin="8,0"
VerticalContentAlignment="Center"
ToolTip.Tip="{x:Static resx:ResUI.MsgFilterTitle}" />
Watermark="{x:Static resx:ResUI.MsgFilterTitle}" />
<Button
x:Name="btnCopy"
@@ -62,18 +62,18 @@
x:Name="togAutoRefresh"
Margin="8,0"
HorizontalAlignment="Left"
IsChecked="True" />
IsChecked="True"
Theme="{StaticResource SimpleToggleSwitch}" />
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
IsVisible="False"
Text="{x:Static resx:ResUI.TbAutoScrollToEnd}" />
<ToggleSwitch
x:Name="togScrollToEnd"
Margin="8,0"
HorizontalAlignment="Left"
IsChecked="True"
IsVisible="False" />
Theme="{StaticResource SimpleToggleSwitch}" />
</WrapPanel>
<TextBox
Name="txtMsg"

View File

@@ -1,103 +1,54 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using System.Reactive.Disposables;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views
{
public partial class MsgView : UserControl
public partial class MsgView : ReactiveUserControl<MsgViewModel>
{
private static Config? _config;
private string lastMsgFilter = string.Empty;
private bool lastMsgFilterNotAvailable;
private ConcurrentBag<string> _lstMsg = [];
public MsgView()
{
InitializeComponent();
_config = LazyConfig.Instance.Config;
MessageBus.Current.Listen<string>(Global.CommandSendMsgView).Subscribe(x => DelegateAppendText(x));
//Global.PresetMsgFilters.ForEach(it =>
//{
// cmbMsgFilter.Items.Add(it);
//});
if (!_config.uiItem.mainMsgFilter.IsNullOrEmpty())
ViewModel = new MsgViewModel(UpdateViewHandler);
this.WhenActivated(disposables =>
{
cmbMsgFilter.Text = _config.uiItem.mainMsgFilter;
}
cmbMsgFilter.TextChanged += (s, e) =>
{
_config.uiItem.mainMsgFilter = cmbMsgFilter.Text?.ToString();
};
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
});
}
private void DelegateAppendText(string msg)
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
Dispatcher.UIThread.Post(() => AppendText(msg), DispatcherPriority.ApplicationIdle);
switch (action)
{
case EViewAction.DispatcherShowMsg:
if (obj is null) return false;
Dispatcher.UIThread.Post(() =>
ShowMsg(obj),
DispatcherPriority.ApplicationIdle);
break;
}
return await Task.FromResult(true);
}
public void AppendText(string msg)
private void ShowMsg(object msg)
{
if (msg == Global.CommandClearMsg)
{
ClearMsg();
return;
}
if (togAutoRefresh.IsChecked == false)
{
return;
}
var MsgFilter = cmbMsgFilter.Text?.ToString();
if (MsgFilter != lastMsgFilter) lastMsgFilterNotAvailable = false;
if (!Utils.IsNullOrEmpty(MsgFilter) && !lastMsgFilterNotAvailable)
{
try
{
if (!Regex.IsMatch(msg, MsgFilter))
{
return;
}
}
catch (Exception)
{
lastMsgFilterNotAvailable = true;
}
}
lastMsgFilter = MsgFilter;
ShowMsg(msg);
txtMsg.Text = msg.ToString();
if (togScrollToEnd.IsChecked ?? true)
{
txtMsg.CaretIndex = int.MaxValue;
}
}
private void ShowMsg(string msg)
{
if (_lstMsg.Count > 999)
{
ClearMsg();
}
if (!msg.EndsWith(Environment.NewLine))
{
_lstMsg.Add(Environment.NewLine);
}
_lstMsg.Add(msg);
// if (!msg.EndsWith(Environment.NewLine))
// {
// _lstMsg.Add(Environment.NewLine);
// }
this.txtMsg.Text = string.Join("", _lstMsg);
}
public void ClearMsg()
{
_lstMsg.Clear();
ViewModel?.ClearMsg();
txtMsg.Clear();
}

View File

@@ -16,7 +16,7 @@
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
@@ -113,7 +113,7 @@
<ListBox
x:Name="clbdestOverride"
HorizontalAlignment="Left"
Classes="Margin4"
Classes="Margin8"
SelectionMode="Multiple"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
</StackPanel>
@@ -309,12 +309,12 @@
x:Name="txtUpMbps"
Width="90"
Classes="Margin8"
ToolTip.Tip="Up" />
Watermark="Up" />
<TextBox
x:Name="txtDownMbps"
Width="90"
Classes="Margin8"
ToolTip.Tip="Down" />
Watermark="Down" />
</StackPanel>
<TextBlock

View File

@@ -75,7 +75,7 @@
Width="200"
Margin="4,0"
VerticalContentAlignment="Center"
ToolTip.Tip="{x:Static resx:ResUI.MsgServerTitle}" />
Watermark="{x:Static resx:ResUI.MsgServerTitle}" />
</WrapPanel>
<DataGrid
x:Name="lstProfiles"

View File

@@ -32,27 +32,27 @@
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="outboundTag" />
<ComboBox
x:Name="cmbOutboundTag"
Grid.Row="0"
Grid.Column="1"
Width="200"
Classes="Margin4"
Classes="Margin8"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
HorizontalAlignment="Left"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbRuleMatchingTips}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="port" />
<TextBox
x:Name="txtPort"
@@ -60,29 +60,29 @@
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
Classes="Margin4">
<Button Click="linkRuleobjectDoc_Click">
Classes="Margin8">
<HyperlinkButton Classes="WithIcon" Click="linkRuleobjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbRuleobjectDoc}" />
</Button>
</HyperlinkButton>
</TextBlock>
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="protocol" />
<ListBox
x:Name="clbProtocol"
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin4"
Classes="Margin8"
SelectionMode="Multiple"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
@@ -90,13 +90,13 @@
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="inboundTag" />
<ListBox
x:Name="clbInboundTag"
Grid.Row="3"
Grid.Column="1"
Classes="Margin4"
Classes="Margin8"
SelectionMode="Multiple"
Theme="{DynamicResource PureCardRadioGroupListBox}" />
@@ -104,34 +104,34 @@
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="network" />
<ComboBox
x:Name="cmbNetwork"
Grid.Row="4"
Grid.Column="1"
Width="200"
Classes="Margin4"
Classes="Margin8"
MaxDropDownHeight="1000" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="enabled" />
<ToggleSwitch
x:Name="togEnabled"
Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8" />
<TextBlock
Grid.Row="5"
Grid.Column="2"
HorizontalAlignment="Left"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbRoutingTips}" />
</Grid>

View File

@@ -62,7 +62,7 @@
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvRemarks}" />
<StackPanel
Grid.Row="0"
@@ -76,25 +76,25 @@
Width="300"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap" />
<TextBlock
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvSort}" />
<TextBox
x:Name="txtSort"
Width="100"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8" />
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbdomainStrategy}" />
<StackPanel
Grid.Row="1"
@@ -103,22 +103,22 @@
<ComboBox
x:Name="cmbdomainStrategy"
Width="200"
Classes="Margin4" />
Classes="Margin8" />
<TextBlock
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
<ComboBox
x:Name="cmbdomainStrategy4Singbox"
Width="200"
Classes="Margin4" />
Classes="Margin8" />
</StackPanel>
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvUrl}" />
<TextBox
x:Name="txtUrl"
@@ -127,14 +127,14 @@
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvCustomIcon}" />
<TextBox
x:Name="txtCustomIcon"
@@ -143,23 +143,23 @@
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap" />
<Button
x:Name="btnBrowseCustomIcon"
Grid.Row="3"
Grid.Column="2"
Classes="Margin4"
Classes="Margin8"
Content="{x:Static resx:ResUI.TbBrowse}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4">
<Button Click="linkCustomRulesetPath4Singbox">
Classes="Margin8">
<HyperlinkButton Classes="WithIcon" Click="linkCustomRulesetPath4Singbox">
<TextBlock Text="{x:Static resx:ResUI.LvCustomRulesetPath4Singbox}" />
</Button>
</HyperlinkButton>
</TextBlock>
<TextBox
x:Name="txtCustomRulesetPath4Singbox"
@@ -168,13 +168,13 @@
Width="600"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap" />
<Button
x:Name="btnBrowseCustomRulesetPath4Singbox"
Grid.Row="4"
Grid.Column="2"
Classes="Margin4"
Classes="Margin8"
Content="{x:Static resx:ResUI.TbBrowse}" />
</Grid>

View File

@@ -26,9 +26,9 @@
</Menu>
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center">
<Button Click="linkdomainStrategy_Click">
<HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy_Click">
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy}" />
</Button>
</HyperlinkButton>
</TextBlock>
<ComboBox
x:Name="cmbdomainStrategy"
@@ -45,9 +45,9 @@
Margin="8,0,0,0" />
<Separator />
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center">
<Button Click="linkdomainStrategy4Singbox_Click">
<HyperlinkButton Classes="WithIcon" Click="linkdomainStrategy4Singbox_Click">
<TextBlock Text="{x:Static resx:ResUI.TbdomainStrategy4Singbox}" />
</Button>
</HyperlinkButton>
</TextBlock>
<ComboBox
x:Name="cmbdomainStrategy4Singbox"

View File

@@ -17,7 +17,7 @@
<DockPanel Classes="Margin8">
<StackPanel
HorizontalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
DockPanel.Dock="Bottom"
Orientation="Horizontal">
<Button
@@ -60,14 +60,14 @@
<TextBlock
Grid.Row="0"
Grid.Column="0"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.menuSubscription}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvRemarks}" />
<TextBox
@@ -75,53 +75,53 @@
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvUrl}" />
<TextBox
x:Name="txtUrl"
Grid.Row="2"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
Watermark="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvEnabled}" />
<DockPanel
Grid.Row="3"
Grid.Column="1"
Classes="Margin4">
Classes="Margin8">
<ToggleSwitch
x:Name="togEnable"
HorizontalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
DockPanel.Dock="Left" />
<TextBox
x:Name="txtAutoUpdateInterval"
Width="200"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
DockPanel.Dock="Right"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
Watermark="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}" />
</DockPanel>
@@ -129,21 +129,21 @@
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
Classes="Margin8"
Watermark="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvConvertTarget}" />
<ComboBox
x:Name="cmbConvertTarget"
@@ -151,29 +151,29 @@
Grid.Column="1"
Width="200"
VerticalAlignment=" "
Classes="Margin4"
Classes="Margin8"
ToolTip.Tip="{x:Static resx:ResUI.LvConvertTargetTip}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvUserAgent}" />
<TextBox
x:Name="txtUserAgent"
Grid.Row="7"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
Watermark="{x:Static resx:ResUI.SubUrlTips}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvSort}" />
<TextBox
x:Name="txtSort"
@@ -181,42 +181,42 @@
Grid.Column="1"
Width="100"
HorizontalAlignment="Left"
Classes="Margin4" />
Classes="Margin8" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvPrevProfile}" />
<TextBox
x:Name="txtPrevProfile"
Grid.Row="9"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
Classes="Margin8"
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvNextProfile}" />
<TextBox
x:Name="txtNextProfile"
Grid.Row="10"
Grid.Column="1"
VerticalAlignment="Center"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
Classes="Margin8"
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
Margin="4"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.TbPreSocksPort4Sub}" />
<TextBox
x:Name="txtPreSocksPort"
@@ -225,15 +225,16 @@
Width="200"
Margin="4"
HorizontalAlignment="Left"
Classes="Margin4"
ToolTip.Tip="{x:Static resx:ResUI.TipPreSocksPort}" />
Classes="Margin8"
ToolTip.Tip="{x:Static resx:ResUI.TipPreSocksPort}"
Watermark="{x:Static resx:ResUI.TipPreSocksPort}" />
<TextBlock
Grid.Row="12"
Grid.Column="0"
Grid.ColumnSpan="2"
VerticalAlignment="Center"
Classes="Margin4"
Classes="Margin8"
Text="{x:Static resx:ResUI.LvMoreUrl}" />
<TextBox
x:Name="txtMoreUrl"
@@ -242,10 +243,10 @@
MinHeight="100"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Classes="TextArea Margin4"
Classes="TextArea Margin8"
MinLines="4"
TextWrapping="Wrap"
ToolTip.Tip="{x:Static resx:ResUI.SubUrlTips}" />
Watermark="{x:Static resx:ResUI.SubUrlTips}" />
</Grid>
</ScrollViewer>
</DockPanel>

View File

@@ -7,7 +7,6 @@
<ApplicationIcon>v2rayN.ico</ApplicationIcon>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<FileVersion>6.55</FileVersion>
</PropertyGroup>
<ItemGroup>
@@ -26,8 +25,8 @@
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.3" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.6" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0.2" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.1.0.2" />
<PackageReference Include="Semi.Avalonia" Version="11.1.0.4" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.1.0.4" />
<PackageReference Include="ReactiveUI" Version="20.1.1" />
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
</ItemGroup>

View File

@@ -1,9 +1,9 @@
<Application
x:Class="v2rayN.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
ShutdownMode="OnExplicitShutdown"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>

View File

@@ -35,5 +35,25 @@ namespace v2rayN
return true;
}
public static bool? SaveFileDialog(out string fileName, string filter)
{
fileName = string.Empty;
SaveFileDialog fileDialog = new()
{
Filter = filter,
FilterIndex = 2,
RestoreDirectory = true
};
if (fileDialog.ShowDialog() != true)
{
return false;
}
fileName = fileDialog.FileName;
return true;
}
}
}

View File

@@ -11,7 +11,7 @@ namespace v2rayN.Converters
try
{
var fontFamily = LazyConfig.Instance.Config.uiItem.currentFontFamily;
if (!Utils.IsNullOrEmpty(fontFamily))
if (Utils.IsNotEmpty(fontFamily))
{
var fontPath = Utils.GetFontsPath();
MyFont = new FontFamily(new Uri(@$"file:///{fontPath}\"), $"./#{fontFamily}");

View File

@@ -156,7 +156,7 @@ namespace v2rayN.ViewModels
y => y != null && !y.IsNullOrEmpty())
.Subscribe(c =>
{
if (!Utils.IsNullOrEmpty(CurrentLanguage) && _config.uiItem.currentLanguage != CurrentLanguage)
if (Utils.IsNotEmpty(CurrentLanguage) && _config.uiItem.currentLanguage != CurrentLanguage)
{
_config.uiItem.currentLanguage = CurrentLanguage;
Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage);

View File

@@ -0,0 +1,256 @@
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.BackupAndRestoreView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:v2rayN.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="600"
d:DesignWidth="800"
x:TypeArguments="vms:BackupAndRestoreViewModel"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Popupbox.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<DockPanel Margin="16">
<DockPanel Margin="8" DockPanel.Dock="Bottom">
<Button
Width="100"
Margin="8"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Content="{x:Static resx:ResUI.menuClose}"
DockPanel.Dock="Right"
IsCancel="True"
IsDefault="True"
Style="{StaticResource MaterialDesignFlatButton}" />
<TextBlock
x:Name="txtMsg"
Margin="8"
HorizontalAlignment="Left"
Style="{StaticResource ToolbarTextBlock}" />
</DockPanel>
<StackPanel>
<materialDesign:Card Width="Auto" Margin="8">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="8"
Style="{StaticResource ModuleTitle}"
Text="{x:Static resx:ResUI.menuLocalBackupAndRestore}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.menuLocalBackup}" />
<Button
x:Name="menuLocalBackup"
Grid.Row="1"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Content="{x:Static resx:ResUI.menuLocalBackup}"
Style="{StaticResource DefButton}" />
<Separator
Grid.Row="2"
Grid.ColumnSpan="2"
Style="{StaticResource MaterialDesignLightSeparator}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.menuLocalRestore}" />
<Button
x:Name="menuLocalRestore"
Grid.Row="3"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Content="{x:Static resx:ResUI.menuLocalRestore}"
Style="{StaticResource DefButton}" />
</Grid>
</materialDesign:Card>
<materialDesign:Card Width="Auto" Margin="8">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="8"
Style="{StaticResource ModuleTitle}"
Text="{x:Static resx:ResUI.menuRemoteBackupAndRestore}" />
<materialDesign:PopupBox
Padding="8,0"
HorizontalAlignment="Right"
StaysOpen="True"
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
<StackPanel Margin="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="300" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvWebDavUrl}" />
<TextBox
x:Name="txtWebDavUrl"
Grid.Row="0"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource DefTextBox}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvWebDavUserName}" />
<TextBox
x:Name="txtWebDavUserName"
Grid.Row="1"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvWebDavPassword}" />
<TextBox
x:Name="txtWebDavPassword"
Grid.Row="2"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvWebDavDirName}" />
<TextBox
x:Name="txtWebDavDirName"
Grid.Row="3"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource DefTextBox}" />
<Button
x:Name="menuWebDavCheck"
Grid.Row="4"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Content="{x:Static resx:ResUI.LvWebDavCheck}"
Style="{StaticResource DefButton}" />
</Grid>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.menuRemoteBackup}" />
<Button
x:Name="menuRemoteBackup"
Grid.Row="1"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Content="{x:Static resx:ResUI.menuRemoteBackup}"
Style="{StaticResource DefButton}" />
<Separator
Grid.Row="2"
Grid.ColumnSpan="3"
Style="{StaticResource MaterialDesignLightSeparator}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="8"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.menuRemoteRestore}" />
<Button
x:Name="menuRemoteRestore"
Grid.Row="3"
Grid.Column="1"
Margin="8"
VerticalAlignment="Center"
Content="{x:Static resx:ResUI.menuRemoteRestore}"
Style="{StaticResource DefButton}" />
</Grid>
</materialDesign:Card>
</StackPanel>
</DockPanel>
</reactiveui:ReactiveUserControl>

View File

@@ -0,0 +1,65 @@
using ReactiveUI;
using Splat;
using System.Reactive.Disposables;
using System.Windows;
namespace v2rayN.Views
{
public partial class BackupAndRestoreView
{
private NoticeHandler? _noticeHandler;
public BackupAndRestoreView()
{
InitializeComponent();
menuLocalBackup.Click += MenuLocalBackup_Click;
menuLocalRestore.Click += MenuLocalRestore_Click;
ViewModel = new BackupAndRestoreViewModel(UpdateViewHandler);
_noticeHandler = Locator.Current.GetService<NoticeHandler>();
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.OperationMsg, v => v.txtMsg.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.url, v => v.txtWebDavUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.userName, v => v.txtWebDavUserName.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.password, v => v.txtWebDavPassword.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.dirName, v => v.txtWebDavDirName.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.WebDavCheckCmd, v => v.menuWebDavCheck).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoteBackupCmd, v => v.menuRemoteBackup).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoteRestoreCmd, v => v.menuRemoteRestore).DisposeWith(disposables);
});
}
private void MenuRemoteRestore_Click(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
private void MenuLocalBackup_Click(object sender, RoutedEventArgs e)
{
if (UI.SaveFileDialog(out string fileName, "Zip|*.zip") != true)
{
return;
}
ViewModel?.LocalBackup(fileName);
}
private void MenuLocalRestore_Click(object sender, RoutedEventArgs e)
{
if (UI.OpenFileDialog(out string fileName, "Zip|*.zip|All|*.*") != true)
{
return;
}
ViewModel?.LocalRestore(fileName);
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
return await Task.FromResult(true);
}
}
}

View File

@@ -22,32 +22,25 @@
Orientation="Horizontal">
<TextBlock
Grid.Row="9"
Grid.Column="0"
Margin="{StaticResource SettingItemMargin}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
<ToggleButton
x:Name="togEnableCheckPreReleaseUpdate"
Grid.Row="9"
Grid.Column="1"
Margin="{StaticResource SettingItemMargin}"
HorizontalAlignment="Left" />
<Button
x:Name="btnCheckUpdate"
Grid.Row="2"
Width="100"
Margin="8"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Content="{x:Static resx:ResUI.menuCheckUpdate}"
IsCancel="True"
IsDefault="True"
Style="{StaticResource MaterialDesignFlatButton}" />
<Button
Grid.Row="2"
Width="100"
Margin="8"
HorizontalAlignment="Right"
@@ -56,7 +49,6 @@
IsCancel="True"
IsDefault="True"
Style="{StaticResource MaterialDesignFlatButton}" />
</StackPanel>
<StackPanel>
@@ -106,7 +98,6 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DockPanel>
</reactiveui:ReactiveUserControl>

View File

@@ -34,6 +34,7 @@ namespace v2rayN.Views
ViewModel?.UpdateViewResult((CheckUpdateItem)obj);
}), DispatcherPriority.Normal);
break;
case EViewAction.DispatcherCheckUpdateFinished:
if (obj is null) return false;
Application.Current?.Dispatcher.Invoke((() =>
@@ -41,7 +42,6 @@ namespace v2rayN.Views
ViewModel?.UpdateFinishedResult((bool)obj);
}), DispatcherPriority.Normal);
break;
}
return await Task.FromResult(true);

View File

@@ -1,11 +1,11 @@
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.ClashConnectionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="450"
@@ -20,6 +20,15 @@
DockPanel.Dock="Top"
Orientation="Horizontal">
<TextBox
x:Name="txtHostFilter"
Width="200"
Margin="8,0"
VerticalContentAlignment="Center"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.ConnectionsHostFilterTitle}"
materialDesign:TextFieldAssist.HasClearButton="True"
Style="{StaticResource DefTextBox}" />
<TextBlock
Margin="8,0"
VerticalAlignment="Center"

View File

@@ -23,6 +23,7 @@ namespace v2rayN.Views
this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);

View File

@@ -15,7 +15,6 @@ namespace v2rayN.Views
{
InitializeComponent();
this.Owner = Application.Current.MainWindow;
_config = LazyConfig.Instance.Config;
_config.globalHotkeys ??= new List<KeyEventItem>();

View File

@@ -186,14 +186,14 @@
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuClearServerStatistics}" />
<Separator Margin="-40,5" />
<MenuItem
x:Name="menuBackupAndRestore"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuBackupAndRestore}" />
<MenuItem
x:Name="menuOpenTheFileLocation"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
<!--<MenuItem
x:Name="menuImportOldGuiConfig"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuImportOldGuiConfig}" />-->
</MenuItem>
</Menu>
<Separator />

View File

@@ -15,8 +15,9 @@ namespace v2rayN.Views
{
public partial class MainWindow
{
private static Config _config;
private static Config _config;
private CheckUpdateView? _checkUpdateView;
private BackupAndRestoreView? _backupAndRestoreView;
public MainWindow()
{
@@ -33,6 +34,7 @@ namespace v2rayN.Views
menuClose.Click += menuClose_Click;
menuExit.Click += menuExit_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
MessageBus.Current.Listen<string>(Global.CommandSendSnackMsg).Subscribe(x => DelegateSnackMsg(x));
ViewModel = new MainWindowViewModel(UpdateViewHandler);
@@ -71,14 +73,6 @@ namespace v2rayN.Views
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.OpenTheFileLocationCmd, v => v.menuOpenTheFileLocation).DisposeWith(disposables);
//this.BindCommand(ViewModel, vm => vm.ImportOldGuiConfigCmd, v => v.menuImportOldGuiConfig).DisposeWith(disposables);
//check update
//this.BindCommand(ViewModel, vm => vm.CheckUpdateNCmd, v => v.menuCheckUpdateN).DisposeWith(disposables);
//this.BindCommand(ViewModel, vm => vm.CheckUpdateXrayCoreCmd, v => v.menuCheckUpdateXrayCore).DisposeWith(disposables);
//this.BindCommand(ViewModel, vm => vm.CheckUpdateClashMetaCoreCmd, v => v.menuCheckUpdateMihomoCore).DisposeWith(disposables);
//this.BindCommand(ViewModel, vm => vm.CheckUpdateSingBoxCoreCmd, v => v.menuCheckUpdateSingBoxCore).DisposeWith(disposables);
//this.BindCommand(ViewModel, vm => vm.CheckUpdateGeoCmd, v => v.menuCheckUpdateGeo).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
@@ -196,17 +190,14 @@ namespace v2rayN.Views
AddHelpMenuItem();
}
private void MenuCheckUpdate_Click(object sender, RoutedEventArgs e)
{
_checkUpdateView ??= new CheckUpdateView();
DialogHost.Show(_checkUpdateView, "RootDialog");
}
#region Event
private void OnProgramStarted(object state, bool timeout)
{
ShowHideWindow(true);
Application.Current?.Dispatcher.Invoke((Action)(() =>
{
ShowHideWindow(true);
}));
}
private void DelegateSnackMsg(string content)
@@ -290,7 +281,10 @@ namespace v2rayN.Views
break;
case EViewAction.Shutdown:
Application.Current.Shutdown();
Application.Current?.Dispatcher.Invoke((() =>
{
Application.Current.Shutdown();
}), DispatcherPriority.Normal);
break;
case EViewAction.ScanScreenTask:
@@ -427,6 +421,18 @@ namespace v2rayN.Views
ViewModel?.ScanScreenTaskAsync(result);
}
private void MenuCheckUpdate_Click(object sender, RoutedEventArgs e)
{
_checkUpdateView ??= new CheckUpdateView();
DialogHost.Show(_checkUpdateView, "RootDialog");
}
private void MenuBackupAndRestore_Click(object sender, RoutedEventArgs e)
{
_backupAndRestoreView ??= new BackupAndRestoreView();
DialogHost.Show(_backupAndRestoreView, "RootDialog");
}
#endregion Event
#region UI

View File

@@ -1,13 +1,16 @@
<UserControl
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.MsgView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
d:DesignHeight="450"
d:DesignWidth="800"
x:TypeArguments="vms:MsgViewModel"
mc:Ignorable="d">
<DockPanel Margin="2">
<WrapPanel
@@ -23,8 +26,7 @@
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgFilterTitle}"
materialDesign:TextFieldAssist.HasClearButton="True"
IsEditable="True"
Style="{StaticResource DefComboBox}"
TextBoxBase.TextChanged="cmbMsgFilter_TextChanged" />
Style="{StaticResource DefComboBox}" />
<Button
x:Name="btnCopy"
Width="24"
@@ -96,4 +98,4 @@
</TextBox.ContextMenu>
</TextBox>
</DockPanel>
</UserControl>
</reactiveui:ReactiveUserControl>

View File

@@ -1,22 +1,23 @@
using ReactiveUI;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Reactive.Disposables;
using System.Windows;
using System.Windows.Threading;
namespace v2rayN.Views
{
public partial class MsgView
{
private static Config? _config;
private string lastMsgFilter = string.Empty;
private bool lastMsgFilterNotAvailable;
public MsgView()
{
InitializeComponent();
_config = LazyConfig.Instance.Config;
MessageBus.Current.Listen<string>(Global.CommandSendMsgView).Subscribe(x => DelegateAppendText(x));
ViewModel = new MsgViewModel(UpdateViewHandler);
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
});
btnCopy.Click += menuMsgViewCopyAll_Click;
btnClear.Click += menuMsgViewClear_Click;
@@ -29,70 +30,37 @@ namespace v2rayN.Views
{
cmbMsgFilter.Items.Add(it);
});
if (!_config.uiItem.mainMsgFilter.IsNullOrEmpty())
{
cmbMsgFilter.Text = _config.uiItem.mainMsgFilter;
}
}
private void DelegateAppendText(string msg)
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
Dispatcher.BeginInvoke(AppendText, DispatcherPriority.ApplicationIdle, msg);
}
public void AppendText(string msg)
{
if (msg == Global.CommandClearMsg)
switch (action)
{
ClearMsg();
return;
}
if (togAutoRefresh.IsChecked == false)
{
return;
}
var MsgFilter = cmbMsgFilter.Text.TrimEx();
if (MsgFilter != lastMsgFilter) lastMsgFilterNotAvailable = false;
if (!Utils.IsNullOrEmpty(MsgFilter) && !lastMsgFilterNotAvailable)
{
try
{
if (!Regex.IsMatch(msg, MsgFilter)) // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD>
case EViewAction.DispatcherShowMsg:
if (obj is null) return false;
Application.Current?.Dispatcher.Invoke((() =>
{
return;
}
}
catch (Exception)
{
lastMsgFilterNotAvailable = true;
}
ShowMsg(obj);
}), DispatcherPriority.ApplicationIdle);
break;
}
lastMsgFilter = MsgFilter;
ShowMsg(msg);
return await Task.FromResult(true);
}
private void ShowMsg(object msg)
{
txtMsg.BeginChange();
txtMsg.Text = msg.ToString();
if (togScrollToEnd.IsChecked ?? true)
{
txtMsg.ScrollToEnd();
}
}
private void ShowMsg(string msg)
{
if (txtMsg.LineCount > 999)
{
ClearMsg();
}
this.txtMsg.AppendText(msg);
if (!msg.EndsWith(Environment.NewLine))
{
this.txtMsg.AppendText(Environment.NewLine);
}
txtMsg.EndChange();
}
public void ClearMsg()
{
ViewModel?.ClearMsg();
txtMsg.Clear();
}
@@ -118,10 +86,5 @@ namespace v2rayN.Views
{
ClearMsg();
}
private void cmbMsgFilter_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
_config.uiItem.mainMsgFilter = cmbMsgFilter.Text.TrimEx();
}
}
}

View File

@@ -1,5 +1,4 @@
using MaterialDesignThemes.Wpf;
using Microsoft.Win32;
using ReactiveUI;
using Splat;
using System.Reactive.Disposables;
@@ -129,17 +128,11 @@ namespace v2rayN.Views
case EViewAction.SaveFileDialog:
if (obj is null) return false;
SaveFileDialog fileDialog = new()
{
Filter = "Config|*.json",
FilterIndex = 2,
RestoreDirectory = true
};
if (fileDialog.ShowDialog() != true)
if (UI.SaveFileDialog(out string fileName, "Config|*.json") != true)
{
return false;
}
ViewModel?.Export2ClientConfigResult(fileDialog.FileName, (ProfileItem)obj);
ViewModel?.Export2ClientConfigResult(fileName, (ProfileItem)obj);
break;
case EViewAction.AddServerWindow:

View File

@@ -1,14 +1,14 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.RoutingRuleSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
xmlns:conv="clr-namespace:v2rayN.Converters"
Title="{x:Static resx:ResUI.menuRoutingRuleSetting}"
Width="960"
Height="700"

View File

@@ -1,14 +1,14 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.SubEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
xmlns:conv="clr-namespace:v2rayN.Converters"
Title="{x:Static resx:ResUI.menuSubSetting}"
Width="700"
Height="600"

View File

@@ -1,12 +1,10 @@
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.ThemeSettingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:v2rayN.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:v2rayN.ViewModels"
d:DesignHeight="450"

View File

@@ -10,7 +10,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationIcon>v2rayN.ico</ApplicationIcon>
<Copyright>Copyright © 2017-2024 (GPLv3)</Copyright>
<FileVersion>6.56</FileVersion>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
</PropertyGroup>

View File

@@ -66,7 +66,7 @@ namespace v2rayUpgrade
{
string thisAppOldFile = $"{GetExePath()}.tmp";
File.Delete(thisAppOldFile);
string startKey = "v2rayN/";
string splitKey = "/";
using ZipArchive archive = ZipFile.OpenRead(fileName);
foreach (ZipArchiveEntry entry in archive.Entries)
@@ -77,11 +77,11 @@ namespace v2rayUpgrade
{
continue;
}
string fullName = entry.FullName;
if (fullName.StartsWith(startKey))
{
fullName = fullName[startKey.Length..];
}
var lst = entry.FullName.Split(splitKey);
if (lst.Length == 1) continue;
string fullName = string.Join(splitKey, lst[1..lst.Length]);
if (string.Equals(GetExePath(), GetPath(fullName), StringComparison.OrdinalIgnoreCase))
{
File.Move(GetExePath(), thisAppOldFile);
@@ -90,6 +90,8 @@ namespace v2rayUpgrade
string entryOutputPath = GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
entry.ExtractToFile(entryOutputPath, true);
Console.WriteLine(entryOutputPath);
}
catch (Exception ex)
{
@@ -104,11 +106,12 @@ namespace v2rayUpgrade
}
if (sb.Length > 0)
{
Console.WriteLine("Upgrade Failed,Hold ctrl + c to copy to clipboard.\n" +
"(升级失败,按住ctrl+c可以复制到剪贴板)." + sb.ToString());
Console.WriteLine("Upgrade Failed.\n" +
"(升级失败)." + sb.ToString());
return;
}
Console.WriteLine("Start v2rayN, please wait...(正在重启,请等待)");
Process.Start("v2rayN");
}