45 Commits
Author SHA1 Message Date
Stone_Red f0b0934b06 Catch SocketException when listing remote files 2025-03-03 22:01:05 +01:00
Stone_Red e41a9eff45 Update/Replace NuGet libraries 2025-03-03 21:21:29 +01:00
Stone_Red 4bd949ef79 Fix receiving of NetworkClient SendAsync<T> answer 2023-12-19 00:54:41 +01:00
Stone_Red fabbc52a35 Properly handle unexpected end of stream 2023-12-18 20:14:42 +01:00
Stone_Red 9983170c8a Fix log message formatting 2023-12-18 20:08:44 +01:00
Stone_Red 5cbb18f4b3 Add additional saveguards 2023-12-18 20:01:18 +01:00
Stone_Red 6117fcc81f Add WAV streaming support 2023-12-18 19:52:17 +01:00
Stone_Red fcefa8ade2 Remove GUI project 2023-12-17 16:16:17 +01:00
Stone_Red 67b905dffa Add support for command line arguments 2023-12-17 15:07:01 +01:00
Stone_Red a44d8ebe73 Add estimated time remaining to download progress 2023-12-14 23:54:49 +01:00
Stone_Red 686fe9c116 Change log colors (again) 2023-12-14 23:05:11 +01:00
Stone_Red 04843f737a Fix log color when using light theme 2023-12-14 22:56:02 +01:00
Stone_Red e8ff5fed08 Improve log colors 2023-12-14 22:52:39 +01:00
Stone_Red 44dcd7f41b Improve debug logs 2023-12-14 22:50:38 +01:00
Stone_Red 44ea506609 Change log type 2023-12-14 22:46:36 +01:00
Stone_Red 2699069183 Add even more logs 2023-12-14 22:45:45 +01:00
Stone_Red 85b332ae72 Fix TcpClient getting disposed of too early 2023-12-14 22:42:15 +01:00
Stone_Red 553ebae348 Make sure NetworkClient can properly handle concurrent requests. 2023-12-14 22:34:21 +01:00
Stone_Red a2057e1b50 Add more debug logs 2023-12-14 22:21:07 +01:00
Stone_Red 71b6f3e0df Update log debug command description 2023-12-14 22:12:30 +01:00
Stone_Red c84ebdf7a1 Add debug logs 2023-12-14 22:11:50 +01:00
Stone_Red 62eca1c474 Improve consistency of notification messages 2023-12-14 20:50:41 +01:00
Stone_Red 30ce61cc41 Upgrade project to .NET 8 2023-12-14 19:00:47 +01:00
Stone_Red 9de227050a Change files to file(s) in log message 2023-12-14 18:00:49 +01:00
Stone_Red a07ae4fc4d Add new files count to log 2023-12-14 17:59:05 +01:00
Stone_Red e7c3d6ea44 Add directory watcher functionality 2023-12-14 17:55:20 +01:00
Stone_Red 73de6c8b5e Change socket format in error messages 2023-12-14 14:55:45 +01:00
Stone_Red f90968f155 Change ordering of hosts 2023-12-14 14:16:59 +01:00
Stone_Red 7b4b8e184d Fix get host list log message 2023-12-14 14:16:47 +01:00
Stone_Red 93aef05f3b Fix download speed calculation 2023-12-14 14:16:21 +01:00
Stone_Red d383d4d15b Add download speed to NetworkSocket 2023-12-14 12:29:17 +01:00
Stone_Red 661a506e76 Fix crash when using the sync command 2023-12-14 11:36:01 +01:00
Stone_Red 205e8e0c5e Add log when closing TCP client 2023-12-14 11:28:59 +01:00
Stone_Red f635b0d855 Improve file list commands 2023-12-14 11:21:31 +01:00
Stone_Red ec7bca5fc8 Update Console.Commander.NET package 2023-12-14 11:21:14 +01:00
Stone_Red 6d60f3891d Add sync command 2023-12-14 10:05:53 +01:00
Stone_Red 7913140dcd Print app info when starting 2023-12-14 09:15:23 +01:00
Stone_Red b4a8f83fb7 Add host index support to list files remote command 2023-12-14 09:09:56 +01:00
Stone_Red e20d133936 Add list files remote command 2023-12-14 00:03:42 +01:00
Stone_Red 6f8d15ec88 Improve deletion of port mapping 2023-12-13 23:21:13 +01:00
Stone_Red 8a587ee627 Add new log command and some general command improvments 2023-12-13 21:34:57 +01:00
Stone_Red b009d1ac64 Prevent adding own host to host list 2023-12-13 12:23:35 +01:00
Stone_Red a5983e427c Fix output of add host command 2023-12-12 12:25:51 +01:00
Stone_Red 8ec9a7964a Minor bug fixes 2023-12-01 14:07:52 +01:00
Stone_Red 9310160128 Stop 2023-11-23 12:16:31 +01:00
40 changed files with 1399 additions and 985 deletions
@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Critical Code Smell", "S3998:Threads should not lock on objects with weak identity", Justification = "Not a problem in this case", Scope = "member", Target = "~M:HyperbolicDownloader.LogCommands.OnNotificationMessageRecived(System.Object,HyperbolicDownloaderApi.Managment.NotificationMessageEventArgs)")]
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -12,8 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.1" /> <PackageReference Include="Console.Commander.NET" Version="1.0.2" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+37 -18
View File
@@ -1,47 +1,62 @@
using Commander_Net; using Commander;
using CuteUtils.Misc;
using HyperbolicDownloaderApi.Commands; using HyperbolicDownloaderApi.Commands;
using HyperbolicDownloaderApi.FileProcessing; using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking; using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader; namespace HyperbolicDownloader;
internal class InputHandler internal class InputHandler
{ {
private readonly Commander commander = new Commander(); private readonly Commander.Commander commander = new Commander.Commander();
private bool exit = false; private bool exit = false;
public InputHandler(HostsManager hostsManager, FilesManager filesManager) public InputHandler(HostsManager hostsManager, FilesManager filesManager, DirectoryWatcher directoryWatcher)
{ {
HostCommands hostCommands = new HostCommands(hostsManager); HostCommands hostCommands = new HostCommands(hostsManager);
FileCommands fileCommands = new FileCommands(hostsManager, filesManager); FileCommands fileCommands = new FileCommands(hostsManager, filesManager);
DirectoryCommands directoryCommands = new DirectoryCommands(directoryWatcher);
DownloadCommands downloadCommands = new DownloadCommands(hostsManager, filesManager); DownloadCommands downloadCommands = new DownloadCommands(hostsManager, filesManager);
ClientCommands clientCommands = new ClientCommands(); StreamingCommands streamingCommands = new StreamingCommands(hostsManager);
_ = commander.Register(input => commander.PrintHelp(input), "help"); ClientCommands clientCommands = new ClientCommands();
LogCommands logCommands = new LogCommands();
_ = commander.Register(input => commander.PrintHelp(input), (HelpText)"Lists all commands.", "help", "h", "?");
_ = commander.Register(_ => Console.Clear(), (HelpText)"Clears the console.", "clear", "cls"); _ = commander.Register(_ => Console.Clear(), (HelpText)"Clears the console.", "clear", "cls");
_ = commander.Register(_ => exit = true, (HelpText)"Exits the application.", "exit", "quit"); _ = commander.Register(_ => exit = true, (HelpText)"Exits the application.", "exit", "quit");
_ = commander.Register(clientCommands.ShowInfo, (HelpText)"Displays the private and public IP address.", "info", "inf"); _ = commander.Register(clientCommands.ShowInfo, (HelpText)"Displays the private and public IP address.", "info", "inf");
_ = commander.Register(hostCommands.Discover, (HelpText)"Tries to find other active hosts on the local network.", "discover", "disc"); _ = commander.Register(hostCommands.Discover, (HelpText)"Tries to find other active hosts on the local network.", "discover", "disc");
_ = commander.Register(hostCommands.Sync, (HelpText)"Syncs the host list", "sync");
Command logCommand = commander.Register((_) => logCommands.Log(false), (HelpText)"Displays live log.", "log");
_ = logCommand.Register((_) => logCommands.Log(true), (HelpText)"Displays live debug log.", "debug");
Command getCommand = commander.Register(downloadCommands.GetFile, (HelpText)"Attempts to retrieve a file from another host using a hash.", "get"); Command getCommand = commander.Register(downloadCommands.GetFile, (HelpText)"Attempts to retrieve a file from another host using a hash.", "get");
_ = getCommand.Register(downloadCommands.GetFileFrom, (HelpText)"Attempts to retrieve a file from another host using a .hyper file.", "from"); _ = getCommand.Register(downloadCommands.GetFileFrom, (HelpText)"Attempts to retrieve a file from another host using a .hyper file.", "from");
Command streamCommand = commander.Register(streamingCommands.StreamWav, (HelpText)"(EXPERIMENTAL) Attempts to stream a .wav file from another host using a hash.", "stream");
_ = streamCommand.Register(streamingCommands.GetWavStreamFrom, (HelpText)"(EXPERIMENTAL) Attempts to stream a .wav file from another host using a .hyper file.", "from");
Command generateCommad = commander.Register(fileCommands.GenerateFileFull, (HelpText)"Generates a .hyper file from a file hash.", "generate", "gen"); Command generateCommad = commander.Register(fileCommands.GenerateFileFull, (HelpText)"Generates a .hyper file from a file hash.", "generate", "gen");
_ = generateCommad.Register(fileCommands.GenerateFileSingle, (HelpText)"Generates a .hyper file from a file hash without checking the known hosts. This adds only the local host to the file.", "noscan"); _ = generateCommad.Register(fileCommands.GenerateFileSingle, (HelpText)"Generates a .hyper file from a file hash without checking the known hosts. This adds only the local host to the file.", "noscan");
Command addCommand = commander.Register(fileCommands.AddFile, (HelpText)"Adds a file to the tracking list.", "add"); Command addCommand = commander.Register(fileCommands.AddFile, (HelpText)"Adds a file to the tracking list.", "add");
_ = addCommand.Register(fileCommands.AddFile, (HelpText)"Adds a file to the tracking list.", "file"); _ = addCommand.Register(fileCommands.AddFile, (HelpText)"Adds a file to the tracking list.", "file");
_ = addCommand.Register(directoryCommands.AddDirectory, (HelpText)"Adds a directory to the tracking list.", "directory", "dir");
_ = addCommand.Register(hostCommands.AddHost, (HelpText)"Adds a host to the list of known hosts.", "host"); _ = addCommand.Register(hostCommands.AddHost, (HelpText)"Adds a host to the list of known hosts.", "host");
Command removeCommand = commander.Register(fileCommands.RemoveFile, (HelpText)"Removes a file from the tracking list.", "remove", "rm"); Command removeCommand = commander.Register(fileCommands.RemoveFile, (HelpText)"Removes a file from the tracking list.", "remove", "rm");
_ = removeCommand.Register(fileCommands.RemoveFile, (HelpText)"Removes a file from the tracking list.", "file"); _ = removeCommand.Register(fileCommands.RemoveFile, (HelpText)"Removes a file from the tracking list.", "file");
_ = removeCommand.Register(directoryCommands.RemoveDirectory, (HelpText)"Removes a directory from the tracking list.", "directory", "dir");
_ = removeCommand.Register(hostCommands.RemoveHost, (HelpText)"Removes a host from the list of known hosts.", "host"); _ = removeCommand.Register(hostCommands.RemoveHost, (HelpText)"Removes a host from the list of known hosts.", "host");
Command listCommand = commander.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "list", "ls"); Command listCommand = commander.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "list", "ls");
_ = listCommand.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "files"); Command listFilesCommand = listCommand.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "files");
_ = listFilesCommand.Register(fileCommands.ListFilesRemote, (HelpText)"Lists all files of another host.", "remote");
_ = listCommand.Register(directoryCommands.ListDirectories, (HelpText)"Lists all directories.", "directories", "dirs");
_ = listCommand.Register(hostCommands.ListHosts, (HelpText)"lists all hosts.", "hosts"); _ = listCommand.Register(hostCommands.ListHosts, (HelpText)"lists all hosts.", "hosts");
_ = commander.Register(hostCommands.CheckActiveHosts, (HelpText)"Checks the status of known hosts.", "status", "check"); _ = commander.Register(hostCommands.CheckActiveHosts, (HelpText)"Checks the status of known hosts.", "status", "check");
@@ -56,19 +71,23 @@ internal class InputHandler
Console.CursorVisible = true; Console.CursorVisible = true;
string input = Console.ReadLine()?.Trim() ?? string.Empty; string input = Console.ReadLine()?.Trim() ?? string.Empty;
ExecuteInput(input);
}
}
Console.CursorVisible = false; public void ExecuteInput(string input)
try {
Console.CursorVisible = false;
try
{
if (!commander.Execute(input, out _))
{ {
if (!commander.Execute(input, out _)) ConsoleExt.WriteLine("Unknown command! Use `help` to list all commands.", ConsoleColor.Red);
{
ConsoleExt.WriteLine("Unknown command!", ConsoleColor.Red);
}
}
catch (Exception ex)
{
ConsoleExt.WriteLine($"An unexpected error occurred! {ex}", ConsoleColor.Red);
} }
} }
catch (Exception ex)
{
ConsoleExt.WriteLine($"An unexpected error occurred! {ex}", ConsoleColor.Red);
}
} }
} }
+102
View File
@@ -0,0 +1,102 @@
using CuteUtils.Misc;
using HyperbolicDownloaderApi.Managment;
using System.Net;
namespace HyperbolicDownloader;
internal class LogCommands
{
private bool debug = false;
public void Log(bool debug)
{
this.debug = debug;
Console.Clear();
if (debug)
{
Console.WriteLine("Debug log opened");
}
else
{
Console.WriteLine("Log opened");
}
ApiManager.OnNotificationMessageRecived += OnNotificationMessageRecived;
char c = ' ';
do
{
if (c != '\0')
{
Console.WriteLine("Press 'q' to exit");
}
c = Console.ReadKey(true).KeyChar;
} while (c != 'q');
ApiManager.OnNotificationMessageRecived -= OnNotificationMessageRecived;
Console.Clear();
Console.ResetColor();
Console.WriteLine("Log closed");
}
private void OnNotificationMessageRecived(object? sender, NotificationMessageEventArgs e)
{
if (e.NotificationMessageType == NotificationMessageType.Log || (debug && e.NotificationMessageType == NotificationMessageType.Debug))
{
lock (Console.Out)
{
if (e.Message?.Contains('>') == true && IPAddress.TryParse(e.Message[..e.Message.IndexOf('>')].Trim(), out IPAddress? ipAddress))
{
ConsoleExt.Write($"[{DateTime.Now}] ", ConsoleColor.Gray);
if (e.NotificationMessageType == NotificationMessageType.Debug)
{
ConsoleExt.Write("[DEBUG] ", ConsoleColor.DarkYellow);
}
byte[] ipAddressBytes = ipAddress.GetAddressBytes();
Array.Resize(ref ipAddressBytes, 8);
ConsoleColor consoleColor = (BitConverter.ToInt64(ipAddressBytes) % 12) switch
{
0 => ConsoleColor.Red,
1 => ConsoleColor.Green,
2 => ConsoleColor.Yellow,
3 => ConsoleColor.Blue,
4 => ConsoleColor.Magenta,
5 => ConsoleColor.Cyan,
6 => ConsoleColor.DarkRed,
7 => ConsoleColor.DarkGreen,
8 => ConsoleColor.DarkYellow,
9 => ConsoleColor.DarkBlue,
10 => ConsoleColor.DarkMagenta,
11 => ConsoleColor.DarkCyan,
_ => ConsoleColor.White
};
ConsoleExt.Write(e.Message[..e.Message.IndexOf('>')], consoleColor);
Console.Write(e.Message[e.Message.IndexOf('>')..]);
}
else
{
ConsoleExt.Write($"[{DateTime.Now}] ", ConsoleColor.Gray);
if (e.NotificationMessageType == NotificationMessageType.Debug)
{
ConsoleExt.Write("[DEBUG] ", ConsoleColor.DarkYellow);
}
Console.Write(e.Message);
}
}
}
}
}
+41 -14
View File
@@ -1,9 +1,10 @@
using HyperbolicDownloaderApi.FileProcessing; using CuteUtils.Misc;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment; using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking; using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.ConsoleExtentions; using System.Reflection;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloader; namespace HyperbolicDownloader;
@@ -19,30 +20,48 @@ internal static class Program
ApiManager.OnNotificationMessageRecived += ApiManager_OnNotificationMessageRecived; ApiManager.OnNotificationMessageRecived += ApiManager_OnNotificationMessageRecived;
if (!Directory.Exists(ApiConfiguration.BasePath))
{
_ = Directory.CreateDirectory(ApiConfiguration.BasePath);
}
if (File.Exists(ApiConfiguration.HostsFilePath)) if (File.Exists(ApiConfiguration.HostsFilePath))
{ {
string hostsJson = await File.ReadAllTextAsync(ApiConfiguration.HostsFilePath); string hostsJson = await File.ReadAllTextAsync(ApiConfiguration.HostsFilePath);
apiManager.HostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? new()); _ = apiManager.HostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? []);
} }
if (File.Exists(ApiConfiguration.FilesInfoPath)) if (File.Exists(ApiConfiguration.FilesInfoPath))
{ {
string filesJson = await File.ReadAllTextAsync(ApiConfiguration.FilesInfoPath); string filesJson = await File.ReadAllTextAsync(ApiConfiguration.FilesInfoPath);
apiManager.FilesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? new()); apiManager.FilesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? []);
} }
InputHandler inputHandler = new InputHandler(apiManager.HostsManager, apiManager.FilesManager); if (File.Exists(ApiConfiguration.DirectoriesInfoPath))
{
string directoriesJson = await File.ReadAllTextAsync(ApiConfiguration.DirectoriesInfoPath);
apiManager.DirectoryWatcher.AddRange(JsonSerializer.Deserialize<List<string>>(directoriesJson) ?? []);
}
Console.WriteLine($"HyperbolicDownloader - {Assembly.GetExecutingAssembly().GetName().Version}");
ConsoleExt.WriteLine("https://github.com/Stone-Red-Code/HyperbolicDownloader", ConsoleColor.Blue);
Console.WriteLine();
InputHandler inputHandler = new InputHandler(apiManager.HostsManager, apiManager.FilesManager, apiManager.DirectoryWatcher);
if (args.Length > 0 && File.Exists(args[0])) if (args.Length > 0 && File.Exists(args[0]))
{ {
HyperbolicDownloaderApi.Commands.DownloadCommands downloadCommands = new HyperbolicDownloaderApi.Commands.DownloadCommands(apiManager.HostsManager, apiManager.FilesManager); HyperbolicDownloaderApi.Commands.DownloadCommands downloadCommands = new HyperbolicDownloaderApi.Commands.DownloadCommands(apiManager.HostsManager, apiManager.FilesManager);
downloadCommands.GetFileFrom(args[0]); downloadCommands.GetFileFrom(args[0]);
Console.WriteLine("Do you want to continue using this instance? [y/N]"); _ = Console.ReadLine();
if (char.ToLower(Console.ReadKey().KeyChar) != 'y') return;
{ }
return; else if (args.Length > 0)
} {
ConsoleExt.WriteLine("Warning! You are using command line arguments, which can lead to unexpected behavior with some commands.", ConsoleColor.Yellow);
Console.WriteLine(); Console.WriteLine();
inputHandler.ExecuteInput(string.Join(' ', args));
return;
} }
await Initialize(); await Initialize();
@@ -53,6 +72,8 @@ internal static class Program
private static async Task Initialize() private static async Task Initialize()
{ {
apiManager.DirectoryWatcher.Start();
Console.WriteLine("Searching for a UPnP/NAT-PMP device..."); Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
_ = await ApiManager.OpenPorts(); _ = await ApiManager.OpenPorts();
@@ -63,10 +84,15 @@ internal static class Program
if (!apiManager.StartTcpListener()) if (!apiManager.StartTcpListener())
{ {
_ = Console.ReadLine(); _ = Console.ReadLine();
Environment.Exit(-1);
} }
Console.WriteLine("Starting broadcast listener..."); Console.WriteLine("Starting UDP listener...");
apiManager.StartBroadcastListener(); if (!apiManager.StartBroadcastListener())
{
_ = Console.ReadLine();
Environment.Exit(-2);
}
int activeHostsCount = 0; int activeHostsCount = 0;
if (apiManager.HostsManager.Count > 0) if (apiManager.HostsManager.Count > 0)
@@ -78,7 +104,7 @@ internal static class Program
if (activeHostsCount == 0) if (activeHostsCount == 0)
{ {
ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red); ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red);
ConsoleExt.WriteLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host or use 'discover' to find hosts in the local network.", ConsoleColor.Red); ConsoleExt.WriteLine("Use 'add host <ip address>:<port>' to add a new host or use 'discover' to find hosts in the local network.", ConsoleColor.Red);
} }
Console.WriteLine($"{apiManager.HostsManager.Count} known host(s)."); Console.WriteLine($"{apiManager.HostsManager.Count} known host(s).");
@@ -102,5 +128,6 @@ internal static class Program
{ {
ApiManager.ClosePorts(); ApiManager.ClosePorts();
apiManager.HostsManager.SaveHosts(); apiManager.HostsManager.SaveHosts();
Environment.Exit(0);
} }
} }
@@ -0,0 +1,81 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
namespace HyperbolicDownloaderApi.Commands;
public class DirectoryCommands(DirectoryWatcher directoryWatcher)
{
public void AddDirectory(string directoryPath)
{
if (directoryWatcher.TryAdd(directoryPath, out string? message))
{
ApiManager.SendNotificationMessageNewLine($"Added directory: {directoryPath}", NotificationMessageType.Success);
}
else
{
ApiManager.SendNotificationMessageNewLine(message!, NotificationMessageType.Error);
}
}
public void RemoveDirectory(string args)
{
if (int.TryParse(args, out int index))
{
List<string> fileInfos = directoryWatcher.ToList();
if (index < 1 || index > fileInfos.Count)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
args = fileInfos[index - 1];
}
if (directoryWatcher.TryRemove(args))
{
ApiManager.SendNotificationMessageNewLine($"Removed directory: {args}", NotificationMessageType.Success);
}
else
{
ApiManager.SendNotificationMessageNewLine("Directory is not tracked!", NotificationMessageType.Error);
}
}
public void ListDirectories(string searchString)
{
int index = 0;
int directoryCount = 0;
List<string> directoryInfos = directoryWatcher.ToList();
if (directoryInfos.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No tracked directories!", NotificationMessageType.Warning);
return;
}
foreach (string directoryInfo in directoryInfos)
{
index++;
if (!string.IsNullOrWhiteSpace(searchString) && !directoryInfo.Contains(searchString, StringComparison.OrdinalIgnoreCase))
{
continue;
}
directoryCount++;
ApiManager.SendNotificationMessageNewLine($"{index}) {directoryInfo}");
ApiManager.SendNotificationMessageNewLine(string.Empty);
}
if (directoryCount == 0)
{
ApiManager.SendNotificationMessage($"No directories found containing \"{searchString}\".", NotificationMessageType.Warning);
}
else
{
Console.CursorTop--;
}
}
}
@@ -1,8 +1,9 @@
using HyperbolicDownloaderApi.FileProcessing; using CuteUtils;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment; using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking; using HyperbolicDownloaderApi.Networking;
using HyperbolicDownloaderApi.Utilities;
using Stone_Red_Utilities.StringExtentions;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
@@ -12,17 +13,8 @@ using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands; namespace HyperbolicDownloaderApi.Commands;
public class DownloadCommands public class DownloadCommands(HostsManager hostsManager, FilesManager filesManager)
{ {
private readonly HostsManager hostsManager;
private readonly FilesManager filesManager;
public DownloadCommands(HostsManager hostsManager, FilesManager filesManager)
{
this.hostsManager = hostsManager;
this.filesManager = filesManager;
}
public void GetFileFrom(string path) public void GetFileFrom(string path)
{ {
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
@@ -36,6 +28,7 @@ public class DownloadCommands
if (!File.Exists(fullPath)) if (!File.Exists(fullPath))
{ {
ApiManager.SendNotificationMessageNewLine("Invalid file path!", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("Invalid file path!", NotificationMessageType.Error);
return;
} }
string json = File.ReadAllText(fullPath); string json = File.ReadAllText(fullPath);
@@ -47,7 +40,7 @@ public class DownloadCommands
return; return;
} }
hostsManager.AddRange(publicHyperFileInfo.Hosts); _ = hostsManager.AddRange(publicHyperFileInfo.Hosts);
GetFile(publicHyperFileInfo.Hash); GetFile(publicHyperFileInfo.Hash);
} }
@@ -109,7 +102,7 @@ public class DownloadCommands
byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}"); byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}");
nwStream.Write(bytesToSend); nwStream.Write(bytesToSend);
nwStream.ReadTimeout = 30000; nwStream.ReadTimeout = 5000;
int bytesRead; int bytesRead;
try try
@@ -148,8 +141,6 @@ public class DownloadCommands
ApiManager.SendNotificationMessageNewLine($"File name: {fileName}"); ApiManager.SendNotificationMessageNewLine($"File name: {fileName}");
ApiManager.SendNotificationMessageNewLine($"Starting download..."); ApiManager.SendNotificationMessageNewLine($"Starting download...");
int totalBytesRead = 0;
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
{ {
_ = Directory.CreateDirectory(directoryPath); _ = Directory.CreateDirectory(directoryPath);
@@ -157,12 +148,14 @@ public class DownloadCommands
using FileStream? fileStream = new FileStream(filePath, FileMode.Create); using FileStream? fileStream = new FileStream(filePath, FileMode.Create);
int bytesInOneSecond = 0; int totalBytesRead = 0;
int unitsPerSecond = 0; int bytesPerSecond = 0;
string unit = "Kb"; int transferRate = 0;
TimeSpan timeRemaining = TimeSpan.Zero;
Stopwatch stopWatch = new Stopwatch(); Stopwatch stopWatch = new Stopwatch();
stopWatch.Start(); stopWatch.Start();
while (totalBytesRead < fileSize) while (totalBytesRead < fileSize)
{ {
try try
@@ -182,26 +175,18 @@ public class DownloadCommands
fileStream.Write(reciveBuffer, 0, bytesRead); fileStream.Write(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
bytesInOneSecond += bytesRead; bytesPerSecond += bytesRead;
if (stopWatch.Elapsed.TotalSeconds >= 1) if (stopWatch.Elapsed.TotalSeconds >= 1)
{ {
unitsPerSecond = (int)(bytesInOneSecond * stopWatch.Elapsed.TotalSeconds); transferRate = bytesPerSecond;
if (unitsPerSecond > 125000) host.DownloadSpeed = (host.DownloadSpeed + bytesPerSecond) / 2;
{ timeRemaining = TimeSpan.FromSeconds((fileSize - totalBytesRead) / (double)host.DownloadSpeed);
unitsPerSecond /= 125000; bytesPerSecond = 0;
unit = "Mb";
}
else
{
unitsPerSecond /= 125;
unit = "Kb";
}
bytesInOneSecond = 0;
stopWatch.Restart(); stopWatch.Restart();
} }
ApiManager.SendNotificationMessage($"\rDownloading: {Math.Clamp(Math.Ceiling(100d / fileSize * totalBytesRead), 0, 100)}% {totalBytesRead / 1000}/{fileSize / 1000}KB [{unitsPerSecond}{unit}/s] "); ApiManager.SendNotificationMessage($"\rDownloading: [{Math.Clamp(Math.Ceiling(100d / fileSize * totalBytesRead), 0, 100)}%] [{UnitFormatter.FileSize(totalBytesRead)}/{UnitFormatter.FileSize(fileSize)}] [{UnitFormatter.TransferRate(transferRate)}] [ETA: {timeRemaining:hh\\:mm\\:ss}] ");
} }
fileStream.Close(); fileStream.Close();
+176 -27
View File
@@ -2,22 +2,16 @@
using HyperbolicDownloaderApi.Managment; using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking; using HyperbolicDownloaderApi.Networking;
using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands; namespace HyperbolicDownloaderApi.Commands;
public class FileCommands public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
{ {
private readonly HostsManager hostsManager; private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
private readonly FilesManager filesManager;
public FileCommands(HostsManager hostsManager, FilesManager filesManager)
{
this.hostsManager = hostsManager;
this.filesManager = filesManager;
}
public void AddFile(string path) public void AddFile(string path)
{ {
@@ -32,11 +26,24 @@ public class FileCommands
} }
} }
public void RemoveFile(string hash) public void RemoveFile(string args)
{ {
hash = hash.Trim().ToLower(); args = args.Trim().ToLower();
if (filesManager.TryRemove(hash)) if (int.TryParse(args, out int index))
{
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (index < 1 || index > fileInfos.Count)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
args = fileInfos[index - 1].Hash;
}
if (filesManager.TryRemove(args))
{ {
ApiManager.SendNotificationMessageNewLine($"Successfully removed file!", NotificationMessageType.Success); ApiManager.SendNotificationMessageNewLine($"Successfully removed file!", NotificationMessageType.Success);
} }
@@ -46,9 +53,10 @@ public class FileCommands
} }
} }
public void ListFiles(string _) public void ListFiles(string searchString)
{ {
int index = 0; int index = 0;
int fileCount = 0;
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList(); List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (fileInfos.Count == 0) if (fileInfos.Count == 0)
@@ -60,14 +68,129 @@ public class FileCommands
foreach (PrivateHyperFileInfo fileInfo in fileInfos) foreach (PrivateHyperFileInfo fileInfo in fileInfos)
{ {
index++; index++;
if (!string.IsNullOrWhiteSpace(searchString) && !fileInfo.FilePath.Contains(searchString, StringComparison.OrdinalIgnoreCase))
{
continue;
}
fileCount++;
ApiManager.SendNotificationMessageNewLine($"{index}) {fileInfo.FilePath}"); ApiManager.SendNotificationMessageNewLine($"{index}) {fileInfo.FilePath}");
ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}"); ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}");
ApiManager.SendNotificationMessageNewLine(string.Empty); ApiManager.SendNotificationMessageNewLine(string.Empty);
} }
if (fileCount == 0)
{
ApiManager.SendNotificationMessage($"No files found containing \"{searchString}\".", NotificationMessageType.Warning);
}
else
{
Console.CursorTop--;
}
}
public void ListFilesRemote(string args)
{
args = args.Trim();
string searchString = string.Empty;
if (args.Contains(' '))
{
searchString = args.Substring(args.IndexOf(' ') + 1, args.Length - args.IndexOf(' ') - 1);
args = args[..args.IndexOf(' ')];
}
if (int.TryParse(args, out int index))
{
List<NetworkSocket> hosts = hostsManager.ToList();
if (index < 1 || index > hosts.Count)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
args = $"{hosts[index - 1].IPAddress}:{hosts[index - 1].Port}";
}
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: <ip address>:<port>", NotificationMessageType.Error);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
IPAddress? ipAddress;
_ = int.TryParse(portInput, out int port);
if (port is < 1000 or >= 6000)
{
ApiManager.SendNotificationMessageNewLine("Invalid port number!", NotificationMessageType.Error);
return;
}
else if (!IPAddress.TryParse(ipAddressInput, out ipAddress))
{
try
{
ipAddress = Dns.GetHostAddresses(ipAddressInput).FirstOrDefault();
}
catch (SocketException ex)
{
Debug.WriteLine(ex);
}
}
if (ipAddress is null)
{
ApiManager.SendNotificationMessageNewLine("Invalid IP address!", NotificationMessageType.Error);
return;
}
ApiManager.SendNotificationMessageNewLine($"Requesting file list from {ipAddress}:{port}...");
Task<List<HyperFileDto>?> sendTask = NetworkClient.SendAsync<List<HyperFileDto>>(ipAddress, port, "GetFilesList", searchString);
_ = sendTask.Wait(1000);
if (!sendTask.IsCompletedSuccessfully)
{
ApiManager.SendNotificationMessageNewLine("Invalid host!", NotificationMessageType.Error);
return;
}
List<HyperFileDto>? fileInfos = sendTask.Result;
if (fileInfos?.Count == 0 && !string.IsNullOrEmpty(searchString))
{
ApiManager.SendNotificationMessageNewLine($"No files found containing \"{searchString}\".", NotificationMessageType.Warning);
}
else if (fileInfos is null || fileInfos.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No tracked files!", NotificationMessageType.Warning);
return;
}
index = 0;
foreach (HyperFileDto fileInfo in fileInfos)
{
index++;
ApiManager.SendNotificationMessageNewLine($"{index}) {fileInfo.Name}");
ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}");
ApiManager.SendNotificationMessageNewLine(string.Empty);
}
Console.CursorTop--; Console.CursorTop--;
} }
public void GenerateFileSingle(string hash) public void GenerateFileSingle(string args)
{ {
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles"); string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
@@ -75,9 +198,23 @@ public class FileCommands
_ = Directory.CreateDirectory(directoryPath); _ = Directory.CreateDirectory(directoryPath);
} }
hash = hash.Trim().ToLower(); args = args.Trim().ToLower();
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo)) PrivateHyperFileInfo? localHyperFileInfo;
if (int.TryParse(args, out int index))
{
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (index < 1 || index > fileInfos.Count)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
localHyperFileInfo = fileInfos[index - 1];
}
else if (!filesManager.TryGet(args, out localHyperFileInfo))
{ {
ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
return; return;
@@ -86,7 +223,7 @@ public class FileCommands
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath); string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper"); string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash); PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash);
NetworkSocket? localHost = ApiManager.GetLocalSocket(); NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null) if (localHost is null)
@@ -97,8 +234,7 @@ public class FileCommands
publicHyperFileInfo.Hosts.Add(localHost); publicHyperFileInfo.Hosts.Add(localHost);
JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true }; string json = JsonSerializer.Serialize(publicHyperFileInfo, jsonSerializerOptions);
string json = JsonSerializer.Serialize(publicHyperFileInfo, options);
File.WriteAllText(filePath, json); File.WriteAllText(filePath, json);
@@ -106,7 +242,7 @@ public class FileCommands
ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}"); ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
} }
public void GenerateFileFull(string hash) public void GenerateFileFull(string args)
{ {
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles"); string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
@@ -114,9 +250,23 @@ public class FileCommands
_ = Directory.CreateDirectory(directoryPath); _ = Directory.CreateDirectory(directoryPath);
} }
hash = hash.Trim().ToLower(); args = args.Trim().ToLower();
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo)) PrivateHyperFileInfo? localHyperFileInfo;
if (int.TryParse(args, out int index))
{
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (index < 1 || index > fileInfos.Count)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
localHyperFileInfo = fileInfos[index - 1];
}
else if (!filesManager.TryGet(args, out localHyperFileInfo))
{ {
ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
return; return;
@@ -125,7 +275,7 @@ public class FileCommands
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath); string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper"); string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash); PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash);
NetworkSocket? localHost = ApiManager.GetLocalSocket(); NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null) if (localHost is null)
@@ -148,7 +298,7 @@ public class FileCommands
Console.CursorLeft = 0; Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash); Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", localHyperFileInfo.Hash);
_ = sendTask.Wait(1000); _ = sendTask.Wait(1000);
@@ -175,8 +325,7 @@ public class FileCommands
publicHyperFileInfo.Hosts.Add(localHost); publicHyperFileInfo.Hosts.Add(localHost);
JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true }; string json = JsonSerializer.Serialize(publicHyperFileInfo, jsonSerializerOptions);
string json = JsonSerializer.Serialize(publicHyperFileInfo, options);
File.WriteAllText(filePath, json); File.WriteAllText(filePath, json);
@@ -1,5 +1,6 @@
using HyperbolicDownloaderApi.Managment; using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking; using HyperbolicDownloaderApi.Networking;
using HyperbolicDownloaderApi.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
@@ -7,15 +8,8 @@ using System.Net.Sockets;
namespace HyperbolicDownloaderApi.Commands; namespace HyperbolicDownloaderApi.Commands;
public class HostCommands public class HostCommands(HostsManager hostsManager)
{ {
private readonly HostsManager hostsManager;
public HostCommands(HostsManager hostsManager)
{
this.hostsManager = hostsManager;
}
public void Discover(string _) public void Discover(string _)
{ {
ApiManager.SendNotificationMessageNewLine("Running local discovery routine...", NotificationMessageType.Info); ApiManager.SendNotificationMessageNewLine("Running local discovery routine...", NotificationMessageType.Info);
@@ -25,7 +19,16 @@ public class HostCommands
BroadcastClient.Send(ApiConfiguration.BroadcastPort, ApiConfiguration.PrivatePort.ToString()); BroadcastClient.Send(ApiConfiguration.BroadcastPort, ApiConfiguration.PrivatePort.ToString());
Thread.Sleep(3000); Thread.Sleep(3000);
ApiManager.SendNotificationMessageNewLine($"Found {hostsManager.Count - hostsCountBefore} host(s)", NotificationMessageType.Info); int newHostsCount = hostsManager.Count - hostsCountBefore;
if (newHostsCount > 0)
{
ApiManager.SendNotificationMessageNewLine($"Found {newHostsCount} host(s).", NotificationMessageType.Info);
}
else
{
ApiManager.SendNotificationMessageNewLine($"No new hosts added.", NotificationMessageType.Warning);
}
} }
public void CheckActiveHosts(string _) public void CheckActiveHosts(string _)
@@ -34,7 +37,7 @@ public class HostCommands
if (activeHostsCount == 0) if (activeHostsCount == 0)
{ {
ApiManager.SendNotificationMessageNewLine("No active hosts found!", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("No active hosts found!", NotificationMessageType.Error);
ApiManager.SendNotificationMessageNewLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host.", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("Use 'add host <ip address>:<port>' to add a new host.", NotificationMessageType.Error);
} }
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info); ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
@@ -49,7 +52,7 @@ public class HostCommands
if (hosts.Count == 0) if (hosts.Count == 0)
{ {
ApiManager.SendNotificationMessageNewLine("No known hosts", NotificationMessageType.Warning); ApiManager.SendNotificationMessageNewLine("No known hosts.", NotificationMessageType.Warning);
return; return;
} }
@@ -58,6 +61,7 @@ public class HostCommands
index++; index++;
ApiManager.SendNotificationMessageNewLine($"{index}) {host.IPAddress}:{host.Port}", NotificationMessageType.Info); ApiManager.SendNotificationMessageNewLine($"{index}) {host.IPAddress}:{host.Port}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"Last active: {host.LastActive}", NotificationMessageType.Info); ApiManager.SendNotificationMessageNewLine($"Last active: {host.LastActive}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"Download speed: {(host.DownloadSpeed <= 0 ? "N/A" : UnitFormatter.TransferRate(host.DownloadSpeed))}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info); ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
} }
Console.CursorTop--; Console.CursorTop--;
@@ -67,9 +71,23 @@ public class HostCommands
{ {
string[] parts = args.Split(":"); string[] parts = args.Split(":");
if (int.TryParse(args, out int index))
{
if (hostsManager.Count < index || index < 1)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
hostsManager.Remove(hostsManager.ToList()[index - 1], true);
ApiManager.SendNotificationMessageNewLine($"Successfully Removed host!", NotificationMessageType.Success);
return;
}
if (parts.Length != 2) if (parts.Length != 2)
{ {
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: <ip address>:<port>", NotificationMessageType.Error);
return; return;
} }
@@ -93,7 +111,7 @@ public class HostCommands
if (!hostsManager.Contains(hostToRemove)) if (!hostsManager.Contains(hostToRemove))
{ {
ApiManager.SendNotificationMessageNewLine("Host not in list", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("Host not in list!", NotificationMessageType.Error);
return; return;
} }
@@ -107,7 +125,7 @@ public class HostCommands
if (parts.Length != 2) if (parts.Length != 2)
{ {
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: <ip address>:<port>", NotificationMessageType.Error);
return; return;
} }
@@ -144,25 +162,60 @@ public class HostCommands
{ {
ApiManager.SendNotificationMessageNewLine("Waiting for response...", NotificationMessageType.Info); ApiManager.SendNotificationMessageNewLine("Waiting for response...", NotificationMessageType.Info);
NetworkSocket? localSocket = ApiManager.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue); NetworkSocket? localSocket = ApiManager.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue);
List<NetworkSocket>? recivedHosts = NetworkClient.Send<List<NetworkSocket>>(ipAddress, port, "GetHostsList", localSocket); Task<List<NetworkSocket>?> sendTask = NetworkClient.SendAsync<List<NetworkSocket>>(ipAddress, port, "GetHostsList", localSocket);
_ = sendTask.Wait(5000);
if (!sendTask.IsCompletedSuccessfully)
{
ApiManager.SendNotificationMessageNewLine($"No response from {ipAddress}:{port}", NotificationMessageType.Error);
return;
}
List<NetworkSocket>? recivedHosts = sendTask.Result;
if (recivedHosts is not null) if (recivedHosts is not null)
{ {
ApiManager.SendNotificationMessageNewLine($"Success! Added {recivedHosts.Count} new host(s).", NotificationMessageType.Success); int newHosts = hostsManager.AddRange(recivedHosts);
hostsManager.AddRange(recivedHosts);
if (hostsManager.Add(new NetworkSocket(ipAddress.ToString(), port, DateTime.Now)))
{
newHosts++;
}
if (newHosts > 0)
{
ApiManager.SendNotificationMessageNewLine($"Added {newHosts} new host(s).", NotificationMessageType.Success);
}
else
{
ApiManager.SendNotificationMessageNewLine($"No new hosts added.", NotificationMessageType.Warning);
}
} }
else else
{ {
ApiManager.SendNotificationMessageNewLine($"Invalid response!", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine($"Invalid response!", NotificationMessageType.Error);
} }
} }
catch (SocketException ex) catch (Exception ex) when (ex is SocketException or IOException or AggregateException)
{
ApiManager.SendNotificationMessageNewLine($"Invalid host! Error message: {ex.Message}", NotificationMessageType.Error);
}
catch (IOException ex)
{ {
ApiManager.SendNotificationMessageNewLine($"Invalid host! Error message: {ex.Message}", NotificationMessageType.Error); ApiManager.SendNotificationMessageNewLine($"Invalid host! Error message: {ex.Message}", NotificationMessageType.Error);
} }
} }
public void Sync(string _)
{
int newHostsCount = hostsManager.Sync();
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
if (newHostsCount > 0)
{
ApiManager.SendNotificationMessage($"Added {newHostsCount} new host(s).");
}
else
{
ApiManager.SendNotificationMessage($"No new hosts added.", NotificationMessageType.Warning);
}
}
} }
@@ -0,0 +1,247 @@
using CuteUtils;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using NAudio.Utils;
using NAudio.Wave;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands;
public class StreamingCommands(HostsManager hostsManager)
{
public void GetWavStreamFrom(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
ApiManager.SendNotificationMessageNewLine("Path is empty!", NotificationMessageType.Error);
return;
}
string fullPath = Path.GetFullPath(path);
if (!File.Exists(fullPath))
{
ApiManager.SendNotificationMessageNewLine("Invalid file path!", NotificationMessageType.Error);
return;
}
string json = File.ReadAllText(fullPath);
PublicHyperFileInfo? publicHyperFileInfo = JsonSerializer.Deserialize<PublicHyperFileInfo>(json);
if (publicHyperFileInfo == null)
{
ApiManager.SendNotificationMessageNewLine("Parsing file failed!", NotificationMessageType.Error);
return;
}
_ = hostsManager.AddRange(publicHyperFileInfo.Hosts);
StreamWav(publicHyperFileInfo.Hash);
}
public void StreamWav(string hash)
{
if (string.IsNullOrEmpty(hash))
{
ApiManager.SendNotificationMessageNewLine("No hash value specified!", NotificationMessageType.Error);
return;
}
hash = hash.Trim().ToLower();
foreach (NetworkSocket host in hostsManager.ToList())
{
bool validIpAdress = IPAddress.TryParse(host.IPAddress, out IPAddress? ipAddress);
if (!validIpAdress)
{
hostsManager.Remove(host, true);
continue;
}
ApiManager.SendNotificationMessage($"{host.IPAddress}:{host.Port} > ???", NotificationMessageType.Warning);
Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash);
_ = sendTask.Wait(1000);
if (!sendTask.IsCompletedSuccessfully)
{
Console.CursorLeft = 0;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
hostsManager.Remove(host);
continue;
}
else if (!sendTask.Result)
{
host.LastActive = DateTime.Now;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", NotificationMessageType.Error);
continue;
}
host.LastActive = DateTime.Now;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Has the requested file", NotificationMessageType.Success);
ApiManager.SendNotificationMessageNewLine("Requesting stream...");
using TcpClient tcpClient = new TcpClient();
tcpClient.Connect(ipAddress!, host.Port);
tcpClient.ReceiveBufferSize = 6400;
NetworkStream nwStream = tcpClient.GetStream();
byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
byte[] reciveBuffer = new byte[6400];
byte[] bytesToSend = Encoding.ASCII.GetBytes($"StreamWav {hash}");
nwStream.Write(bytesToSend);
nwStream.ReadTimeout = 5000;
int bytesRead;
try
{
bytesRead = nwStream.Read(buffer, 0, 1000);
}
catch (IOException)
{
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Lost connection to other host!", NotificationMessageType.Error);
continue;
}
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
string[] parts = dataReceived.Split('/');
if (parts.Length != 5) //If received data does not contain 5 parts -> error
{
ApiManager.SendNotificationMessageNewLine(dataReceived, NotificationMessageType.Error);
continue;
}
if (!int.TryParse(parts[0], out int dataLength) || dataLength <= 0)
{
ApiManager.SendNotificationMessageNewLine("Invalid data length!", NotificationMessageType.Error);
continue;
}
if (!int.TryParse(parts[2], out int sampleRate) || sampleRate <= 0)
{
ApiManager.SendNotificationMessageNewLine("Invalid sample rate!", NotificationMessageType.Error);
continue;
}
if (!int.TryParse(parts[3], out int bitsPerSample) || bitsPerSample <= 0)
{
ApiManager.SendNotificationMessageNewLine("Invalid bits per sample!", NotificationMessageType.Error);
continue;
}
if (!int.TryParse(parts[4], out int channels) || channels <= 0)
{
ApiManager.SendNotificationMessageNewLine("Invalid channels!", NotificationMessageType.Error);
continue;
}
string fileName = parts[1].ToFileName();
ApiManager.SendNotificationMessageNewLine($"File name: {fileName}");
ApiManager.SendNotificationMessageNewLine($"Starting stream...");
ApiManager.SendNotificationMessageNewLine("Controls: [p]lay, [s]top");
BufferedWaveProvider bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(sampleRate, bitsPerSample, channels));
using WaveOutEvent player = new WaveOutEvent();
player.Init(bufferedWaveProvider);
player.Play();
int totalBytesRead = 0;
TimeSpan totalTime = TimeSpan.FromSeconds(dataLength / (double)sampleRate / channels / (bitsPerSample / 8));
Task task = Task.Run(async () =>
{
while (player.GetPosition() < dataLength && player.PlaybackState != PlaybackState.Stopped)
{
Console.Write($"\r[{player.PlaybackState,-7}] {player.GetPositionTimeSpan():hh\\:mm\\:ss}/{totalTime:hh\\:mm\\:ss}");
if (IsBufferNearlyFull(bufferedWaveProvider))
{
await Task.Delay(100);
continue;
}
try
{
bytesRead = nwStream.Read(reciveBuffer, 0, reciveBuffer.Length);
if (bytesRead == 0 && bufferedWaveProvider.BufferedBytes == 0)
{
player.Stop();
ApiManager.SendNotificationMessage($"\r[{player.PlaybackState,-7}]");
}
}
catch (IOException ex)
{
Debug.WriteLine(ex);
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Lost connection to other host!", NotificationMessageType.Error);
break;
}
bytesRead = Math.Min(bytesRead, dataLength - totalBytesRead);
bufferedWaveProvider.AddSamples(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
}
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Stream ended!", NotificationMessageType.Warning);
});
while (!task.IsCompleted)
{
ApiManager.SendNotificationMessage($"\r[{player.PlaybackState,-7}]");
char c = Console.ReadKey(true).KeyChar;
if (c == 'p')
{
if (player.PlaybackState == PlaybackState.Playing)
{
player.Pause();
}
else
{
player.Play();
}
}
else if (c == 's')
{
player.Stop();
ApiManager.SendNotificationMessage($"\r[{player.PlaybackState,-7}]");
break;
}
}
hostsManager.SaveHosts();
return;
}
ApiManager.SendNotificationMessageNewLine("None of the available hosts have the requested file!", NotificationMessageType.Error);
hostsManager.SaveHosts();
}
private static bool IsBufferNearlyFull(BufferedWaveProvider bufferedWaveProvider)
{
return bufferedWaveProvider is not null &&
bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes
< bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4;
}
}
@@ -0,0 +1,105 @@
using HyperbolicDownloaderApi.Managment;
using System.Text.Json;
using System.Timers;
namespace HyperbolicDownloaderApi.FileProcessing;
public class DirectoryWatcher
{
private readonly FilesManager filesManager;
private readonly List<string> directories = [];
private readonly System.Timers.Timer timer = new(1);
public DirectoryWatcher(FilesManager filesManager)
{
this.filesManager = filesManager;
timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed;
}
public bool TryAdd(string path, out string? errorMessage)
{
if (directories.Contains(path))
{
errorMessage = "Directory already tracked";
return false;
}
directories.Add(path);
errorMessage = null;
SaveDirectories();
return true;
}
public void AddRange(IEnumerable<string> directoryInfos)
{
foreach (string directoryInfo in directoryInfos)
{
if (Directory.Exists(directoryInfo) && !directories.Contains(directoryInfo))
{
directories.Add(directoryInfo);
}
}
SaveDirectories();
}
public bool TryRemove(string path)
{
if (!directories.Contains(path))
{
return false;
}
_ = directories.Remove(path);
SaveDirectories();
return true;
}
public void SaveDirectories()
{
File.WriteAllText(ApiConfiguration.DirectoriesInfoPath, JsonSerializer.Serialize(directories));
}
public List<string> ToList()
{
return directories;
}
public void Start()
{
timer.Start();
}
private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
{
ApiManager.SendNotificationMessageNewLine("Checking for new files...", NotificationMessageType.Log);
timer.Interval = TimeSpan.FromMinutes(1).TotalMilliseconds;
int newFilesCount = 0;
foreach (string directory in directories)
{
ApiManager.SendNotificationMessageNewLine($"Checking directory: {directory}", NotificationMessageType.Log);
string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
foreach (string file in files)
{
if (filesManager.TryAdd(file, out _, out _))
{
newFilesCount++;
}
}
}
ApiManager.SendNotificationMessageNewLine($"Finished checking for new files. Found {newFilesCount} new file(s).", NotificationMessageType.Log);
filesManager.RemoveFilesThatDontExist();
}
}
@@ -1,12 +1,13 @@
using HyperbolicDownloaderApi.Managment; using HyperbolicDownloaderApi.Managment;
using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloaderApi.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
public class FilesManager public class FilesManager
{ {
private readonly List<PrivateHyperFileInfo> files = new List<PrivateHyperFileInfo>(); private readonly List<PrivateHyperFileInfo> files = [];
public bool TryAdd(string filePath, out PrivateHyperFileInfo? fileInfo, out string? errorMessage) public bool TryAdd(string filePath, out PrivateHyperFileInfo? fileInfo, out string? errorMessage)
{ {
@@ -31,10 +32,16 @@ public class FilesManager
if (Contains(hash)) if (Contains(hash))
{ {
fileInfo = null; fileInfo = null;
Debug.WriteLine(filePath + "-.-" + hash);
errorMessage = "File already tracked!"; errorMessage = "File already tracked!";
return false; return false;
} }
if (files.Exists(f => f.FilePath == fullPath))
{
_ = files.RemoveAll(f => f.FilePath == fullPath);
}
fileInfo = new PrivateHyperFileInfo(hash, fullPath); fileInfo = new PrivateHyperFileInfo(hash, fullPath);
files.Add(fileInfo); files.Add(fileInfo);
@@ -83,12 +90,26 @@ public class FilesManager
public bool Contains(string? hash) public bool Contains(string? hash)
{ {
return files.Any(f => f.Hash == hash); return files.Exists(f => f.Hash == hash);
} }
public List<PrivateHyperFileInfo> ToList() public List<PrivateHyperFileInfo> ToList()
{ {
return files.ToList(); return [.. files];
}
internal void RemoveFilesThatDontExist()
{
List<PrivateHyperFileInfo> filesToRemove = files
.Where(f => !File.Exists(f.FilePath))
.ToList();
foreach (PrivateHyperFileInfo fileToRemove in filesToRemove)
{
_ = files.Remove(fileToRemove);
}
SaveFiles();
} }
private void SaveFiles() private void SaveFiles()
@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
namespace HyperbolicDownloaderApi.FileProcessing;
internal class HyperFileDto
{
public string Hash { get; set; }
public string Name { get; set; }
[JsonConstructor]
public HyperFileDto(string hash, string name)
{
Hash = hash;
Name = name;
}
public HyperFileDto(PrivateHyperFileInfo privateHyperFileInfo)
{
Hash = privateHyperFileInfo.Hash;
Name = Path.GetFileName(privateHyperFileInfo.FilePath);
}
}
@@ -1,13 +1,7 @@
namespace HyperbolicDownloaderApi.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
public class PrivateHyperFileInfo public class PrivateHyperFileInfo(string hash, string filePath)
{ {
public string Hash { get; set; } public string Hash { get; set; } = hash;
public string FilePath { get; set; } public string FilePath { get; set; } = filePath;
public PrivateHyperFileInfo(string hash, string filePath)
{
Hash = hash;
FilePath = filePath;
}
} }
@@ -5,7 +5,7 @@ namespace HyperbolicDownloaderApi.FileProcessing;
public class PublicHyperFileInfo public class PublicHyperFileInfo
{ {
public string Hash { get; set; } = string.Empty; public string Hash { get; set; } = string.Empty;
public List<NetworkSocket> Hosts { get; set; } = new(); public List<NetworkSocket> Hosts { get; set; } = [];
public PublicHyperFileInfo() public PublicHyperFileInfo()
{ {
@@ -0,0 +1,9 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Major Code Smell", "S6561:Avoid using \"DateTime.Now\" for benchmarking or timing operations", Justification = "Stop")]
[assembly: SuppressMessage("Minor Code Smell", "S3604:Member initializer values should not be redundant", Justification = "False positive")]
@@ -1,14 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Open.Nat" Version="2.1.0" /> <PackageReference Include="CuteUtils" Version="1.0.0" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" /> <PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="SharpOpenNat" Version="4.0.17" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,13 +1,12 @@
using System.Reflection; namespace HyperbolicDownloaderApi.Managment;
namespace HyperbolicDownloaderApi.Managment;
public static class ApiConfiguration public static class ApiConfiguration
{ {
public const int BroadcastPort = 2155; public const int BroadcastPort = 2155;
public const int PrivatePort = 3055; public const int PrivatePort = 3055;
public static int PublicPort { get; set; } public static int PublicPort { get; set; }
public static string BasePath { get; } = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) ?? string.Empty; public static string BasePath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "StoneRed", "HyperbolicDownloader");
public static string HostsFilePath { get; } = Path.Combine(BasePath, "Hosts.json"); public static string HostsFilePath { get; } = Path.Combine(BasePath, "Hosts.json");
public static string FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json"); public static string FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json");
public static string DirectoriesInfoPath { get; } = Path.Combine(BasePath, "Directories.json");
} }
+60 -35
View File
@@ -1,7 +1,7 @@
using HyperbolicDownloaderApi.FileProcessing; using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking; using HyperbolicDownloaderApi.Networking;
using Open.Nat; using SharpOpenNat;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
@@ -13,19 +13,20 @@ public class ApiManager
{ {
public static event EventHandler<NotificationMessageEventArgs>? OnNotificationMessageRecived; public static event EventHandler<NotificationMessageEventArgs>? OnNotificationMessageRecived;
public static IPAddress? PublicIpAddress => device?.GetExternalIPAsync().GetAwaiter().GetResult();
public FilesManager FilesManager { get; } = new();
public HostsManager HostsManager { get; } = new();
private static readonly Random random = new(); private static readonly Random random = new();
private static NatDevice? device; private static INatDevice? device;
private static Mapping? portMapping; private static Mapping? portMapping;
private readonly BroadcastClient broadcastClient = new BroadcastClient(); private readonly BroadcastClient broadcastClient = new BroadcastClient();
private readonly NetworkClient networkClient; private readonly NetworkClient networkClient;
public static IPAddress? PublicIpAddress => device?.GetExternalIPAsync().GetAwaiter().GetResult();
public FilesManager FilesManager { get; } = new();
public HostsManager HostsManager { get; } = new();
public DirectoryWatcher DirectoryWatcher { get; }
public ApiManager() public ApiManager()
{ {
networkClient = new(FilesManager); networkClient = new(FilesManager);
DirectoryWatcher = new(FilesManager);
} }
public static NetworkSocket? GetLocalSocket() public static NetworkSocket? GetLocalSocket()
@@ -58,8 +59,7 @@ public class ApiManager
{ {
ApiConfiguration.PublicPort = random.Next(1000, 6000); ApiConfiguration.PublicPort = random.Next(1000, 6000);
NatDiscoverer? discoverer = new NatDiscoverer(); device = await OpenNat.Discoverer.DiscoverDeviceAsync();
device = await discoverer.DiscoverDeviceAsync();
IPAddress? ip = await device.GetExternalIPAsync(); IPAddress? ip = await device.GetExternalIPAsync();
SendNotificationMessageNewLine($"The public IP address is: {ip} ", NotificationMessageType.Success); SendNotificationMessageNewLine($"The public IP address is: {ip} ", NotificationMessageType.Success);
@@ -81,38 +81,45 @@ public class ApiManager
SendNotificationMessageNewLine($"An error occurred while mapping the private port ({ApiConfiguration.PrivatePort}) to the public port ({ApiConfiguration.PublicPort})! Error message: {ex.Message}", NotificationMessageType.Error); SendNotificationMessageNewLine($"An error occurred while mapping the private port ({ApiConfiguration.PrivatePort}) to the public port ({ApiConfiguration.PublicPort})! Error message: {ex.Message}", NotificationMessageType.Error);
return false; return false;
} }
catch (SocketException ex)
{
SendNotificationMessageNewLine($"An error occurred while mapping the private port ({ApiConfiguration.PrivatePort}) to the public port ({ApiConfiguration.PublicPort})! Error message: {ex.Message}", NotificationMessageType.Error);
return false;
}
} }
public static void ClosePorts() public static void ClosePorts()
{ {
SendNotificationMessageNewLine("Closing ports...", NotificationMessageType.Info); SendNotificationMessageNewLine("Closing ports...", NotificationMessageType.Info);
if (device is not null) if (device is not null && portMapping is not null)
{ {
try try
{ {
IEnumerable<Mapping>? mappings = device.GetAllMappingsAsync().GetAwaiter().GetResult(); device.DeletePortMapAsync(portMapping).GetAwaiter().GetResult();
foreach (Mapping? mapping in mappings)
{
if (mapping.Description.Contains("HyperbolicDowloader") && mapping.PrivateIP.ToString() == portMapping?.PrivateIP.ToString())
{
device.DeletePortMapAsync(mapping).GetAwaiter().GetResult();
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
SendNotificationMessageNewLine(ex.ToString(), NotificationMessageType.Error); SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Error);
} }
} }
SendNotificationMessageNewLine("Ports closed!", NotificationMessageType.Warning); SendNotificationMessageNewLine("Ports closed!", NotificationMessageType.Warning);
Environment.Exit(0);
} }
public void StartBroadcastListener() public bool StartBroadcastListener()
{ {
broadcastClient.StartListening(ApiConfiguration.BroadcastPort); try
broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived; {
broadcastClient.StartListening(ApiConfiguration.BroadcastPort);
broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived;
}
catch (SocketException ex)
{
SendNotificationMessageNewLine($"An error occurred while starting the TCP listener! Error message: {ex.Message}", NotificationMessageType.Error); // net stop hens && net start hns
return false;
}
return true;
} }
public bool StartTcpListener() public bool StartTcpListener()
@@ -123,6 +130,7 @@ public class ApiManager
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer); networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage); networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("HasFile", HasFile); networkClient.ListenTo<string>("HasFile", HasFile);
networkClient.ListenTo<string>("GetFilesList", GetFileList);
networkClient.StartListening(ApiConfiguration.PrivatePort); networkClient.StartListening(ApiConfiguration.PrivatePort);
} }
catch (SocketException ex) catch (SocketException ex)
@@ -134,6 +142,21 @@ public class ApiManager
return true; return true;
} }
internal static void SendNotificationMessage(string message, NotificationMessageType messageType = NotificationMessageType.Info)
{
OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message));
}
internal static void SendNotificationMessageNewLine(string message, NotificationMessageType messageType = NotificationMessageType.Info)
{
OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message + Environment.NewLine));
}
private static void ReciveMessage(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
SendNotificationMessageNewLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.", NotificationMessageType.Info);
}
private async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs) private async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs)
{ {
IPAddress remoteIpAddress = recivedEventArgs.IPEndPoint.Address; IPAddress remoteIpAddress = recivedEventArgs.IPEndPoint.Address;
@@ -156,7 +179,7 @@ public class ApiManager
if (success) if (success)
{ {
HostsManager.Add(new NetworkSocket(remoteIpAddress.ToString(), remotePort, DateTime.Now)); _ = HostsManager.Add(new NetworkSocket(remoteIpAddress.ToString(), remotePort, DateTime.Now));
try try
{ {
await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend); await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend);
@@ -171,11 +194,14 @@ public class ApiManager
private void DiscoverAnswer(object? sender, MessageRecivedEventArgs<List<NetworkSocket>> recivedEventArgs) private void DiscoverAnswer(object? sender, MessageRecivedEventArgs<List<NetworkSocket>> recivedEventArgs)
{ {
SendNotificationMessageNewLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s).", NotificationMessageType.Info); SendNotificationMessageNewLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s).", NotificationMessageType.Info);
HostsManager.AddRange(recivedEventArgs.Data); SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Discovery answer {string.Join(", ", recivedEventArgs.Data)}", NotificationMessageType.Log);
_ = HostsManager.AddRange(recivedEventArgs.Data);
} }
private async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs) private async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{ {
SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting host list", NotificationMessageType.Log);
List<NetworkSocket> hostsToSend = HostsManager.ToList(); List<NetworkSocket> hostsToSend = HostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket(); NetworkSocket? localSocket = GetLocalSocket();
@@ -190,7 +216,7 @@ public class ApiManager
if (recivedEventArgs.Data.Port != 0) if (recivedEventArgs.Data.Port != 0)
{ {
HostsManager.Add(recivedEventArgs.Data); _ = HostsManager.Add(recivedEventArgs.Data);
} }
await recivedEventArgs.SendResponseAsync(hostsToSend); await recivedEventArgs.SendResponseAsync(hostsToSend);
@@ -198,21 +224,20 @@ public class ApiManager
private async void HasFile(object? sender, MessageRecivedEventArgs<string> recivedEventArgs) private async void HasFile(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{ {
SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Check if file exists [{recivedEventArgs.Data}]", NotificationMessageType.Log);
await recivedEventArgs.SendResponseAsync(FilesManager.Contains(recivedEventArgs.Data)); await recivedEventArgs.SendResponseAsync(FilesManager.Contains(recivedEventArgs.Data));
} }
private void ReciveMessage(object? sender, MessageRecivedEventArgs<string> recivedEventArgs) private async void GetFileList(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{ {
SendNotificationMessageNewLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.", NotificationMessageType.Info); SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting file list", NotificationMessageType.Log);
}
internal static void SendNotificationMessage(string message, NotificationMessageType messageType = NotificationMessageType.Info) List<HyperFileDto> files = [.. FilesManager
{ .ToList()
OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message)); .Select(f => new HyperFileDto(f))
} .Where(f => f.Name.Contains(recivedEventArgs.Data))];
internal static void SendNotificationMessageNewLine(string message, NotificationMessageType messageType = NotificationMessageType.Info) await recivedEventArgs.SendResponseAsync(files);
{
OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message + Environment.NewLine));
} }
} }
@@ -1,20 +1,16 @@
namespace HyperbolicDownloaderApi.Managment; namespace HyperbolicDownloaderApi.Managment;
public class NotificationMessageEventArgs : EventArgs public class NotificationMessageEventArgs(NotificationMessageType notificationMessageType, string? message) : EventArgs
{ {
public NotificationMessageType NotificationMessageType { get; } public NotificationMessageType NotificationMessageType { get; } = notificationMessageType;
public string? Message { get; } public string? Message { get; } = message;
public NotificationMessageEventArgs(NotificationMessageType notificationMessageType, string? message)
{
NotificationMessageType = notificationMessageType;
Message = message;
}
} }
public enum NotificationMessageType public enum NotificationMessageType
{ {
Info, Info,
Log,
Debug,
Success, Success,
Warning, Warning,
Error Error
@@ -11,9 +11,8 @@ internal class BroadcastClient
{ {
public event EventHandler<BroadcastRecivedEventArgs>? OnBroadcastRecived; public event EventHandler<BroadcastRecivedEventArgs>? OnBroadcastRecived;
public bool IsListening { get; private set; } = false;
private UdpClient? udpListener; private UdpClient? udpListener;
public bool IsListening { get; private set; } = false;
public static void Send(int port, string message) public static void Send(int port, string message)
{ {
@@ -58,7 +57,7 @@ internal class BroadcastClient
udpListener = new UdpClient(port); udpListener = new UdpClient(port);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, port); IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, port);
_ = Task.Run(() => _ = new TaskFactory().StartNew(() =>
{ {
while (IsListening) while (IsListening)
{ {
@@ -67,7 +66,7 @@ internal class BroadcastClient
OnBroadcastRecived?.Invoke(this, new BroadcastRecivedEventArgs(groupEP, message)); OnBroadcastRecived?.Invoke(this, new BroadcastRecivedEventArgs(groupEP, message));
} }
}); }, TaskCreationOptions.LongRunning);
} }
public void StopListening() public void StopListening()
@@ -2,14 +2,8 @@
namespace HyperbolicDownloaderApi.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class BroadcastRecivedEventArgs : EventArgs internal class BroadcastRecivedEventArgs(IPEndPoint ipEndPoint, string message) : EventArgs
{ {
public BroadcastRecivedEventArgs(IPEndPoint iPEndPoint, string message) public IPEndPoint IPEndPoint { get; } = ipEndPoint;
{ public string Message { get; } = message;
IPEndPoint = iPEndPoint;
Message = message;
}
public IPEndPoint IPEndPoint { get; }
public string Message { get; }
} }
@@ -1,13 +1,7 @@
namespace HyperbolicDownloaderApi.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class DataContainer internal class DataContainer(string eventName, string jsonData)
{ {
public string EventName { get; set; } public string EventName { get; set; } = eventName;
public string JsonData { get; set; } public string JsonData { get; set; } = jsonData;
public DataContainer(string eventName, string jsonData)
{
JsonData = jsonData;
EventName = eventName;
}
} }
@@ -1,5 +1,6 @@
using HyperbolicDownloaderApi.Managment; using HyperbolicDownloaderApi.Managment;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.Json; using System.Text.Json;
@@ -7,45 +8,47 @@ namespace HyperbolicDownloaderApi.Networking;
public class HostsManager public class HostsManager
{ {
private List<NetworkSocket> hosts = new List<NetworkSocket>(); private List<NetworkSocket> hosts = [];
public int Count => hosts.Count; public int Count => hosts.Count;
public void AddRange(IEnumerable<NetworkSocket> hosts) public int AddRange(IEnumerable<NetworkSocket> hosts)
{ {
foreach (NetworkSocket host in hosts) int newHosts = hosts.Count(Add);
{
Add(host);
}
SaveHosts(); SaveHosts();
return newHosts;
} }
public void Add(NetworkSocket host) public bool Add(NetworkSocket host)
{ {
if (!Contains(host)) if (!Contains(host) && !host.Equals(ApiManager.GetLocalSocket()))
{ {
hosts.Add(host); hosts.Add(host);
return true;
} }
SaveHosts(); SaveHosts();
return false;
} }
public void Remove(NetworkSocket host, bool forceRemove = false) public void Remove(NetworkSocket host, bool forceRemove = false)
{ {
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0) || forceRemove) if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0) || forceRemove)
{ {
_ = hosts.RemoveAll(x => x.IPAddress == host.IPAddress && x.Port == host.Port); _ = hosts.RemoveAll(x => x.Equals(host));
} }
SaveHosts(); SaveHosts();
} }
public bool Contains(NetworkSocket host) public bool Contains(NetworkSocket host)
{ {
return hosts.Any(x => x.IPAddress == host.IPAddress && x.Port == host.Port); return hosts.Exists(x => x.Equals(host));
} }
public int CheckHostsActivity() public int CheckHostsActivity()
{ {
List<NetworkSocket> hostsToRemove = new List<NetworkSocket>(); List<NetworkSocket> hostsToRemove = [];
int activeHostsCount = 0; int activeHostsCount = 0;
foreach (NetworkSocket host in hosts) foreach (NetworkSocket host in hosts)
{ {
@@ -54,7 +57,7 @@ public class HostsManager
try try
{ {
_ = tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(500); _ = tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(1000);
Console.CursorLeft = 0; Console.CursorLeft = 0;
if (tcpClient.Connected) if (tcpClient.Connected)
{ {
@@ -91,14 +94,65 @@ public class HostsManager
return activeHostsCount; return activeHostsCount;
} }
public int Sync()
{
int newHostsCount = 0;
foreach (NetworkSocket host in hosts.ToArray())
{
ApiManager.SendNotificationMessage($"{host.IPAddress}:{host.Port} > ???", NotificationMessageType.Warning);
try
{
NetworkSocket? localSocket = ApiManager.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue);
Task<List<NetworkSocket>?> sendTask = NetworkClient.SendAsync<List<NetworkSocket>>(IPAddress.Parse(host.IPAddress), host.Port, "GetHostsList", localSocket);
_ = sendTask.Wait(1000);
Console.CursorLeft = 0;
if (sendTask.IsCompletedSuccessfully)
{
int newHosts = AddRange(sendTask.Result ?? []);
newHostsCount += newHosts;
if (newHosts > 0)
{
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Found {newHosts} new hosts", NotificationMessageType.Success);
}
else
{
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > No new hosts found", NotificationMessageType.Warning);
}
host.LastActive = DateTime.Now;
}
else
{
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
}
}
catch
{
Console.CursorLeft = 0;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
}
}
SaveHosts();
return newHostsCount;
}
public List<NetworkSocket> ToList() public List<NetworkSocket> ToList()
{ {
return hosts.ToList(); return [.. hosts];
} }
public void SaveHosts() public void SaveHosts()
{ {
hosts = hosts.OrderByDescending(h => h.LastActive).ToList(); hosts = [.. hosts
.OrderByDescending(s => s.DownloadSpeed)
.ThenByDescending(s => s.LastActive)];
File.WriteAllText(ApiConfiguration.HostsFilePath, JsonSerializer.Serialize(hosts)); File.WriteAllText(ApiConfiguration.HostsFilePath, JsonSerializer.Serialize(hosts));
} }
} }
@@ -5,20 +5,11 @@ using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class MessageRecivedEventArgs<T> : EventArgs internal class MessageRecivedEventArgs<T>(NetworkStream networkStream, IPAddress ipAddress, T data) : EventArgs
{ {
private readonly NetworkStream networkStream; public T Data { get; set; } = data;
public MessageRecivedEventArgs(NetworkStream networkStream, IPAddress ipAddress, T data) public IPAddress IpAddress { get; set; } = ipAddress;
{
this.networkStream = networkStream;
Data = data;
IpAddress = ipAddress;
}
public T Data { get; set; }
public IPAddress IpAddress { get; set; }
public async Task SendResponseAsync(object response) public async Task SendResponseAsync(object response)
{ {
@@ -1,4 +1,7 @@
using HyperbolicDownloaderApi.FileProcessing; using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using NAudio.Wave;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
@@ -8,25 +11,15 @@ using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class NetworkClient internal class NetworkClient(FilesManager filesManager)
{ {
public bool IsListening { get; private set; } = false; private readonly Dictionary<string, (Type type, Delegate method)> events = [];
private TcpListener? tcpListener; private TcpListener? tcpListener;
private readonly FilesManager filesManager; public bool IsListening { get; private set; } = false;
private readonly Dictionary<string, (Type type, Delegate method)> events = new();
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)
{ {
if (remoteIp is null) ArgumentNullException.ThrowIfNull(remoteIp);
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient(); TcpClient client = new TcpClient();
@@ -39,9 +32,23 @@ internal class NetworkClient
await nwStream.WriteAsync(bytesToSend); await nwStream.WriteAsync(bytesToSend);
List<byte> bytes = [];
byte[] bytesToRead = new byte[client.ReceiveBufferSize]; byte[] bytesToRead = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(bytesToRead.AsMemory(0, client.ReceiveBufferSize));
string response = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead); while (nwStream.CanRead)
{
int bytesRead = await nwStream.ReadAsync(bytesToRead.AsMemory(0, client.ReceiveBufferSize));
if (bytesRead == 0)
{
break;
}
bytes.AddRange(bytesToRead.AsMemory(0, bytesRead).ToArray());
}
string response = Encoding.ASCII.GetString(bytes.ToArray());
client.Close(); client.Close();
@@ -57,10 +64,7 @@ internal class NetworkClient
public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data) public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data)
{ {
if (remoteIp is null) ArgumentNullException.ThrowIfNull(remoteIp);
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient(); TcpClient client = new TcpClient();
@@ -98,98 +102,27 @@ internal class NetworkClient
tcpListener.Start(); tcpListener.Start();
IsListening = true; IsListening = true;
_ = Task.Run(async () => _ = new TaskFactory().StartNew(() =>
{ {
while (IsListening) while (IsListening)
{ {
try try
{ {
ApiManager.SendNotificationMessageNewLine("Listening for connections...", NotificationMessageType.Debug);
TcpClient client = tcpListener.AcceptTcpClient(); TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize)); ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Connected", NotificationMessageType.Debug);
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead); _ = Task.Run(() => HandleRequest(client));
if (dataReceived.StartsWith("Download"))
{
_ = Upload(client, dataReceived[8..]);
continue;
}
if (string.IsNullOrWhiteSpace(dataReceived))
{
client.Close();
continue;
}
DataContainer? dataContainer = JsonSerializer.Deserialize<DataContainer>(dataReceived);
if (dataContainer is not null && events.ContainsKey(dataContainer.EventName))
{
(Type type, Delegate method) = events[dataContainer.EventName];
Type eventArgsType = typeof(MessageRecivedEventArgs<>).MakeGenericType(type);
object? eventArgs = Activator.CreateInstance(
eventArgsType,
nwStream,
(client.Client.RemoteEndPoint as IPEndPoint)?.Address,
JsonSerializer.Deserialize(dataContainer.JsonData, type));
_ = (method?.DynamicInvoke(this, eventArgs));
}
client.Close();
} }
catch (SocketException ex) catch (SocketException ex)
{ {
if (ex.SocketErrorCode != SocketError.Interrupted) ApiManager.SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Log);
{
throw;
}
} }
} }
tcpListener.Stop(); tcpListener.Stop();
}); }, TaskCreationOptions.LongRunning);
}
private async Task Upload(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 64000;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
FileInfo fileInfo = new FileInfo(hyperFileInfo.FilePath);
bytesToSend = Encoding.ASCII.GetBytes($"{fileInfo.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}");
Array.Resize(ref bytesToSend, 1000);
await nwStream.WriteAsync(bytesToSend);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000).Where(chunk => chunk is not null))
{
await nwStream.WriteAsync(chunk);
}
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
client.Close();
}
} }
public void StopListening() public void StopListening()
@@ -202,4 +135,200 @@ internal class NetworkClient
{ {
events.Add(eventName, (typeof(T), eventHandler)); events.Add(eventName, (typeof(T), eventHandler));
} }
private async Task HandleRequest(TcpClient client)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Handling request", NotificationMessageType.Debug);
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Reading data", NotificationMessageType.Debug);
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize));
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Data read", NotificationMessageType.Debug);
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Data: {dataReceived}", NotificationMessageType.Debug);
if (dataReceived.StartsWith("Download"))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Download request", NotificationMessageType.Debug);
_ = Upload(client, dataReceived[8..]);
return;
}
if (dataReceived.StartsWith("StreamWav"))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Download request", NotificationMessageType.Debug);
_ = StreamWav(client, dataReceived[9..]);
return;
}
if (string.IsNullOrWhiteSpace(dataReceived))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Disconnected", NotificationMessageType.Debug);
client.Close();
return;
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Deserializing data", NotificationMessageType.Debug);
DataContainer? dataContainer = JsonSerializer.Deserialize<DataContainer>(dataReceived);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Data deserialized", NotificationMessageType.Debug);
if (dataContainer is not null && events.TryGetValue(dataContainer.EventName, out (Type type, Delegate method) value))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Found event", NotificationMessageType.Debug);
(Type type, Delegate method) = value;
Type eventArgsType = typeof(MessageRecivedEventArgs<>).MakeGenericType(type);
object? eventArgs = Activator.CreateInstance(
eventArgsType,
nwStream,
(client.Client.RemoteEndPoint as IPEndPoint)?.Address,
JsonSerializer.Deserialize(dataContainer.JsonData, type));
_ = (method?.DynamicInvoke(this, eventArgs));
}
else
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Event not found", NotificationMessageType.Debug);
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Closing connection", NotificationMessageType.Debug);
client.Close();
ApiManager.SendNotificationMessageNewLine($"{(client.Client?.RemoteEndPoint as IPEndPoint)?.Address} > Connection closed", NotificationMessageType.Debug);
}
private async Task Upload(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Requesting file download [{hash}]", NotificationMessageType.Log);
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 64000;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Accepting file download [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
FileInfo fileInfo = new FileInfo(hyperFileInfo.FilePath);
bytesToSend = Encoding.ASCII.GetBytes($"{fileInfo.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}");
await nwStream.WriteAsync(bytesToSend);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Starting file download of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000).Where(chunk => chunk is not null))
{
await nwStream.WriteAsync(chunk);
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Completed download of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > File not found [{hash}]", NotificationMessageType.Log);
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Error downloading file {ex.Message} [{hash}]", NotificationMessageType.Log);
Debug.WriteLine(ex);
}
finally
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Closing connection", NotificationMessageType.Debug);
client.Close();
ApiManager.SendNotificationMessageNewLine($"{(client.Client?.RemoteEndPoint as IPEndPoint)?.Address} > Connection closed", NotificationMessageType.Debug);
}
}
private async Task StreamWav(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Requesting file stream [{hash}]", NotificationMessageType.Log);
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 6400;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Accepting file stream [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
WaveFileReader reader;
try
{
reader = new WaveFileReader(hyperFileInfo.FilePath);
}
catch (FormatException ex)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Error streaming file {ex.Message} [{hash}]", NotificationMessageType.Log);
Debug.WriteLine(ex);
bytesToSend = Encoding.ASCII.GetBytes("Invalid file format!");
await nwStream.WriteAsync(bytesToSend);
return;
}
bytesToSend = Encoding.ASCII.GetBytes($"{reader.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}/{reader.WaveFormat.SampleRate}/{reader.WaveFormat.BitsPerSample}/{reader.WaveFormat.Channels}");
await nwStream.WriteAsync(bytesToSend);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Starting file stream of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
byte[]? buffer = new byte[64000];
while (reader.Position < reader.Length)
{
int bytesRead = reader.Read(buffer, 0, 6400);
await nwStream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Completed stream of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
reader.Close();
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > File not found [{hash}]", NotificationMessageType.Log);
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Error streaming file {ex.Message} [{hash}]", NotificationMessageType.Log);
Debug.WriteLine(ex);
}
finally
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Closing connection", NotificationMessageType.Debug);
client.Close();
ApiManager.SendNotificationMessageNewLine($"{(client.Client?.RemoteEndPoint as IPEndPoint)?.Address} > Connection closed", NotificationMessageType.Debug);
}
}
} }
@@ -1,15 +1,25 @@
namespace HyperbolicDownloaderApi.Networking; namespace HyperbolicDownloaderApi.Networking;
public class NetworkSocket public class NetworkSocket(string ipAddress, int port, DateTime lastActive)
{ {
public string IPAddress { get; set; } public string IPAddress { get; set; } = ipAddress;
public int Port { get; set; } public int Port { get; set; } = port;
public DateTime LastActive { get; set; } public DateTime LastActive { get; set; } = lastActive;
public NetworkSocket(string ipAddress, int port, DateTime lastActive) public long DownloadSpeed { get; set; }
public override bool Equals(object? obj)
{ {
IPAddress = ipAddress; if (obj is not NetworkSocket networkSocket)
Port = port; {
LastActive = lastActive; return false;
}
return networkSocket.IPAddress == IPAddress && networkSocket.Port == Port;
}
public override int GetHashCode()
{
return HashCode.Combine(IPAddress, Port);
} }
} }
@@ -0,0 +1,38 @@
namespace HyperbolicDownloaderApi.Utilities;
internal static class UnitFormatter
{
public static string TransferRate(long bytesPerSecond)
{
string[] ordinals = ["", "K", "M", "G", "T", "P", "E"];
decimal rate = bytesPerSecond * 8;
int ordinal = 0;
while (rate > 1000)
{
rate /= 1000;
ordinal++;
}
return $"{Math.Round(rate, 0, MidpointRounding.AwayFromZero)}{ordinals[ordinal]}bps";
}
public static string FileSize(long bytes)
{
string[] ordinals = ["", "K", "M", "G", "T", "P", "E"];
decimal rate = bytes;
int ordinal = 0;
while (rate > 1000)
{
rate /= 1000;
ordinal++;
}
return $"{Math.Round(rate, 0, MidpointRounding.AwayFromZero)}{ordinals[ordinal]}B";
}
}
-454
View File
@@ -1,454 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
-12
View File
@@ -1,12 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HyperbolicDownloaderGUI"
x:Class="HyperbolicDownloaderGUI.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>
-30
View File
@@ -1,30 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using HyperbolicDownloaderGUI.ViewModels;
using HyperbolicDownloaderGUI.Views;
namespace HyperbolicDownloaderGUI
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
</ItemGroup>
</Project>
-28
View File
@@ -1,28 +0,0 @@
using Avalonia;
using Avalonia.ReactiveUI;
using System;
namespace HyperbolicDownloaderGUI
{
internal static class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
}
}
}
-32
View File
@@ -1,32 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using HyperbolicDownloaderGUI.ViewModels;
using System;
namespace HyperbolicDownloaderGUI
{
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
}
@@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HyperbolicDownloaderGUI.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
public string Greeting => "Welcome to Avalonia!";
}
}
@@ -1,12 +0,0 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace HyperbolicDownloaderGUI.ViewModels
{
public class ViewModelBase : ReactiveObject
{
}
}
@@ -1,17 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:HyperbolicDownloaderGUI.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="HyperbolicDownloaderGUI.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="HyperbolicDownloaderGUI">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Window>
@@ -1,22 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace HyperbolicDownloaderGUI.Views
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
+10 -11
View File
@@ -1,28 +1,27 @@
# HyperbolicDownloader # HyperbolicDownloader
> A cross-platform P2P file sharing CLI tool & protocol > A cross-platform P2P file sharing CLI client
## Usage ## Usage
1. Download one of the [releases](https://github.com/Stone-Red-Code/HyperbolicDownloader/releases). 1. Download one of the [releases](https://github.com/Stone-Red-Code/HyperbolicDownloader/releases)
1. Execute the `HyperbolicDownloader` file. 1. Exacute the `HyperbolicDownloader` file
1. Wait until the setup process is finished. 1. Wait until the setup process is finished
1. Next steps: 1. Do one of the below
- Add a host to your client using the [add](#add) command and use the [get](#get) command to retrieve a file. - Add a host to your client using the [add](#add) command and use the [get](#get) command tho retrive a file
- Add the host `hyper.stone-red.net:3055` (`add host hyper.stone-red.net:3055`) to the hosts list to obtain a few existing hosts and immediately gain access to all of my files hosted on the HyperbolicDownloader network.\ - Use the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate) command to generate a `.hyper` file and the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrive a file trough the generated `.hyper` file
- Use the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate) command to generate a `.hyper` file and the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrieve a file through the generated `.hyper` file.
## How it works ## How it works
When you start the program, it first tries to open a public port via UPnP/NAT-PMP. If it succeeds, you can communicate with the client using the public IP address and port. When you start the program, it first tries to open a public port via UPnP/NAT-PMP. If it succeeds, you can communicate with the client using the public IP address and port.
If it is unable to find a UPnP/NAT-PMP device, you will need to manually set up port forwarding on your client's IP address to port "3055". The public port has to be between 1000 and 6000 here. If it is unable to find a UPnP/NAT-PMP device, you will need to manually set up port forwarding on your client's IP address to port "3055". The public port has to be between 1000 and 6000 here.
HyperbolicDownloader can retrieve files from other computers (aka hosts) using an SHA 512 hash. HyperbolicDownloader can retrieve files from other computers (aka hosts) using a SHA 512 hash.
The client checks all known hosts to see if it could find the requested file. If one of the hosts has the requested file, it immediately downloads it. The client checks all known hosts to see if it could find the requested file. If one of the hosts has the requested file, it immediately downloads it.
After the file is completely downloaded, it is validated by comparing the hash entered with that of the file received. If the hash does not match, you will receive a warning message, and you can download the file again if needed.\ After the file is completely downloaded, it is validated by comparing the hash entered with that of the file received. If the hash does not match, you will receive a warning message and you can download the file again if needed.\
This makes it very difficult to tamper with requested files, as long as the source from which you obtain the hash/`.hpyer` file is trusted. This makes it very difficult to tamper with requested files, as long as the source from which you obtain the hash/`.hpyer` file is trusted.
You can generate `.hyper` files with your client using the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate). Command\ You can generate `.hyper` files with your client using the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate). command\
These files contain the hash value of the actual file and the hosts that should have the requested file. These files contain the hash value of the actual file and the hosts that should have the requested file.
You can use the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrieve the file or if you are using Windows you can right-click the `.hyper` file, select `open with` and select the HyperbolicDownloader executable. You can use the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrieve the file or if you are using Windows you can right-click the `.hyper` file, select `open with` and select the HyperbolicDownloader executable.