diff --git a/Tagashi File Hub.sln b/Tagashi File Hub.sln index 4af8c31..226467a 100644 --- a/Tagashi File Hub.sln +++ b/Tagashi File Hub.sln @@ -5,7 +5,13 @@ VisualStudioVersion = 17.7.33913.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tagashi File Hub", "src\Tagashi File Hub\Tagashi File Hub.csproj", "{A4C5EF53-50CC-49D4-B750-C7FB2F848455}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagashiFileHub.Core", "src\TagashiFileHub.Core\TagashiFileHub.Core.csproj", "{39536C05-9C87-44CA-A753-6A317F252666}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagashiFileHub.Core", "src\TagashiFileHub.Core\TagashiFileHub.Core.csproj", "{39536C05-9C87-44CA-A753-6A317F252666}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagashiFIleHub.Service", "src\TagashiFIleHub.Service\TagashiFIleHub.Service.csproj", "{368C0084-EBB4-4A9A-9E01-84C48CF184D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagashiFileHub.Communication.Abstractions", "src\TagashiFileHub.Communication.Abstractions\TagashiFileHub.Communication.Abstractions.csproj", "{587B3D46-1114-4A84-8F33-4EEA501AE613}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagashiFileHub.Communication.IPC", "src\TagashiFileHub.Communication.IPC\TagashiFileHub.Communication.IPC.csproj", "{64A274BB-678E-42C5-BD69-6BF7F04AD026}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +27,18 @@ Global {39536C05-9C87-44CA-A753-6A317F252666}.Debug|Any CPU.Build.0 = Debug|Any CPU {39536C05-9C87-44CA-A753-6A317F252666}.Release|Any CPU.ActiveCfg = Release|Any CPU {39536C05-9C87-44CA-A753-6A317F252666}.Release|Any CPU.Build.0 = Release|Any CPU + {368C0084-EBB4-4A9A-9E01-84C48CF184D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {368C0084-EBB4-4A9A-9E01-84C48CF184D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {368C0084-EBB4-4A9A-9E01-84C48CF184D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {368C0084-EBB4-4A9A-9E01-84C48CF184D9}.Release|Any CPU.Build.0 = Release|Any CPU + {587B3D46-1114-4A84-8F33-4EEA501AE613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {587B3D46-1114-4A84-8F33-4EEA501AE613}.Debug|Any CPU.Build.0 = Debug|Any CPU + {587B3D46-1114-4A84-8F33-4EEA501AE613}.Release|Any CPU.ActiveCfg = Release|Any CPU + {587B3D46-1114-4A84-8F33-4EEA501AE613}.Release|Any CPU.Build.0 = Release|Any CPU + {64A274BB-678E-42C5-BD69-6BF7F04AD026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64A274BB-678E-42C5-BD69-6BF7F04AD026}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64A274BB-678E-42C5-BD69-6BF7F04AD026}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64A274BB-678E-42C5-BD69-6BF7F04AD026}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/TagashiFIleHub.Service/Program.cs b/src/TagashiFIleHub.Service/Program.cs new file mode 100644 index 0000000..83960bc --- /dev/null +++ b/src/TagashiFIleHub.Service/Program.cs @@ -0,0 +1,14 @@ +using TagashiFileHub.Communication.IPC; +using TagashiFIleHub.Service; + +var host = Host.CreateDefaultBuilder(args) + .UseWindowsService(config => { config.ServiceName = "Tagashi File Hub"; }) + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddHostedService(); + services.AddLogging(); + }) + .Build(); + +host.Run(); \ No newline at end of file diff --git a/src/TagashiFIleHub.Service/Properties/launchSettings.json b/src/TagashiFIleHub.Service/Properties/launchSettings.json new file mode 100644 index 0000000..9403105 --- /dev/null +++ b/src/TagashiFIleHub.Service/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "TagashiFIleHub.Service": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/TagashiFIleHub.Service/TagashiFIleHub.Service.csproj b/src/TagashiFIleHub.Service/TagashiFIleHub.Service.csproj new file mode 100644 index 0000000..dbfb89a --- /dev/null +++ b/src/TagashiFIleHub.Service/TagashiFIleHub.Service.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + dotnet-TagashiFIleHub.Service-f42ced2e-207a-4058-b846-f77037e5ac5a + + + + + + + + + + + + diff --git a/src/TagashiFIleHub.Service/Worker.cs b/src/TagashiFIleHub.Service/Worker.cs new file mode 100644 index 0000000..c47f469 --- /dev/null +++ b/src/TagashiFIleHub.Service/Worker.cs @@ -0,0 +1,28 @@ +using TagashiFileHub.Communication.IPC; + +namespace TagashiFIleHub.Service; + +internal class Worker : BackgroundService +{ + private readonly ILogger? _logger; + private readonly TagashiServer _server; + + public Worker(ILogger? logger, TagashiServer server) + { + _logger = logger; + _server = server; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger?.LogInformation("Starting service worker"); + + await _server.StartAsync("Service", stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + _logger?.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } +} \ No newline at end of file diff --git a/src/TagashiFIleHub.Service/appsettings.Development.json b/src/TagashiFIleHub.Service/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/src/TagashiFIleHub.Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/TagashiFIleHub.Service/appsettings.json b/src/TagashiFIleHub.Service/appsettings.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/src/TagashiFIleHub.Service/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/TagashiFileHub.Communication.Abstractions/ITagashiClient.cs b/src/TagashiFileHub.Communication.Abstractions/ITagashiClient.cs new file mode 100644 index 0000000..d7364e3 --- /dev/null +++ b/src/TagashiFileHub.Communication.Abstractions/ITagashiClient.cs @@ -0,0 +1,5 @@ +namespace TagashiFileHub.Communication.Abstractions; + +public interface ITagashiClient +{ +} \ No newline at end of file diff --git a/src/TagashiFileHub.Communication.Abstractions/ITagashiServer.cs b/src/TagashiFileHub.Communication.Abstractions/ITagashiServer.cs new file mode 100644 index 0000000..4eae804 --- /dev/null +++ b/src/TagashiFileHub.Communication.Abstractions/ITagashiServer.cs @@ -0,0 +1,5 @@ +namespace TagashiFileHub.Communication.Abstractions; + +public interface ITagashiServer +{ +} \ No newline at end of file diff --git a/src/TagashiFileHub.Communication.Abstractions/TagashiFileHub.Communication.Abstractions.csproj b/src/TagashiFileHub.Communication.Abstractions/TagashiFileHub.Communication.Abstractions.csproj new file mode 100644 index 0000000..cfadb03 --- /dev/null +++ b/src/TagashiFileHub.Communication.Abstractions/TagashiFileHub.Communication.Abstractions.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/src/TagashiFileHub.Communication.IPC/IpcUtils.cs b/src/TagashiFileHub.Communication.IPC/IpcUtils.cs new file mode 100644 index 0000000..2059d82 --- /dev/null +++ b/src/TagashiFileHub.Communication.IPC/IpcUtils.cs @@ -0,0 +1,11 @@ +namespace TagashiFileHub.Communication.IPC; + +internal static class IpcUtils +{ + public static string PipeNamePrefix => "TagashiFileHub"; + + public static string PipeName(string name) + { + return $"{PipeNamePrefix}.{name}"; + } +} \ No newline at end of file diff --git a/src/TagashiFileHub.Communication.IPC/TagashiClient.cs b/src/TagashiFileHub.Communication.IPC/TagashiClient.cs new file mode 100644 index 0000000..f126475 --- /dev/null +++ b/src/TagashiFileHub.Communication.IPC/TagashiClient.cs @@ -0,0 +1,63 @@ +using System.IO.Pipes; +using Microsoft.Extensions.Logging; +using TagashiFileHub.Communication.Abstractions; + +namespace TagashiFileHub.Communication.IPC; + +public class TagashiClient : ITagashiClient, IDisposable +{ + private readonly ILogger? _logger; + + public TagashiClient(ILogger? logger) + { + _logger = logger; + } + + private CancellationTokenSource DisconnectingSource { get; } = new(); + + private NamedPipeClientStream? ClientPipe { get; set; } + + public void Dispose() + { + TryDisconnectCurrentPipe(); + } + + private void TryDisconnectCurrentPipe() + { + _logger?.LogInformation("Closing current pipe connection if existing."); + DisconnectingSource.Cancel(false); + ClientPipe?.Close(); + ClientPipe?.Dispose(); + ClientPipe = null; + } + + public async Task ConnectAsync(string pipeName) + { + if (ClientPipe is not null) + { + _logger?.LogWarning("Client pipe already opened."); + TryDisconnectCurrentPipe(); + } + + _logger?.LogInformation($"Creating pipe for \".\" with name \"{pipeName}\""); + ClientPipe = new NamedPipeClientStream(".", IpcUtils.PipeName(pipeName), PipeDirection.InOut, PipeOptions.None); + + await ClientPipe.ConnectAsync().ConfigureAwait(false); + + DisconnectingSource.TryReset(); + _ = NewListenTask(ClientPipe, DisconnectingSource.Token); + } + + private static Task NewListenTask(NamedPipeClientStream clientPipe, CancellationToken cancellationToken) + { + return Task.Factory.StartNew(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + using var reader = new StreamReader(clientPipe); + + var message = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); + } +} \ No newline at end of file diff --git a/src/TagashiFileHub.Communication.IPC/TagashiFileHub.Communication.IPC.csproj b/src/TagashiFileHub.Communication.IPC/TagashiFileHub.Communication.IPC.csproj new file mode 100644 index 0000000..78c7b9b --- /dev/null +++ b/src/TagashiFileHub.Communication.IPC/TagashiFileHub.Communication.IPC.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + diff --git a/src/TagashiFileHub.Communication.IPC/TagashiServer.cs b/src/TagashiFileHub.Communication.IPC/TagashiServer.cs new file mode 100644 index 0000000..69417b4 --- /dev/null +++ b/src/TagashiFileHub.Communication.IPC/TagashiServer.cs @@ -0,0 +1,66 @@ +using System.Collections.Concurrent; +using System.IO.Pipes; +using Microsoft.Extensions.Logging; +using TagashiFileHub.Communication.Abstractions; + +namespace TagashiFileHub.Communication.IPC; + +public class TagashiServer : ITagashiServer +{ + private readonly ConcurrentBag _clients = new(); + private readonly ILogger? _logger; + + public TagashiServer(ILogger? logger = null) + { + _logger = logger; + } + + public async Task StartAsync(string pipeName, CancellationToken cancellationToken) + { + await Task.Run(async () => + { + _logger?.LogInformation($"Starting up tagashi server for pipe \"{pipeName}\""); + while (!cancellationToken.IsCancellationRequested) + { + await using var serverPipe = new NamedPipeServerStream(IpcUtils.PipeName(pipeName), PipeDirection.InOut, + 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + await serverPipe.WaitForConnectionAsync(cancellationToken); + + using var reader = new StreamReader(serverPipe); + await using var writer = new StreamWriter(serverPipe) { AutoFlush = true }; + + _clients.Add(writer); + + await HandleClientInternalAsync(reader, writer, cancellationToken).ConfigureAwait(false); + } + + _logger?.LogInformation($"Shut down tagashi server for pipe \"{pipeName}\""); + }, cancellationToken).ConfigureAwait(false); + } + + private async Task HandleClientInternalAsync(StreamReader reader, StreamWriter writer, + CancellationToken cancellationToken) + { + try + { + _logger?.LogInformation("New client connected"); + + while (!cancellationToken.IsCancellationRequested) + { + var message = await reader.ReadLineAsync(cancellationToken); + if (message == null) + break; + } + } + catch + { + // TODO + } + finally + { + _clients.TryTake(out writer!); + writer.Close(); + _logger?.LogInformation("Client disconnected"); + } + } +} \ No newline at end of file