Compare commits
28 Commits
2c790f9b29
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d55c1c533 | |||
| e4ee03364c | |||
| c77b3e0742 | |||
| e391f0238a | |||
| 823169ca91 | |||
| ee20c9c5ec | |||
| af658b1656 | |||
| 26c118195c | |||
| c461418185 | |||
| 29cde170c6 | |||
| 448161239e | |||
| a4c2ae3e28 | |||
| 1eb208cba3 | |||
| 2b5d6b6a54 | |||
| 386d71260c | |||
| dc35725b64 | |||
| 7d814ee4cb | |||
| 9082d7a4d0 | |||
| f9e4e533bb | |||
| 4da75bfa8a | |||
| cfa26ce807 | |||
| f2cdfc86b7 | |||
| fa9c5ac689 | |||
| 613b11e88c | |||
| 052169df1c | |||
| 826c11aa55 | |||
| 7fccb3810f | |||
| 4c08ad0064 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ nuget/
|
||||
.idea/
|
||||
.editorconfig
|
||||
*.user
|
||||
*.DotSettings
|
||||
|
||||
#backups
|
||||
.old*/
|
||||
|
||||
Submodule DTLib.Demystifier updated: b19c39b684...4eaade6e92
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!--package info-->
|
||||
<PackageId>DTLib.Logging.Microsoft</PackageId>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.1.3</Version>
|
||||
<Authors>Timerix</Authors>
|
||||
<Description>DTLib logger wrapper with dependency injection</Description>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
@@ -11,16 +11,16 @@
|
||||
<Configuration>Release</Configuration>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<!--compilation properties-->
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<!--language features-->
|
||||
<LangVersion>12</LangVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--external dependencies-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--DTLib dependencies-->
|
||||
@@ -28,6 +28,6 @@
|
||||
<ProjectReference Include="..\DTLib\DTLib.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<PackageReference Include="DTLib" Version="1.4.*" />
|
||||
<PackageReference Include="DTLib" Version="1.7.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
30
DTLib.Web/DTLib.Web.csproj
Normal file
30
DTLib.Web/DTLib.Web.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!--package info-->
|
||||
<PackageId>DTLib.Web</PackageId>
|
||||
<Version>1.4.0</Version>
|
||||
<Authors>Timerix</Authors>
|
||||
<Description>HTTP Server with simple routing</Description>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
<RepositoryUrl>https://timerix.ddns.net:3322/Timerix/DTLib</RepositoryUrl>
|
||||
<PackageProjectUrl>https://timerix.ddns.net:3322/Timerix/DTLib</PackageProjectUrl>
|
||||
<Configuration>Release</Configuration>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<!--compilation properties-->
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<!--language features-->
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--DTLib dependencies-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<ProjectReference Include="..\DTLib\DTLib.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<PackageReference Include="DTLib" Version="1.7.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
18
DTLib.Web/HttpMethod.cs
Normal file
18
DTLib.Web/HttpMethod.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace DTLib.Web;
|
||||
|
||||
/// <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods"/>
|
||||
[Flags]
|
||||
public enum HttpMethod : ushort
|
||||
{
|
||||
NONE = 0,
|
||||
GET = 1,
|
||||
POST = 2,
|
||||
PUT = 4,
|
||||
DELETE = 8,
|
||||
PATCH = 16,
|
||||
HEAD = 32,
|
||||
OPTIONS = 64,
|
||||
TRACE = 128,
|
||||
CONNECT = 256,
|
||||
ANY = 65535
|
||||
}
|
||||
9
DTLib.Web/Routes/DelegateRouteHandler.cs
Normal file
9
DTLib.Web/Routes/DelegateRouteHandler.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public class DelegateRouteHandler(
|
||||
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> routeHandler
|
||||
) : IRouteHandler
|
||||
{
|
||||
public Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
=> routeHandler(ctx, requestLogger);
|
||||
}
|
||||
6
DTLib.Web/Routes/IRouteHandler.cs
Normal file
6
DTLib.Web/Routes/IRouteHandler.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public interface IRouteHandler
|
||||
{
|
||||
Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger);
|
||||
}
|
||||
6
DTLib.Web/Routes/IRouter.cs
Normal file
6
DTLib.Web/Routes/IRouter.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public interface IRouter
|
||||
{
|
||||
Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger);
|
||||
}
|
||||
40
DTLib.Web/Routes/ServeFilesRouteHandler.cs
Normal file
40
DTLib.Web/Routes/ServeFilesRouteHandler.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : IRouteHandler
|
||||
{
|
||||
public async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
{
|
||||
if (ctx.Request.HttpMethod != "GET")
|
||||
return HttpStatusCode.BadRequest;
|
||||
|
||||
string requestPath = ctx.Request.Url?.AbsolutePath!;
|
||||
if (string.IsNullOrEmpty(requestPath) || requestPath == "/")
|
||||
{
|
||||
requestPath = _homePageUrl;
|
||||
}
|
||||
|
||||
string ext = Path.Extension(requestPath).Str;
|
||||
IOPath filePath = Path.Concat(_publicDir, requestPath);
|
||||
if (!File.Exists(filePath))
|
||||
return HttpStatusCode.NotFound;
|
||||
|
||||
List<(string key, string val)> headers = ext switch
|
||||
{
|
||||
"html" => [("Content-Type", "text/html")],
|
||||
"css" => [("Content-Type", "text/css")],
|
||||
"js" or "jsx" or "ts" or "tsx" or "map" => [("Content-Type", "text/javascript")],
|
||||
_ =>
|
||||
[
|
||||
("Content-Type", "binary/octet-stream"),
|
||||
("Content-Disposition", $"attachment filename={filePath.LastName()}")
|
||||
]
|
||||
};
|
||||
foreach (var header in headers)
|
||||
ctx.Response.Headers.Set(header.key, header.val);
|
||||
|
||||
var fileStream = File.OpenRead(filePath);
|
||||
ctx.Response.ContentLength64 = fileStream.Length;
|
||||
await fileStream.CopyToAsync(ctx.Response.OutputStream);
|
||||
return HttpStatusCode.OK;
|
||||
}
|
||||
}
|
||||
65
DTLib.Web/Routes/SimpleRouter.cs
Normal file
65
DTLib.Web/Routes/SimpleRouter.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace DTLib.Web.Routes;
|
||||
|
||||
public class SimpleRouter : IRouter
|
||||
{
|
||||
/// route for any url that doesn't have its own handler
|
||||
public record RouteWithMethod(HttpMethod method, IRouteHandler routeHandler)
|
||||
{
|
||||
public bool CheckMethod(HttpMethod requestMethod) => (requestMethod & method) != 0;
|
||||
|
||||
public bool CheckMethod(string requestMethodStr)
|
||||
=> Enum.TryParse<HttpMethod>(requestMethodStr, out var requestMethod)
|
||||
&& CheckMethod(requestMethod);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, RouteWithMethod> _routes = new();
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RouteWithMethod? DefaultRoute { get; set; }
|
||||
|
||||
public SimpleRouter(ILogger logger)
|
||||
{
|
||||
_logger = new ContextLogger(nameof(SimpleRouter), logger);
|
||||
}
|
||||
|
||||
public void MapRoute(string url, HttpMethod method, IRouteHandler route)
|
||||
=> _routes.Add(url, new RouteWithMethod(method, route));
|
||||
|
||||
public void MapRoute(string url, HttpMethod method,
|
||||
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> route)
|
||||
=> MapRoute(url, method, new DelegateRouteHandler(route));
|
||||
|
||||
public async Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
{
|
||||
HttpStatusCode status = HttpStatusCode.InternalServerError;
|
||||
try
|
||||
{
|
||||
string? requestPath = ctx.Request.Url?.AbsolutePath;
|
||||
if (string.IsNullOrEmpty(requestPath))
|
||||
requestPath = "/";
|
||||
|
||||
if(!_routes.TryGetValue(requestPath!, out var routeWithMethod))
|
||||
routeWithMethod = DefaultRoute;
|
||||
|
||||
if (routeWithMethod is null)
|
||||
{
|
||||
_logger.LogWarn(nameof(SimpleRouter),
|
||||
$"couldn't resolve request path {ctx.Request.HttpMethod} {requestPath}");
|
||||
status = HttpStatusCode.NotFound;
|
||||
}
|
||||
else if (!routeWithMethod.CheckMethod(ctx.Request.HttpMethod))
|
||||
{
|
||||
_logger.LogWarn(nameof(SimpleRouter),
|
||||
$"received request with invalid method {ctx.Request.HttpMethod} {requestPath}");
|
||||
status = HttpStatusCode.MethodNotAllowed;
|
||||
}
|
||||
else status = await routeWithMethod.routeHandler.HandleRequest(ctx, requestLogger);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctx.Response.StatusCode = (int)status;
|
||||
await ctx.Response.OutputStream.FlushAsync();
|
||||
ctx.Response.OutputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
DTLib.Web/WebApp.cs
Normal file
73
DTLib.Web/WebApp.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Text;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using DTLib.Filesystem;
|
||||
global using DTLib.Logging;
|
||||
global using System.Net;
|
||||
using System.Diagnostics;
|
||||
using DTLib.Extensions;
|
||||
using DTLib.Web.Routes;
|
||||
|
||||
namespace DTLib.Web;
|
||||
|
||||
public class WebApp
|
||||
{
|
||||
private readonly string _baseUrl;
|
||||
private readonly ContextLogger _logger;
|
||||
private readonly IRouter _router;
|
||||
private readonly CancellationToken _stopToken;
|
||||
|
||||
public WebApp(string baseUrl, ILogger logger, IRouter router, CancellationToken stopToken = default)
|
||||
{
|
||||
_logger = new ContextLogger(nameof(WebApp), logger);
|
||||
_baseUrl = baseUrl;
|
||||
_stopToken = stopToken;
|
||||
_router = router;
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
_logger.LogInfo($"starting server at '{_baseUrl}'...");
|
||||
HttpListener server = new HttpListener();
|
||||
server.Prefixes.Add(_baseUrl);
|
||||
server.Start();
|
||||
_logger.LogInfo("server started");
|
||||
long requestId = 1;
|
||||
|
||||
try
|
||||
{
|
||||
while (!_stopToken.IsCancellationRequested)
|
||||
{
|
||||
var ctx = await server.GetContextAsync().AsCancellable(_stopToken);
|
||||
HandleRequestAsync(ctx, new ContextLogger($"Request-{requestId++}", _logger));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{}
|
||||
|
||||
server.Stop();
|
||||
_logger.LogInfo("server stopped");
|
||||
}
|
||||
|
||||
private async void HandleRequestAsync(HttpListenerContext ctx, ContextLogger requestLogger)
|
||||
{
|
||||
try
|
||||
{
|
||||
requestLogger.LogInfo($"{ctx.Request.HttpMethod} {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint}...");
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
await _router.Resolve(ctx, requestLogger);
|
||||
stopwatch.Stop();
|
||||
requestLogger.LogInfo($"responded {ctx.Response.StatusCode}" +
|
||||
$" ({(HttpStatusCode)ctx.Response.StatusCode})" +
|
||||
$" in {stopwatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
requestLogger.LogWarn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Submodule DTLib.XXHash updated: 895d53d362...3e1a2c00e6
@@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Logging.Microsoft", "
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.XXHash", "DTLib.XXHash\DTLib.XXHash\DTLib.XXHash.csproj", "{C7029741-816D-41B2-A2C4-E20565B1739D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Web", "DTLib.Web\DTLib.Web.csproj", "{9A3220EB-CCED-4172-9BD4-C3700FF36539}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -44,6 +46,10 @@ Global
|
||||
{C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,86 +1,76 @@
|
||||
namespace DTLib.Console;
|
||||
|
||||
//
|
||||
// вывод и ввод цветного текста в консоли
|
||||
// работает медленнее чем хотелось бы
|
||||
//
|
||||
public static class ColoredConsole
|
||||
{
|
||||
// парсит название цвета в ConsoleColor
|
||||
public static ConsoleColor ParseColor(string color) => color switch
|
||||
{
|
||||
//case "magneta":
|
||||
"m" => ConsoleColor.Magenta,
|
||||
//case "green":
|
||||
"g" => ConsoleColor.Green,
|
||||
//case "red":
|
||||
"r" => ConsoleColor.Red,
|
||||
//case "yellow":
|
||||
"y" => ConsoleColor.Yellow,
|
||||
//case "white":
|
||||
"w" => ConsoleColor.White,
|
||||
//case "blue":
|
||||
"b" => ConsoleColor.Blue,
|
||||
//case "cyan":
|
||||
"c" => ConsoleColor.Cyan,
|
||||
//case "h":
|
||||
"h" or "gray" => ConsoleColor.Gray,
|
||||
//case "black":
|
||||
"black" => ConsoleColor.Black,
|
||||
_ => throw new Exception($"ColoredConsole.ParseColor({color}) error: incorrect color"),
|
||||
};
|
||||
public static int Width => System.Console.WindowWidth;
|
||||
public static int Height => System.Console.WindowHeight;
|
||||
|
||||
public static void Write(ConsoleColor color,string msg)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Fg(ConsoleColor color) => System.Console.ForegroundColor = color;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Bg(ConsoleColor color) => System.Console.BackgroundColor = color;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResetColor() => System.Console.ResetColor();
|
||||
|
||||
/// <summary>
|
||||
/// Clears console buffer and resets color
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Clear()
|
||||
{
|
||||
System.Console.ForegroundColor = color;
|
||||
ResetColor();
|
||||
System.Console.Clear();
|
||||
}
|
||||
|
||||
public static void Write(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
if(fg != null) Fg(fg.Value);
|
||||
if(bg != null) Bg(bg.Value);
|
||||
System.Console.Write(msg);
|
||||
System.Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
public static void Write(string msg) => Write(ConsoleColor.Gray, msg);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteLine() => System.Console.Write('\n');
|
||||
|
||||
// вывод цветного текста
|
||||
public static void Write(params string[] input)
|
||||
public static void WriteLine(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
if (input.Length == 1)
|
||||
{
|
||||
Write(input[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.Length % 2 != 0)
|
||||
throw new Exception("ColoredConsole.Write() error: every text string must have color string before");
|
||||
|
||||
for (ushort i = 0; i < input.Length; i++)
|
||||
{
|
||||
System.Console.ForegroundColor = ParseColor(input[i++]);
|
||||
System.Console.Write(input[i]);
|
||||
}
|
||||
System.Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
public static void WriteLine() => System.Console.WriteLine();
|
||||
public static void WriteLine(ConsoleColor color,string msg)
|
||||
{
|
||||
System.Console.ForegroundColor = color;
|
||||
System.Console.WriteLine(msg);
|
||||
System.Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
public static void WriteLine(params string[] input)
|
||||
{
|
||||
Write(input);
|
||||
Write(msg, fg, bg);
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
// ввод цветного текста
|
||||
public static string? Read(ConsoleColor color)
|
||||
public static string ReadLine(ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
System.Console.ForegroundColor = color;
|
||||
var r = System.Console.ReadLine();
|
||||
System.Console.ForegroundColor = ConsoleColor.Gray;
|
||||
return r;
|
||||
if(fg != null) Fg(fg.Value);
|
||||
if(bg != null) Bg(bg.Value);
|
||||
return System.Console.ReadLine() ?? string.Empty;
|
||||
}
|
||||
|
||||
public static string? Read(string color) => Read(ParseColor(color));
|
||||
public static void WriteHLine(char c, ConsoleColor? fg = null, ConsoleColor? bg = null) =>
|
||||
WriteLine(c.Multiply(Width - 1), fg, bg);
|
||||
|
||||
public static void WriteTitle(string title, char spacing = '-',
|
||||
string left_framing = "[", string right_framing = "]",
|
||||
ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
{
|
||||
int both_spacing_length = Width - title.Length - left_framing.Length - right_framing.Length - 1;
|
||||
int left_spacing_length = both_spacing_length / 2;
|
||||
int right_spacing_length = left_spacing_length + both_spacing_length % 2;
|
||||
|
||||
var b = new StringBuilder();
|
||||
if(left_spacing_length > 0)
|
||||
b.Append(spacing.Multiply(left_spacing_length));
|
||||
b.Append(left_framing);
|
||||
b.Append(title);
|
||||
b.Append(right_framing);
|
||||
if(right_spacing_length > 0)
|
||||
b.Append(spacing.Multiply(right_spacing_length));
|
||||
|
||||
WriteLine(b.ToString(), fg, bg);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteCentred(string title, ConsoleColor? fg = null, ConsoleColor? bg = null)
|
||||
=> WriteTitle(title, ' ', "", "", fg, bg);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,96 @@
|
||||
namespace DTLib.Console;
|
||||
|
||||
#nullable enable
|
||||
public class LaunchArgument
|
||||
public record LaunchArgument
|
||||
{
|
||||
public string[] Aliases;
|
||||
public string Description;
|
||||
protected string? ParamName1;
|
||||
protected string? ParamName2;
|
||||
public Action? Handler;
|
||||
public Action<string>? HandlerWithArg1;
|
||||
public Action<string, string>? HandlerWithArg2;
|
||||
public int RequiredArgsCount;
|
||||
public int Priority;
|
||||
public struct Param
|
||||
{
|
||||
public readonly string Name;
|
||||
public string? Value { get; internal set; } = null;
|
||||
|
||||
private LaunchArgument(string[] aliases, string description, int priority)
|
||||
public Param(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string[] Aliases;
|
||||
public readonly string Description;
|
||||
public readonly int Priority;
|
||||
public readonly Param[] Params;
|
||||
|
||||
private readonly Action? Handler;
|
||||
private readonly Action<string>? Handler1Param;
|
||||
private readonly Action<string, string>? Handler2Params;
|
||||
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action handler, int priority = 0)
|
||||
{
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action handler, int priority = 0)
|
||||
: this(aliases, description, priority)
|
||||
{
|
||||
Handler = handler;
|
||||
RequiredArgsCount = 0;
|
||||
Params = [];
|
||||
}
|
||||
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action<string> handler, string paramName1, int priority=0)
|
||||
: this(aliases, description, priority)
|
||||
{
|
||||
HandlerWithArg1 = handler;
|
||||
ParamName1 = paramName1;
|
||||
RequiredArgsCount = 1;
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
Priority = priority;
|
||||
|
||||
Handler1Param = handler;
|
||||
Params = [
|
||||
new Param(paramName1)
|
||||
];
|
||||
}
|
||||
public LaunchArgument(string[] aliases, string description,
|
||||
Action<string, string> handler, string paramName1, string paramName2, int priority=0)
|
||||
: this(aliases, description, priority)
|
||||
{
|
||||
HandlerWithArg2 = handler;
|
||||
ParamName1 = paramName1;
|
||||
ParamName2 = paramName2;
|
||||
RequiredArgsCount = 2;
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
Priority = priority;
|
||||
|
||||
Handler2Params = handler;
|
||||
Params = [
|
||||
new Param(paramName1),
|
||||
new Param(paramName2),
|
||||
];
|
||||
}
|
||||
|
||||
public StringBuilder AppendHelpInfo(StringBuilder b)
|
||||
internal StringBuilder AppendHelpInfo(StringBuilder b)
|
||||
{
|
||||
b.Append(Aliases[0]);
|
||||
for (int i = 1; i < Aliases.Length; i++)
|
||||
b.Append(", ").Append(Aliases[i]);
|
||||
if (!string.IsNullOrEmpty(ParamName1))
|
||||
b.Append(" [").Append(ParamName1).Append("] ");
|
||||
if (!string.IsNullOrEmpty(ParamName2))
|
||||
b.Append(" [").Append(ParamName2).Append("] ");
|
||||
b.Append("- ").Append(Description);
|
||||
foreach (var param in Params)
|
||||
b.Append(" [").Append(param.Name).Append("]");
|
||||
b.Append(" - ").Append(Description);
|
||||
return b;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{{{{{Aliases.MergeToString(", ")}}}, Handler: {Handler is null}, HandlerWithArg: {HandlerWithArg1 is null}}}";
|
||||
internal void Handle()
|
||||
{
|
||||
switch (Params.Length)
|
||||
{
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(Params.Length.ToString());
|
||||
case 0:
|
||||
Handler!.Invoke();
|
||||
break;
|
||||
case 1:
|
||||
if (Params[0].Value is null)
|
||||
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
|
||||
Handler1Param!.Invoke(Params[0].Value!);
|
||||
break;
|
||||
case 2:
|
||||
if (Params[0].Value is null)
|
||||
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
|
||||
if (Params[1].Value is null)
|
||||
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[1] value");
|
||||
Handler2Params!.Invoke(Params[0].Value!, Params[1].Value!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,135 @@ namespace DTLib.Console;
|
||||
|
||||
public class LaunchArgumentParser
|
||||
{
|
||||
private Dictionary<string, LaunchArgument> argDict = new();
|
||||
private List<LaunchArgument> argList = new();
|
||||
public bool ExitIfNoArgs = true;
|
||||
public string HelpMessageHeader = "USAGE:";
|
||||
public bool AllowedNoArguments;
|
||||
public bool AllowedUnknownArguments;
|
||||
// ReSharper disable once CollectionNeverQueried.Global
|
||||
public readonly List<string> UnknownArguments = new();
|
||||
|
||||
public class ExitAfterHelpException : Exception
|
||||
private readonly Dictionary<string, LaunchArgument> argDict = new();
|
||||
private readonly List<LaunchArgument> argList = new();
|
||||
|
||||
public LaunchArgumentParser()
|
||||
{
|
||||
internal ExitAfterHelpException() : base("your program can use this exception to exit after displaying help message")
|
||||
{ }
|
||||
var help = new LaunchArgument(new[] { "h", "help" },
|
||||
"shows help message", HelpHandler);
|
||||
Add(help);
|
||||
var helpArg = new LaunchArgument(new[] { "ha", "helparg" },
|
||||
"shows help message for specific argument",
|
||||
HelpArgHandler, "argument");
|
||||
Add(helpArg);
|
||||
}
|
||||
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this() => WithArgs(arguments);
|
||||
public LaunchArgumentParser(params LaunchArgument[] arguments) : this() => WithArgs(arguments);
|
||||
|
||||
public LaunchArgumentParser WithArgs(IEnumerable<LaunchArgument> args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
Add(arg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser WithArgs(params LaunchArgument[] args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
Add(arg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser WithHelpMessageHeader(string header)
|
||||
{
|
||||
HelpMessageHeader = header;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser AllowNoArguments()
|
||||
{
|
||||
AllowedNoArguments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser AllowUnknownArguments()
|
||||
{
|
||||
AllowedUnknownArguments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public string CreateHelpMessage()
|
||||
{
|
||||
StringBuilder b = new();
|
||||
StringBuilder b = new(HelpMessageHeader);
|
||||
foreach (var arg in argList)
|
||||
arg.AppendHelpInfo(b).Append('\n');
|
||||
b.Remove(b.Length-1, 1);
|
||||
{
|
||||
b.Append('\n');
|
||||
arg.AppendHelpInfo(b);
|
||||
}
|
||||
return b.ToString();
|
||||
}
|
||||
|
||||
public string CreateHelpArgMessage(string argAlias)
|
||||
{
|
||||
StringBuilder b = new();
|
||||
var arg = Parse(argAlias);
|
||||
if(!TryParseArg(argAlias, out var arg))
|
||||
throw new Exception($"unknown argument '{argAlias}'");
|
||||
arg.AppendHelpInfo(b);
|
||||
return b.ToString();
|
||||
}
|
||||
|
||||
public void Add(LaunchArgument arg)
|
||||
{
|
||||
argList.Add(arg);
|
||||
foreach (string alias in arg.Aliases)
|
||||
argDict.Add(alias, arg);
|
||||
}
|
||||
|
||||
public bool TryParseArg(string argAlias, out LaunchArgument arg)
|
||||
{
|
||||
// different argument providing patterns
|
||||
arg = null!;
|
||||
return argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg) || // --arg
|
||||
argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg) || // -arg
|
||||
argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg); // /arg
|
||||
}
|
||||
|
||||
/// <param name="args">program launch args</param>
|
||||
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
|
||||
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
|
||||
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
|
||||
public void ParseAndHandle(string[] args)
|
||||
{
|
||||
// show help message and throw ExitAfterHelpException
|
||||
if (args.Length == 0 && !AllowedNoArguments)
|
||||
HelpHandler();
|
||||
|
||||
List<LaunchArgument> execQueue = new();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (!TryParseArg(args[i], out var arg))
|
||||
{
|
||||
if (!AllowedUnknownArguments)
|
||||
throw new Exception($"unknown argument '{args[i]}'");
|
||||
UnknownArguments.Add(args[i]);
|
||||
}
|
||||
for (int j = 0; j < arg.Params.Length; j++)
|
||||
{
|
||||
if (++i >= args.Length)
|
||||
throw new Exception(
|
||||
$"argument '{arg.Aliases[0]}' should have parameter '{arg.Params[j]}' after it");
|
||||
arg.Params[j].Value = args[i];
|
||||
}
|
||||
|
||||
execQueue.Add(arg);
|
||||
}
|
||||
|
||||
// ascending sort by priority
|
||||
execQueue.Sort((a0, a1) => a0.Priority - a1.Priority);
|
||||
// finally executing handlers
|
||||
foreach (var a in execQueue)
|
||||
a.Handle();
|
||||
}
|
||||
|
||||
private void HelpHandler()
|
||||
{
|
||||
System.Console.WriteLine(CreateHelpMessage());
|
||||
@@ -39,104 +143,11 @@ public class LaunchArgumentParser
|
||||
throw new ExitAfterHelpException();
|
||||
}
|
||||
|
||||
|
||||
public LaunchArgumentParser()
|
||||
public class ExitAfterHelpException : Exception
|
||||
{
|
||||
public ExitAfterHelpException()
|
||||
: base("your program can use this exception to exit after displaying help message")
|
||||
{
|
||||
var help = new LaunchArgument(new[] { "h", "help" },
|
||||
"shows help message", HelpHandler);
|
||||
Add(help);
|
||||
var helpArg = new LaunchArgument( new[]{ "ha", "helparg" },
|
||||
"shows help message for particular argument",
|
||||
HelpArgHandler, "argAlias");
|
||||
Add(helpArg);
|
||||
}
|
||||
|
||||
public LaunchArgumentParser WithNoExit()
|
||||
{
|
||||
ExitIfNoArgs = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this()
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
Add(arg);
|
||||
}
|
||||
public LaunchArgumentParser(params LaunchArgument[] arguments) : this()
|
||||
{
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
Add(arguments[i]);
|
||||
}
|
||||
|
||||
public void Add(LaunchArgument arg)
|
||||
{
|
||||
argList.Add(arg);
|
||||
for(int a=0; a<arg.Aliases.Length; a++)
|
||||
argDict.Add(arg.Aliases[a], arg);
|
||||
}
|
||||
|
||||
public LaunchArgument Parse(string argAlias)
|
||||
{
|
||||
// different argument providing patterns
|
||||
if (!argDict.TryGetValue(argAlias, out var arg) && // arg
|
||||
!(argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg)) && // --arg
|
||||
!(argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg)) && // -arg
|
||||
!(argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg))) // /arg
|
||||
throw new Exception($"invalid argument: {argAlias}\n{CreateHelpMessage()}");
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
/// <param name="args">program launch args</param>
|
||||
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
|
||||
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
|
||||
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
|
||||
public void ParseAndHandle(string[] args)
|
||||
{
|
||||
// show help and throw
|
||||
if (args.Length == 0 && ExitIfNoArgs)
|
||||
HelpHandler();
|
||||
|
||||
List<LaunchArgument> execQueue = new();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
LaunchArgument arg = Parse(args[i]);
|
||||
|
||||
switch (arg.RequiredArgsCount)
|
||||
{
|
||||
case 0:
|
||||
if (arg.Handler is null)
|
||||
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
if (arg.HandlerWithArg1 is null)
|
||||
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
if (i + 1 >= args.Length)
|
||||
throw new Exception($"argument <{args[i]}> should have a parameter after it");
|
||||
string arg1 = args[++i];
|
||||
arg.Handler = () => arg.HandlerWithArg1(arg1);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (arg.HandlerWithArg2 is null)
|
||||
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
if (i + 2 >= args.Length)
|
||||
throw new Exception($"argument <{args[i]}> should have two params after it");
|
||||
string arg1 = args[++i], arg2 = args[++i];
|
||||
arg.Handler = () => arg.HandlerWithArg2(arg1, arg2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
execQueue.Add(arg);
|
||||
}
|
||||
|
||||
// ascending sort by priority
|
||||
execQueue.Sort((a0, a1) => a0.Priority-a1.Priority);
|
||||
// finally executing handlers
|
||||
foreach (var a in execQueue)
|
||||
a.Handler!();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!--package info-->
|
||||
<PackageId>DTLib</PackageId>
|
||||
<Version>1.4.0</Version>
|
||||
<Version>1.7.4</Version>
|
||||
<Authors>Timerix</Authors>
|
||||
<Description>Library for all my C# projects</Description>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
@@ -14,7 +14,7 @@
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<!--language features-->
|
||||
<LangVersion>12</LangVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
|
||||
@@ -31,6 +31,6 @@
|
||||
<ProjectReference Include="..\DTLib.Demystifier\DTLib.Demystifier.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<PackageReference Include="DTLib.Demystifier" Version="1.1.0" />
|
||||
<PackageReference Include="DTLib.Demystifier" Version="1.1.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=console/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>
|
||||
37
DTLib/Extensions/TaskExtensions.cs
Normal file
37
DTLib/Extensions/TaskExtensions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace DTLib.Extensions;
|
||||
|
||||
public static class TaskExtensions
|
||||
{
|
||||
// https://stackoverflow.com/a/69861689
|
||||
public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
|
||||
{
|
||||
if (!token.CanBeCanceled)
|
||||
{
|
||||
return task;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
// This cancels the returned task:
|
||||
// 1. If the token has been canceled, it cancels the TCS straightaway
|
||||
// 2. Otherwise, it attempts to cancel the TCS whenever
|
||||
// the token indicates cancelled
|
||||
token.Register(() => tcs.TrySetCanceled(token),
|
||||
useSynchronizationContext: false);
|
||||
|
||||
task.ContinueWith(t =>
|
||||
{
|
||||
// Complete the TCS per task status
|
||||
// If the TCS has been cancelled, this continuation does nothing
|
||||
if (task.IsCanceled)
|
||||
tcs.TrySetCanceled();
|
||||
else if (task.IsFaulted)
|
||||
tcs.TrySetException(t.Exception!);
|
||||
else tcs.TrySetResult(t.Result);
|
||||
},
|
||||
CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,13 @@ public static class Directory
|
||||
if (dir.Contains(Path.Sep))
|
||||
{
|
||||
var parentDir = dir.ParentDir();
|
||||
if(!Exists(parentDir))
|
||||
if (!Exists(parentDir))
|
||||
Create(parentDir);
|
||||
}
|
||||
|
||||
System.IO.Directory.CreateDirectory(dir.Str);
|
||||
}
|
||||
|
||||
/// копирует все файлы и папки
|
||||
public static void Copy(IOPath sourceDir, IOPath newDir, bool owerwrite)
|
||||
{
|
||||
@@ -54,7 +55,7 @@ public static class Directory
|
||||
Delete(target_path);
|
||||
else throw new Exception($"directory {target_path} already exists");
|
||||
}
|
||||
else Directory.Create(target_path.ParentDir());
|
||||
else Create(target_path.ParentDir());
|
||||
System.IO.Directory.Move(current_path.Str, target_path.Str);
|
||||
}
|
||||
|
||||
@@ -86,9 +87,9 @@ public static class Directory
|
||||
all_subdirs = new List<IOPath>();
|
||||
return GetAllFiles_internal(dir, all_subdirs);
|
||||
}
|
||||
|
||||
private static List<IOPath> GetAllFiles_internal(IOPath dir, List<IOPath>? all_subdirs)
|
||||
{
|
||||
bool rememberSubdirs = all_subdirs is not null;
|
||||
var all_files = new List<IOPath>();
|
||||
IOPath[] cur_files = GetFiles(dir);
|
||||
for (int i = 0; i < cur_files.Length; i++)
|
||||
@@ -96,20 +97,12 @@ public static class Directory
|
||||
IOPath[] cur_subdirs = GetDirectories(dir);
|
||||
for (int i = 0; i < cur_subdirs.Length; i++)
|
||||
{
|
||||
if(rememberSubdirs)
|
||||
all_subdirs?.Add(cur_subdirs[i]);
|
||||
all_files.AddRange(GetAllFiles_internal(cur_subdirs[i], all_subdirs));
|
||||
}
|
||||
|
||||
return all_files;
|
||||
}
|
||||
|
||||
public static string GetCurrent() => System.IO.Directory.GetCurrentDirectory();
|
||||
|
||||
public static void CreateSymlink(string sourcePath, string symlinkPath)
|
||||
{
|
||||
if (symlinkPath.Contains(Path.Sep))
|
||||
Create(Path.ParentDir(symlinkPath));
|
||||
if (!Symlink.CreateSymbolicLink(symlinkPath, sourcePath, Symlink.SymlinkTarget.Directory))
|
||||
throw new InvalidOperationException($"some error occured while creating symlink\nDirectory.CreateSymlink({symlinkPath}, {sourcePath})");
|
||||
}
|
||||
public static IOPath GetCurrent() => new IOPath(System.IO.Directory.GetCurrentDirectory(), true);
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace DTLib.Filesystem;
|
||||
using FileMode = System.IO.FileMode;
|
||||
using FileAccess = System.IO.FileAccess;
|
||||
|
||||
namespace DTLib.Filesystem;
|
||||
|
||||
public static class File
|
||||
{
|
||||
@@ -14,23 +17,22 @@ public static class File
|
||||
|
||||
Directory.Create(file.ParentDir());
|
||||
using System.IO.FileStream stream = System.IO.File.Create(file.Str);
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
public static void Copy(IOPath srcPath, IOPath newPath, bool overwrite)
|
||||
{
|
||||
if (Exists(newPath))
|
||||
{
|
||||
if(overwrite) System.IO.File.Delete(newPath.Str);
|
||||
if (overwrite)
|
||||
Delete(newPath);
|
||||
else throw new Exception($"file <{newPath}> alredy exists");
|
||||
}
|
||||
else Directory.Create(newPath.ParentDir());
|
||||
using var srcFile=System.IO.File.Open(srcPath.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
|
||||
using var newFile=System.IO.File.Open(newPath.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
|
||||
|
||||
using var srcFile = OpenRead(srcPath);
|
||||
using var newFile = OpenWrite(newPath);
|
||||
srcFile.CopyTo(newFile);
|
||||
srcFile.Close();
|
||||
newFile.Flush();
|
||||
newFile.Close();
|
||||
}
|
||||
|
||||
public static void Move(IOPath current_path, IOPath target_path, bool overwrite)
|
||||
@@ -42,6 +44,7 @@ public static class File
|
||||
else throw new Exception($"file {target_path} already exists");
|
||||
}
|
||||
else Directory.Create(target_path.ParentDir());
|
||||
|
||||
System.IO.File.Move(current_path.Str, target_path.Str);
|
||||
}
|
||||
|
||||
@@ -49,62 +52,81 @@ public static class File
|
||||
|
||||
public static byte[] ReadAllBytes(IOPath file)
|
||||
{
|
||||
|
||||
using System.IO.FileStream stream = OpenRead(file);
|
||||
int size = GetSize(file).ToInt();
|
||||
byte[] output = new byte[size];
|
||||
if (stream.Read(output, 0, size) < size)
|
||||
throw new Exception("can't read all bytes");
|
||||
stream.Close();
|
||||
return output;
|
||||
}
|
||||
|
||||
public static string ReadAllText(IOPath file) => ReadAllBytes(file).BytesToString(StringConverter.UTF8);
|
||||
|
||||
public static async Task<byte[]> ReadAllBytesAsync(IOPath file)
|
||||
{
|
||||
using System.IO.FileStream stream = OpenRead(file);
|
||||
int size = GetSize(file).ToInt();
|
||||
byte[] output = new byte[size];
|
||||
if (await stream.ReadAsync(output, 0, size).ConfigureAwait(false) < size)
|
||||
throw new Exception("can't read all bytes");
|
||||
return output;
|
||||
}
|
||||
|
||||
public static string ReadAllText(IOPath file) =>
|
||||
ReadAllBytes(file).BytesToString(StringConverter.UTF8);
|
||||
public static async Task<string> ReadAllTextAsync(IOPath file) =>
|
||||
(await ReadAllBytesAsync(file)).BytesToString(StringConverter.UTF8);
|
||||
|
||||
public static void WriteAllBytes(IOPath file, byte[] content)
|
||||
{
|
||||
using System.IO.FileStream stream = OpenWrite(file);
|
||||
stream.Write(content, 0, content.Length);
|
||||
stream.Close();
|
||||
}
|
||||
public static async Task WriteAllBytesAsync(IOPath file, byte[] content)
|
||||
{
|
||||
using System.IO.FileStream stream = OpenWrite(file);
|
||||
await stream.WriteAsync(content, 0, content.Length);
|
||||
}
|
||||
|
||||
public static void WriteAllText(IOPath file, string content) => WriteAllBytes(file, content.ToBytes(StringConverter.UTF8));
|
||||
public static void WriteAllText(IOPath file, string content) =>
|
||||
WriteAllBytes(file, content.ToBytes(StringConverter.UTF8));
|
||||
public static async Task WriteAllTextAsync(IOPath file, string content) =>
|
||||
await WriteAllBytesAsync(file, content.ToBytes(StringConverter.UTF8));
|
||||
|
||||
public static void AppendAllBytes(IOPath file, byte[] content)
|
||||
{
|
||||
using System.IO.FileStream stream = OpenAppend(file);
|
||||
stream.Write(content, 0, content.Length);
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
public static void AppendAllText(IOPath file, string content) => AppendAllBytes(file, content.ToBytes(StringConverter.UTF8));
|
||||
public static async Task AppendAllBytesAsync(IOPath file, byte[] content)
|
||||
{
|
||||
using System.IO.FileStream stream = OpenAppend(file);
|
||||
await stream.WriteAsync(content, 0, content.Length);
|
||||
}
|
||||
|
||||
public static void AppendAllText(IOPath file, string content) =>
|
||||
AppendAllBytes(file, content.ToBytes(StringConverter.UTF8));
|
||||
|
||||
public static async Task AppendAllTextAsync(IOPath file, string content) =>
|
||||
await AppendAllBytesAsync(file, content.ToBytes(StringConverter.UTF8));
|
||||
|
||||
public static System.IO.FileStream OpenRead(IOPath file)
|
||||
{
|
||||
if (!Exists(file))
|
||||
throw new Exception($"file not found: <{file}>");
|
||||
return System.IO.File.Open(file.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
|
||||
return System.IO.File.Open(file.Str, FileMode.Open, FileAccess.Read,
|
||||
System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete);
|
||||
}
|
||||
|
||||
public static System.IO.FileStream OpenWrite(IOPath file)
|
||||
{
|
||||
if (Exists(file))
|
||||
Delete(file);
|
||||
Create(file);
|
||||
return System.IO.File.Open(file.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
|
||||
Directory.Create(file.ParentDir());
|
||||
return System.IO.File.Open(file.Str, FileMode.Create, FileAccess.Write, System.IO.FileShare.Read);
|
||||
}
|
||||
|
||||
public static System.IO.FileStream OpenAppend(IOPath file)
|
||||
{
|
||||
|
||||
Create(file);
|
||||
return System.IO.File.Open(file.Str, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
public static void CreateSymlink(IOPath sourcePath, IOPath symlinkPath)
|
||||
{
|
||||
if (symlinkPath.Contains(Path.Sep))
|
||||
Directory.Create(symlinkPath.ParentDir());
|
||||
if (!Symlink.CreateSymbolicLink(symlinkPath.Str, sourcePath.Str, Symlink.SymlinkTarget.File))
|
||||
throw new InvalidOperationException($"some error occured while creating symlink\nFile.CreateSymlink({symlinkPath}, {sourcePath})");
|
||||
Directory.Create(file.ParentDir());
|
||||
return System.IO.File.Open(file.Str, FileMode.Append, FileAccess.Write, System.IO.FileShare.Read);
|
||||
}
|
||||
}
|
||||
@@ -15,43 +15,54 @@ public readonly struct IOPath
|
||||
|
||||
public IOPath(char[] path, bool separatorsFixed=false)
|
||||
{
|
||||
if (path.Length == 0)
|
||||
throw new Exception("path is null or empty");
|
||||
Str = separatorsFixed ? new string(path) : FixSeparators(path);
|
||||
}
|
||||
|
||||
public IOPath(string path, bool separatorsFixed=false)
|
||||
public IOPath(string path, bool separatorsFixed=false) : this(path.ToCharArray(), separatorsFixed)
|
||||
{
|
||||
if (path.IsNullOrEmpty())
|
||||
throw new Exception("path is null or empty");
|
||||
Str = separatorsFixed ? path : FixSeparators(path.ToCharArray());
|
||||
}
|
||||
|
||||
static string FixSeparators(char[] path)
|
||||
{
|
||||
int length = path.Length;
|
||||
if (path[length-1] == Path.Sep || path[length-1] == Path.NotSep)
|
||||
char lastChar = path[path.Length - 1];
|
||||
if (lastChar == Path.Sep || lastChar == Path.NotSep)
|
||||
length--; // removing trailing sep
|
||||
char[] fixed_path = new char[length];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
bool prevWasSeparator = false;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (path[i] == Path.NotSep)
|
||||
fixed_path[i] = Path.Sep;
|
||||
else fixed_path[i] = path[i];
|
||||
{
|
||||
// prevent double separators like this "a//b"
|
||||
if(!prevWasSeparator)
|
||||
sb.Append(Path.Sep);
|
||||
prevWasSeparator = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(path[i]);
|
||||
prevWasSeparator = false;
|
||||
}
|
||||
return new string(fixed_path);
|
||||
}
|
||||
|
||||
public static IOPath[] ArrayCast(string[] a, bool correct_separators=false)
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static IOPath[] ArrayCast(string[] a, bool separatorsFixed=false)
|
||||
{
|
||||
IOPath[] b = new IOPath[a.Length];
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
b[i] = new IOPath(a[i], correct_separators);
|
||||
b[i] = new IOPath(a[i], separatorsFixed);
|
||||
return b;
|
||||
}
|
||||
public static IOPath[] ListCast(IList<string> a, bool correct_separators=false)
|
||||
public static IOPath[] ListCast(IList<string> a, bool separatorsFixed=false)
|
||||
{
|
||||
IOPath[] b = new IOPath[a.Count];
|
||||
for (int i = 0; i < a.Count; i++)
|
||||
b[i] = new IOPath(a[i], correct_separators);
|
||||
b[i] = new IOPath(a[i], separatorsFixed);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace DTLib.Filesystem;
|
||||
public static class Path
|
||||
{
|
||||
public static readonly char Sep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '\\' : '/';
|
||||
public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\' ;
|
||||
public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\';
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -25,14 +25,19 @@ public static class Path
|
||||
char c = r[i];
|
||||
switch (c)
|
||||
{
|
||||
case '\n': case '\r':
|
||||
case ':': case ';':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case ':':
|
||||
case ';':
|
||||
break;
|
||||
case '/': case '\\':
|
||||
case '/':
|
||||
case '\\':
|
||||
b.Append('-');
|
||||
break;
|
||||
case '<': case '>':
|
||||
case '?': case '|':
|
||||
case '<':
|
||||
case '>':
|
||||
case '?':
|
||||
case '|':
|
||||
b.Append('_');
|
||||
break;
|
||||
case '"':
|
||||
@@ -46,6 +51,7 @@ public static class Path
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new IOPath(b.ToString(), true);
|
||||
}
|
||||
|
||||
@@ -53,15 +59,15 @@ public static class Path
|
||||
private static void CopyTo(this string s, char[] b, int startIndex)
|
||||
{
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
b[startIndex+i] = s[i];
|
||||
b[startIndex + i] = s[i];
|
||||
}
|
||||
#endif
|
||||
|
||||
public static IOPath Concat(params IOPath[] parts)
|
||||
{
|
||||
var needSeparator = new bool[parts.Length-1];
|
||||
var needSeparator = new bool[parts.Length - 1];
|
||||
int lengthSum = 0;
|
||||
for (int i = 0; i < parts.Length-1; i++)
|
||||
for (int i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
lengthSum += parts[i].Length;
|
||||
if (!parts[i].Str.EndsWith(Sep) && !parts[i + 1].Str.StartsWith(Sep))
|
||||
@@ -71,13 +77,14 @@ public static class Path
|
||||
}
|
||||
else needSeparator[i] = false;
|
||||
}
|
||||
lengthSum += parts[parts.Length-1].Length;
|
||||
|
||||
lengthSum += parts[parts.Length - 1].Length;
|
||||
var buffer = new char[lengthSum];
|
||||
parts[0].Str.CopyTo(buffer, 0);
|
||||
int copiedChars = parts[0].Length;
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
if (needSeparator[i-1])
|
||||
if (needSeparator[i - 1])
|
||||
buffer[copiedChars++] = Sep;
|
||||
parts[i].Str.CopyTo(buffer, copiedChars);
|
||||
copiedChars += parts[i].Length;
|
||||
@@ -89,28 +96,40 @@ public static class Path
|
||||
/// returns just dir name or file name with extension
|
||||
public static IOPath LastName(this IOPath path)
|
||||
{
|
||||
if(path.Length < 2)
|
||||
return path;
|
||||
int i = path.LastIndexOf(Sep);
|
||||
if (i == path.Length - 1) // ends with separator
|
||||
i = path.LastIndexOf(Sep, i-1);
|
||||
if (i == -1) return path;
|
||||
return path.Substring(i+1);
|
||||
i = path.LastIndexOf(Sep, i - 1);
|
||||
if (i == -1)
|
||||
return path;
|
||||
return path.Substring(i + 1);
|
||||
}
|
||||
|
||||
public static IOPath Extension(this IOPath path)
|
||||
{
|
||||
int i = path.LastIndexOf('.');
|
||||
if (i == -1) return LastName(path);
|
||||
if (i == -1)
|
||||
return LastName(path);
|
||||
return path.Substring(i + 1);
|
||||
}
|
||||
|
||||
public static IOPath RemoveExtension(this IOPath path)
|
||||
{
|
||||
int i = path.LastIndexOf('.');
|
||||
if (i > 0)
|
||||
return path.Substring(0, i);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static IOPath ParentDir(this IOPath path)
|
||||
{
|
||||
int i = path.LastIndexOf(Sep);
|
||||
if (i == path.Length - 1) // ends with separator
|
||||
i = path.LastIndexOf(Sep, i-1);
|
||||
i = path.LastIndexOf(Sep, i - 1);
|
||||
if (i == -1) // no parent dir
|
||||
return $".{Sep}";
|
||||
return path.Remove(i+1);
|
||||
return path.Remove(i + 1);
|
||||
}
|
||||
|
||||
public static IOPath ReplaceBase(this IOPath path, IOPath baseDir, IOPath otherDir)
|
||||
@@ -124,6 +143,6 @@ public static class Path
|
||||
{
|
||||
if (!path.StartsWith(baseDir))
|
||||
throw new Exception($"path <{path}> doesnt starts with <{baseDir}");
|
||||
return path.Substring(baseDir.Length+1);
|
||||
return path.Substring(baseDir.Length + 1);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace DTLib.Filesystem;
|
||||
|
||||
internal class Symlink
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern bool CreateSymbolicLink(string symlinkName, string sourceName, SymlinkTarget type);
|
||||
|
||||
internal enum SymlinkTarget
|
||||
{
|
||||
File,
|
||||
Directory
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,16 @@ namespace DTLib.Logging;
|
||||
|
||||
public class DefaultLogFormat : ILogFormat
|
||||
{
|
||||
|
||||
public bool PrintTimeStamp { get; set; }
|
||||
public bool PrintContext { get; set; }
|
||||
public bool PrintSeverity { get; set; }
|
||||
|
||||
public DefaultLogFormat(bool printTimeStamp = true, bool printContext = true, bool printSeverity = true)
|
||||
{
|
||||
PrintTimeStamp = printTimeStamp;
|
||||
PrintContext = printContext;
|
||||
PrintSeverity = printSeverity;
|
||||
}
|
||||
public bool PrintTimeStamp { get; set; } = true;
|
||||
public bool PrintContext { get; set; } = true;
|
||||
public bool PrintSeverity { get; set; } = true;
|
||||
public string TimeStampFormat { get; set; } = MyTimeFormat.ForText;
|
||||
|
||||
public string CreateMessage(string context, LogSeverity severity, object message)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (PrintTimeStamp)
|
||||
sb.Append('[').Append(DateTime.Now.ToString(MyTimeFormat.ForText)).Append(']');
|
||||
sb.Append('[').Append(DateTime.Now.ToString(TimeStampFormat)).Append(']');
|
||||
if (PrintContext && PrintSeverity)
|
||||
sb.Append('[').Append(context).Append('/').Append(severity.ToString()).Append(']');
|
||||
else if(PrintContext)
|
||||
|
||||
@@ -2,9 +2,5 @@ namespace DTLib.Logging;
|
||||
|
||||
public interface ILogFormat
|
||||
{
|
||||
bool PrintTimeStamp { get; set; }
|
||||
bool PrintContext { get; set; }
|
||||
bool PrintSeverity { get; set; }
|
||||
|
||||
string CreateMessage(string context, LogSeverity severity, object message);
|
||||
}
|
||||
@@ -11,8 +11,8 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_debugLogEnabled = value;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].DebugLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.DebugLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_infoLogEnabled = true;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].InfoLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.InfoLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_warnLogEnabled = value;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].WarnLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.WarnLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,46 +44,38 @@ public class CompositeLogger : ILogger
|
||||
set
|
||||
{
|
||||
_errorLogenabled = value;
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].ErrorLogEnabled = value;
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.ErrorLogEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ILogFormat Format { get; set; }
|
||||
public readonly List<ILogger> ChildLoggers;
|
||||
|
||||
protected ILogger[] _loggers;
|
||||
private bool _debugLogEnabled =
|
||||
#if DEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
private bool _debugLogEnabled;
|
||||
private bool _infoLogEnabled = true;
|
||||
private bool _warnLogEnabled = true;
|
||||
private bool _errorLogenabled = true;
|
||||
|
||||
public CompositeLogger(ILogFormat format, params ILogger[] loggers)
|
||||
public CompositeLogger(ILogFormat format, params ILogger[] childLoggers)
|
||||
{
|
||||
Format = format;
|
||||
_loggers = loggers;
|
||||
ChildLoggers = new List<ILogger>(childLoggers);
|
||||
}
|
||||
|
||||
public CompositeLogger(params ILogger[] loggers) : this(new DefaultLogFormat(), loggers)
|
||||
public CompositeLogger(params ILogger[] childLoggers) : this(new DefaultLogFormat(), childLoggers)
|
||||
{}
|
||||
|
||||
|
||||
public void Log(string context, LogSeverity severity, object message, ILogFormat format)
|
||||
{
|
||||
if(!this.CheckSeverity(severity))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].Log(context, severity, message, format);
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.Log(context, severity, message, format);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _loggers.Length; i++)
|
||||
_loggers[i].Dispose();
|
||||
foreach (var childLogger in ChildLoggers)
|
||||
childLogger.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class ConsoleLogger : ILogger
|
||||
|
||||
var msg = format.CreateMessage(context, severity, message);
|
||||
lock (consolelocker)
|
||||
ColoredConsole.WriteLine(ColorFromSeverity(severity),msg);
|
||||
ColoredConsole.WriteLine(msg, fg: ColorFromSeverity(severity));
|
||||
}
|
||||
|
||||
private static ConsoleColor ColorFromSeverity(LogSeverity severity)
|
||||
|
||||
@@ -15,7 +15,7 @@ public class FileLogger : ILogger
|
||||
{
|
||||
Format = format;
|
||||
LogfileName = logfile;
|
||||
LogfileStream = File.OpenAppend(logfile);
|
||||
LogfileStream = File.OpenWrite(logfile);
|
||||
}
|
||||
|
||||
public FileLogger(IOPath logfile) : this(logfile, new DefaultLogFormat())
|
||||
@@ -46,8 +46,8 @@ public class FileLogger : ILogger
|
||||
{
|
||||
try
|
||||
{
|
||||
LogfileStream?.Flush();
|
||||
LogfileStream?.Dispose();
|
||||
LogfileStream.Flush();
|
||||
LogfileStream.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException) { }
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using DTLib.Logging;
|
||||
namespace DTLib.Logging;
|
||||
|
||||
namespace launcher_client;
|
||||
|
||||
public interface IConsoleWrapper : IDisposable
|
||||
public interface ILineWriter : IDisposable
|
||||
{
|
||||
public void WriteLine(string msg);
|
||||
}
|
||||
|
||||
public class ConsoleWrapperLogger : ILogger
|
||||
public class LineWriterLogger : ILogger
|
||||
{
|
||||
public bool DebugLogEnabled { get; set; } = false;
|
||||
public bool InfoLogEnabled { get; set; } = true;
|
||||
@@ -15,16 +13,16 @@ public class ConsoleWrapperLogger : ILogger
|
||||
public bool ErrorLogEnabled { get; set; } = true;
|
||||
public ILogFormat Format { get; set; }
|
||||
|
||||
private readonly IConsoleWrapper _consoleWrapper;
|
||||
private readonly ILineWriter _lineWriter;
|
||||
|
||||
public ConsoleWrapperLogger(IConsoleWrapper consoleWrapper, ILogFormat format)
|
||||
public LineWriterLogger(ILineWriter lineWriter, ILogFormat format)
|
||||
{
|
||||
_consoleWrapper = consoleWrapper;
|
||||
_lineWriter = lineWriter;
|
||||
Format = format;
|
||||
}
|
||||
|
||||
public ConsoleWrapperLogger(IConsoleWrapper consoleWrapper)
|
||||
: this(consoleWrapper, new DefaultLogFormat())
|
||||
public LineWriterLogger(ILineWriter lineWriter)
|
||||
: this(lineWriter, new DefaultLogFormat())
|
||||
{}
|
||||
|
||||
public void Log(string context, LogSeverity severity, object message, ILogFormat format)
|
||||
@@ -33,14 +31,14 @@ public class ConsoleWrapperLogger : ILogger
|
||||
return;
|
||||
|
||||
var msg = format.CreateMessage(context, severity, message);
|
||||
lock (_consoleWrapper)
|
||||
_consoleWrapper.WriteLine(msg);
|
||||
lock (_lineWriter)
|
||||
_lineWriter.WriteLine(msg);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_consoleWrapper.Dispose();
|
||||
_lineWriter.Dispose();
|
||||
}
|
||||
|
||||
~ConsoleWrapperLogger() => Dispose();
|
||||
~LineWriterLogger() => Dispose();
|
||||
}
|
||||
@@ -4,4 +4,6 @@ public static class MyTimeFormat
|
||||
{
|
||||
public const string ForFileNames="yyyy.MM.dd_HH-mm-ss_zz";
|
||||
public const string ForText="yyyy.MM.dd HH:mm:ss zz";
|
||||
public const string TimeOnly="HH:mm:ss";
|
||||
public const string DateOnly="yyyy.MM.dd";
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace DTLib;
|
||||
namespace DTLib;
|
||||
|
||||
//
|
||||
// простой и понятный класс для выполнения каких-либо действий в отдельном потоке раз в некоторое время
|
||||
//
|
||||
public class Timer
|
||||
public class Timer : IDisposable
|
||||
{
|
||||
private Task _timerTask;
|
||||
private Task? _timerTask;
|
||||
private CancellationTokenSource _cts = new();
|
||||
private bool _repeat;
|
||||
private int _delayMs;
|
||||
@@ -19,27 +17,42 @@ public class Timer
|
||||
_repeat = repeat;
|
||||
_delayMs = delayMs;
|
||||
_action = action;
|
||||
_timerTask = new Task(Loop, _cts.Token);
|
||||
}
|
||||
|
||||
private void Loop()
|
||||
public void InvokeAction()
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_cts.Token.IsCancellationRequested)
|
||||
return;
|
||||
Task.Delay(_delayMs).Wait();
|
||||
if (_cts.Token.IsCancellationRequested)
|
||||
return;
|
||||
_action?.Invoke();
|
||||
} while (_repeat);
|
||||
_action();
|
||||
}
|
||||
|
||||
public void Start() => _timerTask.Start();
|
||||
public void Start()
|
||||
{
|
||||
_timerTask = new Task(Loop);
|
||||
_timerTask.Start();
|
||||
}
|
||||
|
||||
// завершение потока
|
||||
public void Stop()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
private void Loop()
|
||||
{
|
||||
var ct = _cts.Token;
|
||||
do
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
return;
|
||||
Task.Delay(_delayMs).Wait();
|
||||
if (ct.IsCancellationRequested)
|
||||
return;
|
||||
InvokeAction();
|
||||
} while (_repeat);
|
||||
}
|
||||
}
|
||||
|
||||
69
DTLib/TransformStream.cs
Normal file
69
DTLib/TransformStream.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.IO;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace DTLib;
|
||||
|
||||
/// <summary>
|
||||
/// Stream wrapper for some operations over data chunks.
|
||||
/// You can add multiple transform operations.
|
||||
/// <example><code>
|
||||
/// using var pipe = new TransformStream(File.OpenRead("encrypted"))
|
||||
/// .AddTransform(ReportProgress)
|
||||
/// .AddTransform(Decrypt);
|
||||
/// using var o = File.OpenWrite("decrypted");
|
||||
/// pipe.CopyTo(o);
|
||||
/// </code></example>
|
||||
/// </summary>
|
||||
public class TransformStream : Stream
|
||||
{
|
||||
private readonly Stream _inputStream;
|
||||
|
||||
public delegate void TransformFuncDelegate(byte[] buffer, int offset, int count);
|
||||
private List<TransformFuncDelegate> _transformFunctions = new();
|
||||
|
||||
public TransformStream(Stream inputStream)
|
||||
{
|
||||
_inputStream = inputStream;
|
||||
}
|
||||
|
||||
public TransformStream AddTransform(TransformFuncDelegate f)
|
||||
{
|
||||
_transformFunctions.Add(f);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransformStream AddTransforms(IEnumerable<TransformFuncDelegate> f)
|
||||
{
|
||||
_transformFunctions.AddRange(f);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool CanRead => _inputStream.CanRead;
|
||||
public override bool CanSeek => _inputStream.CanSeek;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => _inputStream.Length;
|
||||
public override long Position { get => _inputStream.Position; set => _inputStream.Position = value; }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = _inputStream.Read(buffer, offset, count);
|
||||
if(read > 0)
|
||||
{
|
||||
for (int i = 0; i < _transformFunctions.Count; i++)
|
||||
{
|
||||
_transformFunctions[i].Invoke(buffer, offset, read);
|
||||
}
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
public override void Flush() {}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => _inputStream.Seek(offset, origin);
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
|
||||
|
||||
public override void Close() => _inputStream.Close();
|
||||
}
|
||||
24
pack.sh
24
pack.sh
@@ -1,17 +1,10 @@
|
||||
#!/usr/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
if [[ -d nuget ]]; then
|
||||
echo "archiving old ./nuget/ content"
|
||||
mkdir -p nuget_old
|
||||
TIMESTAMP=$(date +%Y.%m.%d_%H-%M-%S)
|
||||
mv nuget "nuget_$TIMESTAMP"
|
||||
tar cvf "nuget_old/nuget_$TIMESTAMP.tar" "nuget_$TIMESTAMP"
|
||||
rm -rf "nuget_$TIMESTAMP"
|
||||
fi
|
||||
rm -rf nuget
|
||||
mkdir nuget
|
||||
|
||||
function create_package() {
|
||||
function build_package() {
|
||||
echo "----------[$1]----------"
|
||||
cd "$1" || return 1
|
||||
dotnet pack -c Release -o bin/pack_tmp || return 1
|
||||
@@ -21,9 +14,12 @@ function create_package() {
|
||||
cd ..
|
||||
}
|
||||
|
||||
echo "building packages"
|
||||
create_package DTLib.Demystifier
|
||||
create_package DTLib.XXHash
|
||||
create_package DTLib
|
||||
create_package DTLib.Logging.Microsoft
|
||||
packages_to_build="$@"
|
||||
if [ -z "$packages_to_build" ]; then
|
||||
packages_to_build='DTLib.Demystifier DTLib.XXHash DTLib DTLib.Logging.Microsoft DTLib.Web'
|
||||
fi
|
||||
echo "building packages $packages_to_build"
|
||||
for p in $packages_to_build; do
|
||||
build_package "$p"
|
||||
done
|
||||
ls -shk nuget
|
||||
|
||||
Reference in New Issue
Block a user