- Add command system

- Add basic file sharing
This commit is contained in:
Stone_Red
2022-01-27 01:36:30 +01:00
parent 33ecedba21
commit 8897a566b3
10 changed files with 398 additions and 29 deletions
@@ -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<byte[]> 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;
}
}
}
}
@@ -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<bool> ValidateHashAsync(string filePath, string hash)
{
return await CalculateHashAsync(filePath) == hash;
}
public static bool ValidateHash(string filePath, string hash)
{
return CalculateHash(filePath) == hash;
}
}
@@ -0,0 +1,89 @@
using System.Text.Json;
namespace HyperbolicDownloader.FileProcessing;
internal class FilesManager
{
private readonly List<HyperFileInfo> files = new List<HyperFileInfo>();
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<HyperFileInfo> 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<HyperFileInfo> ToList()
{
return files.ToList();
}
private void SaveFiles()
{
File.WriteAllText(Program.FilesInfoPath, JsonSerializer.Serialize(files));
}
}
@@ -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;
}
}
@@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.0" />
<PackageReference Include="Open.NAT" Version="2.1.0" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup>
@@ -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<string, (Type type, Delegate method)> events = new();
public NetworkClient()
public NetworkClient(FilesManager filesManager)
{
this.filesManager = filesManager;
}
public static async Task<T?> SendAsync<T>(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();
@@ -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; }
}
+39 -16
View File
@@ -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<List<NetworkSocket>>(hostsJson) ?? new());
hostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? new());
}
if (File.Exists(FilesInfoPath))
{
string filesJson = await File.ReadAllTextAsync(FilesInfoPath);
filesManager.AddRange(JsonSerializer.Deserialize<List<HyperFileInfo>>(filesJson) ?? new());
}
Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
@@ -48,6 +57,8 @@ internal class Program
{
networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList);
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("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<NetworkSocket> hostsToSend = hosts.ToList();
List<NetworkSocket> 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<List<NetworkSocket>> 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<string> recivedEventArgs)
{
Console.WriteLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.");
}
private static async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{
List<NetworkSocket> hostsToSend = hosts.ToList();
List<NetworkSocket> 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<string> recivedEventArgs)
{
await recivedEventArgs.SendResponseAsync(filesManager.Contains(recivedEventArgs.Data));
}
public static async Task<bool> OpenPorts()
{
try
@@ -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<bool> sendTask = NetworkClient.SendAsync<bool>(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;
}
}
@@ -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
{