From 8897a566b3227f2c793988d045b43ca064fbdff5 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 27 Jan 2022 01:36:30 +0100 Subject: [PATCH] - Add command system - Add basic file sharing --- .../FileProcessing/FileCompressor.cs | 20 ++- .../FileProcessing/FileValidator.cs | 10 ++ .../FileProcessing/FilesManager.cs | 89 ++++++++++ .../FileProcessing/HyperFileInfo.cs | 13 ++ .../HyperbolicDownloader.csproj | 1 + .../Networking/NetworkClient.cs | 66 ++++++- .../Networking/NetworkSocket.cs | 6 +- HyperbolicDownloader/Program.cs | 55 ++++-- .../UserInterface/InputHandler.cs | 165 ++++++++++++++++++ .../{ => UserInterface}/Setup.cs | 2 +- 10 files changed, 398 insertions(+), 29 deletions(-) create mode 100644 HyperbolicDownloader/FileProcessing/FilesManager.cs create mode 100644 HyperbolicDownloader/FileProcessing/HyperFileInfo.cs create mode 100644 HyperbolicDownloader/UserInterface/InputHandler.cs rename HyperbolicDownloader/{ => UserInterface}/Setup.cs (97%) diff --git a/HyperbolicDownloader/FileProcessing/FileCompressor.cs b/HyperbolicDownloader/FileProcessing/FileCompressor.cs index 03fd772..9b05462 100644 --- a/HyperbolicDownloader/FileProcessing/FileCompressor.cs +++ b/HyperbolicDownloader/FileProcessing/FileCompressor.cs @@ -2,9 +2,9 @@ namespace HyperbolicDownloader.FileProcessing; -internal class FileCompressor +internal static class FileCompressor { - private static void CompressFile(string inputFilePath, string compressedFilePath) + public static void CompressFile(string inputFilePath, string compressedFilePath) { using FileStream originalFileStream = File.Open(inputFilePath, FileMode.Open); using FileStream compressedFileStream = File.Create(compressedFilePath); @@ -12,11 +12,25 @@ internal class FileCompressor originalFileStream.CopyTo(compressor); } - private static void DecompressFile(string compressedFilePath, string outputFilePath) + public static void DecompressFile(string compressedFilePath, string outputFilePath) { using FileStream compressedFileStream = File.Open(compressedFilePath, FileMode.Open); using FileStream outputFileStream = File.Create(outputFilePath); using GZipStream? decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress); decompressor.CopyTo(outputFileStream); } + + public static IEnumerable ReadChunks(string path, int chunkSize) + { + byte[] buffer = new byte[chunkSize]; + int bytesRead; + using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read)) + using (BufferedStream bs = new BufferedStream(fs)) + { + while ((bytesRead = bs.Read(buffer, 0, chunkSize)) != 0) + { + yield return buffer; + } + } + } } \ No newline at end of file diff --git a/HyperbolicDownloader/FileProcessing/FileValidator.cs b/HyperbolicDownloader/FileProcessing/FileValidator.cs index 06f44b5..0bacae9 100644 --- a/HyperbolicDownloader/FileProcessing/FileValidator.cs +++ b/HyperbolicDownloader/FileProcessing/FileValidator.cs @@ -12,8 +12,18 @@ internal class FileValidator return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } + public static string CalculateHash(string filePath) + { + return CalculateHashAsync(filePath).GetAwaiter().GetResult(); + } + public static async Task ValidateHashAsync(string filePath, string hash) { return await CalculateHashAsync(filePath) == hash; } + + public static bool ValidateHash(string filePath, string hash) + { + return CalculateHash(filePath) == hash; + } } \ No newline at end of file diff --git a/HyperbolicDownloader/FileProcessing/FilesManager.cs b/HyperbolicDownloader/FileProcessing/FilesManager.cs new file mode 100644 index 0000000..a287855 --- /dev/null +++ b/HyperbolicDownloader/FileProcessing/FilesManager.cs @@ -0,0 +1,89 @@ +using System.Text.Json; + +namespace HyperbolicDownloader.FileProcessing; + +internal class FilesManager +{ + private readonly List files = new List(); + + public bool TryAdd(string filePath, out HyperFileInfo? fileInfo, out string? errorMessage) + { + string fullPath = Path.GetFullPath(filePath); + + if (!File.Exists(fullPath)) + { + fileInfo = null; + errorMessage = "Invalid file path!"; + return false; + } + + string hash = FileValidator.CalculateHash(filePath); + + if (Contains(hash)) + { + fileInfo = null; + errorMessage = "File already tracked!"; + return false; + } + + fileInfo = new HyperFileInfo(hash, fullPath); + + files.Add(fileInfo); + + SaveFiles(); + + errorMessage = null; + return true; + } + + public void AddRange(IEnumerable fileInfos) + { + foreach (HyperFileInfo fileInfo in fileInfos) + { + if (File.Exists(fileInfo.FilePath) && !Contains(fileInfo.Hash)) + { + files.Add(fileInfo); + } + } + + SaveFiles(); + } + + public bool TryGet(string hash, out HyperFileInfo? fileInfo) + { + if (Contains(hash)) + { + fileInfo = files.First(x => x.Hash == hash); + return true; + } + else + { + fileInfo = null; + return false; + } + } + + public bool TryRemove(string hash) + { + files.RemoveAll(f => f.Hash == hash); + + SaveFiles(); + + return true; + } + + public bool Contains(string? hash) + { + return files.Any(f => f.Hash == hash); + } + + public List ToList() + { + return files.ToList(); + } + + private void SaveFiles() + { + File.WriteAllText(Program.FilesInfoPath, JsonSerializer.Serialize(files)); + } +} \ No newline at end of file diff --git a/HyperbolicDownloader/FileProcessing/HyperFileInfo.cs b/HyperbolicDownloader/FileProcessing/HyperFileInfo.cs new file mode 100644 index 0000000..5896956 --- /dev/null +++ b/HyperbolicDownloader/FileProcessing/HyperFileInfo.cs @@ -0,0 +1,13 @@ +namespace HyperbolicDownloader.FileProcessing; + +internal class HyperFileInfo +{ + public string Hash { get; set; } = string.Empty; + public string FilePath { get; set; } = string.Empty; + + public HyperFileInfo(string hash, string filePath) + { + Hash = hash; + FilePath = filePath; + } +} \ No newline at end of file diff --git a/HyperbolicDownloader/HyperbolicDownloader.csproj b/HyperbolicDownloader/HyperbolicDownloader.csproj index 6246172..3546f3f 100644 --- a/HyperbolicDownloader/HyperbolicDownloader.csproj +++ b/HyperbolicDownloader/HyperbolicDownloader.csproj @@ -8,6 +8,7 @@ + diff --git a/HyperbolicDownloader/Networking/NetworkClient.cs b/HyperbolicDownloader/Networking/NetworkClient.cs index dc96712..4dc9574 100644 --- a/HyperbolicDownloader/Networking/NetworkClient.cs +++ b/HyperbolicDownloader/Networking/NetworkClient.cs @@ -1,19 +1,23 @@ -using System.Net; +using HyperbolicDownloader.FileProcessing; + +using System.Net; using System.Net.Sockets; using System.Text; using System.Text.Json; -namespace HyperbolicDownloader +namespace HyperbolicDownloader.Networking { internal class NetworkClient { public bool IsListening { get; private set; } = false; private TcpListener? tcpListener; + private FilesManager filesManager; private readonly Dictionary events = new(); - public NetworkClient() + public NetworkClient(FilesManager filesManager) { + this.filesManager = filesManager; } public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data) @@ -93,22 +97,28 @@ namespace HyperbolicDownloader tcpListener.Start(); IsListening = true; - Task.Run(() => + Task.Run(async () => { while (IsListening) { try { - using TcpClient client = tcpListener.AcceptTcpClient(); + TcpClient client = tcpListener.AcceptTcpClient(); NetworkStream nwStream = client.GetStream(); byte[] buffer = new byte[client.ReceiveBufferSize]; - int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize); + int bytesRead = await nwStream.ReadAsync(buffer, 0, client.ReceiveBufferSize); string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead); + if (dataReceived.StartsWith("Download")) + { + _ = Upload(client, dataReceived[8..]); + continue; + } if (string.IsNullOrWhiteSpace(dataReceived)) { + client.Close(); continue; } @@ -128,6 +138,8 @@ namespace HyperbolicDownloader method?.DynamicInvoke(this, eventArgs); } + + client.Close(); } catch (SocketException ex) { @@ -141,6 +153,48 @@ namespace HyperbolicDownloader }); } + private async Task Upload(TcpClient client, string hash) + { + byte[] bytesToSend; + hash = hash.Trim(); + NetworkStream nwStream = client.GetStream(); + + if (filesManager.TryGet(hash, out HyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath)) + { + if (hash != await FileValidator.CalculateHashAsync(hyperFileInfo.FilePath)) + { + bytesToSend = Encoding.ASCII.GetBytes("Hash does not match!"); + await nwStream.WriteAsync(bytesToSend); + return; + } + + //string compressedFilePath = $"{hash + DateTime.Now.Millisecond}.temp"; + + //FileCompressor.CompressFile(hyperFileInfo.FilePath, compressedFilePath); + + FileInfo compressedFileInfo = new FileInfo(hyperFileInfo.FilePath); + + bytesToSend = Encoding.ASCII.GetBytes(compressedFileInfo.Length.ToString()); + await nwStream.WriteAsync(bytesToSend); + + foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 1000)) + { + if (chunk is not null) + { + await nwStream.WriteAsync(chunk); + } + } + + // File.Delete(compressedFilePath); + } + else + { + bytesToSend = Encoding.ASCII.GetBytes("File not found!"); + await nwStream.WriteAsync(bytesToSend); + } + client.Close(); + } + public void StopListening() { tcpListener?.Stop(); diff --git a/HyperbolicDownloader/Networking/NetworkSocket.cs b/HyperbolicDownloader/Networking/NetworkSocket.cs index d676b83..0d1a396 100644 --- a/HyperbolicDownloader/Networking/NetworkSocket.cs +++ b/HyperbolicDownloader/Networking/NetworkSocket.cs @@ -2,12 +2,12 @@ internal class NetworkSocket { + public string IPAddress { get; set; } + public int Port { get; set; } + public NetworkSocket(string ipAddress, int port) { IPAddress = ipAddress; Port = port; } - - public string IPAddress { get; set; } - public int Port { get; set; } } \ No newline at end of file diff --git a/HyperbolicDownloader/Program.cs b/HyperbolicDownloader/Program.cs index 1a9780c..c433f42 100644 --- a/HyperbolicDownloader/Program.cs +++ b/HyperbolicDownloader/Program.cs @@ -1,5 +1,7 @@  +using HyperbolicDownloader.FileProcessing; using HyperbolicDownloader.Networking; +using HyperbolicDownloader.UserInterface; using Open.Nat; @@ -15,15 +17,16 @@ namespace HyperbolicDownloader; internal class Program { private const int BroadcastPort = 2155; - public const string HostsFilePath = "Hosts.json"; + public const string FilesInfoPath = "Files.json"; private static int publicPort; private static readonly int privatePort = 3055; private static NatDevice? device; private static Mapping? portMapping; - private static readonly HostsManager hosts = new(); - private static readonly NetworkClient networkClient = new(); + private static readonly HostsManager hostsManager = new(); + private static readonly FilesManager filesManager = new FilesManager(); + private static readonly NetworkClient networkClient = new(filesManager); private static readonly Random random = new Random(); private static async Task Main() @@ -33,7 +36,13 @@ internal class Program if (File.Exists(HostsFilePath)) { string hostsJson = await File.ReadAllTextAsync(HostsFilePath); - hosts.AddRange(JsonSerializer.Deserialize>(hostsJson) ?? new()); + hostsManager.AddRange(JsonSerializer.Deserialize>(hostsJson) ?? new()); + } + + if (File.Exists(FilesInfoPath)) + { + string filesJson = await File.ReadAllTextAsync(FilesInfoPath); + filesManager.AddRange(JsonSerializer.Deserialize>(filesJson) ?? new()); } Console.WriteLine("Searching for a UPnP/NAT-PMP device..."); @@ -48,6 +57,8 @@ internal class Program { networkClient.ListenTo("GetHostsList", GetHostList); networkClient.ListenTo>("DiscoverAnswer", DiscoverAnswer); + networkClient.ListenTo("Message", ReciveMessage); + networkClient.ListenTo("HasFile", HasFile); networkClient.StartListening(privatePort); } catch (SocketException ex) @@ -62,31 +73,33 @@ internal class Program broadcastClient.Send(BroadcastPort, privatePort.ToString()); await Task.Delay(5000); - if (hosts.Count > 0) + if (hostsManager.Count > 0) { Console.WriteLine("Checking if hosts are active..."); - hosts.RemoveInactiveHosts(); + hostsManager.RemoveInactiveHosts(); } - if (hosts.Count == 0) + if (hostsManager.Count == 0) { - hosts.AddRange(await Setup.ConfigureHost()); + hostsManager.AddRange(await UserInterface.Setup.ConfigureHost()); Console.WriteLine("Checking if hosts are active..."); - hosts.RemoveInactiveHosts(); + hostsManager.RemoveInactiveHosts(); } - Console.WriteLine($"{hosts.Count} active host(s)."); + Console.WriteLine($"{hostsManager.Count} active host(s)."); Console.WriteLine("Starting broadcast listener..."); broadcastClient.StartListening(BroadcastPort); broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived; ConsoleExt.WriteLine("Done", ConsoleColor.Green); - await Task.Delay(-1); + + new InputHandler(hostsManager, filesManager).ReadInput(); + ClosePorts(); } private static async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs) { Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {recivedEventArgs.IPEndPoint.Address}"); - List hostsToSend = hosts.ToList(); + List hostsToSend = hostsManager.ToList(); NetworkSocket? localSocket = GetLocalSocket(); @@ -102,7 +115,7 @@ internal class Program if (success) { - hosts.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort)); + hostsManager.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort)); try { await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend); @@ -117,12 +130,17 @@ internal class Program private static void DiscoverAnswer(object? sender, MessageRecivedEventArgs> recivedEventArgs) { Console.WriteLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s)."); - hosts.AddRange(recivedEventArgs.Data); + hostsManager.AddRange(recivedEventArgs.Data); + } + + private static void ReciveMessage(object? sender, MessageRecivedEventArgs recivedEventArgs) + { + Console.WriteLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}."); } private static async void GetHostList(object? sender, MessageRecivedEventArgs recivedEventArgs) { - List hostsToSend = hosts.ToList(); + List hostsToSend = hostsManager.ToList(); NetworkSocket? localSocket = GetLocalSocket(); @@ -136,12 +154,17 @@ internal class Program if (recivedEventArgs.Data.Port != 0) { - hosts.Add(recivedEventArgs.Data); + hostsManager.Add(recivedEventArgs.Data); } await recivedEventArgs.SendResponseAsync(hostsToSend); } + private static async void HasFile(object? sender, MessageRecivedEventArgs recivedEventArgs) + { + await recivedEventArgs.SendResponseAsync(filesManager.Contains(recivedEventArgs.Data)); + } + public static async Task OpenPorts() { try diff --git a/HyperbolicDownloader/UserInterface/InputHandler.cs b/HyperbolicDownloader/UserInterface/InputHandler.cs new file mode 100644 index 0000000..aae30d9 --- /dev/null +++ b/HyperbolicDownloader/UserInterface/InputHandler.cs @@ -0,0 +1,165 @@ +using Commander_Net; + +using HyperbolicDownloader.FileProcessing; +using HyperbolicDownloader.Networking; + +using Stone_Red_Utilities.ConsoleExtentions; + +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace HyperbolicDownloader.UserInterface; + +internal class InputHandler +{ + private readonly HostsManager hostsManager; + private readonly FilesManager filesManager; + private readonly Commander commander = new Commander(); + private bool exit = false; + + public InputHandler(HostsManager hostsManager, FilesManager filesManager) + { + this.hostsManager = hostsManager; + commander.Register((_) => Console.Clear(), "clear"); + commander.Register(Exit, "exit"); + commander.Register(GetFile, "get"); + commander.Register(AddFile, "add"); + commander.Register(ListFiles, "list"); + this.filesManager = filesManager; + } + + public void ReadInput() + { + while (!exit) + { + Console.Write("> "); + string input = Console.ReadLine() ?? string.Empty; + + if (!commander.Execute(input)) + { + ConsoleExt.WriteLine("Unknown command!", ConsoleColor.Red); + } + } + } + + private void GetFile(string hash) + { + if (string.IsNullOrEmpty(hash)) + { + Console.WriteLine("No hash value specified!"); + } + + foreach (NetworkSocket host in hostsManager.ToList()) + { + bool validIpAdress = IPAddress.TryParse(host.IPAddress, out IPAddress? ipAddress); + + if (validIpAdress) + { + ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow); + try + { + Console.CursorLeft = 0; + + Task sendTask = NetworkClient.SendAsync(ipAddress!, host.Port, "HasFile", hash); + + _ = sendTask.Wait(1000); + + if (sendTask.Result) + { + ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Has the requested file", ConsoleColor.Green); + Console.WriteLine("Requesting file..."); + + TcpClient tcpClient = new TcpClient(); + tcpClient.Connect(ipAddress!, host.Port); + + NetworkStream nwStream = tcpClient.GetStream(); + byte[] buffer = new byte[tcpClient.ReceiveBufferSize]; + byte[] reciveBuffer = new byte[1000]; + + byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}"); + nwStream.Write(bytesToSend); + int bytesRead = nwStream.Read(buffer, 0, tcpClient.ReceiveBufferSize); + + string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead); + + if (int.TryParse(dataReceived, out int fileSize)) //If received data is not a size an error occurred + { + Console.WriteLine($"Starting download..."); + + int totalBytesRead = 0; + + using FileStream? fileStream = new FileStream($"{hash}.txt", FileMode.Create); + + while (totalBytesRead < fileSize) + { + bytesRead = nwStream.Read(reciveBuffer, 0, reciveBuffer.Length); + + bytesRead = Math.Min(reciveBuffer.Length, fileSize - totalBytesRead); + + fileStream.Write(reciveBuffer, 0, bytesRead); + totalBytesRead += bytesRead; + + Console.CursorLeft = 0; + Console.Out.WriteAsync($"Downloading: {Math.Ceiling(100d / fileSize * totalBytesRead)}% {totalBytesRead}/{fileSize}"); + } + + fileStream.Close(); + //FileCompressor.DecompressFile($"{hash}.gz", $"result.txt"); + Console.WriteLine(); + ConsoleExt.WriteLine("Done", ConsoleColor.Green); + return; + } + else + { + ConsoleExt.WriteLine(dataReceived, ConsoleColor.Red); + } + } + else + { + ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", ConsoleColor.Red); + } + } + catch (SocketException) + { + Console.CursorLeft = 0; + ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red); + } + } + else + { + hostsManager.Remove(host); + } + } + } + + private void AddFile(string args) + { + if (filesManager.TryAdd(args, out HyperFileInfo? fileInfo, out string? message)) + { + ConsoleExt.WriteLine($"Added file: {fileInfo!.FilePath}", ConsoleColor.Green); + Console.WriteLine($"Hash: {fileInfo.Hash}"); + } + else + { + ConsoleExt.WriteLine(message, ConsoleColor.Red); + } + } + + private void ListFiles(string _) + { + int index = 0; + foreach (HyperFileInfo fileInfo in filesManager.ToList()) + { + index++; + Console.WriteLine($"{index}) {fileInfo.FilePath}"); + Console.WriteLine($"Hash: {fileInfo.Hash}"); + Console.WriteLine(); + } + } + + private void Exit(string _) + { + exit = true; + } +} \ No newline at end of file diff --git a/HyperbolicDownloader/Setup.cs b/HyperbolicDownloader/UserInterface/Setup.cs similarity index 97% rename from HyperbolicDownloader/Setup.cs rename to HyperbolicDownloader/UserInterface/Setup.cs index 870b4dd..3a21f20 100644 --- a/HyperbolicDownloader/Setup.cs +++ b/HyperbolicDownloader/UserInterface/Setup.cs @@ -5,7 +5,7 @@ using Stone_Red_Utilities.ConsoleExtentions; using System.Net; using System.Net.Sockets; -namespace HyperbolicDownloader; +namespace HyperbolicDownloader.UserInterface; internal static class Setup {