- 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; 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 originalFileStream = File.Open(inputFilePath, FileMode.Open);
using FileStream compressedFileStream = File.Create(compressedFilePath); using FileStream compressedFileStream = File.Create(compressedFilePath);
@@ -12,11 +12,25 @@ internal class FileCompressor
originalFileStream.CopyTo(compressor); 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 compressedFileStream = File.Open(compressedFilePath, FileMode.Open);
using FileStream outputFileStream = File.Create(outputFilePath); using FileStream outputFileStream = File.Create(outputFilePath);
using GZipStream? decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress); using GZipStream? decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress);
decompressor.CopyTo(outputFileStream); 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(); 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) public static async Task<bool> ValidateHashAsync(string filePath, string hash)
{ {
return await CalculateHashAsync(filePath) == 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> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.0" />
<PackageReference Include="Open.NAT" Version="2.1.0" /> <PackageReference Include="Open.NAT" Version="2.1.0" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" /> <PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup> </ItemGroup>
@@ -1,19 +1,23 @@
using System.Net; using HyperbolicDownloader.FileProcessing;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloader namespace HyperbolicDownloader.Networking
{ {
internal class NetworkClient internal class NetworkClient
{ {
public bool IsListening { get; private set; } = false; public bool IsListening { get; private set; } = false;
private TcpListener? tcpListener; private TcpListener? tcpListener;
private FilesManager filesManager;
private readonly Dictionary<string, (Type type, Delegate method)> events = new(); 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) public static async Task<T?> SendAsync<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
@@ -93,22 +97,28 @@ namespace HyperbolicDownloader
tcpListener.Start(); tcpListener.Start();
IsListening = true; IsListening = true;
Task.Run(() => Task.Run(async () =>
{ {
while (IsListening) while (IsListening)
{ {
try try
{ {
using TcpClient client = tcpListener.AcceptTcpClient(); TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream nwStream = client.GetStream(); NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize]; 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); string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
if (dataReceived.StartsWith("Download"))
{
_ = Upload(client, dataReceived[8..]);
continue;
}
if (string.IsNullOrWhiteSpace(dataReceived)) if (string.IsNullOrWhiteSpace(dataReceived))
{ {
client.Close();
continue; continue;
} }
@@ -128,6 +138,8 @@ namespace HyperbolicDownloader
method?.DynamicInvoke(this, eventArgs); method?.DynamicInvoke(this, eventArgs);
} }
client.Close();
} }
catch (SocketException ex) 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() public void StopListening()
{ {
tcpListener?.Stop(); tcpListener?.Stop();
@@ -2,12 +2,12 @@
internal class NetworkSocket internal class NetworkSocket
{ {
public string IPAddress { get; set; }
public int Port { get; set; }
public NetworkSocket(string ipAddress, int port) public NetworkSocket(string ipAddress, int port)
{ {
IPAddress = ipAddress; IPAddress = ipAddress;
Port = port; 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.Networking;
using HyperbolicDownloader.UserInterface;
using Open.Nat; using Open.Nat;
@@ -15,15 +17,16 @@ namespace HyperbolicDownloader;
internal class Program internal class Program
{ {
private const int BroadcastPort = 2155; private const int BroadcastPort = 2155;
public const string HostsFilePath = "Hosts.json"; public const string HostsFilePath = "Hosts.json";
public const string FilesInfoPath = "Files.json";
private static int publicPort; private static int publicPort;
private static readonly int privatePort = 3055; private static readonly int privatePort = 3055;
private static NatDevice? device; private static NatDevice? device;
private static Mapping? portMapping; private static Mapping? portMapping;
private static readonly HostsManager hosts = new(); private static readonly HostsManager hostsManager = new();
private static readonly NetworkClient networkClient = new(); private static readonly FilesManager filesManager = new FilesManager();
private static readonly NetworkClient networkClient = new(filesManager);
private static readonly Random random = new Random(); private static readonly Random random = new Random();
private static async Task Main() private static async Task Main()
@@ -33,7 +36,13 @@ internal class Program
if (File.Exists(HostsFilePath)) if (File.Exists(HostsFilePath))
{ {
string hostsJson = await File.ReadAllTextAsync(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..."); Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
@@ -48,6 +57,8 @@ internal class Program
{ {
networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList); networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList);
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer); networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("HasFile", HasFile);
networkClient.StartListening(privatePort); networkClient.StartListening(privatePort);
} }
catch (SocketException ex) catch (SocketException ex)
@@ -62,31 +73,33 @@ internal class Program
broadcastClient.Send(BroadcastPort, privatePort.ToString()); broadcastClient.Send(BroadcastPort, privatePort.ToString());
await Task.Delay(5000); await Task.Delay(5000);
if (hosts.Count > 0) if (hostsManager.Count > 0)
{ {
Console.WriteLine("Checking if hosts are active..."); 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..."); 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..."); Console.WriteLine("Starting broadcast listener...");
broadcastClient.StartListening(BroadcastPort); broadcastClient.StartListening(BroadcastPort);
broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived; broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived;
ConsoleExt.WriteLine("Done", ConsoleColor.Green); 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) private static async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs)
{ {
Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {recivedEventArgs.IPEndPoint.Address}"); Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {recivedEventArgs.IPEndPoint.Address}");
List<NetworkSocket> hostsToSend = hosts.ToList(); List<NetworkSocket> hostsToSend = hostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket(); NetworkSocket? localSocket = GetLocalSocket();
@@ -102,7 +115,7 @@ internal class Program
if (success) if (success)
{ {
hosts.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort)); hostsManager.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort));
try try
{ {
await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend); 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) private static void DiscoverAnswer(object? sender, MessageRecivedEventArgs<List<NetworkSocket>> recivedEventArgs)
{ {
Console.WriteLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s)."); 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) private static async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{ {
List<NetworkSocket> hostsToSend = hosts.ToList(); List<NetworkSocket> hostsToSend = hostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket(); NetworkSocket? localSocket = GetLocalSocket();
@@ -136,12 +154,17 @@ internal class Program
if (recivedEventArgs.Data.Port != 0) if (recivedEventArgs.Data.Port != 0)
{ {
hosts.Add(recivedEventArgs.Data); hostsManager.Add(recivedEventArgs.Data);
} }
await recivedEventArgs.SendResponseAsync(hostsToSend); 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() public static async Task<bool> OpenPorts()
{ {
try 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;
using System.Net.Sockets; using System.Net.Sockets;
namespace HyperbolicDownloader; namespace HyperbolicDownloader.UserInterface;
internal static class Setup internal static class Setup
{ {