Browse Source

sln foldering; add PictureMover; WIP CompServ

HOME 3 months ago
parent
commit
f580f2f2d7
32 changed files with 1336 additions and 0 deletions
  1. 13 0
      CompressService/CompServ.ClientLibrary/CompServ.ClientLibrary.csproj
  2. 32 0
      CompressService/CompServ.ClientLibrary/CompServClient.cs
  3. 19 0
      CompressService/CompServ.ClientLibrary/CompServHubClient.cs
  4. 33 0
      CompressService/CompServ.ClientLibrary/CompServWorkerClient.cs
  5. 18 0
      CompressService/CompServ.Hub/CompServ.Hub.csproj
  6. 62 0
      CompressService/CompServ.Hub/Program.cs
  7. 17 0
      CompressService/CompServ.Hub/WorkerController.cs
  8. 14 0
      CompressService/CompServ.Shared/CompServ.Shared.csproj
  9. 21 0
      CompressService/CompServ.Shared/CompServConst.cs
  10. 20 0
      CompressService/CompServ.Shared/CompressRequestModel.cs
  11. 60 0
      CompressService/CompServ.Shared/ModelExtensionMethod.cs
  12. 14 0
      CompressService/CompServ.Tests/CompServ.Tests.csproj
  13. 30 0
      CompressService/CompServ.Tests/Program.cs
  14. 8 0
      CompressService/CompServ.Tests/Properties/launchSettings.json
  15. 20 0
      CompressService/CompServ.Worker/CompServ.Worker.csproj
  16. 19 0
      CompressService/CompServ.Worker/ConsoleTitleUpdateService.cs
  17. 26 0
      CompressService/CompServ.Worker/HubRegister.cs
  18. 114 0
      CompressService/CompServ.Worker/Program.cs
  19. 11 0
      CompressService/CompServ.Worker/Properties/launchSettings.json
  20. 12 0
      CompressService/CompServ.Worker/StatusHolder.cs
  21. 73 0
      CompressService/CompServ.Worker/WorkerController.cs
  22. 61 0
      PictureMover/CoverForm.Designer.cs
  23. 192 0
      PictureMover/CoverForm.cs
  24. 123 0
      PictureMover/CoverForm.resx
  25. 64 0
      PictureMover/MainForm.Designer.cs
  26. 24 0
      PictureMover/MainForm.cs
  27. 120 0
      PictureMover/MainForm.resx
  28. 15 0
      PictureMover/PictureMover.csproj
  29. 17 0
      PictureMover/Program.cs
  30. 12 0
      PictureMover/Properties/launchSettings.json
  31. 71 0
      StrangeTools.sln
  32. 1 0
      StrangeTools.sln.DotSettings

+ 13 - 0
CompressService/CompServ.ClientLibrary/CompServ.ClientLibrary.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\CompServ.Shared\CompServ.Shared.csproj" />
+  </ItemGroup>
+
+</Project>

+ 32 - 0
CompressService/CompServ.ClientLibrary/CompServClient.cs

@@ -0,0 +1,32 @@
+using System.Net;
+using static CompServ.CompServConst;
+
+namespace CompServ.ClientLibrary;
+
+public abstract class CompServClient(string server, string apiCheckAlive, string aliveMessage)
+{
+    protected readonly Uri ServerUri = new Uri(server);
+
+    public async Task<bool> CheckAliveAsync(int timeoutInSecond = 1)
+    {
+        try
+        {
+            var httpRequest = new HttpRequestMessage(HttpMethod.Get, apiCheckAlive)
+            {
+                Version = HttpVersion.Version20,
+                VersionPolicy = HttpVersionPolicy.RequestVersionExact,
+            };
+            using var client = new HttpClient { BaseAddress = ServerUri };
+            client.Timeout = TimeSpan.FromSeconds(timeoutInSecond);
+            var httpResponse = await client.SendAsync(httpRequest);
+            httpResponse.EnsureSuccessStatusCode();
+            var rm = await httpResponse.Content.ReadAsStringAsync();
+
+            return rm == aliveMessage;
+        }
+        catch
+        {
+            return false;
+        }
+    }
+}

+ 19 - 0
CompressService/CompServ.ClientLibrary/CompServHubClient.cs

@@ -0,0 +1,19 @@
+using static CompServ.CompServConst;
+
+namespace CompServ.ClientLibrary;
+
+public class CompServHubClient(string server) : CompServClient(server, ApiPathHubRootForCheckAlive, AliveMessageHub)
+{
+    public async Task RegisterWorker(int port)
+    {
+    }
+
+    public async Task UnRegisterWorker(int port)
+    {
+    }
+
+    public async Task<string> GetWorker()
+    {
+        throw new NotImplementedException();
+    }
+}

+ 33 - 0
CompressService/CompServ.ClientLibrary/CompServWorkerClient.cs

@@ -0,0 +1,33 @@
+using static CompServ.CompServConst;
+using HttpMethod = System.Net.Http.HttpMethod;
+using HttpVersion = System.Net.HttpVersion;
+
+namespace CompServ.ClientLibrary
+{
+    public class CompServWorkerClient(string server) : CompServClient(server, ApiPathWorkerRootForCheckAlive, AliveMessageWorker)
+    {
+        public async Task<byte[]> CompressAsync(CompressRequestModel request)
+        {
+            var message = request.BuildRequestMessage();
+
+            var http = new HttpClient { BaseAddress = ServerUri };
+            var r = await http.SendAsync(message);
+            r.EnsureSuccessStatusCode();
+
+            var compressedBytes = await r.Content.ReadAsByteArrayAsync();
+            return compressedBytes;
+        }
+
+        public async Task<byte[]> DecompressAsync(DecompressRequestModel request)
+        {
+            var message = request.BuildRequestMessage();
+
+            var http = new HttpClient { BaseAddress = ServerUri };
+            var r = await http.SendAsync(message);
+            r.EnsureSuccessStatusCode();
+
+            var bytes = await r.Content.ReadAsByteArrayAsync();
+            return bytes;
+        }
+    }
+}

+ 18 - 0
CompressService/CompServ.Hub/CompServ.Hub.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\CompServ.Shared\CompServ.Shared.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.App" Version="8.0.0" />
+  </ItemGroup>
+
+</Project>

+ 62 - 0
CompressService/CompServ.Hub/Program.cs

@@ -0,0 +1,62 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
+
+var builder = WebApplication.CreateBuilder(args);
+
+//控制台日志格式
+builder.Services.AddLogging(opt =>
+{
+    opt.AddSimpleConsole(p =>
+    {
+        p.TimestampFormat = "[dd HH:mm:ss] ";
+        p.SingleLine = true;
+        p.ColorBehavior = LoggerColorBehavior.Enabled;
+    });
+});
+
+builder.Services.Configure<LoggerFilterOptions>(options =>
+{
+    options.AddFilter((category, level) =>
+    {
+        if (level == LogLevel.Debug) return false;
+        if (level == LogLevel.Trace) return false;
+
+        if (category == "Microsoft.AspNetCore.Hosting.Diagnostics" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Routing.EndpointMiddleware" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Server.Kestrel" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Server.Kestrel.Http2" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Mvc.Infrastructure.ContentResultExecutor" && level == LogLevel.Information) return false;
+
+        return true; // 其他日志保留
+    });
+});
+
+//配置HTTP1、HTTP2端口
+builder.WebHost.UseKestrel((_, kso) =>
+{
+    kso.ListenAnyIP(0, lo => lo.Protocols = HttpProtocols.Http2);
+    kso.ListenAnyIP(0, lo => lo.Protocols = HttpProtocols.Http1);
+});
+
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.Use(async (context, next) =>
+{
+    context.Request.EnableBuffering(); // this used to be EnableRewind
+    await next(context);
+});
+
+app.MapControllers();
+
+app.MapGet("/", () =>"I am hub!");
+
+
+await app.RunAsync();

+ 17 - 0
CompressService/CompServ.Hub/WorkerController.cs

@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using static CompServ.CompServConst;
+
+namespace CompServ.Hub
+{
+    [ApiController]
+    public class HubController : ControllerBase
+    {
+        [HttpGet]
+        [Route("/")]
+        public async Task<ActionResult> CheckAlive() => await Task.FromResult(Content(AliveMessageHub));
+
+
+
+
+    }
+}

+ 14 - 0
CompressService/CompServ.Shared/CompServ.Shared.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <RootNamespace>CompServ</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.App" Version="8.0.0" />
+  </ItemGroup>
+
+</Project>

+ 21 - 0
CompressService/CompServ.Shared/CompServConst.cs

@@ -0,0 +1,21 @@
+namespace CompServ
+{
+    public static class CompServConst
+    {
+        public const string AliveMessageHub = "This comp-serv-hub is alive.";
+        public const string AliveMessageWorker = "This comp-serv-worker is alive.";
+
+        public const string ApiPathWorkerRootForCheckAlive = "/worker/";
+        public const string ApiPathWorkerCompress = "/worker/compress";
+        public const string ApiPathWorkerDecompress = "/worker/decompress";
+
+        public const string RequestHeaderCompressLevel = "x-comp-serv-compress-level";
+        public const string RequestHeaderCompressThreads = "x-comp-serv-compress-threads";
+
+        public const string ApiPathHubRootForCheckAlive = "/hub/";
+        public const string ApiPathHubRegisterWorker = "/hub/register-worker";
+        public const string ApiPathHubUnRegisterWorker = "/hub/un-register-worker";
+        public const string ApiPathHubGetWorker = "/hub/get-worker";
+
+    }
+}

+ 20 - 0
CompressService/CompServ.Shared/CompressRequestModel.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CompServ
+{
+    public class CompressRequestModel
+    {
+        public int Level { get; set; } = 0;
+        public int Threads { get; set; } = 1;
+        public ArraySegment<byte> DataToCompress { get; set; }
+    }
+
+    public class DecompressRequestModel
+    {
+        public ArraySegment<byte> DataToDecompress { get; set; }
+    }
+}

+ 60 - 0
CompressService/CompServ.Shared/ModelExtensionMethod.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using static CompServ.CompServConst;
+
+namespace CompServ
+{
+    public static class ModelExtensionMethod
+    {
+        public static async Task<CompressRequestModel> ExtractCompressRequestAsync(this HttpRequest request)
+        {
+            if (int.TryParse(request.Headers[RequestHeaderCompressThreads], out var cThread) == false) cThread = 1;
+            if (int.TryParse(request.Headers[RequestHeaderCompressLevel], out var cLevel) == false) cLevel = 0;
+
+            var msInput = request.ContentLength.HasValue
+                ? new MemoryStream((int)request.ContentLength.Value)
+                : new MemoryStream();
+
+            request.Body.Position = 0;
+            await request.Body.CopyToAsync(msInput);
+
+            return new CompressRequestModel
+            {
+                Level = cLevel,
+                Threads = cThread,
+                DataToCompress = msInput.ToArray()
+            };
+        }
+
+        public static HttpRequestMessage BuildRequestMessage(this CompressRequestModel model)
+        {
+            return new HttpRequestMessage(HttpMethod.Post, ApiPathWorkerCompress)
+            {
+                Headers =
+                {
+                    {RequestHeaderCompressLevel,model.Level.ToString()},
+                    {RequestHeaderCompressThreads,model.Threads.ToString()},
+
+                },
+                Version = HttpVersion.Version20,
+                VersionPolicy = HttpVersionPolicy.RequestVersionExact,
+                Content = new ByteArrayContent(model.DataToCompress.Array!, model.DataToCompress.Offset, model.DataToCompress.Count)
+            };
+        }
+
+        public static HttpRequestMessage BuildRequestMessage(this DecompressRequestModel model)
+        {
+            return new HttpRequestMessage(HttpMethod.Post, ApiPathWorkerDecompress)
+            {
+                Content = new ByteArrayContent(model.DataToDecompress.Array!, model.DataToDecompress.Offset, model.DataToDecompress.Count),
+                Version = HttpVersion.Version20,
+                VersionPolicy = HttpVersionPolicy.RequestVersionExact,
+            };
+        }
+    }
+}

+ 14 - 0
CompressService/CompServ.Tests/CompServ.Tests.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\CompServ.ClientLibrary\CompServ.ClientLibrary.csproj" />
+  </ItemGroup>
+
+</Project>

+ 30 - 0
CompressService/CompServ.Tests/Program.cs

@@ -0,0 +1,30 @@
+// See https://aka.ms/new-console-template for more information
+
+using CompServ;
+using CompServ.ClientLibrary;
+
+Console.WriteLine("Hello, World!");
+
+var wClient = new CompServWorkerClient(args[0]);
+
+Console.WriteLine("Checking alive...");
+var isAlive = await wClient.CheckAliveAsync();
+Console.WriteLine("Alive: " + isAlive);
+
+if (isAlive)
+{
+    var dataToCompress1 = new byte[4096];
+    dataToCompress1[0] = (byte)'a';
+    dataToCompress1[1] = (byte)'b';
+    dataToCompress1[2] = (byte)'c';
+
+    var compressedBytes = await wClient.CompressAsync(new CompressRequestModel { Level = 1, Threads = 1, DataToCompress = dataToCompress1 });
+
+    var decompress = await wClient.DecompressAsync(compressedBytes);
+
+    int bp = 0;
+}
+
+Console.WriteLine();
+Console.Write("Press ENTER to exit...");
+Console.ReadLine();

+ 8 - 0
CompressService/CompServ.Tests/Properties/launchSettings.json

@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "CompServ.Tests": {
+      "commandName": "Project",
+      "commandLineArgs": "http://localhost:11076"
+    }
+  }
+}

+ 20 - 0
CompressService/CompServ.Worker/CompServ.Worker.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="XZ.NET-netstandard" Version="2.0.0.2" />
+    <PackageReference Include="Microsoft.AspNetCore.App" Version="8.0.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\CompServ.ClientLibrary\CompServ.ClientLibrary.csproj" />
+    <ProjectReference Include="..\CompServ.Shared\CompServ.Shared.csproj" />
+  </ItemGroup>
+
+</Project>

+ 19 - 0
CompressService/CompServ.Worker/ConsoleTitleUpdateService.cs

@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Hosting;
+
+namespace CompServ.Worker;
+
+internal class ConsoleTitleUpdateService : BackgroundService
+{
+    private const int UpdatePerSecond = 2;
+
+    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+    {
+        while (stoppingToken.IsCancellationRequested == false)
+        {
+            await Task.Delay(1000 / UpdatePerSecond, stoppingToken);
+
+            var dt = DateTime.Now;
+            Console.Title = $"XZSx{StatusHolder.Ratio} [{dt:dd HH:mm:ss.f}]";
+        }
+    }
+}

+ 26 - 0
CompressService/CompServ.Worker/HubRegister.cs

@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace CompServ.Worker;
+
+internal class HubRegister(IServiceProvider serviceProvider, ILogger<HubRegister> logger)
+{
+    private int _listeningPort;
+
+    public async Task Register()
+    {
+        var svr = serviceProvider.GetRequiredService<IServer>();
+        var saf = svr.Features.GetRequiredFeature<IServerAddressesFeature>();
+        _listeningPort = new Uri(saf.Addresses.First()).Port;
+
+        logger.LogWarning($"TODO: Register to hub <{StatusHolder.HubServer}> with port <{_listeningPort}> and ratio <{StatusHolder.Ratio}>");
+    }
+
+    public async Task UnRegister()
+    {
+        logger.LogWarning($"TODO: UnRegister to hub <{StatusHolder.HubServer}> with port <{_listeningPort}>");
+    }
+}

+ 114 - 0
CompressService/CompServ.Worker/Program.cs

@@ -0,0 +1,114 @@
+using CompServ.Worker;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
+using System;
+using CompServ.ClientLibrary;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http.Features;
+
+if (args.Length > 0)
+{
+    StatusHolder.HubServer = args[0];
+    if (args.Length > 1 && int.TryParse(args[1], out var ratioArg) && ratioArg > 1) StatusHolder.Ratio = ratioArg;
+}
+
+var builder = WebApplication.CreateBuilder(args);
+
+//控制台日志格式
+builder.Services.AddLogging(opt =>
+{
+    opt.AddSimpleConsole(p =>
+    {
+        p.TimestampFormat = "[dd HH:mm:ss] ";
+        p.SingleLine = true;
+        p.ColorBehavior = LoggerColorBehavior.Enabled;
+    });
+});
+
+builder.Services.Configure<LoggerFilterOptions>(options =>
+{
+    options.AddFilter((category, level) =>
+    {
+        if (level == LogLevel.Debug) return false;
+        if (level == LogLevel.Trace) return false;
+
+        if (category == "Microsoft.AspNetCore.Hosting.Diagnostics" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Routing.EndpointMiddleware" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Server.Kestrel" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Server.Kestrel.Http2" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Mvc.Infrastructure.ContentResultExecutor" && level == LogLevel.Information) return false;
+        if (category == "Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor" && level == LogLevel.Information) return false;
+
+        return true; // 其他日志保留
+    });
+});
+
+//配置HTTP1、HTTP2端口
+builder.WebHost.UseKestrel((_, kso) =>
+{
+    kso.ListenAnyIP(0, lo => lo.Protocols = HttpProtocols.Http2);
+    kso.ListenAnyIP(0, lo => lo.Protocols = HttpProtocols.Http1);
+});
+
+builder.Services.Configure<KestrelServerOptions>(options =>
+{
+    options.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 设置最大请求体大小 100MB
+});
+
+try
+{
+    Console.Title = "...";
+    builder.Services.AddHostedService<ConsoleTitleUpdateService>();
+}
+catch
+{
+    //EAT ERR
+}
+
+if (StatusHolder.HubServer != null) builder.Services.AddSingleton<HubRegister>();
+
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.Use(async (context, next) =>
+{
+    context.Request.EnableBuffering(); // this used to be EnableRewind
+    await next(context);
+});
+
+app.MapControllers();
+
+app.MapGet("/", () => "I am worker!");
+
+
+await app.StartAsync();
+// ↑↓ await app.RunAsync();
+
+{
+    var logger = app.Services.GetRequiredService<ILogger<Program>>();
+    logger.LogInformation("Self check...");
+
+    var svr = app.Services.GetRequiredService<IServer>();
+    var saf = svr.Features.GetRequiredFeature<IServerAddressesFeature>();
+    var listeningPort = new Uri(saf.Addresses.First()).Port;
+
+    var client = new CompServWorkerClient("http://localhost:" + listeningPort);
+    var r = await client.CheckAliveAsync();
+    logger.LogInformation("Self check resunt: " + r);
+}
+
+var hubRegister = app.Services.GetService<HubRegister>();
+await (hubRegister?.Register() ?? Task.CompletedTask);
+
+
+await app.WaitForShutdownAsync();
+await (hubRegister?.UnRegister() ?? Task.CompletedTask);

+ 11 - 0
CompressService/CompServ.Worker/Properties/launchSettings.json

@@ -0,0 +1,11 @@
+{
+  "profiles": {
+    "CompServ.Worker": {
+      "commandName": "Project",
+      "commandLineArgs": "http://192.68.5.33:7757 2"
+    },
+    "CompServ.Worker blank cli": {
+      "commandName": "Project"      
+    }
+  }
+}

+ 12 - 0
CompressService/CompServ.Worker/StatusHolder.cs

@@ -0,0 +1,12 @@
+namespace CompServ.Worker;
+
+internal static class StatusHolder
+{
+    public static string? HubServer;
+    public static int Ratio = 1;
+    public static int RequestIn;
+    public static int RequestSuccess;
+    public static int RequestOut;
+    public static long BytesIn;
+    public static long BytesOut;
+}

+ 73 - 0
CompressService/CompServ.Worker/WorkerController.cs

@@ -0,0 +1,73 @@
+using Microsoft.AspNetCore.Mvc;
+using XZ.NET;
+using static CompServ.CompServConst;
+
+namespace CompServ.Worker
+{
+    [ApiController]
+    public class WorkerController : ControllerBase
+    {
+        [HttpGet]
+        [Route(ApiPathWorkerRootForCheckAlive)]
+        public async Task<ActionResult> CheckAlive() => await Task.FromResult(Content(AliveMessageWorker));
+
+        [HttpPost]
+        [Route(ApiPathWorkerCompress)]
+        public async Task<ActionResult> Compress()
+        {
+            Interlocked.Increment(ref StatusHolder.RequestIn);
+            try
+            {
+                var req = await HttpContext.Request.ExtractCompressRequestAsync();
+
+                Interlocked.Add(ref StatusHolder.BytesIn, req.DataToCompress.Count);
+
+                var msOutput = new MemoryStream();
+                var xz = new XZOutputStream(msOutput, req.Threads, (uint)req.Level, true);
+                await xz.WriteAsync(req.DataToCompress);
+                xz.Close();
+
+                Interlocked.Add(ref StatusHolder.BytesOut, msOutput.Length);
+
+                msOutput.Position = 0;
+                var fileStreamResult = File(msOutput, "compressed/xz");
+                Interlocked.Increment(ref StatusHolder.RequestSuccess);
+                return fileStreamResult;
+            }
+            finally
+            {
+                Interlocked.Increment(ref StatusHolder.RequestOut);
+            }
+        }
+
+        [HttpPost]
+        [Route(ApiPathWorkerDecompress)]
+        public async Task<ActionResult> Decompress()
+        {
+            Interlocked.Increment(ref StatusHolder.RequestIn);
+            try
+            {
+                var msInput = HttpContext.Request.ContentLength.HasValue
+                    ? new MemoryStream((int)HttpContext.Request.ContentLength.Value)
+                    : new MemoryStream();
+
+                await HttpContext.Request.Body.CopyToAsync(msInput);
+                msInput.Position = 0;
+
+                var xz = new XZInputStream(msInput);
+                var msOutput = new MemoryStream();
+                await xz.CopyToAsync(msOutput);
+                xz.Close();
+
+                msOutput.Position = 0;
+                var fileStreamResult = File(msOutput, "binary/data");
+                Interlocked.Increment(ref StatusHolder.RequestSuccess);
+                return fileStreamResult;
+            }
+            finally
+            {
+                Interlocked.Increment(ref StatusHolder.RequestOut);
+            }
+        }
+    }
+}

+ 61 - 0
PictureMover/CoverForm.Designer.cs

@@ -0,0 +1,61 @@
+namespace PictureMover
+{
+    partial class CoverForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            components = new System.ComponentModel.Container();
+            timer1 = new System.Windows.Forms.Timer(components);
+            SuspendLayout();
+            // 
+            // timer1
+            // 
+            timer1.Enabled = true;
+            timer1.Interval = 500;
+            timer1.Tick += timer1_Tick;
+            // 
+            // Form2
+            // 
+            AllowDrop = true;
+            AutoScaleDimensions = new SizeF(7F, 17F);
+            AutoScaleMode = AutoScaleMode.Font;
+            ClientSize = new Size(800, 450);
+            FormBorderStyle = FormBorderStyle.None;
+            Name = "Form2";
+            Opacity = 0.01D;
+            ShowInTaskbar = false;
+            Text = "Form2";
+            DragDrop += Form_DragDrop;
+            DragEnter += Form_DragEnter;
+            ResumeLayout(false);
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Timer timer1;
+    }
+}

+ 192 - 0
PictureMover/CoverForm.cs

@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace PictureMover
+{
+    public partial class CoverForm : Form
+    {
+        private readonly MainForm _mainForm;
+
+        private string? _currentPath;
+
+        public CoverForm(MainForm mainForm)
+        {
+            _mainForm = mainForm;
+            InitializeComponent();
+            MouseWheel += Form_MouseWheel;
+            PreviewKeyDown += Form_PreviewKeyDown;
+        }
+
+        private async void Form_MouseWheel(object? sender, MouseEventArgs e)
+        {
+            Console.WriteLine("w " + e.Delta);
+            if (_currentPath == null) return;
+
+            if (e.Delta < 0)
+            {
+                //next
+                await NextFile(true);
+            }
+            else if (_currentPath != null)
+            {
+                //prev
+                await NextFile(false);
+            }
+        }
+
+        private async void Form_PreviewKeyDown(object? sender, PreviewKeyDownEventArgs e)
+        {
+            Console.WriteLine($"k {e.KeyCode}");
+            if (e.KeyCode == Keys.Escape) _mainForm.Close();
+
+            if (_currentPath == null) return;
+
+            if (e.KeyCode == Keys.Home) await JumpToBoundFile(true);
+            if (e.KeyCode == Keys.End) await JumpToBoundFile(false);
+            if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter || e.KeyCode == Keys.PageDown) await NextFile(true);
+            if (e.KeyCode == Keys.Back || e.KeyCode == Keys.PageUp) await NextFile(false);
+
+            if (e.KeyCode == Keys.B)
+            {
+                var src = _currentPath;
+                await NextFile(true);
+                MoveToOuterDotSuffixDir(src, "b");
+            }
+
+            if (e.KeyCode == Keys.S)
+            {
+                var src = _currentPath;
+                await NextFile(true);
+                MoveToOuterDotSuffixDir(src, "s");
+            }
+        }
+
+        private void Form_DragEnter(object sender, DragEventArgs e)
+        {
+            if (e.Data?.GetDataPresent(DataFormats.FileDrop) == true)
+            {
+                if (e.Data.GetData(DataFormats.FileDrop) is string[] arr)
+                {
+                    if (arr.Length == 1 && File.Exists(arr[0])) e.Effect = DragDropEffects.Link;
+                }
+            }
+        }
+
+        private async void Form_DragDrop(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetData(DataFormats.FileDrop) is string[] arr)
+            {
+                if (arr.Length == 1 && File.Exists(arr[0]))
+                {
+                    var path = arr[0];
+                    await ShowFileAsync(path);
+                }
+            }
+        }
+
+        private void timer1_Tick(object sender, EventArgs e)
+        {
+            var location = _mainForm.PointToScreen(Point.Empty);
+            if (Location != location) Location = location;
+
+            var size = _mainForm.ClientSize;
+            if (Size != size) Size = size;
+        }
+
+        private async Task NextFile(bool isNext)
+        {
+            if (_currentPath == null) return;
+            var nears = GetFilesAround(_currentPath);
+            if (nears.Length == 0) return;
+            var index = Array.IndexOf(nears, _currentPath);
+
+            if (isNext)
+            {
+                var lastIndex = nears.Length - 1;
+
+                if (index == lastIndex) return;
+
+                var nextIndex = index + 1;
+                if (nextIndex >= nears.Length) nextIndex = lastIndex;
+
+                var next = nears[nextIndex];
+                await ShowFileAsync(next);
+            }
+            else
+            {
+                if (index > 0)
+                {
+                    var nextIndex = index - 1;
+
+                    var next = nears[nextIndex];
+                    await ShowFileAsync(next);
+                }
+            }
+        }
+
+        private async Task JumpToBoundFile(bool first)
+        {
+            if (_currentPath == null) return;
+            var nears = GetFilesAround(_currentPath);
+            if (nears.Length == 0) return;
+            var nextIndex = first ? 0 : nears.Length - 1;
+            var next = nears[nextIndex];
+            await ShowFileAsync(next);
+        }
+
+        private async Task ShowFileAsync(string path)
+        {
+            await _mainForm.webView21.EnsureCoreWebView2Async();
+
+            var nears = GetFilesAround(path);
+            if (nears.Length == 0)
+            {
+                _mainForm.Text = "- no file -";
+                _mainForm.webView21.CoreWebView2.Navigate("about:blank");
+                _currentPath = null;
+                return;
+            }
+
+            var index = Array.IndexOf(nears, path);
+            _mainForm.Text = $"({nears.Length}/{index + 1}) {path}";
+            _mainForm.webView21.CoreWebView2.Navigate("file:///" + path);
+            _currentPath = path;
+        }
+
+        private void MoveToOuterDotSuffixDir(string src, string s)
+        {
+            if (File.Exists(src))
+            {
+                var dir = Path.GetDirectoryName(src);
+                var parentDir = Path.GetDirectoryName(dir);
+                var name = Path.GetFileName(dir) + "." + s;
+                var dstDir = Path.Combine(parentDir, name);
+                if (Directory.Exists(dstDir) == false) Directory.CreateDirectory(dstDir);
+                var dst = Path.Combine(dstDir, Path.GetFileName(src));
+                File.Move(src, dst);
+            }
+
+            if (_currentPath != null)
+            {
+                var nears = GetFilesAround(_currentPath);
+                var index = Array.IndexOf(nears, _currentPath);
+                _mainForm.Text = $"({nears.Length}/{index + 1}) {_currentPath}";
+            }
+        }
+
+        private string[] GetFilesAround(string filePath)
+        {
+            var ext = Path.GetExtension(filePath);
+            var dir = Path.GetDirectoryName(filePath);
+            return Directory.GetFiles(dir, "*" + ext);
+        }
+    }
+}

+ 123 - 0
PictureMover/CoverForm.resx

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema 
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>

+ 64 - 0
PictureMover/MainForm.Designer.cs

@@ -0,0 +1,64 @@
+namespace PictureMover
+{
+    partial class MainForm
+    {
+        /// <summary>
+        ///  Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        ///  Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        ///  Required method for Designer support - do not modify
+        ///  the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
+            ((System.ComponentModel.ISupportInitialize)webView21).BeginInit();
+            SuspendLayout();
+            // 
+            // webView21
+            // 
+            webView21.AllowExternalDrop = true;
+            webView21.CreationProperties = null;
+            webView21.DefaultBackgroundColor = Color.White;
+            webView21.Dock = DockStyle.Fill;
+            webView21.Location = new Point(0, 0);
+            webView21.Name = "webView21";
+            webView21.Size = new Size(800, 450);
+            webView21.TabIndex = 0;
+            webView21.ZoomFactor = 1D;
+            // 
+            // Form1
+            // 
+            AutoScaleDimensions = new SizeF(7F, 17F);
+            AutoScaleMode = AutoScaleMode.Font;
+            ClientSize = new Size(800, 450);
+            Controls.Add(webView21);
+            Name = "Form1";
+            Text = "Form1";
+            Shown += Form1_Shown;
+            ((System.ComponentModel.ISupportInitialize)webView21).EndInit();
+            ResumeLayout(false);
+        }
+
+        #endregion
+
+        public Microsoft.Web.WebView2.WinForms.WebView2 webView21;
+    }
+}

+ 24 - 0
PictureMover/MainForm.cs

@@ -0,0 +1,24 @@
+namespace PictureMover
+{
+    public partial class MainForm : Form
+    {
+        private CoverForm _form2;
+
+        public MainForm()
+        {
+            InitializeComponent();
+            _form2 = new CoverForm(this);
+        }
+
+        private void Form1_Enter(object? sender, EventArgs e)
+        {
+            _form2.Focus();
+        }
+
+        private void Form1_Shown(object sender, EventArgs e)
+        {
+            _form2.Show(this);
+            Enter += Form1_Enter;
+        }
+    }
+}

+ 120 - 0
PictureMover/MainForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema 
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 15 - 0
PictureMover/PictureMover.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <OutputType>exe</OutputType>
+    <TargetFramework>net8.0-windows</TargetFramework>
+    <Nullable>enable</Nullable>
+    <UseWindowsForms>true</UseWindowsForms>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2210.55" />
+  </ItemGroup>
+
+</Project>

+ 17 - 0
PictureMover/Program.cs

@@ -0,0 +1,17 @@
+namespace PictureMover
+{
+    internal static class Program
+    {
+        /// <summary>
+        ///  The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            // To customize application configuration such as set high DPI settings or default font,
+            // see https://aka.ms/applicationconfiguration.
+            ApplicationConfiguration.Initialize();
+            Application.Run(new MainForm());
+        }
+    }
+}

+ 12 - 0
PictureMover/Properties/launchSettings.json

@@ -0,0 +1,12 @@
+{
+  "profiles": {
+    "PictureMover": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "applicationUrl": "https://localhost:23694;http://localhost:23695"
+    }
+  }
+}

+ 71 - 0
StrangeTools.sln

@@ -48,6 +48,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InstallerIsSuck", "Installe
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileCharsetConvert", "FileCharsetConvert\FileCharsetConvert.csproj", "{50E8583A-2B8B-44C0-A397-95A0BD1B0807}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CompressService", "CompressService", "{BEE3ED11-286D-4A11-BABA-431FAFF39B51}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompServ.ClientLibrary", "CompressService\CompServ.ClientLibrary\CompServ.ClientLibrary.csproj", "{DF433BFC-1E7C-400A-9212-35E444BDFDE5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompServ.Hub", "CompressService\CompServ.Hub\CompServ.Hub.csproj", "{C8FEF36D-4B98-4105-A8B1-53E005505980}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompServ.Shared", "CompressService\CompServ.Shared\CompServ.Shared.csproj", "{ED6FA939-DC14-4CAF-910C-5860117FF9A4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompServ.Tests", "CompressService\CompServ.Tests\CompServ.Tests.csproj", "{91C7F45F-D3F7-4AF1-BF59-5FC30DD5184F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompServ.Worker", "CompressService\CompServ.Worker\CompServ.Worker.csproj", "{2F7ED95A-58ED-43CF-A934-D591C822C5EC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Older", "Older", "{84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PictureMover", "PictureMover\PictureMover.csproj", "{C94C8E6E-1051-49AD-A539-E0BC8D365C59}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "A2024", "A2024", "{3120ADE6-C606-42F1-9AA8-B7F1A8933CD7}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -134,10 +152,63 @@ Global
 		{50E8583A-2B8B-44C0-A397-95A0BD1B0807}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{50E8583A-2B8B-44C0-A397-95A0BD1B0807}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{50E8583A-2B8B-44C0-A397-95A0BD1B0807}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DF433BFC-1E7C-400A-9212-35E444BDFDE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DF433BFC-1E7C-400A-9212-35E444BDFDE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DF433BFC-1E7C-400A-9212-35E444BDFDE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DF433BFC-1E7C-400A-9212-35E444BDFDE5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C8FEF36D-4B98-4105-A8B1-53E005505980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C8FEF36D-4B98-4105-A8B1-53E005505980}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C8FEF36D-4B98-4105-A8B1-53E005505980}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C8FEF36D-4B98-4105-A8B1-53E005505980}.Release|Any CPU.Build.0 = Release|Any CPU
+		{ED6FA939-DC14-4CAF-910C-5860117FF9A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{ED6FA939-DC14-4CAF-910C-5860117FF9A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{ED6FA939-DC14-4CAF-910C-5860117FF9A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{ED6FA939-DC14-4CAF-910C-5860117FF9A4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{91C7F45F-D3F7-4AF1-BF59-5FC30DD5184F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{91C7F45F-D3F7-4AF1-BF59-5FC30DD5184F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{91C7F45F-D3F7-4AF1-BF59-5FC30DD5184F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{91C7F45F-D3F7-4AF1-BF59-5FC30DD5184F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2F7ED95A-58ED-43CF-A934-D591C822C5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2F7ED95A-58ED-43CF-A934-D591C822C5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2F7ED95A-58ED-43CF-A934-D591C822C5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2F7ED95A-58ED-43CF-A934-D591C822C5EC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C94C8E6E-1051-49AD-A539-E0BC8D365C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C94C8E6E-1051-49AD-A539-E0BC8D365C59}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C94C8E6E-1051-49AD-A539-E0BC8D365C59}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C94C8E6E-1051-49AD-A539-E0BC8D365C59}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{7E8C017E-78B6-4AC1-B042-1BDFAF461EE7} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{CE26341A-CE0B-4B1C-97A5-BA7E8A9ACFDD} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{7A3E4609-7FDD-4CE9-8671-82F7049F51B0} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{7EDCED25-C644-4545-BDF1-E6D13ECB3C0D} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{FAF6A76A-BE48-435B-B45A-A5AEB9A48F01} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{9FA46C6F-A981-4303-8256-EC9B6536BFE5} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{FBEB7CB4-4D77-4424-A1E6-C56617AA2F02} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{261D52DE-F168-41D2-AD50-78B5B1D801E3} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{07AF01F0-8941-46AA-AC2C-148E76156AA7} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{C0A07E8F-37E4-4550-9D85-0073D494B6BA} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{1299B566-FB2B-46D7-B6F4-BC0D72E10979} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{A01E5694-1C4D-4E11-A73A-70AEE4D98B2E} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{62A1F458-B9ED-4443-8B94-E24ECF57AC9B} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{9670672F-A051-46D4-AF50-B71E3E9815E2} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{158AC9F8-3798-4F08-8028-3F261180BB0B} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{6A1E8847-2290-462F-95D8-F2517C377130} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{F5E85481-3D66-4613-8A38-9FB863248D18} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{4DF53F11-E296-4E40-83AB-B91B51750B81} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{443F2A10-DF96-4EA6-9CD9-6C2E2C782D3B} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{50E8583A-2B8B-44C0-A397-95A0BD1B0807} = {84ACDDE9-A7B5-4528-A5BA-7BF76B0E6BAF}
+		{BEE3ED11-286D-4A11-BABA-431FAFF39B51} = {3120ADE6-C606-42F1-9AA8-B7F1A8933CD7}
+		{DF433BFC-1E7C-400A-9212-35E444BDFDE5} = {BEE3ED11-286D-4A11-BABA-431FAFF39B51}
+		{C8FEF36D-4B98-4105-A8B1-53E005505980} = {BEE3ED11-286D-4A11-BABA-431FAFF39B51}
+		{ED6FA939-DC14-4CAF-910C-5860117FF9A4} = {BEE3ED11-286D-4A11-BABA-431FAFF39B51}
+		{91C7F45F-D3F7-4AF1-BF59-5FC30DD5184F} = {BEE3ED11-286D-4A11-BABA-431FAFF39B51}
+		{2F7ED95A-58ED-43CF-A934-D591C822C5EC} = {BEE3ED11-286D-4A11-BABA-431FAFF39B51}
+		{C94C8E6E-1051-49AD-A539-E0BC8D365C59} = {3120ADE6-C606-42F1-9AA8-B7F1A8933CD7}
+	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {017A8C58-F476-47E7-9CBE-077A98A76AB4}
 	EndGlobalSection

+ 1 - 0
StrangeTools.sln.DotSettings

@@ -1,3 +1,4 @@
 <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/UserDictionary/Words/=Redirector/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Serv/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Wasapi/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>