1
This commit is contained in:
@@ -7,6 +7,7 @@ using Cysharp.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
// ReSharper disable StringLiteralTypo
|
||||
#if NET5_0_OR_GREATER
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
#endif
|
||||
@@ -19,6 +20,10 @@ namespace BITKit
|
||||
}
|
||||
public class BITApp
|
||||
{
|
||||
public static async UniTask SwitchToMainThread()
|
||||
{
|
||||
await UniTask.SwitchToSynchronizationContext(SynchronizationContext);
|
||||
}
|
||||
public static class Time
|
||||
{
|
||||
public static float DeltaTime { get; internal set; }
|
||||
@@ -59,7 +64,89 @@ namespace BITKit
|
||||
"TextCopy",
|
||||
"Blazored",
|
||||
"TextCopy",
|
||||
"mattatz"
|
||||
"mattatz",
|
||||
"TrailsFX",
|
||||
"Knife",
|
||||
"Needle",
|
||||
"NiceIO",
|
||||
"AndroidPlayerBuildProgram",
|
||||
"DocCodeExamples",
|
||||
"System",
|
||||
"UnityEngine",
|
||||
"Unity",
|
||||
"Microsoft",
|
||||
"UnityEditor",
|
||||
"Google",
|
||||
"Mono",
|
||||
"ZXing",
|
||||
"ImmersiveVRTools",
|
||||
"MonKey",
|
||||
"FLib",
|
||||
"Kcp",
|
||||
"Udx",
|
||||
"Sirenix",
|
||||
"TMPro",
|
||||
"RotaryHeart",
|
||||
"Cinemachine",
|
||||
"ParadoxNotion",
|
||||
"Net",
|
||||
"VSCodeEditor",
|
||||
"AOT",
|
||||
"UnityEditorInternal",
|
||||
"UnityEngineInternal",
|
||||
"JetBrains",
|
||||
"Bee",
|
||||
"NotInvited",
|
||||
"HighlightPlus",
|
||||
"DG",
|
||||
"Hierarchy2",
|
||||
"Cysharp",
|
||||
"JetBrains",
|
||||
"Packages",
|
||||
"Newtonsoft_X",
|
||||
"Binding",
|
||||
"NodeCanvas",
|
||||
"SaveDuringPlay",
|
||||
"LimWorks",
|
||||
"MagicaCloth2",
|
||||
"FastScriptReload",
|
||||
"ParrelSync",
|
||||
"KinematicCharacterController",
|
||||
"LimWorksEditor",
|
||||
"BuildComponent",
|
||||
"dnlib",
|
||||
"BigIntegerLibrary",
|
||||
"Ionic",
|
||||
"log4net",
|
||||
"ImmersiveVrToolsCommon",
|
||||
"NUnit",
|
||||
"HarmonyLib",
|
||||
"MonoMod",
|
||||
"WebDav",
|
||||
"PlasticGui",
|
||||
"Codice",
|
||||
"GluonGui",
|
||||
"PlasticPipe",
|
||||
"XDiffGui",
|
||||
"MacFsWatcher",
|
||||
"MacUI",
|
||||
"PlayerBuildProgramLibrary",
|
||||
"ExCSS",
|
||||
"ScriptCompilationBuildProgram",
|
||||
"BeeBuildProgramCommon",
|
||||
"Accessibility",
|
||||
"CodiceApp",
|
||||
"Newtonsoft",
|
||||
"MergetoolGui",
|
||||
"TreeEditor",
|
||||
"MackySoft",
|
||||
"FullscreenEditor",
|
||||
"mattatz",
|
||||
"AYellowpaper",
|
||||
"kcp2k",
|
||||
"MeshCombineStudio",
|
||||
"AmazingAssets",
|
||||
"Utilities"
|
||||
};
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
@@ -144,7 +231,8 @@ namespace BITKit
|
||||
public static InitializationState State;
|
||||
public static Assembly[] Assemblies;
|
||||
public static AppSettings Settings { get; protected set; }
|
||||
public static async void Start(string appName = nameof(BITApp),AppSettings settings=default)
|
||||
private static DateTime InitialTime { get; set; }=DateTime.Now;
|
||||
public static async UniTask Start(string appName = nameof(BITApp),AppSettings settings=default)
|
||||
{
|
||||
Time.TimeAsDouble = 0;
|
||||
Time.DeltaTime = 1 / 60f;
|
||||
@@ -153,7 +241,7 @@ namespace BITKit
|
||||
CancellationTokenSource = new CancellationTokenSource();
|
||||
AppName = appName;
|
||||
ThreadHelper.LogCurrentThread();
|
||||
|
||||
InitialTime = DateTime.Now;
|
||||
await Init();
|
||||
}
|
||||
private static async Task Init()
|
||||
@@ -182,10 +270,16 @@ namespace BITKit
|
||||
reflectionHelperWatch.Start();
|
||||
await ReflectionHelper.Init();
|
||||
reflectionHelperWatch.Stop();
|
||||
|
||||
Stopwatch commandWatch = new();
|
||||
await BITCommands.InitializeAsync();
|
||||
commandWatch.Stop();
|
||||
|
||||
|
||||
stopwatch.Stop();
|
||||
State = InitializationState.Initialized;
|
||||
BIT4Log.Log<BITApp>($"已完成初始化,耗时:{stopwatch.ElapsedMilliseconds}ms");
|
||||
BIT4Log.Log<BITApp>($"反射初始化耗时:{reflectionHelperWatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
@@ -196,11 +290,18 @@ namespace BITKit
|
||||
}
|
||||
public static void Stop()
|
||||
{
|
||||
var runTime = DateTime.Now - InitialTime;
|
||||
|
||||
BIT4Log.Log<BITApp>($"正在停止{nameof(BITApp)}");
|
||||
CancellationTokenSource.Cancel();
|
||||
State = InitializationState.None;
|
||||
|
||||
BITCommands.Dispose();
|
||||
|
||||
BIT4Log.Log<BITApp>($"已停止{nameof(BITApp)}");
|
||||
BIT4Log.Log<BITApp>($"运行时间:{runTime}");
|
||||
BIT4Log.Log<BITApp>("Exit Code:0");
|
||||
|
||||
}
|
||||
public static void Run(string path, string WorkingDirectory = "")
|
||||
{
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#if NETCOREAPP
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
namespace BITKit;
|
||||
|
||||
public class BITAppForNet
|
||||
@@ -18,7 +18,7 @@ public class BITAppForNet
|
||||
BIT4Log.OnSetConsoleColor += color => Console.ForegroundColor = color;
|
||||
BIT4Log.OnNextLine += Console.WriteLine;
|
||||
|
||||
BITApp.Start(name);
|
||||
await BITApp.Start(name);
|
||||
await BITBinary.Start();
|
||||
}
|
||||
public static UniTask DisposeAsync()
|
||||
|
@@ -9,4 +9,14 @@ namespace BITKit
|
||||
{
|
||||
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
|
||||
public class CustomTypeAttribute : System.Attribute
|
||||
{
|
||||
public readonly Type Type;
|
||||
public CustomTypeAttribute(Type type)
|
||||
{
|
||||
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
87
Assets/BITKit/Core/Auth/Core/IAuthService.cs
Normal file
87
Assets/BITKit/Core/Auth/Core/IAuthService.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BITKit.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// 授权服务
|
||||
/// </summary>
|
||||
public interface IAuthService
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否已授权
|
||||
/// </summary>
|
||||
bool IsAuthorized { get; }
|
||||
/// <summary>
|
||||
/// 是否正在授权
|
||||
/// </summary>
|
||||
bool IsAuthorizing { get; }
|
||||
/// <summary>
|
||||
/// 异步开始授权
|
||||
/// </summary>
|
||||
/// <param name="token">令牌</param>
|
||||
UniTask AuthorizeAsync(string token);
|
||||
/// <summary>
|
||||
/// 异步取消授权
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
UniTask CancelAuthorizationAsync();
|
||||
/// <summary>
|
||||
/// 开始授权的回调
|
||||
/// </summary>
|
||||
event Action<string> OnAuthorize;
|
||||
/// <summary>
|
||||
/// 已授权的回调
|
||||
/// </summary>
|
||||
event Action<string> OnAuthorized;
|
||||
/// <summary>
|
||||
/// 取消授权的回调
|
||||
/// </summary>
|
||||
event Action<string> UnAuthorize;
|
||||
/// <summary>
|
||||
/// 授权失败的回调
|
||||
/// </summary>
|
||||
event Action<string> OnAuthorizeFailure;
|
||||
}
|
||||
public abstract class AuthServiceImplement:IAuthService
|
||||
{
|
||||
protected abstract IAuthService service { get; }
|
||||
bool IAuthService.IsAuthorized => service.IsAuthorized;
|
||||
|
||||
bool IAuthService.IsAuthorizing => service.IsAuthorizing;
|
||||
|
||||
UniTask IAuthService.AuthorizeAsync(string token)
|
||||
{
|
||||
return service.AuthorizeAsync(token);
|
||||
}
|
||||
|
||||
UniTask IAuthService.CancelAuthorizationAsync()
|
||||
{
|
||||
return service.CancelAuthorizationAsync();
|
||||
}
|
||||
|
||||
event Action<string> IAuthService.OnAuthorize
|
||||
{
|
||||
add => service.OnAuthorize += value;
|
||||
remove => service.OnAuthorize -= value;
|
||||
}
|
||||
|
||||
event Action<string> IAuthService.OnAuthorized
|
||||
{
|
||||
add => service.OnAuthorized += value;
|
||||
remove => service.OnAuthorized -= value;
|
||||
}
|
||||
|
||||
event Action<string> IAuthService.UnAuthorize
|
||||
{
|
||||
add => service.UnAuthorize += value;
|
||||
remove => service.UnAuthorize -= value;
|
||||
}
|
||||
|
||||
event Action<string> IAuthService.OnAuthorizeFailure
|
||||
{
|
||||
add => service.OnAuthorizeFailure += value;
|
||||
remove => service.OnAuthorizeFailure -= value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
#if UNITY
|
||||
#if NET5_0_OR_GREATER
|
||||
#else
|
||||
using UnityEngine;
|
||||
#endif
|
||||
using System;
|
||||
@@ -6,7 +7,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
#if NET5_0_OR_GREATER
|
||||
using Microsoft.SqlServer.Server;
|
||||
#endif
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BITKit
|
||||
@@ -14,27 +17,37 @@ namespace BITKit
|
||||
public class BITBinary
|
||||
{
|
||||
private static readonly Dictionary<string, INetMessageReader> netReaders = new();
|
||||
private static readonly List<Type> serializableTypes = new();
|
||||
#if NET5_0_OR_GREATER
|
||||
public static readonly List<Type> serializableTypes = new();
|
||||
#endif
|
||||
|
||||
public static async UniTask Start()
|
||||
{
|
||||
netReaders.Clear();
|
||||
#if NET5_0_OR_GREATER
|
||||
serializableTypes.Clear();
|
||||
#endif
|
||||
foreach (var x in await ReflectionHelper.GetInstances<INetMessageReader>())
|
||||
{
|
||||
var typeName = x.GetMessageType().FullName;
|
||||
if (typeName == null) continue;
|
||||
netReaders.Add(typeName, x);
|
||||
BIT4Log.Log<BITBinary>($"已注册类型:{typeName}");
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
var serializes = await ReflectionHelper.GetInstances<IBinarySerialize>();
|
||||
#if UNITY
|
||||
serializes = serializes.Where(x => x is not UnityEngine.Object);
|
||||
#else
|
||||
|
||||
#endif
|
||||
foreach (var x in serializes)
|
||||
{
|
||||
serializableTypes.Add(x.GetType());
|
||||
BIT4Log.Log<BITBinary>($"已注册类型:{x.GetType().FullName}");
|
||||
}
|
||||
// #if NET5_0_OR_GREATER
|
||||
// #else
|
||||
// serializes = serializes.Where(x => x is not UnityEngine.Object);
|
||||
// #endif
|
||||
// foreach (var x in serializes)
|
||||
// {
|
||||
// serializableTypes.Add(x.GetType());
|
||||
// BIT4Log.Log<BITBinary>($"已注册类型:{x.GetType().FullName}");
|
||||
// }
|
||||
}
|
||||
|
||||
public static object Read<T>(byte[] buffer) => (T)ReadAsValue(buffer);
|
||||
@@ -60,18 +73,12 @@ namespace BITKit
|
||||
|
||||
var typeName = reader.ReadString();
|
||||
if (netReaders.TryGetValue(typeName, out var netReader))
|
||||
{
|
||||
return netReader.ReadBinaryAsObject(reader);
|
||||
}
|
||||
var json = reader.ReadString();
|
||||
json = reader.ReadString();
|
||||
try
|
||||
{
|
||||
|
||||
if (BITSharp.TryGetTypeFromFullName(typeName, out var type))
|
||||
{
|
||||
return JsonConvert.DeserializeObject(json, type);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -119,10 +126,12 @@ namespace BITKit
|
||||
{
|
||||
netReader.WriteBinaryAsObject(writer,value);
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
else if( value is IBinarySerialize serialize)
|
||||
{
|
||||
serialize.Write(writer);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
//throw new Exception($"没有找到{value.GetType().Name}的Binary写入方法");
|
||||
@@ -139,10 +148,12 @@ namespace BITKit
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (serializableTypes.Any(x => x.FullName == typeName))
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (serializableTypes.Any(x => x.FullName == typeName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -54,6 +54,19 @@ namespace BITKit
|
||||
}
|
||||
public static async void Excute(string cmd)
|
||||
{
|
||||
var cmdSplit = cmd.Split("|");
|
||||
if (cmdSplit.Length is 1 or 0)
|
||||
{
|
||||
cmdSplit = cmd.Split("\n");
|
||||
}
|
||||
if (cmdSplit.Length > 1)
|
||||
{
|
||||
foreach (var x in cmdSplit)
|
||||
{
|
||||
Excute(x);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await UniTask.SwitchToThreadPool();
|
||||
await TaskHelper.WaitUntil(() => state is InitializationState.Initialized);
|
||||
var split = cmd.Split(" ").ToList();
|
||||
@@ -87,27 +100,24 @@ namespace BITKit
|
||||
}
|
||||
static Dictionary<string, MethodInfo> methodInfos = new();
|
||||
static InitializationState state;
|
||||
[ExcuteOnStart]
|
||||
public static void Start()
|
||||
public static async UniTask InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Init();
|
||||
await Init();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
|
||||
}
|
||||
[ExcuteOnStop]
|
||||
public static void Stop()
|
||||
public static void Dispose()
|
||||
{
|
||||
state = 0;
|
||||
methodInfos.Clear();
|
||||
}
|
||||
static async void Init()
|
||||
private static async UniTask Init()
|
||||
{
|
||||
state = InitializationState.Initializing;
|
||||
await UniTask.SwitchToThreadPool();
|
||||
|
20
Assets/BITKit/Core/Crypto/Core/BITCrypto.cs
Normal file
20
Assets/BITKit/Core/Crypto/Core/BITCrypto.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
namespace BITKit.Crypto
|
||||
{
|
||||
public class BITCrypto:ICryptography
|
||||
{
|
||||
public string Salt { get; set; } = "2196F3";
|
||||
public string Hash(string password)
|
||||
{
|
||||
var data = System.Text.Encoding.UTF8.GetBytes(password + Salt);
|
||||
return Convert.ToBase64String(data);
|
||||
}
|
||||
|
||||
public bool Verify(string password, string hash)
|
||||
{
|
||||
return Hash(password) == hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
Assets/BITKit/Core/Crypto/Core/ICryptography.cs
Normal file
26
Assets/BITKit/Core/Crypto/Core/ICryptography.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BITKit.Crypto
|
||||
{
|
||||
/// <summary>
|
||||
/// 加密接口
|
||||
/// </summary>
|
||||
public interface ICryptography
|
||||
{
|
||||
/// <summary>
|
||||
/// 盐
|
||||
/// </summary>
|
||||
public string Salt { get; set; }
|
||||
/// <summary>
|
||||
/// 获取Hash
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public string Hash(string password);
|
||||
/// <summary>
|
||||
/// 验证密码是否有效
|
||||
/// </summary>
|
||||
/// <param name="password">明文密码</param>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public bool Verify(string password, string hash);
|
||||
}
|
||||
}
|
Binary file not shown.
@@ -3,7 +3,10 @@ using System.Collections.Generic;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 双缓冲区
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface IDoubleBuffer<T>
|
||||
{
|
||||
T Current { get; }
|
||||
@@ -11,7 +14,10 @@ namespace BITKit
|
||||
event Action<T> OnRelease;
|
||||
bool TryGetRelease(out T result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IDoubleBuffer{T}"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class DoubleBuffer<T> : IDoubleBuffer<T>
|
||||
{
|
||||
public T Current
|
||||
|
@@ -1,5 +1,9 @@
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.ComponentModel.Design;
|
||||
#if NET5_0_OR_GREATER
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
#endif
|
||||
namespace BITKit.Core.Entites
|
||||
{
|
||||
/// <summary>
|
||||
@@ -8,14 +12,11 @@ namespace BITKit.Core.Entites
|
||||
public interface IEntity
|
||||
{
|
||||
ulong Id { get; }
|
||||
#if NET5_0_OR_GREATER
|
||||
bool TryGetComponent<T>(out T component) where T : IEntityComponent;
|
||||
CancellationToken CancellationToken { get; }
|
||||
bool TryGetComponent<T>(out T component);
|
||||
IEntityComponent[] Components { get; }
|
||||
bool RegisterComponent<T>(T component) where T : IEntityComponent;
|
||||
#else
|
||||
|
||||
#endif
|
||||
|
||||
bool RegisterComponent<T>(T component);
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 基本实体组件
|
||||
@@ -24,6 +25,9 @@ namespace BITKit.Core.Entites
|
||||
{
|
||||
Type BaseType { get; }
|
||||
IEntity Entity { get; set; }
|
||||
#if NET5_0_OR_GREATER
|
||||
void BuildService(IServiceCollection serviceCollection);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 基本实体服务
|
||||
@@ -61,24 +65,28 @@ namespace BITKit.Core.Entites
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
IEntity Get(ulong id);
|
||||
|
||||
/// <summary>
|
||||
/// 查询Entity,例如
|
||||
/// </summary>
|
||||
/// <para>var rotationEntities=EntitiesService.Query<RotationComponent></para>
|
||||
IEntity[] Query<T>() where T : IEntityComponent;
|
||||
IEntity[] Query<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 查询1个组件
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
T[] QueryComponents<T>() where T : IEntityComponent;
|
||||
T[] QueryComponents<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 查询2个组件
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T1"></typeparam>
|
||||
/// <returns></returns>
|
||||
ValueTuple<T,T1>[] QueryComponents<T,T1>()where T : IEntityComponent where T1 : IEntityComponent;
|
||||
ValueTuple<T, T1>[] QueryComponents<T, T1>();
|
||||
|
||||
/// <summary>
|
||||
/// 查询3个组件
|
||||
/// </summary>
|
||||
@@ -86,7 +94,7 @@ namespace BITKit.Core.Entites
|
||||
/// <typeparam name="T1"></typeparam>
|
||||
/// <typeparam name="T2"></typeparam>
|
||||
/// <returns></returns>
|
||||
ValueTuple<T,T1,T2>[] QueryComponents<T,T1,T2>() where T : IEntityComponent where T1 : IEntityComponent where T2 : IEntityComponent;
|
||||
|
||||
ValueTuple<T, T1, T2>[] QueryComponents<T, T1, T2>();
|
||||
|
||||
}
|
||||
}
|
16
Assets/BITKit/Core/Extensions/Func.cs
Normal file
16
Assets/BITKit/Core/Extensions/Func.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
public static class FuncExtensions
|
||||
{
|
||||
public static IEnumerable<Func<T0,T1>> CastAsFunc<T0,T1>(this Func<T0,T1> self)
|
||||
{
|
||||
return self.GetInvocationList().Cast<Func<T0, T1>>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
19
Assets/BITKit/Core/Extensions/HttpClient.cs
Normal file
19
Assets/BITKit/Core/Extensions/HttpClient.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
public static class HttpClientExtensions
|
||||
{
|
||||
public static async UniTask<string> PostJsonAsync(this HttpClient self, string requestUrl, object value , CancellationToken cancellationToken=default)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(value);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
var response =await self.PostAsync(requestUrl, content, cancellationToken);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,6 +33,8 @@ namespace BITKit.Apps
|
||||
/// </summary>
|
||||
event Action<string> OnDownloadComplete;
|
||||
|
||||
event Action OnDetectedLatestVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 下载最新版本
|
||||
/// </summary>
|
||||
|
@@ -44,6 +44,19 @@ namespace BITKit
|
||||
return list;
|
||||
}
|
||||
|
||||
public static bool TryGetIndexOf<T>(this IEnumerable<T> self, Func<T, bool> factory, out int index)
|
||||
{
|
||||
index = -1;
|
||||
var enumerable = self as T[] ?? self.ToArray();
|
||||
for (var i = 0; i < enumerable.Length; i++)
|
||||
{
|
||||
var item = enumerable.ElementAt(i);
|
||||
if (!factory.Invoke(item)) continue;
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool Contains<T>(IEnumerable<T> a, IEnumerable<T> b)
|
||||
{
|
||||
foreach (var x in b)
|
||||
|
83
Assets/BITKit/Core/Models/ContextModel.cs
Normal file
83
Assets/BITKit/Core/Models/ContextModel.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
namespace BITKit
|
||||
// ReSharper restore CheckNamespace
|
||||
{
|
||||
public record ContextModel
|
||||
{
|
||||
public static ContextModel Get(object data)
|
||||
{
|
||||
return new ContextModel()
|
||||
{
|
||||
StatusCode = 200,
|
||||
Message = "success",
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
public static ContextModel Error(string message)
|
||||
{
|
||||
return new ContextModel()
|
||||
{
|
||||
StatusCode = 500,
|
||||
Message = message,
|
||||
Data = false
|
||||
};
|
||||
}
|
||||
public static ContextModel GetFromJson(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new ContextModel();
|
||||
var jObject = JObject.Parse(json);
|
||||
if(jObject.TryGetValue("status_code",out var statusCode))
|
||||
result.StatusCode = statusCode.Value<int>();
|
||||
if(jObject.TryGetValue("message",out var message))
|
||||
result.Message = message.Value<string>();
|
||||
if (jObject.TryGetValue("data", out var data))
|
||||
result.Data = data;
|
||||
return JsonConvert.DeserializeObject<ContextModel>(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.Warning(json);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public static implicit operator string(ContextModel self)
|
||||
{
|
||||
return self.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this);
|
||||
}
|
||||
|
||||
[JsonProperty("status_code")]
|
||||
public int StatusCode;
|
||||
[JsonProperty("message")]
|
||||
public string Message=string.Empty;
|
||||
[JsonProperty("data")]
|
||||
public object Data=string.Empty;
|
||||
[JsonIgnore]
|
||||
public bool IsSuccess => StatusCode is 200 or 0;
|
||||
public bool TryAs<T>(out T value)
|
||||
{
|
||||
switch (Data)
|
||||
{
|
||||
case T t:
|
||||
value = t;
|
||||
return true;
|
||||
case JToken jToken:
|
||||
value = jToken.ToObject<T>();
|
||||
return true;
|
||||
default:
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
// ReSharper disable CheckNamespace
|
||||
namespace BITKit
|
||||
// ReSharper restore CheckNamespace
|
||||
{
|
||||
public record ContextModel
|
||||
{
|
||||
public static ContextModel Get(object data)
|
||||
{
|
||||
return new ContextModel()
|
||||
{
|
||||
code = 200,
|
||||
message = "success",
|
||||
data = data
|
||||
};
|
||||
}
|
||||
public static ContextModel Error(object data)
|
||||
{
|
||||
return new ContextModel()
|
||||
{
|
||||
code = 500,
|
||||
message = "failed",
|
||||
data = data
|
||||
};
|
||||
}
|
||||
public static implicit operator string(ContextModel self)
|
||||
{
|
||||
return JsonConvert.SerializeObject(self);
|
||||
}
|
||||
public int code;
|
||||
public string message=string.Empty;
|
||||
public object data=string.Empty;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
#if UNITY
|
||||
#else
|
||||
#if NET5_0_OR_GREATER
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
#else
|
||||
#endif
|
||||
using System;
|
||||
|
||||
|
15
Assets/BITKit/Core/Net/Http/Core/IHttpListenerService.cs
Normal file
15
Assets/BITKit/Core/Net/Http/Core/IHttpListenerService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace BITKit.Net.Http
|
||||
{
|
||||
public interface IHttpListenerService
|
||||
{
|
||||
bool IsListening { get; }
|
||||
int Port { get; set; }
|
||||
public event Func<HttpListenerRequest,HttpContent> OnRequest;
|
||||
void Start();
|
||||
void Stop();
|
||||
}
|
||||
}
|
105
Assets/BITKit/Core/Net/Http/HttpListenerService.cs
Normal file
105
Assets/BITKit/Core/Net/Http/HttpListenerService.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
|
||||
namespace BITKit.Net.Http
|
||||
{
|
||||
public class HttpListenerService:IHttpListenerService
|
||||
{
|
||||
public bool IsListening=>_httpListener.IsListening;
|
||||
public int Port { get; set; }
|
||||
private readonly HttpListener _httpListener = new();
|
||||
public event Func<HttpListenerRequest,HttpContent> OnRequest;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private CancellationToken _cancellationToken=>_cancellationTokenSource.Token;
|
||||
|
||||
public HttpListenerService()
|
||||
{
|
||||
Port = 7001;
|
||||
}
|
||||
public HttpListenerService(int port)
|
||||
{
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (HttpListener.IsSupported is false)
|
||||
{
|
||||
throw new NotImplementedException("HttpListener is not supported this platform");
|
||||
}
|
||||
_httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
|
||||
//_httpListener.Prefixes.Add($"http://localhost:{Port}/");
|
||||
#if NET5_0_OR_GREATER
|
||||
_httpListener.Prefixes.Add($"http://localhost:{Port}/");
|
||||
#else
|
||||
_httpListener.Prefixes.Add($"http://*:{Port}/");
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
Thread thread = new(Process);
|
||||
thread.Start();
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
_httpListener.Stop();
|
||||
BIT4Log.Log<IHttpListenerService>("Is Trying To Stop");
|
||||
}
|
||||
private void Process()
|
||||
{
|
||||
_httpListener.Start();
|
||||
BIT4Log.Log<IHttpListenerService>($"{Port}\tIs Listening");
|
||||
while (_cancellationToken.IsCancellationRequested is false)
|
||||
{
|
||||
var result = _httpListener.BeginGetContext(ListenerCallback, _httpListener);
|
||||
result.AsyncWaitHandle.WaitOne();
|
||||
}
|
||||
BIT4Log.Log<IHttpListenerService>($"{Port}\tStopped");
|
||||
}
|
||||
|
||||
private void ListenerCallback(IAsyncResult result)
|
||||
{
|
||||
if (_cancellationToken.IsCancellationRequested) return;
|
||||
|
||||
var context = _httpListener.EndGetContext(result);
|
||||
var request = context.Request;
|
||||
|
||||
var response = context.Response;
|
||||
|
||||
|
||||
var output = response.OutputStream;
|
||||
|
||||
if (request.RawUrl is "/favicon.ico")
|
||||
{
|
||||
output.Close();
|
||||
response.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启用CORS
|
||||
response.Headers.Add("Access-Control-Allow-Origin", "*"); // 允许任何来源访问,生产环境应更具体设置
|
||||
response.Headers.Add("Access-Control-Allow-Methods", "*"); // 允许的HTTP方法
|
||||
response.Headers.Add("Access-Control-Allow-Headers", "*"); // 允许的标头
|
||||
|
||||
|
||||
var content = OnRequest?.Invoke(request);
|
||||
|
||||
var buffer = StringHelper.GetBytes(ContextModel.Error("没有注册请求事件"));
|
||||
if (content is not null)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
buffer = content!.ReadAsByteArrayAsync(_cancellationToken).Result;
|
||||
#else
|
||||
buffer = content!.ReadAsByteArrayAsync().Result;
|
||||
#endif
|
||||
}
|
||||
response.ContentLength64 = buffer.Length;
|
||||
output.Write(buffer);
|
||||
output.Close();
|
||||
response.Close();
|
||||
}
|
||||
}
|
||||
}
|
16
Assets/BITKit/Core/Net/LAN/Core/ILANBroadcaster.cs
Normal file
16
Assets/BITKit/Core/Net/LAN/Core/ILANBroadcaster.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace BITKit.Net.LAN
|
||||
{
|
||||
public interface ILANBroadcaster
|
||||
{
|
||||
int Port { get; set; }
|
||||
byte[] Buffer { get; set; }
|
||||
event Action<EndPoint, string> OnReceive;
|
||||
void StartBroadcast();
|
||||
void StopBroadcast();
|
||||
void StartListen();
|
||||
void StopListen();
|
||||
}
|
||||
}
|
108
Assets/BITKit/Core/Net/LAN/UdpBasedLanBroadcaster.cs
Normal file
108
Assets/BITKit/Core/Net/LAN/UdpBasedLanBroadcaster.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace BITKit.Net.LAN
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于UDP的LAN广播
|
||||
/// </summary>
|
||||
public class UdpBasedLanBroadcaster : ILANBroadcaster
|
||||
{
|
||||
public int Port { get; set; }
|
||||
public byte[] Buffer { get; set; } = StringHelper.GetBytes("Hello World");
|
||||
private bool _isBroadcasting;
|
||||
private bool _isListening;
|
||||
|
||||
public UdpBasedLanBroadcaster()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public event Action<EndPoint, string> OnReceive;
|
||||
|
||||
public void StartBroadcast()
|
||||
{
|
||||
_isBroadcasting = true;
|
||||
Thread thread = new(Process)
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
thread.Start();
|
||||
BIT4Log.Log<ILANBroadcaster>($"开始广播端口{Port}");
|
||||
}
|
||||
|
||||
public void StopBroadcast()
|
||||
{
|
||||
_isBroadcasting = false;
|
||||
BIT4Log.Log<ILANBroadcaster>($"停止广播端口 {Port}");
|
||||
}
|
||||
|
||||
public void StartListen()
|
||||
{
|
||||
_isListening = true;
|
||||
var thread = new Thread(ReceiveThread)
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
thread.Start();
|
||||
BIT4Log.Log<ILANBroadcaster>($"开始监听端口:{Port}");
|
||||
}
|
||||
|
||||
public void StopListen()
|
||||
{
|
||||
_isListening = false;
|
||||
BIT4Log.Log<ILANBroadcaster>($"停止监听端口{Port}");
|
||||
}
|
||||
|
||||
|
||||
private void Process()
|
||||
{
|
||||
var udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
|
||||
|
||||
var endpoint = new IPEndPoint(IPAddress.Broadcast, Port);
|
||||
//其实 IPAddress.Broadcast 就是 255.255.255.255
|
||||
//下面代码与上面有相同的作用
|
||||
//IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 8080);
|
||||
try
|
||||
{
|
||||
while (_isBroadcasting)
|
||||
{
|
||||
udpClient.Send(Buffer, Buffer.Length, endpoint);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
udpClient.Dispose();
|
||||
}
|
||||
|
||||
private void ReceiveThread()
|
||||
{
|
||||
try
|
||||
{
|
||||
var udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, Port));
|
||||
var endpoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
while (_isListening)
|
||||
{
|
||||
var buf = udpClient.Receive(ref endpoint);
|
||||
var msg = Encoding.Default.GetString(buf);
|
||||
if (OnReceive is not null)
|
||||
OnReceive(endpoint, msg);
|
||||
else
|
||||
BIT4Log.Log<ILANBroadcaster>($"Receive From {endpoint}:\t{msg}");
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,11 +7,11 @@
|
||||
}
|
||||
public interface IOptional<T>
|
||||
{
|
||||
bool Allow { get; }
|
||||
T Value { get; }
|
||||
bool Allow { get; set; }
|
||||
T Value { get; set; }
|
||||
}
|
||||
[System.Serializable]
|
||||
public record Optional<T> : IOptional<T>
|
||||
public class Optional<T> : IOptional<T>
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
bool allow;
|
||||
@@ -23,8 +23,18 @@
|
||||
T value;
|
||||
#endif
|
||||
public bool Allow { get => allow; set => allow = value;}
|
||||
public T Value { get => value; set => this.value = value; }
|
||||
public void SetValueThenAllow(T newValue) => value = newValue;
|
||||
public T Value
|
||||
{
|
||||
get => value;
|
||||
set=>this.value=value;
|
||||
}
|
||||
|
||||
public void SetValueThenAllow(T newValue)
|
||||
{
|
||||
value = newValue;
|
||||
allow = true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Allow = false;
|
||||
|
@@ -1,9 +1,12 @@
|
||||
namespace BITKit
|
||||
using System;
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅者,发布者下发任务给支持并优先级最高的订阅者
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[Obsolete]
|
||||
public interface TaskSubscriber<in T>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,6 +25,7 @@
|
||||
/// 发布接口,向发布者下发任务
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[Obsolete("Use Func Instanced",true)]
|
||||
public interface TaskPublisher<in T>
|
||||
{
|
||||
/// <summary>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
// ReSharper disable UnassignedGetOnlyAutoProperty
|
||||
|
||||
namespace BITKit.UX
|
||||
{
|
||||
@@ -44,7 +45,7 @@ namespace BITKit.UX
|
||||
}
|
||||
public abstract class UXPanelImplement:IUXPanel
|
||||
{
|
||||
private IUXPanel _iuxPanelImplementation1;
|
||||
protected virtual IUXPanel _iuxPanelImplementation1 { get; }
|
||||
protected abstract IUXPanel _iuxPanelImplementation { get; }
|
||||
public bool IsAnimate => _iuxPanelImplementation.IsAnimate;
|
||||
|
||||
|
@@ -11,6 +11,7 @@ namespace BITKit
|
||||
public const string Status = "Status";
|
||||
public const string State = "State";
|
||||
public const string Settings = "Settings";
|
||||
public const string Services = "Services";
|
||||
public const string AudioClip = "AudioClip";
|
||||
public const string AudioSource = "AudioSource";
|
||||
public const string Paths = "Paths";
|
||||
|
@@ -4,6 +4,7 @@
|
||||
{
|
||||
string Get();
|
||||
string Value => Get();
|
||||
string Replace(string value) => Get().Replace("{x}",value);
|
||||
}
|
||||
|
||||
public interface IReference<T>
|
||||
@@ -37,9 +38,12 @@
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEngine.TextArea]
|
||||
#endif
|
||||
public string value;
|
||||
public override string Get() => value;
|
||||
public override string ToString() => value;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
|
@@ -5,17 +5,12 @@ namespace BITKit
|
||||
{
|
||||
public class ValidHandle
|
||||
{
|
||||
public ValidHandle() { Init(); }
|
||||
public ValidHandle() {}
|
||||
public ValidHandle(Action<bool> boolDelegate)
|
||||
{
|
||||
AddListener(boolDelegate);
|
||||
Init();
|
||||
EventOnEnableChanged?.Invoke(enableHandle);
|
||||
}
|
||||
void Init()
|
||||
{
|
||||
AddListener(OnInoke);
|
||||
}
|
||||
public static implicit operator bool(ValidHandle validHandle)
|
||||
{
|
||||
return validHandle.enableHandle;
|
||||
@@ -24,8 +19,6 @@ namespace BITKit
|
||||
public bool Allow => this;
|
||||
|
||||
private bool enableHandle;
|
||||
private int enableElementCount;
|
||||
private int disableElementCount;
|
||||
private readonly List<object> objs = new List<object>();
|
||||
private readonly List<object> disableObjs = new List<object>();
|
||||
private bool tempEnable;
|
||||
@@ -43,7 +36,7 @@ namespace BITKit
|
||||
}
|
||||
CheckEnable();
|
||||
}
|
||||
protected virtual void CheckEnable()
|
||||
protected void CheckEnable()
|
||||
{
|
||||
tempEnable = objs.Count > 0 && disableObjs.Count == 0;
|
||||
if (tempEnable != enableHandle)
|
||||
@@ -51,13 +44,8 @@ namespace BITKit
|
||||
enableHandle = tempEnable;
|
||||
if (EventOnEnableChanged is not null)
|
||||
EventOnEnableChanged.Invoke(enableHandle);
|
||||
OnEnable(enableHandle);
|
||||
|
||||
}
|
||||
enableElementCount = objs.Count;
|
||||
disableElementCount = disableObjs.Count;
|
||||
}
|
||||
protected virtual void OnEnable(bool enable) { }
|
||||
public virtual void RemoveElement(object obj)
|
||||
{
|
||||
if (objs.Contains(obj))
|
||||
@@ -93,7 +81,7 @@ namespace BITKit
|
||||
}
|
||||
CheckEnable();
|
||||
}
|
||||
public virtual void RemoveDisableElements(object obj)
|
||||
public void RemoveDisableElements(object obj)
|
||||
{
|
||||
if (disableObjs.Contains(obj))
|
||||
{
|
||||
@@ -104,7 +92,7 @@ namespace BITKit
|
||||
}
|
||||
CheckEnable();
|
||||
}
|
||||
public virtual void SetElements(object obj, bool add = true)
|
||||
public void SetElements(object obj, bool add = true)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
@@ -126,29 +114,33 @@ namespace BITKit
|
||||
RemoveDisableElements(obj);
|
||||
}
|
||||
}
|
||||
public virtual void Invoke()
|
||||
public void Invoke()
|
||||
{
|
||||
bool enable = disableObjs.Count == 0 && objs.Count > 0;
|
||||
EventOnEnableChanged.Invoke(enable);
|
||||
var enable = disableObjs.Count == 0 && objs.Count > 0;
|
||||
EventOnEnableChanged?.Invoke(enable);
|
||||
}
|
||||
public virtual void Invoke(bool value)
|
||||
public void Invoke(bool value)
|
||||
{
|
||||
EventOnEnableChanged.Invoke(value);
|
||||
EventOnEnableChanged?.Invoke(value);
|
||||
}
|
||||
public virtual void OnInoke(bool value)
|
||||
{
|
||||
|
||||
}
|
||||
public virtual void AddListener(Action<bool> action)
|
||||
public void AddListener(Action<bool> action)
|
||||
{
|
||||
EventOnEnableChanged+= action;
|
||||
}
|
||||
public virtual void RemoveListener(Action<bool> action)
|
||||
public void RemoveListener(Action<bool> action)
|
||||
{
|
||||
if(EventOnEnableChanged is not null && action is not null)
|
||||
{
|
||||
EventOnEnableChanged -= action;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
objs.Clear();
|
||||
disableObjs.Clear();
|
||||
Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ namespace BITKit
|
||||
[BITCommand]
|
||||
public static void Set(string key, string value)
|
||||
{
|
||||
Data.Set(key, value);
|
||||
if (Guid.TryParse(value, out var guidResult))
|
||||
{
|
||||
Data.Set(key, guidResult);
|
||||
@@ -35,6 +34,10 @@ namespace BITKit
|
||||
{
|
||||
Data.Set(key, intResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Data.Set(key, value);
|
||||
}
|
||||
}
|
||||
[BITCommand]
|
||||
public static void SetContainer(string key, string typeName, string json)
|
||||
|
@@ -32,6 +32,12 @@ namespace BITKit
|
||||
|
||||
return GetPath(paths);
|
||||
}
|
||||
public static void EnsureDirectoryCreated(string path)
|
||||
{
|
||||
path = Path.GetDirectoryName(path);
|
||||
if (Directory.Exists(path) is true) return;
|
||||
if (path != null) Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public static string GetFilePath(params string[] paths)
|
||||
{
|
||||
|
Reference in New Issue
Block a user