mirror of https://github.com/2dust/v2rayN
Add backup and restore
parent
d5f1cc99ac
commit
beddc71ed8
|
@ -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,5 +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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -204,6 +204,8 @@ namespace ServiceLib.Handler
|
|||
};
|
||||
}
|
||||
|
||||
config.webDavItem ??= new();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace ServiceLib.Handler
|
|||
MessageBus.Current.SendMessage(content, Global.CommandSendMsgView);
|
||||
}
|
||||
|
||||
public void SendMessageEx(string? content )
|
||||
public void SendMessageEx(string? content)
|
||||
{
|
||||
if (content.IsNullOrEmpty())
|
||||
{
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
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 = "mywebdir";
|
||||
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;
|
||||
}
|
||||
|
||||
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> CheckProp()
|
||||
{
|
||||
if (_client is null) return false;
|
||||
try
|
||||
{
|
||||
var result = await _client.Propfind(_webDir);
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var result2 = await _client.Mkcol(_webDir); // create a directory
|
||||
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;
|
||||
}
|
||||
if (await CheckProp() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> PutFile(string fileName)
|
||||
{
|
||||
if (await GetClient() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (await CheckProp() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (await CheckProp() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var response = await _client.GetRawFile($"{_webDir}/{_webFileName}");
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
SaveLog(response.Description);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -47,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; }
|
||||
|
|
|
@ -248,4 +248,12 @@
|
|||
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; }
|
||||
}
|
||||
}
|
|
@ -582,6 +582,42 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 WebDav Check 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string LvWebDavCheck {
|
||||
get {
|
||||
return ResourceManager.GetString("LvWebDavCheck", 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 +726,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 +906,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 +1167,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>
|
||||
|
|
|
@ -1279,4 +1279,37 @@
|
|||
<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>
|
||||
</root>
|
|
@ -1276,4 +1276,37 @@
|
|||
<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>
|
||||
</root>
|
|
@ -1156,4 +1156,37 @@
|
|||
<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>
|
||||
</root>
|
|
@ -13,6 +13,7 @@
|
|||
<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="WebDav.Client" Version="2.8.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="16.1.0" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ using DynamicData.Binding;
|
|||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Splat;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive;
|
||||
|
||||
namespace ServiceLib.ViewModels
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
<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" />
|
||||
</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}" />
|
||||
|
||||
<Button
|
||||
x:Name="menuWebDavCheck"
|
||||
Grid.Row="3"
|
||||
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>
|
|
@ -0,0 +1,63 @@
|
|||
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.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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 />
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace v2rayN.Views
|
|||
{
|
||||
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,7 +73,7 @@ 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.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
|
||||
|
||||
|
@ -188,12 +190,6 @@ 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)
|
||||
|
@ -425,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
|
||||
|
|
Loading…
Reference in New Issue