Compare commits

..

25 Commits
6.56 ... 6.58

Author SHA1 Message Date
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
57 changed files with 20509 additions and 676 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

@@ -106,7 +106,12 @@ namespace ServiceLib.Common
{
try
{
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName);
if (File.Exists(destinationArchiveFileName))
{
File.Delete(destinationArchiveFileName);
}
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true);
}
catch (Exception ex)
{
@@ -115,6 +120,42 @@ 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.IsNullOrEmpty(ignoredName) && file.Name.Contains(ignoredName))
{
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

@@ -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

@@ -593,8 +593,7 @@ namespace ServiceLib.Common
{
try
{
string location = GetExePath();
return FileVersionInfo.GetVersionInfo(location)?.FileVersion ?? "0.0";
return Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString() ?? "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

@@ -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;
}
@@ -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

@@ -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)
{

View File

@@ -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()

View File

@@ -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;

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);
}

View File

@@ -1,6 +1,4 @@
using System.Runtime.InteropServices;
namespace ServiceLib.Handler
namespace ServiceLib.Handler
{
public sealed class LazyConfig
{

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

@@ -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)
@@ -265,14 +254,11 @@ 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)
@@ -287,28 +273,28 @@ namespace ServiceLib.Handler
#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))
{
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 +366,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 +420,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 +437,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 +467,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 +483,6 @@ namespace ServiceLib.Handler
try
{
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
if (File.Exists(fileName))
{
string targetPath = Utils.GetBinPath($"{geoName}.dat");
@@ -531,7 +506,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

@@ -582,6 +582,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 +735,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 +915,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 +1176,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,40 @@
<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>
</root>

View File

@@ -1276,4 +1276,40 @@
<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>
</root>

View File

@@ -1156,4 +1156,40 @@
<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>
</root>

View File

@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>6.58</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.0" />
<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; }

View File

@@ -0,0 +1,156 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
using System.Reactive;
namespace ServiceLib.ViewModels
{
public class BackupAndRestoreViewModel : MyReactiveObject
{
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($"backup_{DateTime.Now:yyyyMMddHHmmss}.zip");
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;
}
//backup first
var fileBackup = Utils.GetBackupPath($"backup_{DateTime.Now:yyyyMMddHHmmss}.zip");
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, true, "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

@@ -135,7 +135,7 @@ namespace ServiceLib.ViewModels
private void UpdateHandler(bool notify, string msg)
{
_noticeHandler?.SendMessage(msg, true);
_noticeHandler?.SendMessageEx(msg);
}
public void ProxiesReload()

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();
@@ -391,7 +362,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 +428,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
@@ -631,7 +620,7 @@ namespace ServiceLib.ViewModels
}
(new UpdateHandler()).RunAvailabilityCheck(async (bool success, string msg) =>
{
_noticeHandler?.SendMessage(msg, true);
_noticeHandler?.SendMessageEx(msg);
if (!_config.uiItem.showInTaskbar)
{
@@ -718,99 +707,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()
@@ -881,7 +777,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 +837,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,111 @@
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;
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.IsNullOrEmpty(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,7 +258,7 @@ namespace ServiceLib.ViewModels
{
if (Utils.IsNullOrEmpty(result.IndexId))
{
_noticeHandler?.SendMessage(result.Delay, true);
_noticeHandler?.SendMessageEx(result.Delay);
_noticeHandler?.Enqueue(result.Delay);
return;
}

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

@@ -104,7 +104,7 @@
<DataTemplate>
<Border
Width="160"
Margin="4,0"
Margin="0"
Padding="0"
Theme="{StaticResource CardBorder}">
<DockPanel>

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">

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

@@ -66,14 +66,12 @@
<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" />
IsChecked="True" />
</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

@@ -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.3" />
<PackageReference Include="Semi.Avalonia.DataGrid" Version="11.1.0.3" />
<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

@@ -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

@@ -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");
}