Add backup and restore

pull/5701/head
2dust 2024-09-07 14:52:33 +08:00
parent d5f1cc99ac
commit beddc71ed8
17 changed files with 889 additions and 14 deletions

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

View File

@ -204,6 +204,8 @@ namespace ServiceLib.Handler
};
}
config.webDavItem ??= new();
return 0;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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