feat: support Xray XDRIVE object storage relay transport (#10546)

This commit is contained in:
youugiuhiuh
2026-11-08 20:02:40 +08:00
committed by GitHub
parent 968beb4432
commit 0f71c46d3b
20 changed files with 1156 additions and 76 deletions
@@ -824,4 +824,135 @@ public class CoreConfigV2rayServiceTests
await proxyOutbound.Should().NotBeNull();
await proxyOutbound!.protocol.Should().BeEqualTo("shadowsocks");
}
[Test]
public async Task GenerateClientConfigContent_XdriveGoogleDrive_ShouldGenerateValidOutbound()
{
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = new ProfileItem
{
IndexId = "node-xdrive-gdrive",
ConfigType = EConfigType.VLESS,
CoreType = ECoreType.Xray,
Remarks = "xdrive-gdrive",
Address = "example.com",
Port = 443,
Password = Guid.NewGuid().ToString(),
Network = nameof(ETransport.xdrive),
MuxEnabled = false,
};
node.SetProtocolExtra(new ProtocolExtraItem { VlessEncryption = Global.None });
node.SetTransportExtra(new TransportExtraItem
{
XdriveService = "google-drive",
XdriveRemoteFolder = "folder-123",
XdriveSecrets = "id,secret,token",
});
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue();
var generatedConfig = JsonNode.Parse(result.Data!.ToString()!)!;
var proxy = generatedConfig["outbounds"]!.AsArray()
.First(outbound => outbound!["tag"]!.GetValue<string>() == Global.ProxyTag)!;
var streamSettings = proxy["streamSettings"]!;
await streamSettings["network"]!.GetValue<string>().Should().BeEqualTo("xdrive");
var xdriveSettings = streamSettings["xdriveSettings"]!;
await xdriveSettings["service"]!.GetValue<string>().Should().BeEqualTo("google-drive");
await xdriveSettings["remoteFolder"]!.GetValue<string>().Should().BeEqualTo("folder-123");
var secrets = xdriveSettings["secrets"]!.AsArray();
await secrets.Count.Should().BeEqualTo(3);
await secrets[0]!.GetValue<string>().Should().BeEqualTo("id");
await secrets[1]!.GetValue<string>().Should().BeEqualTo("secret");
await secrets[2]!.GetValue<string>().Should().BeEqualTo("token");
await proxy["mux"]!["enabled"]!.GetValue<bool>().Should().BeTrue();
}
[Test]
public async Task GenerateClientConfigContent_XdriveS3_ShouldGenerateValidOutbound()
{
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = new ProfileItem
{
IndexId = "node-xdrive-s3",
ConfigType = EConfigType.VLESS,
CoreType = ECoreType.Xray,
Remarks = "xdrive-s3",
Address = "example.com",
Port = 443,
Password = Guid.NewGuid().ToString(),
Network = nameof(ETransport.xdrive),
};
node.SetProtocolExtra(new ProtocolExtraItem { VlessEncryption = Global.None });
node.SetTransportExtra(new TransportExtraItem
{
XdriveService = "s3",
XdriveS3Endpoint = "https://s3.amazonaws.com",
XdriveS3Bucket = "mybucket",
XdriveS3PathStyle = false,
});
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue();
var generatedConfig = JsonNode.Parse(result.Data!.ToString()!)!;
var proxy = generatedConfig["outbounds"]!.AsArray()
.First(outbound => outbound!["tag"]!.GetValue<string>() == Global.ProxyTag)!;
var s3 = proxy["streamSettings"]!["xdriveSettings"]!["s3"]!;
await s3["endpoint"]!.GetValue<string>().Should().BeEqualTo("https://s3.amazonaws.com");
await s3["bucket"]!.GetValue<string>().Should().BeEqualTo("mybucket");
await s3["pathStyle"]!.GetValue<bool>().Should().BeFalse();
}
[Test]
public async Task GenerateClientConfigContent_XdriveExtra_ShouldPopulateParameters()
{
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = new ProfileItem
{
IndexId = "node-xdrive-extra",
ConfigType = EConfigType.VLESS,
CoreType = ECoreType.Xray,
Remarks = "xdrive-extra",
Address = "example.com",
Port = 443,
Password = Guid.NewGuid().ToString(),
Network = nameof(ETransport.xdrive),
};
node.SetProtocolExtra(new ProtocolExtraItem { VlessEncryption = Global.None });
node.SetTransportExtra(new TransportExtraItem
{
XdriveService = "onedrive",
XdriveExtra = """{"segmentBytes":1048576,"flushIntervalMs":500,"pollIntervalMs":200,"maxPollIntervalMs":2000,"eagerWindowMs":1000,"concurrency":4}""",
});
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue();
var generatedConfig = JsonNode.Parse(result.Data!.ToString()!)!;
var proxy = generatedConfig["outbounds"]!.AsArray()
.First(outbound => outbound!["tag"]!.GetValue<string>() == Global.ProxyTag)!;
var xdriveSettings = proxy["streamSettings"]!["xdriveSettings"]!;
await xdriveSettings["segmentBytes"]!.GetValue<int>().Should().BeEqualTo(1048576);
await xdriveSettings["flushIntervalMs"]!.GetValue<int>().Should().BeEqualTo(500);
await xdriveSettings["pollIntervalMs"]!.GetValue<int>().Should().BeEqualTo(200);
await xdriveSettings["maxPollIntervalMs"]!.GetValue<int>().Should().BeEqualTo(2000);
await xdriveSettings["eagerWindowMs"]!.GetValue<int>().Should().BeEqualTo(1000);
await xdriveSettings["concurrency"]!.GetValue<int>().Should().BeEqualTo(4);
}
}
@@ -0,0 +1,164 @@
using System.Reflection;
using ReactiveUI.Builder;
using ServiceLib.Enums;
using ServiceLib.Models.Entities;
using ServiceLib.Resx;
using ServiceLib.Tests.CoreConfig;
using ServiceLib.ViewModels;
namespace ServiceLib.Tests.ViewModels;
[NotInParallel]
public class AddServerViewModelTests
{
static AddServerViewModelTests()
{
RxAppBuilder.CreateReactiveUIBuilder().WithCoreServices().BuildApp();
}
[Test]
public async Task XdriveServices_ShouldContainExpectedServices()
{
await Global.XdriveServices.Count.Should().BeEqualTo(3);
await Global.XdriveServices[0].Should().BeEqualTo("google-drive");
await Global.XdriveServices[1].Should().BeEqualTo("s3");
await Global.XdriveServices[2].Should().BeEqualTo("local");
}
[Test]
public async Task AddServerViewModel_ShouldBindXdrivePropertiesFromTransportExtra()
{
CoreConfigTestFactory.BindAppManagerConfig(CoreConfigTestFactory.CreateConfig());
var profile = new ProfileItem
{
IndexId = "test-node",
ConfigType = EConfigType.VLESS,
Address = "example.com",
Port = 443,
Network = nameof(ETransport.xdrive),
};
profile.SetTransportExtra(new TransportExtraItem
{
XdriveService = "s3",
XdriveRemoteFolder = "/remote/path",
XdriveSecrets = "sec1,sec2",
XdriveS3Endpoint = "s3.example.com",
XdriveS3Region = "us-west-2",
XdriveS3Bucket = "my-bucket",
XdriveS3PathStyle = true,
XdriveExtra = "{\"concurrency\":4}",
});
var vm = new AddServerViewModel(profile);
await vm.XdriveService.Should().BeEqualTo("s3");
await vm.XdriveRemoteFolder.Should().BeEqualTo("/remote/path");
await vm.XdriveSecrets.Should().BeEqualTo("sec1,sec2");
await vm.XdriveS3Endpoint.Should().BeEqualTo("s3.example.com");
await vm.XdriveS3Region.Should().BeEqualTo("us-west-2");
await vm.XdriveS3Bucket.Should().BeEqualTo("my-bucket");
await vm.XdriveS3PathStyle.Should().BeEqualTo(true);
await vm.XdriveExtra.Should().BeEqualTo("{\"concurrency\":4}");
}
[Test]
public async Task AddServerViewModel_TransportFriendlyProperties_ShouldAdaptToXdrive()
{
CoreConfigTestFactory.BindAppManagerConfig(CoreConfigTestFactory.CreateConfig());
var profile = new ProfileItem
{
IndexId = "test-node",
ConfigType = EConfigType.VLESS,
Address = "example.com",
Port = 443,
Network = nameof(ETransport.xdrive),
};
profile.SetTransportExtra(new TransportExtraItem
{
XdriveService = "s3",
XdriveRemoteFolder = "/folder",
XdriveS3Endpoint = "s3.custom.endpoint",
XdriveExtra = "{\"segmentBytes\":1024}",
});
var vm = new AddServerViewModel(profile);
// Getters
await vm.TransportHeaderType.Should().BeEqualTo("s3");
await vm.TransportHost.Should().BeEqualTo("s3.custom.endpoint");
await vm.TransportPath.Should().BeEqualTo("/folder");
await vm.TransportExtraText.Should().BeEqualTo("{\"segmentBytes\":1024}");
// Setters
vm.TransportHeaderType = "google-drive";
await vm.XdriveService.Should().BeEqualTo("google-drive");
await vm.TransportHeaderType.Should().BeEqualTo("google-drive");
vm.TransportHost = "s3.new.endpoint";
await vm.XdriveS3Endpoint.Should().BeEqualTo("s3.new.endpoint");
await vm.TransportHost.Should().BeEqualTo("s3.new.endpoint");
vm.TransportPath = "/new/folder";
await vm.XdriveRemoteFolder.Should().BeEqualTo("/new/folder");
await vm.TransportPath.Should().BeEqualTo("/new/folder");
vm.TransportExtraText = "{\"flushIntervalMs\":500}";
await vm.XdriveExtra.Should().BeEqualTo("{\"flushIntervalMs\":500}");
await vm.TransportExtraText.Should().BeEqualTo("{\"flushIntervalMs\":500}");
}
[Test]
public async Task AddServerViewModel_SaveServerAsync_ShouldPersistXdriveFields()
{
CoreConfigTestFactory.BindAppManagerConfig(CoreConfigTestFactory.CreateConfig());
var profile = new ProfileItem
{
IndexId = "test-node",
Remarks = "test remarks",
ConfigType = EConfigType.VLESS,
Address = "example.com",
Port = 443,
Password = Guid.NewGuid().ToString(),
Network = nameof(ETransport.xdrive),
};
var vm = new AddServerViewModel(profile);
vm.XdriveService = "s3";
vm.XdriveRemoteFolder = "/backup";
vm.XdriveSecrets = "key,secret";
vm.XdriveS3Endpoint = "s3.amazonaws.com";
vm.XdriveS3Region = "eu-central-1";
vm.XdriveS3Bucket = "bucket-1";
vm.XdriveS3PathStyle = false;
vm.XdriveExtra = "{\"concurrency\":8}";
var saveMethod = typeof(AddServerViewModel).GetMethod("SaveServerAsync", BindingFlags.Instance | BindingFlags.NonPublic)!;
await (Task)saveMethod.Invoke(vm, null)!;
var extra = vm.SelectedSource.GetTransportExtra();
await extra.XdriveService.Should().BeEqualTo("s3");
await extra.XdriveRemoteFolder.Should().BeEqualTo("/backup");
await extra.XdriveSecrets.Should().BeEqualTo("key,secret");
await extra.XdriveS3Endpoint.Should().BeEqualTo("s3.amazonaws.com");
await extra.XdriveS3Region.Should().BeEqualTo("eu-central-1");
await extra.XdriveS3Bucket.Should().BeEqualTo("bucket-1");
await extra.XdriveS3PathStyle.Should().BeEqualTo(false);
await extra.XdriveExtra.Should().BeEqualTo("{\"concurrency\":8}");
}
[Test]
public async Task Localization_ShouldProvideXdriveStrings()
{
await ResUI.XdriveService.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveRemoteFolder.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveSecrets.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveS3Endpoint.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveS3Region.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveS3Bucket.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveS3PathStyle.IsNotEmpty().Should().BeTrue();
await ResUI.XdriveExtra.IsNotEmpty().Should().BeTrue();
}
}
+2 -1
View File
@@ -10,5 +10,6 @@ public enum ETransport
h2,
http,
quic,
grpc
grpc,
xdrive
}
+14 -1
View File
@@ -337,7 +337,8 @@ public class Global
"kcp",
"grpc",
"ws",
"httpupgrade"
"httpupgrade",
"xdrive"
];
public static readonly List<string> KcpHeaderTypes =
@@ -455,6 +456,18 @@ public class Global
"stream-one"
];
public static readonly List<string> XdriveServices = ["google-drive", "s3", "local"];
public static readonly List<string> XdriveProviders =
[
"Cloudflare R2",
"Google Drive",
"AWS S3 (Global)",
"Yandex Cloud S3",
"ArvanCloud S3",
"Custom S3"
];
public static readonly List<string> DomainStrategy =
[
"AsIs",
@@ -321,6 +321,7 @@ public class StreamSettings4Ray
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
public MasqueSettings4Ray? masqueSettings { get; set; }
public XdriveSettings4Ray? xdriveSettings { get; set; }
public JsonObject? finalmask { get; set; }
@@ -462,6 +463,28 @@ public class MasqueWarpSettings4Ray
public List<string>? address { get; set; }
}
public class XdriveSettings4Ray
{
public string? service { get; set; }
public string? remoteFolder { get; set; }
public List<string>? secrets { get; set; }
public S3Settings4Ray? s3 { get; set; }
public int? segmentBytes { get; set; }
public int? flushIntervalMs { get; set; }
public int? pollIntervalMs { get; set; }
public int? maxPollIntervalMs { get; set; }
public int? eagerWindowMs { get; set; }
public int? concurrency { get; set; }
}
public class S3Settings4Ray
{
public string? endpoint { get; set; }
public string? region { get; set; }
public string? bucket { get; set; }
public bool? pathStyle { get; set; }
}
public class Finalmask4Ray
{
public List<Mask4Ray>? tcp { get; set; }
@@ -16,4 +16,13 @@ public record TransportExtraItem
public string? KcpHeaderType { get; init; }
public string? KcpSeed { get; init; }
public int? KcpMtu { get; init; }
public string? XdriveService { get; init; }
public string? XdriveRemoteFolder { get; init; }
public string? XdriveSecrets { get; init; }
public string? XdriveS3Endpoint { get; init; }
public string? XdriveS3Region { get; init; }
public string? XdriveS3Bucket { get; init; }
public bool? XdriveS3PathStyle { get; init; }
public string? XdriveExtra { get; init; }
}
+118 -46
View File
@@ -996,6 +996,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Columns 的本地化字符串。
/// </summary>
public static string menuColumns {
get {
return ResourceManager.GetString("menuColumns", resourceCulture);
}
}
/// <summary>
/// 查找类似 Close Connection 的本地化字符串。
/// </summary>
@@ -1032,15 +1041,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Currently frozen first {0} column(s) 的本地化字符串。
/// </summary>
public static string menuCurrentlyFrozenColumns {
get {
return ResourceManager.GetString("menuCurrentlyFrozenColumns", resourceCulture);
}
}
/// <summary>
/// 查找类似 Latency Test 的本地化字符串。
/// </summary>
@@ -1185,15 +1185,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Freeze up to this column 的本地化字符串。
/// </summary>
public static string menuFreezeToThisColumn {
get {
return ResourceManager.GetString("menuFreezeToThisColumn", resourceCulture);
}
}
/// <summary>
/// 查找类似 Full Config Template Setting 的本地化字符串。
/// </summary>
@@ -1239,15 +1230,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Hide this column 的本地化字符串。
/// </summary>
public static string menuHideThisColumn {
get {
return ResourceManager.GetString("menuHideThisColumn", resourceCulture);
}
}
/// <summary>
/// 查找类似 Import Rules From Clipboard 的本地化字符串。
/// </summary>
@@ -1824,15 +1806,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Show columns 的本地化字符串。
/// </summary>
public static string menuShowColumns {
get {
return ResourceManager.GetString("menuShowColumns", resourceCulture);
}
}
/// <summary>
/// 查找类似 Show or hide the main window 的本地化字符串。
/// </summary>
@@ -2022,15 +1995,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Unfreeze all columns 的本地化字符串。
/// </summary>
public static string menuUnfreezeAllColumns {
get {
return ResourceManager.GetString("menuUnfreezeAllColumns", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} Website 的本地化字符串。
/// </summary>
@@ -4949,7 +4913,7 @@ namespace ServiceLib.Resx {
return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture);
}
}
/// <summary>
/// 查找类似 DNS mode 的本地化字符串。
/// </summary>
@@ -5516,5 +5480,113 @@ namespace ServiceLib.Resx {
return ResourceManager.GetString("UpgradeAppNotExistTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Preset Provider 的本地化字符串。
/// </summary>
public static string XdriveProvider {
get {
return ResourceManager.GetString("XdriveProvider", resourceCulture);
}
}
/// <summary>
/// 查找类似 XDRIVE service 的本地化字符串。
/// </summary>
public static string XdriveService {
get {
return ResourceManager.GetString("XdriveService", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remote folder 的本地化字符串。
/// </summary>
public static string XdriveRemoteFolder {
get {
return ResourceManager.GetString("XdriveRemoteFolder", resourceCulture);
}
}
/// <summary>
/// 查找类似 Secrets (comma separated) 的本地化字符串。
/// </summary>
public static string XdriveSecrets {
get {
return ResourceManager.GetString("XdriveSecrets", resourceCulture);
}
}
/// <summary>
/// 查找类似 S3 Endpoint 的本地化字符串。
/// </summary>
public static string XdriveS3Endpoint {
get {
return ResourceManager.GetString("XdriveS3Endpoint", resourceCulture);
}
}
/// <summary>
/// 查找类似 S3 Region 的本地化字符串。
/// </summary>
public static string XdriveS3Region {
get {
return ResourceManager.GetString("XdriveS3Region", resourceCulture);
}
}
/// <summary>
/// 查找类似 S3 Bucket 的本地化字符串。
/// </summary>
public static string XdriveS3Bucket {
get {
return ResourceManager.GetString("XdriveS3Bucket", resourceCulture);
}
}
/// <summary>
/// 查找类似 S3 Force Path Style 的本地化字符串。
/// </summary>
public static string XdriveS3PathStyle {
get {
return ResourceManager.GetString("XdriveS3PathStyle", resourceCulture);
}
}
/// <summary>
/// 查找类似 XDRIVE Extra 的本地化字符串。
/// </summary>
public static string XdriveExtra {
get {
return ResourceManager.GetString("XdriveExtra", resourceCulture);
}
}
/// <summary>
/// 查找类似 Raw JSON, format: { XdriveObject } 的本地化字符串。
/// </summary>
public static string XdriveExtraTip {
get {
return ResourceManager.GetString("XdriveExtraTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 XDRIVE service 的本地化字符串。
/// </summary>
public static string TransportHeaderType6 {
get {
return ResourceManager.GetString("TransportHeaderType6", resourceCulture);
}
}
/// <summary>
/// 查找类似 XDRIVE remote folder 的本地化字符串。
/// </summary>
public static string TransportPathTip6 {
get {
return ResourceManager.GetString("TransportPathTip6", resourceCulture);
}
}
}
}
+36
View File
@@ -1902,4 +1902,40 @@ Təyin edildikdə sertifikat sabitlənəcək və "Təhlükəsiz olmayan əlaqəy
<data name="TbSortingProcess" xml:space="preserve">
<value>Proses</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>Təyin olunmuş təchizatçı</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>Saxlama xidməti (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>Uzaqdakı qovluq (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>Giriş açarları (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>S3 son nöqtəsi (Endpoint)</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>S3 regionu (Region)</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>S3 səbəti (Bucket)</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>S3 ünvanlama rejimi (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE əlavə parametrləri</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>Emal edilməmiş JSON, format: { Xdrive obyekti }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>XDRIVE xidməti</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>XDRIVE uzaqdakı qovluq</value>
</data>
</root>
+36
View File
@@ -1776,4 +1776,40 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>ارائه‌دهنده پیش‌فرض</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>سرویس ذخیره‌سازی (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>پوشه ریموت (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>کلیدهای دسترسی (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>آدرس اندپوینت S3</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>منطقه S3 (region)</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>باکت S3 (bucket)</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>آدرس‌دهی مستقیم S3 (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>تنظیمات پیشرفته XDRIVE</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>فرمت JSON خام: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>سرویس XDRIVE</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>پوشه ریموت XDRIVE</value>
</data>
</root>
+36
View File
@@ -1770,4 +1770,40 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuNewUpdate" xml:space="preserve">
<value>Nouvelle MAJ</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>Fournisseur prédéfini</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>Service de stockage (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>Dossier distant (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>Clés d'accès (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>Point de terminaison S3 (Endpoint)</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>Région S3 (Region)</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>Bucket S3</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>Mode d'adressage S3 (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE Extra</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>JSON brut, format : { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>Service XDRIVE</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>Dossier distant XDRIVE</value>
</data>
</root>
+36
View File
@@ -1773,4 +1773,40 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>Előre beállított szolgáltató</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>Tárhelyszolgáltatás (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>Távoli mappa (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>Hozzáférési kulcsok (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>S3 végpont (Endpoint)</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>S3 régió (Region)</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>S3 tárhely (Bucket)</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>S3 elérési út stílus (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE Extra</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>Raw JSON, formátum: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>XDRIVE szolgáltatás</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>XDRIVE távoli mappa</value>
</data>
</root>
+36
View File
@@ -1797,4 +1797,40 @@ Tindakan "Ambil sertifikat" mungkin gagal jika sertifikat self-signed digunakan
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
<value>Memecah bagian akhir paket menjadi fragmen yang lebih kecil. Hal ini dapat memengaruhi throughput dan latensi.</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>Penyedia Preset</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>Layanan Penyimpanan (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>Folder Jarak Jauh (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>Kredensial Akses (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>Endpoint S3</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>Wilayah S3 (Region)</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>Bucket S3</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>Gaya Jalur S3 (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE Ekstra</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>JSON mentah, format: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>Layanan XDRIVE</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>Folder jarak jauh XDRIVE</value>
</data>
</root>
+35 -11
View File
@@ -1917,8 +1917,8 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="FillCorrectFakeIPv6Range" xml:space="preserve">
<value>Enter a valid IPv6 CIDR range with at least two addresses, for example fc00::/18.</value>
</data>
<data name="menuShowColumns" xml:space="preserve">
<value>Show columns</value>
<data name="menuColumns" xml:space="preserve">
<value>Columns</value>
</data>
<data name="TbWarp" xml:space="preserve">
<value>Warp</value>
@@ -1926,16 +1926,40 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="LvPreResolve" xml:space="preserve">
<value>Pre-resolve server domains</value>
</data>
<data name="menuFreezeToThisColumn" xml:space="preserve">
<value>Freeze up to this column</value>
<data name="XdriveProvider" xml:space="preserve">
<value>Preset Provider</value>
</data>
<data name="menuUnfreezeAllColumns" xml:space="preserve">
<value>Unfreeze all columns</value>
<data name="XdriveService" xml:space="preserve">
<value>Storage service (service)</value>
</data>
<data name="menuCurrentlyFrozenColumns" xml:space="preserve">
<value>Currently frozen first {0} column(s)</value>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>Remote folder (remoteFolder)</value>
</data>
<data name="menuHideThisColumn" xml:space="preserve">
<value>Hide this column</value>
<data name="XdriveSecrets" xml:space="preserve">
<value>Secrets (AK,SK / OAuth)</value>
</data>
</root>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>S3 Endpoint</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>S3 Region</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>S3 Bucket</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>S3 Force Path Style</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE Extra</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>Raw JSON, format: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>XDRIVE service</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>XDRIVE remote folder</value>
</data>
</root>
+36
View File
@@ -1908,4 +1908,40 @@
<data name="menuAddMasqueServer" xml:space="preserve">
<value>Добавить сервер [MASQUE]</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>Шаблон провайдера</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>Облачный сервис (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>Удаленная папка (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>Ключи доступа (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>Эндпоинт S3 (Endpoint)</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>Регион S3 (Region)</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>Бакет S3 (Bucket)</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>Режим адресации S3 (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>Дополнительные параметры XDRIVE (Extra)</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>Сырой JSON, формат: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>XDRIVE-сервис</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>Удаленная папка XDRIVE</value>
</data>
</root>
+35 -11
View File
@@ -1909,8 +1909,8 @@
<data name="FillCorrectFakeIPv6Range" xml:space="preserve">
<value>请输入至少包含两个地址的有效 IPv6 网段,例如 fc00::/18。</value>
</data>
<data name="menuShowColumns" xml:space="preserve">
<value>显示列</value>
<data name="menuColumns" xml:space="preserve">
<value>列</value>
</data>
<data name="TbWarp" xml:space="preserve">
<value>Warp</value>
@@ -1918,16 +1918,40 @@
<data name="LvPreResolve" xml:space="preserve">
<value>预解析节点域名</value>
</data>
<data name="menuFreezeToThisColumn" xml:space="preserve">
<value>冻结至此列</value>
<data name="XdriveProvider" xml:space="preserve">
<value>预设服务商</value>
</data>
<data name="menuUnfreezeAllColumns" xml:space="preserve">
<value>取消所有冻结</value>
<data name="XdriveService" xml:space="preserve">
<value>存储服务 (service)</value>
</data>
<data name="menuCurrentlyFrozenColumns" xml:space="preserve">
<value>当前已冻结前 {0} 列</value>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>远程目录 (remoteFolder)</value>
</data>
<data name="menuHideThisColumn" xml:space="preserve">
<value>隐藏此列</value>
<data name="XdriveSecrets" xml:space="preserve">
<value>访问凭证 Secrets (AK,SK / OAuth)</value>
</data>
</root>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>S3 终端节点</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>S3 区域</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>S3 存储桶</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>S3 强制路径样式 (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE 额外配置</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>原始 JSON,格式: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>XDRIVE 服务</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>XDRIVE 远端目录</value>
</data>
</root>
+36
View File
@@ -1927,4 +1927,40 @@
<data name="FillCorrectFakeIPv6Range" xml:space="preserve">
<value>請輸入至少包含兩個位址的有效 IPv6 網段,例如 fc00::/18。</value>
</data>
<data name="XdriveProvider" xml:space="preserve">
<value>預設服務商</value>
</data>
<data name="XdriveService" xml:space="preserve">
<value>存儲服務 (service)</value>
</data>
<data name="XdriveRemoteFolder" xml:space="preserve">
<value>遠端目錄 (remoteFolder)</value>
</data>
<data name="XdriveSecrets" xml:space="preserve">
<value>存取憑證 Secrets (AK,SK / OAuth)</value>
</data>
<data name="XdriveS3Endpoint" xml:space="preserve">
<value>S3 終端節點</value>
</data>
<data name="XdriveS3Region" xml:space="preserve">
<value>S3 區域</value>
</data>
<data name="XdriveS3Bucket" xml:space="preserve">
<value>S3 儲存桶</value>
</data>
<data name="XdriveS3PathStyle" xml:space="preserve">
<value>S3 強制路徑樣式 (PathStyle)</value>
</data>
<data name="XdriveExtra" xml:space="preserve">
<value>XDRIVE 額外配置</value>
</data>
<data name="XdriveExtraTip" xml:space="preserve">
<value>原始 JSON,格式: { XdriveObject }</value>
</data>
<data name="TransportHeaderType6" xml:space="preserve">
<value>XDRIVE 服務</value>
</data>
<data name="TransportPathTip6" xml:space="preserve">
<value>XDRIVE 遠端目錄</value>
</data>
</root>
@@ -506,6 +506,81 @@ public partial class CoreConfigV2rayService
streamSettings.grpcSettings = grpcSettings;
break;
case nameof(ETransport.xdrive):
streamSettings.network = nameof(ETransport.xdrive);
var xdriveSettings = new XdriveSettings4Ray
{
service = transport.XdriveService,
remoteFolder = transport.XdriveRemoteFolder,
secrets = transport.XdriveSecrets.IsNotEmpty() ? Utils.String2List(transport.XdriveSecrets) : null,
};
if (string.Equals(transport.XdriveService, "s3", StringComparison.OrdinalIgnoreCase)
|| transport.XdriveS3Endpoint.IsNotEmpty()
|| transport.XdriveS3Bucket.IsNotEmpty())
{
xdriveSettings.s3 = new S3Settings4Ray
{
endpoint = transport.XdriveS3Endpoint,
region = transport.XdriveS3Region,
bucket = transport.XdriveS3Bucket,
pathStyle = transport.XdriveS3PathStyle,
};
}
if (transport.XdriveExtra.IsNotEmpty())
{
try
{
var extraNode = JsonUtils.ParseJson(transport.XdriveExtra);
if (extraNode is JsonObject extraObj)
{
if (int.TryParse(extraObj["segmentBytes"]?.ToString(), out var segmentBytes))
{
xdriveSettings.segmentBytes = segmentBytes;
}
if (int.TryParse(extraObj["flushIntervalMs"]?.ToString(), out var flushIntervalMs))
{
xdriveSettings.flushIntervalMs = flushIntervalMs;
}
if (int.TryParse(extraObj["pollIntervalMs"]?.ToString(), out var pollIntervalMs))
{
xdriveSettings.pollIntervalMs = pollIntervalMs;
}
if (int.TryParse(extraObj["maxPollIntervalMs"]?.ToString(), out var maxPollIntervalMs))
{
xdriveSettings.maxPollIntervalMs = maxPollIntervalMs;
}
if (int.TryParse(extraObj["eagerWindowMs"]?.ToString(), out var eagerWindowMs))
{
xdriveSettings.eagerWindowMs = eagerWindowMs;
}
if (int.TryParse(extraObj["concurrency"]?.ToString(), out var concurrency))
{
xdriveSettings.concurrency = concurrency;
}
}
}
catch
{
// ignore extra json parse errors
}
}
if (string.Equals(transport.XdriveService, "google-drive", StringComparison.OrdinalIgnoreCase))
{
xdriveSettings.segmentBytes ??= 524288;
xdriveSettings.flushIntervalMs ??= 10;
xdriveSettings.pollIntervalMs ??= 50;
xdriveSettings.maxPollIntervalMs ??= 200;
xdriveSettings.eagerWindowMs ??= 10000;
xdriveSettings.concurrency ??= 64;
}
streamSettings.xdriveSettings = xdriveSettings;
FillOutboundMux(outbound, true, true);
break;
case "hysteria":
var ports = protocolExtra?.Ports;
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
@@ -148,6 +148,33 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
[Reactive]
public partial int? KcpMtu { get; set; }
[Reactive]
public partial string XdriveProvider { get; set; }
[Reactive]
public partial string XdriveService { get; set; }
[Reactive]
public partial string XdriveRemoteFolder { get; set; }
[Reactive]
public partial string XdriveSecrets { get; set; }
[Reactive]
public partial string XdriveS3Endpoint { get; set; }
[Reactive]
public partial string XdriveS3Region { get; set; }
[Reactive]
public partial string XdriveS3Bucket { get; set; }
[Reactive]
public partial bool? XdriveS3PathStyle { get; set; }
[Reactive]
public partial string XdriveExtra { get; set; }
public string TransportHeaderType
{
get => SelectedSource.GetNetwork() switch
@@ -156,6 +183,7 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
nameof(ETransport.kcp) => KcpHeaderType,
nameof(ETransport.xhttp) => XhttpMode,
nameof(ETransport.grpc) => GrpcMode,
nameof(ETransport.xdrive) => XdriveService,
_ => string.Empty,
};
set
@@ -177,6 +205,10 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
case nameof(ETransport.grpc):
GrpcMode = value;
break;
case nameof(ETransport.xdrive):
XdriveService = value;
break;
}
this.RaisePropertyChanged();
}
@@ -191,6 +223,7 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
nameof(ETransport.httpupgrade) => Host,
nameof(ETransport.xhttp) => Host,
nameof(ETransport.grpc) => GrpcAuthority,
nameof(ETransport.xdrive) => XdriveS3Endpoint,
_ => string.Empty,
};
set
@@ -207,6 +240,10 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
case nameof(ETransport.grpc):
GrpcAuthority = value;
break;
case nameof(ETransport.xdrive):
XdriveS3Endpoint = value;
break;
}
this.RaisePropertyChanged();
}
@@ -221,6 +258,7 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
nameof(ETransport.httpupgrade) => Path,
nameof(ETransport.xhttp) => Path,
nameof(ETransport.grpc) => GrpcServiceName,
nameof(ETransport.xdrive) => XdriveRemoteFolder,
_ => string.Empty,
};
set
@@ -240,6 +278,10 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
case nameof(ETransport.grpc):
GrpcServiceName = value;
break;
case nameof(ETransport.xdrive):
XdriveRemoteFolder = value;
break;
}
this.RaisePropertyChanged();
}
@@ -247,14 +289,23 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
public string TransportExtraText
{
get => SelectedSource.GetNetwork() == nameof(ETransport.xhttp)
? XhttpExtra
: string.Empty;
get => SelectedSource.GetNetwork() switch
{
nameof(ETransport.xhttp) => XhttpExtra,
nameof(ETransport.xdrive) => XdriveExtra,
_ => string.Empty,
};
set
{
if (SelectedSource.GetNetwork() == nameof(ETransport.xhttp))
switch (SelectedSource.GetNetwork())
{
XhttpExtra = value;
case nameof(ETransport.xhttp):
XhttpExtra = value;
break;
case nameof(ETransport.xdrive):
XdriveExtra = value;
break;
}
this.RaisePropertyChanged();
}
@@ -287,8 +338,16 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
.Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.Network)
.Subscribe(_ =>
.Subscribe(network =>
{
if (SelectedSource != null && !network.IsNullOrEmpty())
{
SelectedSource.Network = network;
}
if (network == nameof(ETransport.xdrive))
{
MuxEnabled = true;
}
this.RaisePropertyChanged(nameof(TransportHeaderType));
this.RaisePropertyChanged(nameof(TransportHost));
this.RaisePropertyChanged(nameof(TransportPath));
@@ -298,6 +357,13 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertSha());
this.WhenAnyValue(x => x.XdriveProvider)
.Where(p => p.IsNotEmpty())
.Subscribe(provider =>
{
ApplyXdriveProviderPreset(provider);
});
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.Network = Global.DefaultNetwork;
@@ -358,6 +424,48 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
KcpHeaderType = transport.KcpHeaderType.IsNullOrEmpty() ? Global.None : transport.KcpHeaderType;
KcpSeed = transport.KcpSeed ?? string.Empty;
KcpMtu = transport.KcpMtu;
XdriveService = transport.XdriveService.IsNullOrEmpty() ? (Global.XdriveServices.FirstOrDefault() ?? "google-drive") : transport.XdriveService;
XdriveRemoteFolder = transport.XdriveRemoteFolder ?? string.Empty;
XdriveSecrets = transport.XdriveSecrets ?? string.Empty;
XdriveS3Endpoint = transport.XdriveS3Endpoint ?? string.Empty;
XdriveS3Region = transport.XdriveS3Region ?? string.Empty;
XdriveS3Bucket = transport.XdriveS3Bucket ?? string.Empty;
XdriveS3PathStyle = transport.XdriveS3PathStyle;
XdriveExtra = transport.XdriveExtra ?? string.Empty;
if (transport.XdriveService == "google-drive")
{
XdriveProvider = "Google Drive";
}
else if (transport.XdriveS3Endpoint?.Contains("r2.cloudflarestorage.com") == true)
{
XdriveProvider = "Cloudflare R2";
}
else if (transport.XdriveS3Endpoint?.Contains("amazonaws.com") == true)
{
XdriveProvider = "AWS S3 (Global)";
}
else if (transport.XdriveS3Endpoint?.Contains("yandexcloud.net") == true)
{
XdriveProvider = "Yandex Cloud S3";
}
else if (transport.XdriveS3Endpoint?.Contains("arvanstorage.ir") == true)
{
XdriveProvider = "ArvanCloud S3";
}
else if (SelectedSource.Remarks?.Contains("Cloudflare", StringComparison.OrdinalIgnoreCase) == true
|| (transport.XdriveS3PathStyle == true && transport.XdriveS3Region == "auto"))
{
XdriveProvider = "Cloudflare R2";
}
else if (transport.XdriveService == "s3" || transport.XdriveS3Endpoint.IsNotEmpty())
{
XdriveProvider = "Custom S3";
}
else
{
XdriveProvider = "Cloudflare R2";
}
}
private async Task SaveServerAsync()
@@ -440,6 +548,14 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
KcpHeaderType = KcpHeaderType.NullIfEmpty(),
KcpSeed = KcpSeed.NullIfEmpty(),
KcpMtu = KcpMtu > 0 ? KcpMtu : null,
XdriveService = XdriveService.NullIfEmpty(),
XdriveRemoteFolder = XdriveRemoteFolder.NullIfEmpty(),
XdriveSecrets = XdriveSecrets.NullIfEmpty(),
XdriveS3Endpoint = XdriveS3Endpoint.NullIfEmpty(),
XdriveS3Region = XdriveS3Region.NullIfEmpty(),
XdriveS3Bucket = XdriveS3Bucket.NullIfEmpty(),
XdriveS3PathStyle = XdriveS3PathStyle,
XdriveExtra = XdriveExtra.NullIfEmpty(),
};
SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with
@@ -494,6 +610,44 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
: errorMessage;
}
private void ApplyXdriveProviderPreset(string provider)
{
switch (provider)
{
case "Cloudflare R2":
XdriveService = "s3";
if (XdriveS3Region.IsNullOrEmpty()) XdriveS3Region = "auto";
XdriveS3PathStyle = true;
break;
case "Google Drive":
XdriveService = "google-drive";
SelectedSource.StreamSecurity = Global.StreamSecurity;
if (SelectedSource.Sni.IsNullOrEmpty()) SelectedSource.Sni = "www.google.com";
if (SelectedSource.Alpn.IsNullOrEmpty()) SelectedSource.Alpn = "h2";
if (SelectedSource.Address.IsNullOrEmpty()) SelectedSource.Address = "www.google.com";
if (SelectedSource.Port <= 0) SelectedSource.Port = 443;
break;
case "AWS S3 (Global)":
XdriveService = "s3";
if (XdriveS3Region.IsNullOrEmpty()) XdriveS3Region = "ap-northeast-1";
XdriveS3PathStyle = false;
break;
case "Yandex Cloud S3":
XdriveService = "s3";
if (XdriveS3Region.IsNullOrEmpty()) XdriveS3Region = "ru-central1";
XdriveS3PathStyle = true;
break;
case "ArvanCloud S3":
XdriveService = "s3";
if (XdriveS3Region.IsNullOrEmpty()) XdriveS3Region = "ir-tbz-sh1";
XdriveS3PathStyle = true;
break;
case "Custom S3":
XdriveService = "s3";
break;
}
}
private void UpdateCertSha()
{
if (Cert.IsNullOrEmpty())
@@ -582,6 +736,7 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
nameof(ETransport.httpupgrade) => Host,
nameof(ETransport.xhttp) => Host,
nameof(ETransport.grpc) => GrpcAuthority,
nameof(ETransport.xdrive) => XdriveS3Endpoint,
_ => string.Empty,
};
}
@@ -1050,6 +1050,125 @@
</Expander>
</Grid>
<Grid
x:Name="gridTransportXdrive"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveProvider}" />
<ComboBox
x:Name="cmbProviderXdrive"
Grid.Row="0"
Grid.Column="1"
Width="250"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveService}" />
<ComboBox
x:Name="cmbServiceXdrive"
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveRemoteFolder}" />
<TextBox
x:Name="txtRemoteFolderXdrive"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveSecrets}" />
<TextBox
x:Name="txtSecretsXdrive"
Grid.Row="3"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveS3Endpoint}" />
<TextBox
x:Name="txtEndpointXdrive"
Grid.Row="4"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveS3Bucket}" />
<TextBox
x:Name="txtBucketXdrive"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveS3Region}" />
<TextBox
x:Name="txtRegionXdrive"
Grid.Row="6"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.XdriveS3PathStyle}" />
<ToggleSwitch
x:Name="togPathStyleXdrive"
Grid.Row="7"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Top"
Text="{x:Static resx:ResUI.XdriveExtra}"
TextWrapping="Wrap" />
<Expander
Grid.Row="8"
Grid.Column="1"
IsExpanded="False">
<Expander.Header>
<TextBlock FontWeight="Normal" Text="{x:Static resx:ResUI.XdriveExtraTip}" />
</Expander.Header>
<views:JsonEditor x:Name="txtExtraXdrive" HorizontalAlignment="Stretch" />
</Expander>
</Grid>
<Grid
x:Name="gridTransportKcp"
ColumnDefinitions="300,Auto"
@@ -179,6 +179,19 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.GrpcAuthority, v => v.txtRequestHostGrpc.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.GrpcServiceName, v => v.txtPathGrpc.Text).DisposeWith(disposables);
cmbProviderXdrive.ItemsSource = Global.XdriveProviders;
this.Bind(ViewModel, vm => vm.XdriveProvider, v => v.cmbProviderXdrive.SelectedValue).DisposeWith(disposables);
cmbServiceXdrive.ItemsSource = Global.XdriveServices;
this.Bind(ViewModel, vm => vm.XdriveService, v => v.cmbServiceXdrive.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveRemoteFolder, v => v.txtRemoteFolderXdrive.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveSecrets, v => v.txtSecretsXdrive.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveS3Endpoint, v => v.txtEndpointXdrive.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveS3Bucket, v => v.txtBucketXdrive.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveS3Region, v => v.txtRegionXdrive.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveS3PathStyle, v => v.togPathStyleXdrive.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.XdriveExtra, v => v.txtExtraXdrive.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.StreamSecurity, v => v.cmbStreamSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AllowInsecure, v => v.togAllowInsecure.IsChecked).DisposeWith(disposables);
@@ -359,6 +372,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTransportHttpupgrade.IsVisible = false;
gridTransportXhttp.IsVisible = false;
gridTransportGrpc.IsVisible = false;
gridTransportXdrive.IsVisible = false;
switch (network)
{
@@ -386,6 +400,10 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTransportGrpc.IsVisible = true;
break;
case nameof(ETransport.xdrive):
gridTransportXdrive.IsVisible = true;
break;
default:
gridTransportRaw.IsVisible = true;
break;