72 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
Stone_Red 2003cdcff0 Merge branch 'main' into develop 2023-02-06 15:41:09 +01:00
Stone_Red 6f9626fcfd Add response to discover command. 2023-02-06 15:28:53 +01:00
Stone_Red fb21a9f492 Code cleanup 2023-02-06 15:15:47 +01:00
Stone_Red 7d662dc113 Ignore broadcast messages from own PC 2022-11-25 12:05:34 +01:00
Stone_Red 1f770ce131 Add missing lines to README 2022-04-28 07:28:35 +02:00
Stone_Red 4dd2b7274e Fix StartTcpListener method not stopping program if fails 2022-04-25 11:16:01 +02:00
Stone_Red f5dd85e38c Update Console.Commander.NET to the newest version 2022-04-25 11:15:23 +02:00
Stone_Red 826c6fc7ee Fix command descriptions in README.md 2022-04-25 11:14:27 +02:00
Stone_Red c2b84dd475 Code refactoring 2022-04-22 16:09:21 +02:00
Stone_Red 9f08b5ca94 Merge branch 'develop' 2022-04-21 07:35:57 +02:00
Stone_Red f906d4581c Update naming and add auto retry if hash is wrong 2022-04-21 07:35:52 +02:00
Stone_Red 69929fd58a Add rule to ignore the warning: Mark members as static to .editorconfig 2022-04-21 07:35:50 +02:00
Stone_Red 495a09ec60 Update naming and add auto retry if hash is wrong 2022-04-21 07:34:28 +02:00
Stone_Red 21abb44230 Add rule to ignore the warning: Mark members as static to .editorconfig 2022-04-21 07:33:26 +02:00
Stone_Red 090c23d4a2 Merge pull request #1 from Stone-Red-Code/develop
Develop
2022-03-07 12:29:31 +01:00
Stone_Red 6572e5bda5 Fix bug
- Fix incorrect download speed calculation
- Add debug logging to the download section
2022-03-02 19:05:45 +01:00
Stone_Red a90af811c1 Fix important bug
- Fixed a bug that caused the content of the file to be sent as the filename
2022-02-28 20:28:05 +01:00
Stone_Red 9675361695 Fix code formatting and decrease host answer delay 2022-02-27 13:47:45 +01:00
Stone_Red 715f5de435 Add missing | 2022-02-26 15:32:03 +01:00
Stone_Red a2fa7dd3f8 Resolve domains and code structure improvements
- Resolve domains when adding a new host
- Change `NotificationMessageType.Raw` to `NotificationMessageType.Info`
- Fix wrong color when using the `info` command
2022-02-26 15:29:27 +01:00
Stone_Red cf7f952012 Improve code structure
- Add separate API project
- Add notification system
2022-02-24 22:56:45 +01:00
Stone_Red 6ffdc720a4 Fix .hyper files not adding hosts to host list 2022-02-20 19:15:35 +01:00
Stone_Red 7783ebb2e9 Add GUI template
- Add AvaloniaUI GUI template to the project
- Add missing newline to the CLI output
2022-02-20 16:07:06 +01:00
Stone_Red 162c456ae5 Fix wording of a sentence 2022-02-11 19:20:50 +01:00
Stone_Red c6e04eec96 Update README.md 2022-02-09 19:10:52 +01:00
Stone_Red eedfdf8753 - Add more detailed How it works stuff 2022-02-08 23:16:32 +01:00
Stone_Red f7210d1389 Create LICENSE 2022-02-08 23:03:21 +01:00
48 changed files with 2544 additions and 1345 deletions
+10
View File
@@ -0,0 +1,10 @@
[*.cs]
# S4457: Parameter validation in "async"/"await" methods should be wrapped
dotnet_diagnostic.S4457.severity = silent
# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = silent
# S1172: Unused method parameters should be removed
dotnet_diagnostic.S1172.severity = silent
+11
View File
@@ -5,6 +5,13 @@ VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperbolicDownloader", "HyperbolicDownloader\HyperbolicDownloader.csproj", "{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperbolicDownloader", "HyperbolicDownloader\HyperbolicDownloader.csproj", "{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperbolicDownloaderApi", "HyperbolicDownloaderApi\HyperbolicDownloaderApi.csproj", "{45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CA4F21C6-5B63-46C1-8576-91BB3FAE4FA7}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -15,6 +22,10 @@ Global
{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.ActiveCfg = Release|Any CPU {7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.Build.0 = Release|Any CPU {7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.Build.0 = Release|Any CPU
{45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -1,13 +0,0 @@
namespace HyperbolicDownloader.FileProcessing;
internal class PrivateHyperFileInfo
{
public string Hash { get; set; }
public string FilePath { get; set; }
public PrivateHyperFileInfo(string hash, string filePath)
{
Hash = hash;
FilePath = filePath;
}
}
@@ -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)")]
@@ -1,16 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<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>
<ItemGroup> <ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.0" /> <None Include="..\.editorconfig" Link=".editorconfig" />
<PackageReference Include="Open.NAT" Version="2.1.0" /> </ItemGroup>
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
<ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HyperbolicDownloaderApi\HyperbolicDownloaderApi.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+93
View File
@@ -0,0 +1,93 @@
using Commander;
using CuteUtils.Misc;
using HyperbolicDownloaderApi.Commands;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking;
namespace HyperbolicDownloader;
internal class InputHandler
{
private readonly Commander.Commander commander = new Commander.Commander();
private bool exit = false;
public InputHandler(HostsManager hostsManager, FilesManager filesManager, DirectoryWatcher directoryWatcher)
{
HostCommands hostCommands = new HostCommands(hostsManager);
FileCommands fileCommands = new FileCommands(hostsManager, filesManager);
DirectoryCommands directoryCommands = new DirectoryCommands(directoryWatcher);
DownloadCommands downloadCommands = new DownloadCommands(hostsManager, filesManager);
StreamingCommands streamingCommands = new StreamingCommands(hostsManager);
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(_ => exit = true, (HelpText)"Exits the application.", "exit", "quit");
_ = 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.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");
_ = 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");
_ = 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");
_ = 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");
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(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");
Command listCommand = commander.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "list", "ls");
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");
_ = commander.Register(hostCommands.CheckActiveHosts, (HelpText)"Checks the status of known hosts.", "status", "check");
}
public void ReadInput()
{
while (!exit)
{
Console.WriteLine();
Console.Write("> ");
Console.CursorVisible = true;
string input = Console.ReadLine()?.Trim() ?? string.Empty;
ExecuteInput(input);
}
}
public void ExecuteInput(string input)
{
Console.CursorVisible = false;
try
{
if (!commander.Execute(input, out _))
{
ConsoleExt.WriteLine("Unknown command! Use `help` to list all commands.", 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);
}
}
}
}
}
@@ -1,15 +0,0 @@
using System.Net;
namespace HyperbolicDownloader.Networking;
internal class BroadcastRecivedEventArgs : EventArgs
{
public BroadcastRecivedEventArgs(IPEndPoint iPEndPoint, string message)
{
IPEndPoint = iPEndPoint;
Message = message;
}
public IPEndPoint IPEndPoint { get; }
public string Message { get; }
}
@@ -1,14 +0,0 @@
namespace HyperbolicDownloader
{
internal class DataContainer
{
public string EventName { get; set; }
public string JsonData { get; set; }
public DataContainer(string eventName, string jsonData)
{
JsonData = jsonData;
EventName = eventName;
}
}
}
@@ -1,109 +0,0 @@
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Net.Sockets;
using System.Text.Json;
namespace HyperbolicDownloader;
internal class HostsManager
{
private List<NetworkSocket> hosts = new List<NetworkSocket>();
public int Count => hosts.Count;
public void AddRange(IEnumerable<NetworkSocket> hosts)
{
foreach (NetworkSocket host in hosts)
{
if (!Contains(host))
{
this.hosts.Add(host);
}
}
SaveHosts();
}
public void Add(NetworkSocket host)
{
if (!Contains(host))
{
hosts.Add(host);
}
SaveHosts();
}
public void Remove(NetworkSocket host, bool forceRemove = false)
{
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0) || forceRemove)
{
hosts.RemoveAll(x => x.IPAddress == host.IPAddress && x.Port == host.Port);
}
SaveHosts();
}
public bool Contains(NetworkSocket host)
{
return hosts.Any(x => x.IPAddress == host.IPAddress && x.Port == host.Port);
}
public int CheckHostsActivity()
{
List<NetworkSocket> hostsToRemove = new List<NetworkSocket>();
int activeHostsCount = 0;
foreach (NetworkSocket host in hosts)
{
ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow);
using TcpClient tcpClient = new TcpClient();
try
{
tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(1000);
Console.CursorLeft = 0;
if (tcpClient.Connected)
{
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Active", ConsoleColor.Green);
host.LastActive = DateTime.Now;
activeHostsCount++;
}
else
{
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0))
{
hostsToRemove.Add(host);
}
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red);
}
}
catch
{
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0))
{
hostsToRemove.Add(host);
}
Console.CursorLeft = 0;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red);
}
}
foreach (NetworkSocket host in hostsToRemove)
{
hosts.Remove(host);
}
SaveHosts();
return activeHostsCount;
}
public List<NetworkSocket> ToList()
{
return hosts.ToList();
}
public void SaveHosts()
{
hosts = hosts.OrderByDescending(h => h.LastActive).ToList();
File.WriteAllText(Program.HostsFilePath, JsonSerializer.Serialize(hosts));
}
}
@@ -1,34 +0,0 @@
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloader
{
internal class MessageRecivedEventArgs<T> : EventArgs
{
private readonly NetworkStream networkStream;
public MessageRecivedEventArgs(NetworkStream networkStream, IPAddress ipAddress, T data)
{
this.networkStream = networkStream;
Data = data;
IpAddress = ipAddress;
}
public T Data { get; set; }
public IPAddress IpAddress { get; set; }
public async Task SendResponseAsync(object response)
{
byte[] bytesToSend = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(response));
await networkStream.WriteAsync(bytesToSend);
}
public void SendResponse(object response)
{
SendResponseAsync(response).GetAwaiter().GetResult();
}
}
}
@@ -1,204 +0,0 @@
using HyperbolicDownloader.FileProcessing;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloader.Networking
{
internal class NetworkClient
{
public bool IsListening { get; private set; } = false;
private TcpListener? tcpListener;
private readonly FilesManager filesManager;
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)
{
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
byte[] bytesToRead = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(bytesToRead.AsMemory(0, client.ReceiveBufferSize));
string response = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead);
client.Close();
if (string.IsNullOrWhiteSpace(response))
{
return default;
}
else
{
return JsonSerializer.Deserialize<T>(response);
}
}
public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data)
{
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
client.Close();
}
public static T? Send<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
return SendAsync<T>(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public static void Send(IPAddress remoteIp, int remotePort, string eventName, object data)
{
SendAsync(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public void StartListening(int port)
{
if (IsListening)
{
throw new InvalidOperationException("Already listening!");
}
tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
IsListening = true;
Task.Run(async () =>
{
while (IsListening)
{
try
{
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize));
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
if (dataReceived.StartsWith("Download"))
{
_ = Upload(client, dataReceived[8..]);
continue;
}
if (string.IsNullOrWhiteSpace(dataReceived))
{
client.Close();
continue;
}
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)
{
if (ex.SocketErrorCode != SocketError.Interrupted)
{
throw;
}
}
}
tcpListener.Stop();
});
}
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)}");
await nwStream.WriteAsync(bytesToSend);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000))
{
if (chunk is not null)
{
await nwStream.WriteAsync(chunk);
}
}
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
await nwStream.WriteAsync(bytesToSend);
}
client.Close();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void StopListening()
{
tcpListener?.Stop();
IsListening = false;
}
public void ListenTo<T>(string eventName, EventHandler<MessageRecivedEventArgs<T>> eventHandler)
{
events.Add(eventName, (typeof(T), eventHandler));
}
}
}
@@ -1,15 +0,0 @@
namespace HyperbolicDownloader.Networking;
internal class NetworkSocket
{
public string IPAddress { get; set; }
public int Port { get; set; }
public DateTime LastActive { get; set; }
public NetworkSocket(string ipAddress, int port, DateTime lastActive)
{
IPAddress = ipAddress;
Port = port;
LastActive = lastActive;
}
}
+80 -223
View File
@@ -1,14 +1,9 @@
using HyperbolicDownloader.FileProcessing; using CuteUtils.Misc;
using HyperbolicDownloader.Networking;
using HyperbolicDownloader.UserInterface;
using Open.Nat; using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
@@ -16,261 +11,123 @@ namespace HyperbolicDownloader;
internal static class Program internal static class Program
{ {
public const int BroadcastPort = 2155; private static readonly ApiManager apiManager = new ApiManager();
public static string BasePath { get; } = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) ?? string.Empty;
public static string HostsFilePath { get; } = Path.Combine(BasePath, "Hosts.json");
public static string FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json");
public static int PublicPort { get; private set; }
public static int PrivatePort { get; } = 3055;
public static IPAddress? PublicIpAddress => device?.GetExternalIPAsync().GetAwaiter().GetResult();
private static NatDevice? device;
private static Mapping? portMapping;
private static readonly HostsManager hostsManager = new();
private static readonly FilesManager filesManager = new();
private static readonly NetworkClient networkClient = new(filesManager);
private static readonly Random random = new();
private static async Task Main(string[] args) private static async Task Main(string[] args)
{ {
Console.CancelKeyPress += Console_CancelKeyPress; Console.CancelKeyPress += (_, _) => Close();
Console.CursorVisible = false; Console.CursorVisible = false;
if (File.Exists(HostsFilePath)) ApiManager.OnNotificationMessageRecived += ApiManager_OnNotificationMessageRecived;
if (!Directory.Exists(ApiConfiguration.BasePath))
{ {
string hostsJson = await File.ReadAllTextAsync(HostsFilePath); _ = Directory.CreateDirectory(ApiConfiguration.BasePath);
hostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? new());
} }
if (File.Exists(FilesInfoPath)) if (File.Exists(ApiConfiguration.HostsFilePath))
{ {
string filesJson = await File.ReadAllTextAsync(FilesInfoPath); string hostsJson = await File.ReadAllTextAsync(ApiConfiguration.HostsFilePath);
filesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? new()); _ = apiManager.HostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? []);
} }
InputHandler inputHandler = new InputHandler(hostsManager, filesManager); if (File.Exists(ApiConfiguration.FilesInfoPath))
{
string filesJson = await File.ReadAllTextAsync(ApiConfiguration.FilesInfoPath);
apiManager.FilesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? []);
}
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]))
{ {
UserInterface.Commands.DownloadCommands downloadCommands = new UserInterface.Commands.DownloadCommands(hostsManager, 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));
Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
_ = await OpenPorts();
ConsoleExt.WriteLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()} ", ConsoleColor.Green);
ConsoleExt.WriteLine($"The private port is: {PrivatePort}", ConsoleColor.Green);
Console.WriteLine("Starting TCP listener...");
try
{
networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList);
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("HasFile", HasFile);
networkClient.StartListening(PrivatePort);
}
catch (SocketException ex)
{
ConsoleExt.WriteLine($"An error occurred while starting the TCP listener! Error message: {ex.Message}", ConsoleColor.Red); // net stop hns && net start hns
Console.ReadKey();
return; return;
} }
BroadcastClient broadcastClient = new BroadcastClient(); await Initialize();
Console.WriteLine("Running local discovery routine..."); inputHandler.ReadInput();
BroadcastClient.Send(BroadcastPort, PrivatePort.ToString()); Close();
await Task.Delay(3000); }
private static async Task Initialize()
{
apiManager.DirectoryWatcher.Start();
Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
_ = await ApiManager.OpenPorts();
ConsoleExt.WriteLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()} ", ConsoleColor.Green);
ConsoleExt.WriteLine($"The private port is: {ApiConfiguration.PrivatePort}", ConsoleColor.Green);
Console.WriteLine("Starting TCP listener...");
if (!apiManager.StartTcpListener())
{
_ = Console.ReadLine();
Environment.Exit(-1);
}
Console.WriteLine("Starting UDP listener...");
if (!apiManager.StartBroadcastListener())
{
_ = Console.ReadLine();
Environment.Exit(-2);
}
int activeHostsCount = 0; int activeHostsCount = 0;
if (hostsManager.Count > 0) if (apiManager.HostsManager.Count > 0)
{ {
Console.WriteLine("Checking if hosts are active..."); Console.WriteLine("Checking if hosts are active...");
activeHostsCount = hostsManager.CheckHostsActivity(); activeHostsCount = apiManager.HostsManager.CheckHostsActivity();
} }
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.", 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($"{hostsManager.Count} known host(s)."); Console.WriteLine($"{apiManager.HostsManager.Count} known host(s).");
Console.WriteLine($"{activeHostsCount} active host(s)."); Console.WriteLine($"{activeHostsCount} active host(s).");
Console.WriteLine("Starting broadcast listener...");
broadcastClient.StartListening(BroadcastPort);
broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived;
ConsoleExt.WriteLine("Done", ConsoleColor.Green);
inputHandler.ReadInput(); ConsoleExt.WriteLine("Ready", ConsoleColor.Green);
ClosePorts();
} }
private static async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs) private static void ApiManager_OnNotificationMessageRecived(object? sender, NotificationMessageEventArgs e)
{ {
Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {recivedEventArgs.IPEndPoint.Address}"); switch (e.NotificationMessageType)
List<NetworkSocket> hostsToSend = hostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket();
if (localSocket is null)
{ {
return; case NotificationMessageType.Info: Console.Write(e.Message); break;
} case NotificationMessageType.Success: ConsoleExt.Write(e.Message, ConsoleColor.Green); break;
case NotificationMessageType.Warning: ConsoleExt.Write(e.Message, ConsoleColor.DarkYellow); break;
hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IPEndPoint.Address.ToString()); case NotificationMessageType.Error: ConsoleExt.Write(e.Message, ConsoleColor.Red); break;
hostsToSend.Add(localSocket);
bool success = int.TryParse(recivedEventArgs.Message, out int remotePort);
if (success)
{
hostsManager.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort, DateTime.Now));
try
{
await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend);
}
catch (SocketException ex)
{
Debug.WriteLine(ex);
}
} }
} }
private static void DiscoverAnswer(object? sender, MessageRecivedEventArgs<List<NetworkSocket>> recivedEventArgs) private static void Close()
{ {
Console.WriteLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s)."); ApiManager.ClosePorts();
hostsManager.AddRange(recivedEventArgs.Data); apiManager.HostsManager.SaveHosts();
}
private static void ReciveMessage(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
Console.WriteLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.");
}
private static async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{
List<NetworkSocket> hostsToSend = hostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket();
if (localSocket is null)
{
return;
}
hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IpAddress.ToString());
hostsToSend.Add(localSocket);
if (recivedEventArgs.Data.Port != 0)
{
hostsManager.Add(recivedEventArgs.Data);
}
await recivedEventArgs.SendResponseAsync(hostsToSend);
}
private static async void HasFile(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
await recivedEventArgs.SendResponseAsync(filesManager.Contains(recivedEventArgs.Data));
}
public static async Task<bool> OpenPorts()
{
try
{
PublicPort = random.Next(1000, 6000);
NatDiscoverer? discoverer = new NatDiscoverer();
device = await discoverer.DiscoverDeviceAsync();
IPAddress? ip = await device.GetExternalIPAsync();
ConsoleExt.WriteLine($"The public IP address is: {ip} ", ConsoleColor.Green);
portMapping = new Mapping(Protocol.Tcp, PrivatePort, PublicPort, "HyperbolicDowloader");
await device.CreatePortMapAsync(portMapping);
ConsoleExt.WriteLine($"The public port is: {PublicPort}", ConsoleColor.Green);
return true;
}
catch (NatDeviceNotFoundException)
{
ConsoleExt.WriteLine($"Could not find a UPnP or NAT-PMP device!", ConsoleColor.Red);
return false;
}
catch (MappingException ex)
{
ConsoleExt.WriteLine($"An error occurred while mapping the private port ({PrivatePort}) to the public port ({PublicPort})! Error message: {ex.Message}", ConsoleColor.Red);
return false;
}
}
private static void ClosePorts()
{
Console.WriteLine("Closing ports...");
if (device is not null)
{
try
{
IEnumerable<Mapping>? mappings = device.GetAllMappingsAsync().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)
{
Console.WriteLine(ex);
}
}
ConsoleExt.WriteLine("Ports closed!", ConsoleColor.DarkYellow);
Environment.Exit(0); Environment.Exit(0);
} }
private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e)
{
ClosePorts();
hostsManager.SaveHosts();
}
public static NetworkSocket? GetLocalSocket()
{
int port = PublicPort;
string? ipAddress = null;
if (device is not null)
{
ipAddress = (device.GetExternalIPAsync()).GetAwaiter().GetResult()?.ToString();
}
if (ipAddress is null || ipAddress == "0.0.0.0")
{
ipAddress = NetworkUtilities.GetIP4Adress()?.ToString();
port = PrivatePort;
}
if (ipAddress is null)
{
return null;
}
return new NetworkSocket(ipAddress, port, DateTime.Now);
}
} }
@@ -1,21 +0,0 @@
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class ClientCommands
{
public void ShowInfo(string _)
{
if (Program.PublicIpAddress is not null)
{
ConsoleExt.WriteLine($"The public IP address is: {Program.PublicIpAddress}", ConsoleColor.Green);
ConsoleExt.WriteLine($"The public port is: {Program.PublicPort}", ConsoleColor.Green);
Console.WriteLine();
}
ConsoleExt.WriteLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()}", ConsoleColor.Green);
ConsoleExt.WriteLine($"The private port is: {Program.PrivatePort}", ConsoleColor.Green);
}
}
@@ -1,237 +0,0 @@
using HyperbolicDownloader.FileProcessing;
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using Stone_Red_Utilities.StringExtentions;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class DownloadCommands
{
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)
{
if (string.IsNullOrWhiteSpace(path))
{
ConsoleExt.WriteLine("Path is empty!", ConsoleColor.Red);
return;
}
string fullPath = Path.GetFullPath(path);
if (!File.Exists(fullPath))
{
ConsoleExt.WriteLine("Invalid file path!", ConsoleColor.Red);
}
string json = File.ReadAllText(fullPath);
PublicHyperFileInfo? publicHyperFileInfo = JsonSerializer.Deserialize<PublicHyperFileInfo>(json);
if (publicHyperFileInfo == null)
{
ConsoleExt.WriteLine("Parsing file failed!", ConsoleColor.Red);
return;
}
hostsManager.AddRange(publicHyperFileInfo.Hosts);
GetFile(publicHyperFileInfo.Hash);
}
public void GetFile(string hash)
{
if (string.IsNullOrEmpty(hash))
{
ConsoleExt.WriteLine("No hash value specified!", ConsoleColor.Red);
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;
}
ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow);
Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash);
_ = sendTask.Wait(1000);
if (!sendTask.IsCompletedSuccessfully)
{
Console.CursorLeft = 0;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red);
hostsManager.Remove(host);
continue;
}
else if (!sendTask.Result)
{
host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", ConsoleColor.Red);
continue;
}
host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Has the requested file", ConsoleColor.Green);
Console.WriteLine("Requesting file...");
using TcpClient tcpClient = new TcpClient();
tcpClient.Connect(ipAddress!, host.Port);
tcpClient.ReceiveBufferSize = 64000;
NetworkStream nwStream = tcpClient.GetStream();
byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
byte[] reciveBuffer = new byte[64000];
byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}");
nwStream.Write(bytesToSend);
nwStream.ReadTimeout = 30000;
int bytesRead;
try
{
bytesRead = nwStream.Read(buffer, 0, tcpClient.ReceiveBufferSize);
}
catch (IOException)
{
Console.WriteLine();
ConsoleExt.WriteLine("Lost connection to other host!", ConsoleColor.Red);
continue;
}
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
string[] parts = dataReceived.Split('/');
if (parts.Length == 2) //If received data does not contain 2 parts -> error
{
bool validFileSize = int.TryParse(parts[0], out int fileSize);
if (!validFileSize || fileSize <= 0)
{
ConsoleExt.WriteLine("Invalid file size!", ConsoleColor.Red);
continue;
}
string fileName = parts[1].ToFileName();
string directoryPath = Path.Combine(Program.BasePath, "Downloads");
string filePath = Path.Combine(directoryPath, fileName);
Console.WriteLine($"File name: {fileName}");
Console.WriteLine($"Starting download...");
int totalBytesRead = 0;
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
using FileStream? fileStream = new FileStream(filePath, FileMode.Create);
int bytesInOneSecond = 0;
int unitsPerSecond = 0;
string unit = "Kb";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (totalBytesRead < fileSize)
{
try
{
bytesRead = nwStream.Read(reciveBuffer, 0, reciveBuffer.Length);
}
catch (IOException)
{
Console.WriteLine();
ConsoleExt.WriteLine("Lost connection to other host!", ConsoleColor.Red);
break;
}
bytesRead = Math.Min(bytesRead, fileSize - totalBytesRead);
fileStream.Write(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
bytesInOneSecond += bytesRead;
if (stopWatch.ElapsedMilliseconds >= 1000)
{
unitsPerSecond = (unitsPerSecond + bytesInOneSecond) / 2;
if (unitsPerSecond > 125000)
{
unitsPerSecond /= 125000;
unit = "Mb";
}
else
{
unitsPerSecond /= 125;
unit = "Kb";
}
bytesInOneSecond = 0;
stopWatch.Restart();
}
Console.CursorLeft = 0;
Console.Out.WriteAsync($"Downloading: {Math.Clamp(Math.Ceiling(100d / fileSize * totalBytesRead), 0, 100)}% {totalBytesRead / 1000}/{fileSize / 1000}KB [{unitsPerSecond}{unit}/s] ");
}
fileStream.Close();
if (totalBytesRead < fileSize)
{
continue;
}
Console.WriteLine();
Console.WriteLine("Validating file...");
if (FileValidator.ValidateHash(filePath, hash))
{
_ = filesManager.TryAdd(filePath, out _, out _);
}
else
{
ConsoleExt.WriteLine("Warning: File hash does not match! File might me corrupted or manipulated!", ConsoleColor.DarkYellow);
}
Console.WriteLine($"File saved at: {Path.GetFullPath(filePath)}");
ConsoleExt.WriteLine("Done", ConsoleColor.Green);
stopWatch.Stop();
hostsManager.SaveHosts();
return;
}
else
{
ConsoleExt.WriteLine(dataReceived, ConsoleColor.Red);
}
}
ConsoleExt.WriteLine("None of the available hosts have the requested file!", ConsoleColor.Red);
hostsManager.SaveHosts();
}
}
@@ -1,187 +0,0 @@
using HyperbolicDownloader.FileProcessing;
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Net;
using System.Text.Json;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class FileCommands
{
private readonly HostsManager hostsManager;
private readonly FilesManager filesManager;
public FileCommands(HostsManager hostsManager, FilesManager filesManager)
{
this.hostsManager = hostsManager;
this.filesManager = filesManager;
}
public void AddFile(string path)
{
if (filesManager.TryAdd(path, out PrivateHyperFileInfo? fileInfo, out string? message))
{
ConsoleExt.WriteLine($"Added file: {fileInfo!.FilePath}", ConsoleColor.Green);
Console.WriteLine($"Hash: {fileInfo.Hash}");
}
else
{
ConsoleExt.WriteLine(message, ConsoleColor.Red);
}
}
public void RemoveFile(string hash)
{
hash = hash.Trim().ToLower();
if (filesManager.TryRemove(hash))
{
ConsoleExt.WriteLine($"Successfully removed file!", ConsoleColor.Green);
}
else
{
ConsoleExt.WriteLine("The file is not being tracked!", ConsoleColor.Red);
}
}
public void ListFiles(string _)
{
int index = 0;
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (fileInfos.Count == 0)
{
ConsoleExt.WriteLine("No tracked files!", ConsoleColor.DarkYellow);
return;
}
foreach (PrivateHyperFileInfo fileInfo in fileInfos)
{
index++;
Console.WriteLine($"{index}) {fileInfo.FilePath}");
Console.WriteLine($"Hash: {fileInfo.Hash}");
Console.WriteLine();
}
Console.CursorTop--;
}
public void GenerateFileSingle(string hash)
{
string directoryPath = Path.Combine(Program.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
hash = hash.Trim().ToLower();
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo))
{
ConsoleExt.WriteLine("The file is not being tracked!", ConsoleColor.Red);
return;
}
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash);
NetworkSocket? localHost = Program.GetLocalSocket();
if (localHost is null)
{
ConsoleExt.WriteLine("Network error!", ConsoleColor.Red);
return;
}
publicHyperFileInfo.Hosts.Add(localHost);
JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(publicHyperFileInfo, options);
File.WriteAllText(filePath, json);
ConsoleExt.WriteLine("Done", ConsoleColor.Green);
Console.WriteLine($"File saved at: {Path.GetFullPath(filePath)}");
}
public void GenerateFileFull(string hash)
{
string directoryPath = Path.Combine(Program.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
hash = hash.Trim().ToLower();
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo))
{
ConsoleExt.WriteLine("The file is not being tracked!", ConsoleColor.Red);
return;
}
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash);
NetworkSocket? localHost = Program.GetLocalSocket();
if (localHost is null)
{
ConsoleExt.WriteLine("Network error!", ConsoleColor.Red);
return;
}
foreach (NetworkSocket host in hostsManager.ToList())
{
bool validIpAdress = IPAddress.TryParse(host.IPAddress, out IPAddress? ipAddress);
if (!validIpAdress)
{
hostsManager.Remove(host, true);
continue;
}
ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow);
Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash);
_ = sendTask.Wait(1000);
if (!sendTask.IsCompletedSuccessfully)
{
Console.CursorLeft = 0;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red);
hostsManager.Remove(host);
continue;
}
else if (!sendTask.Result)
{
host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", ConsoleColor.Red);
continue;
}
host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Has the requested file", ConsoleColor.Green);
publicHyperFileInfo.Hosts.Add(host);
}
publicHyperFileInfo.Hosts.Add(localHost);
JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(publicHyperFileInfo, options);
File.WriteAllText(filePath, json);
ConsoleExt.WriteLine("Done", ConsoleColor.Green);
Console.WriteLine($"File saved at: {Path.GetFullPath(filePath)}");
}
}
@@ -1,148 +0,0 @@
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Net;
using System.Net.Sockets;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class HostCommands
{
private readonly HostsManager hostsManager;
public HostCommands(HostsManager hostsManager)
{
this.hostsManager = hostsManager;
}
public void Discover(string _)
{
Console.WriteLine("Running local discovery routine...");
BroadcastClient.Send(Program.BroadcastPort, Program.PrivatePort.ToString());
Thread.Sleep(3000);
}
public void CheckActiveHosts(string _)
{
int activeHostsCount = hostsManager.CheckHostsActivity();
if (activeHostsCount == 0)
{
ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red);
ConsoleExt.WriteLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host.", ConsoleColor.Red);
}
Console.WriteLine($"{hostsManager.Count} known host(s).");
Console.WriteLine($"{activeHostsCount} active host(s).");
}
public void ListHosts(string _)
{
int index = 0;
List<NetworkSocket> hosts = hostsManager.ToList();
if (hosts.Count == 0)
{
ConsoleExt.WriteLine("No known hosts", ConsoleColor.DarkYellow);
return;
}
foreach (NetworkSocket host in hosts)
{
index++;
Console.WriteLine($"{index}) {host.IPAddress}:{host.Port}");
Console.WriteLine($"Last active: {host.LastActive}");
Console.WriteLine();
}
Console.CursorTop--;
}
public void RemoveHost(string args)
{
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ConsoleExt.WriteLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", ConsoleColor.Red);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
_ = int.TryParse(portInput, out int port);
if (port < 1000 || port >= 6000)
{
ConsoleExt.WriteLine("Invalid port number!", ConsoleColor.Red);
return;
}
if (!IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress))
{
ConsoleExt.WriteLine("Invalid IP address!", ConsoleColor.Red);
return;
}
NetworkSocket hostToRemove = new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue);
if (!hostsManager.Contains(hostToRemove))
{
ConsoleExt.WriteLine("Host not in list", ConsoleColor.Red);
return;
}
hostsManager.Remove(new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue), true);
ConsoleExt.WriteLine($"Successfully Removed host!", ConsoleColor.Green);
}
public void AddHost(string args)
{
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ConsoleExt.WriteLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", ConsoleColor.Red);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
_ = int.TryParse(portInput, out int port);
if (port < 1000 || port >= 6000)
{
ConsoleExt.WriteLine("Invalid port number!", ConsoleColor.Red);
}
else if (IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress))
{
try
{
Console.WriteLine("Waiting for response...");
NetworkSocket? localSocket = Program.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue);
List<NetworkSocket>? recivedHosts = NetworkClient.Send<List<NetworkSocket>>(ipAddress, port, "GetHostsList", localSocket);
if (recivedHosts is not null)
{
ConsoleExt.WriteLine($"Success! Added {recivedHosts.Count} new host(s).", ConsoleColor.Green);
hostsManager.AddRange(recivedHosts);
}
else
{
ConsoleExt.WriteLine($"Invalid response!", ConsoleColor.Red);
}
}
catch (SocketException ex)
{
ConsoleExt.WriteLine($"Invalid host! Error message: {ex.Message}", ConsoleColor.Red);
}
catch (IOException ex)
{
ConsoleExt.WriteLine($"Invalid host! Error message: {ex.Message}", ConsoleColor.Red);
}
}
else
{
ConsoleExt.WriteLine("Invalid IP address!", ConsoleColor.Red);
}
}
}
@@ -1,81 +0,0 @@
using Commander_Net;
using HyperbolicDownloader.FileProcessing;
using HyperbolicDownloader.UserInterface.Commands;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader.UserInterface;
internal class InputHandler
{
private readonly HostsManager hostsManager;
private readonly Commander commander = new Commander();
private bool exit = false;
public InputHandler(HostsManager hostsManager, FilesManager filesManager)
{
HostCommands hostCommands = new HostCommands(hostsManager);
FileCommands fileCommands = new FileCommands(hostsManager, filesManager);
DownloadCommands downloadCommands = new DownloadCommands(hostsManager, filesManager);
ClientCommands clientCommands = new ClientCommands();
this.hostsManager = hostsManager;
commander.Register((_) => Console.Clear(), "clear", "cls");
commander.Register(Exit, "exit", "quit");
commander.Register(clientCommands.ShowInfo, "info", "inf");
commander.Register(hostCommands.Discover, "discover", "disc");
Command getCommand = commander.Register(downloadCommands.GetFile, "get");
getCommand.Register(downloadCommands.GetFileFrom, "from");
Command generateCommad = commander.Register(fileCommands.GenerateFileFull, "generate", "gen");
generateCommad.Register(fileCommands.GenerateFileSingle, "noscan");
Command addCommand = commander.Register(fileCommands.AddFile, "add");
addCommand.Register(hostCommands.AddHost, "host");
addCommand.Register(fileCommands.AddFile, "file");
Command removeCommand = commander.Register(fileCommands.RemoveFile, "remove", "rm");
removeCommand.Register(hostCommands.RemoveHost, "host");
removeCommand.Register(fileCommands.RemoveFile, "file");
Command listCommand = commander.Register(fileCommands.ListFiles, "list", "ls");
listCommand.Register(fileCommands.ListFiles, "files");
listCommand.Register(hostCommands.ListHosts, "hosts");
commander.Register(hostCommands.CheckActiveHosts, "status", "check");
}
public void ReadInput()
{
while (!exit)
{
Console.WriteLine();
Console.Write("> ");
Console.CursorVisible = true;
string input = Console.ReadLine()?.Trim() ?? string.Empty;
Console.CursorVisible = false;
try
{
if (!commander.Execute(input))
{
ConsoleExt.WriteLine("Unknown command!", ConsoleColor.Red);
}
}
catch (Exception ex)
{
ConsoleExt.WriteLine($"An unexpected error occurred! {ex}", ConsoleColor.Red);
}
}
}
private void Exit(string _)
{
hostsManager.SaveHosts();
exit = true;
}
}
@@ -0,0 +1,20 @@
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
namespace HyperbolicDownloaderApi.Commands;
public class ClientCommands
{
public void ShowInfo(string _)
{
if (ApiManager.PublicIpAddress is not null)
{
ApiManager.SendNotificationMessageNewLine($"The public IP address is: {ApiManager.PublicIpAddress}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"The public port is: {ApiConfiguration.PublicPort}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
}
ApiManager.SendNotificationMessageNewLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"The private port is: {ApiConfiguration.PrivatePort}", NotificationMessageType.Info);
}
}
@@ -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--;
}
}
}
@@ -0,0 +1,221 @@
using CuteUtils;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using HyperbolicDownloaderApi.Utilities;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands;
public class DownloadCommands(HostsManager hostsManager, FilesManager filesManager)
{
public void GetFileFrom(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);
GetFile(publicHyperFileInfo.Hash);
}
public void GetFile(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 file...");
using TcpClient tcpClient = new TcpClient();
tcpClient.Connect(ipAddress!, host.Port);
tcpClient.ReceiveBufferSize = 64000;
NetworkStream nwStream = tcpClient.GetStream();
byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
byte[] reciveBuffer = new byte[64000];
byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {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 != 2) //If received data does not contain 2 parts -> error
{
ApiManager.SendNotificationMessageNewLine(dataReceived, NotificationMessageType.Error);
continue;
}
bool validFileSize = int.TryParse(parts[0], out int fileSize);
if (!validFileSize || fileSize <= 0)
{
ApiManager.SendNotificationMessageNewLine("Invalid file size!", NotificationMessageType.Error);
continue;
}
string fileName = parts[1].ToFileName();
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "Downloads");
string filePath = Path.Combine(directoryPath, fileName);
ApiManager.SendNotificationMessageNewLine($"File name: {fileName}");
ApiManager.SendNotificationMessageNewLine($"Starting download...");
if (!Directory.Exists(directoryPath))
{
_ = Directory.CreateDirectory(directoryPath);
}
using FileStream? fileStream = new FileStream(filePath, FileMode.Create);
int totalBytesRead = 0;
int bytesPerSecond = 0;
int transferRate = 0;
TimeSpan timeRemaining = TimeSpan.Zero;
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (totalBytesRead < fileSize)
{
try
{
bytesRead = nwStream.Read(reciveBuffer, 0, reciveBuffer.Length);
}
catch (IOException ex)
{
Debug.WriteLine(ex);
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Lost connection to other host!", NotificationMessageType.Error);
break;
}
bytesRead = Math.Min(bytesRead, fileSize - totalBytesRead);
fileStream.Write(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
bytesPerSecond += bytesRead;
if (stopWatch.Elapsed.TotalSeconds >= 1)
{
transferRate = bytesPerSecond;
host.DownloadSpeed = (host.DownloadSpeed + bytesPerSecond) / 2;
timeRemaining = TimeSpan.FromSeconds((fileSize - totalBytesRead) / (double)host.DownloadSpeed);
bytesPerSecond = 0;
stopWatch.Restart();
}
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();
if (totalBytesRead < fileSize)
{
continue;
}
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Validating file...");
if (FileValidator.ValidateHash(filePath, hash))
{
_ = filesManager.TryAdd(filePath, out _, out _);
}
else
{
ApiManager.SendNotificationMessageNewLine("Warning: File hash does not match! File might me corrupted or manipulated! Trying next host...", NotificationMessageType.Warning);
continue;
}
ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
ApiManager.SendNotificationMessageNewLine("Done", NotificationMessageType.Success);
stopWatch.Stop();
hostsManager.SaveHosts();
return;
}
ApiManager.SendNotificationMessageNewLine("None of the available hosts have the requested file!", NotificationMessageType.Error);
hostsManager.SaveHosts();
}
}
@@ -0,0 +1,335 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands;
public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
{
private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
public void AddFile(string path)
{
if (filesManager.TryAdd(path, out PrivateHyperFileInfo? fileInfo, out string? message))
{
ApiManager.SendNotificationMessageNewLine($"Added file: {fileInfo!.FilePath}", NotificationMessageType.Success);
ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}");
}
else
{
ApiManager.SendNotificationMessageNewLine(message!, NotificationMessageType.Error);
}
}
public void RemoveFile(string args)
{
args = args.Trim().ToLower();
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);
}
else
{
ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
}
}
public void ListFiles(string searchString)
{
int index = 0;
int fileCount = 0;
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (fileInfos.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No tracked files!", NotificationMessageType.Warning);
return;
}
foreach (PrivateHyperFileInfo fileInfo in fileInfos)
{
index++;
if (!string.IsNullOrWhiteSpace(searchString) && !fileInfo.FilePath.Contains(searchString, StringComparison.OrdinalIgnoreCase))
{
continue;
}
fileCount++;
ApiManager.SendNotificationMessageNewLine($"{index}) {fileInfo.FilePath}");
ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}");
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--;
}
public void GenerateFileSingle(string args)
{
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath))
{
_ = Directory.CreateDirectory(directoryPath);
}
args = args.Trim().ToLower();
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);
return;
}
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash);
NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null)
{
ApiManager.SendNotificationMessageNewLine("Network error!", NotificationMessageType.Error);
return;
}
publicHyperFileInfo.Hosts.Add(localHost);
string json = JsonSerializer.Serialize(publicHyperFileInfo, jsonSerializerOptions);
File.WriteAllText(filePath, json);
ApiManager.SendNotificationMessageNewLine("Done", NotificationMessageType.Success);
ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
}
public void GenerateFileFull(string args)
{
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath))
{
_ = Directory.CreateDirectory(directoryPath);
}
args = args.Trim().ToLower();
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);
return;
}
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash);
NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null)
{
ApiManager.SendNotificationMessageNewLine("Network error!", NotificationMessageType.Error);
return;
}
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", localHyperFileInfo.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);
publicHyperFileInfo.Hosts.Add(host);
}
publicHyperFileInfo.Hosts.Add(localHost);
string json = JsonSerializer.Serialize(publicHyperFileInfo, jsonSerializerOptions);
File.WriteAllText(filePath, json);
ApiManager.SendNotificationMessageNewLine("Done", NotificationMessageType.Success);
ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
}
}
@@ -0,0 +1,221 @@
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using HyperbolicDownloaderApi.Utilities;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace HyperbolicDownloaderApi.Commands;
public class HostCommands(HostsManager hostsManager)
{
public void Discover(string _)
{
ApiManager.SendNotificationMessageNewLine("Running local discovery routine...", NotificationMessageType.Info);
int hostsCountBefore = hostsManager.Count;
BroadcastClient.Send(ApiConfiguration.BroadcastPort, ApiConfiguration.PrivatePort.ToString());
Thread.Sleep(3000);
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 _)
{
int activeHostsCount = hostsManager.CheckHostsActivity();
if (activeHostsCount == 0)
{
ApiManager.SendNotificationMessageNewLine("No active hosts found!", 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($"{hostsManager.Count} known host(s).", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"{activeHostsCount} active host(s).", NotificationMessageType.Info);
}
public void ListHosts(string _)
{
int index = 0;
List<NetworkSocket> hosts = hostsManager.ToList();
if (hosts.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No known hosts.", NotificationMessageType.Warning);
return;
}
foreach (NetworkSocket host in hosts)
{
index++;
ApiManager.SendNotificationMessageNewLine($"{index}) {host.IPAddress}:{host.Port}", 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);
}
Console.CursorTop--;
}
public void RemoveHost(string args)
{
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)
{
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: <ip address>:<port>", NotificationMessageType.Error);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
_ = int.TryParse(portInput, out int port);
if (port is < 1000 or >= 6000)
{
ApiManager.SendNotificationMessageNewLine("Invalid port number!", NotificationMessageType.Error);
return;
}
if (!IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress))
{
ApiManager.SendNotificationMessageNewLine("Invalid IP address!", NotificationMessageType.Error);
return;
}
NetworkSocket hostToRemove = new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue);
if (!hostsManager.Contains(hostToRemove))
{
ApiManager.SendNotificationMessageNewLine("Host not in list!", NotificationMessageType.Error);
return;
}
hostsManager.Remove(new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue), true);
ApiManager.SendNotificationMessageNewLine($"Successfully Removed host!", NotificationMessageType.Success);
}
public void AddHost(string args)
{
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;
}
try
{
ApiManager.SendNotificationMessageNewLine("Waiting for response...", NotificationMessageType.Info);
NetworkSocket? localSocket = ApiManager.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue);
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)
{
int newHosts = 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
{
ApiManager.SendNotificationMessageNewLine($"Invalid response!", NotificationMessageType.Error);
}
}
catch (Exception ex) when (ex is SocketException or IOException or AggregateException)
{
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,6 +1,6 @@
using System.IO.Compression; using System.IO.Compression;
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal static class FileCompressor internal static class FileCompressor
{ {
@@ -1,8 +1,8 @@
using System.Security.Cryptography; using System.Security.Cryptography;
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal class FileValidator internal static class FileValidator
{ {
public static async Task<string> CalculateHashAsync(string filePath) public static async Task<string> CalculateHashAsync(string filePath)
{ {
@@ -1,10 +1,13 @@
using System.Text.Json; using HyperbolicDownloaderApi.Managment;
namespace HyperbolicDownloader.FileProcessing; using System.Diagnostics;
using System.Text.Json;
internal class FilesManager namespace HyperbolicDownloaderApi.FileProcessing;
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)
{ {
@@ -29,10 +32,16 @@ internal 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);
@@ -81,16 +90,30 @@ internal 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()
{ {
File.WriteAllText(Program.FilesInfoPath, JsonSerializer.Serialize(files)); File.WriteAllText(ApiConfiguration.FilesInfoPath, JsonSerializer.Serialize(files));
} }
} }
@@ -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);
}
}
@@ -0,0 +1,7 @@
namespace HyperbolicDownloaderApi.FileProcessing;
public class PrivateHyperFileInfo(string hash, string filePath)
{
public string Hash { get; set; } = hash;
public string FilePath { get; set; } = filePath;
}
@@ -1,11 +1,11 @@
using HyperbolicDownloader.Networking; using HyperbolicDownloaderApi.Networking;
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal class PublicHyperFileInfo public class PublicHyperFileInfo
{ {
public string Hash { get; set; } = string.Empty; public string Hash { get; set; } = string.Empty;
public List<NetworkSocket> Hosts { get; } = 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")]
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CuteUtils" Version="1.0.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="SharpOpenNat" Version="4.0.17" />
</ItemGroup>
</Project>
@@ -0,0 +1,12 @@
namespace HyperbolicDownloaderApi.Managment;
public static class ApiConfiguration
{
public const int BroadcastPort = 2155;
public const int PrivatePort = 3055;
public static int PublicPort { get; set; }
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 FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json");
public static string DirectoriesInfoPath { get; } = Path.Combine(BasePath, "Directories.json");
}
@@ -0,0 +1,243 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking;
using SharpOpenNat;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace HyperbolicDownloaderApi.Managment;
public class ApiManager
{
public static event EventHandler<NotificationMessageEventArgs>? OnNotificationMessageRecived;
private static readonly Random random = new();
private static INatDevice? device;
private static Mapping? portMapping;
private readonly BroadcastClient broadcastClient = new BroadcastClient();
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()
{
networkClient = new(FilesManager);
DirectoryWatcher = new(FilesManager);
}
public static NetworkSocket? GetLocalSocket()
{
int port = ApiConfiguration.PublicPort;
string? ipAddress = null;
if (device is not null)
{
ipAddress = device.GetExternalIPAsync().GetAwaiter().GetResult()?.ToString();
}
if (ipAddress is null or "0.0.0.0")
{
ipAddress = NetworkUtilities.GetIP4Adress()?.ToString();
port = ApiConfiguration.PrivatePort;
}
if (ipAddress is null)
{
return null;
}
return new NetworkSocket(ipAddress, port, DateTime.Now);
}
public static async Task<bool> OpenPorts()
{
try
{
ApiConfiguration.PublicPort = random.Next(1000, 6000);
device = await OpenNat.Discoverer.DiscoverDeviceAsync();
IPAddress? ip = await device.GetExternalIPAsync();
SendNotificationMessageNewLine($"The public IP address is: {ip} ", NotificationMessageType.Success);
portMapping = new Mapping(Protocol.Tcp, ApiConfiguration.PrivatePort, ApiConfiguration.PublicPort, "HyperbolicDowloader");
await device.CreatePortMapAsync(portMapping);
SendNotificationMessageNewLine($"The public port is: {ApiConfiguration.PublicPort}", NotificationMessageType.Success);
return true;
}
catch (NatDeviceNotFoundException)
{
SendNotificationMessageNewLine($"Could not find a UPnP or NAT-PMP device!", NotificationMessageType.Error);
return false;
}
catch (MappingException 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;
}
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()
{
SendNotificationMessageNewLine("Closing ports...", NotificationMessageType.Info);
if (device is not null && portMapping is not null)
{
try
{
device.DeletePortMapAsync(portMapping).GetAwaiter().GetResult();
}
catch (Exception ex)
{
SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Error);
}
}
SendNotificationMessageNewLine("Ports closed!", NotificationMessageType.Warning);
}
public bool StartBroadcastListener()
{
try
{
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()
{
try
{
networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList);
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("HasFile", HasFile);
networkClient.ListenTo<string>("GetFilesList", GetFileList);
networkClient.StartListening(ApiConfiguration.PrivatePort);
}
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;
}
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)
{
IPAddress remoteIpAddress = recivedEventArgs.IPEndPoint.Address;
Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {remoteIpAddress}");
List<NetworkSocket> hostsToSend = HostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket();
if (localSocket is null || remoteIpAddress.Equals(PublicIpAddress) || remoteIpAddress.Equals(NetworkUtilities.GetIP4Adress()))
{
Debug.WriteLine("Invalid broadcast!");
return;
}
_ = hostsToSend.RemoveAll(x => x.IPAddress == remoteIpAddress.ToString());
hostsToSend.Add(localSocket);
bool success = int.TryParse(recivedEventArgs.Message, out int remotePort);
if (success)
{
_ = HostsManager.Add(new NetworkSocket(remoteIpAddress.ToString(), remotePort, DateTime.Now));
try
{
await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend);
}
catch (SocketException ex)
{
Debug.WriteLine(ex);
}
}
}
private void DiscoverAnswer(object? sender, MessageRecivedEventArgs<List<NetworkSocket>> recivedEventArgs)
{
SendNotificationMessageNewLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s).", NotificationMessageType.Info);
SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Discovery answer {string.Join(", ", recivedEventArgs.Data)}", NotificationMessageType.Log);
_ = HostsManager.AddRange(recivedEventArgs.Data);
}
private async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{
SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting host list", NotificationMessageType.Log);
List<NetworkSocket> hostsToSend = HostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket();
if (localSocket is null)
{
return;
}
_ = hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IpAddress.ToString());
hostsToSend.Add(localSocket);
if (recivedEventArgs.Data.Port != 0)
{
_ = HostsManager.Add(recivedEventArgs.Data);
}
await recivedEventArgs.SendResponseAsync(hostsToSend);
}
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));
}
private async void GetFileList(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting file list", NotificationMessageType.Log);
List<HyperFileDto> files = [.. FilesManager
.ToList()
.Select(f => new HyperFileDto(f))
.Where(f => f.Name.Contains(recivedEventArgs.Data))];
await recivedEventArgs.SendResponseAsync(files);
}
}
@@ -0,0 +1,17 @@
namespace HyperbolicDownloaderApi.Managment;
public class NotificationMessageEventArgs(NotificationMessageType notificationMessageType, string? message) : EventArgs
{
public NotificationMessageType NotificationMessageType { get; } = notificationMessageType;
public string? Message { get; } = message;
}
public enum NotificationMessageType
{
Info,
Log,
Debug,
Success,
Warning,
Error
}
@@ -1,19 +1,18 @@
using Stone_Red_Utilities.ConsoleExtentions; using HyperbolicDownloaderApi.Managment;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
namespace HyperbolicDownloader.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class BroadcastClient 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)
{ {
@@ -26,7 +25,7 @@ internal class BroadcastClient
if (ip4Address is null) if (ip4Address is null)
{ {
ConsoleExt.WriteLine("Could not find suitable network adapter!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("Could not find suitable network adapter!", NotificationMessageType.Error);
return; return;
} }
@@ -34,7 +33,7 @@ internal class BroadcastClient
if (addressInformation is null) if (addressInformation is null)
{ {
ConsoleExt.WriteLine("Could not find suitable network adapter!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("Could not find suitable network adapter!", NotificationMessageType.Error);
return; return;
} }
@@ -43,12 +42,12 @@ internal class BroadcastClient
byte[] sendbuf = Encoding.ASCII.GetBytes(message); byte[] sendbuf = Encoding.ASCII.GetBytes(message);
IPEndPoint ep = new IPEndPoint(broadcast, port); IPEndPoint ep = new IPEndPoint(broadcast, port);
socket.SendTo(sendbuf, ep); _ = socket.SendTo(sendbuf, ep);
} }
public void StartListening(int port) public void StartListening(int port)
{ {
if (IsListening == true) if (IsListening)
{ {
throw new InvalidOperationException("Already listening!"); throw new InvalidOperationException("Already listening!");
} }
@@ -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()
@@ -0,0 +1,9 @@
using System.Net;
namespace HyperbolicDownloaderApi.Networking;
internal class BroadcastRecivedEventArgs(IPEndPoint ipEndPoint, string message) : EventArgs
{
public IPEndPoint IPEndPoint { get; } = ipEndPoint;
public string Message { get; } = message;
}
@@ -0,0 +1,7 @@
namespace HyperbolicDownloaderApi.Networking;
internal class DataContainer(string eventName, string jsonData)
{
public string EventName { get; set; } = eventName;
public string JsonData { get; set; } = jsonData;
}
@@ -0,0 +1,158 @@
using HyperbolicDownloaderApi.Managment;
using System.Net;
using System.Net.Sockets;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
public class HostsManager
{
private List<NetworkSocket> hosts = [];
public int Count => hosts.Count;
public int AddRange(IEnumerable<NetworkSocket> hosts)
{
int newHosts = hosts.Count(Add);
SaveHosts();
return newHosts;
}
public bool Add(NetworkSocket host)
{
if (!Contains(host) && !host.Equals(ApiManager.GetLocalSocket()))
{
hosts.Add(host);
return true;
}
SaveHosts();
return false;
}
public void Remove(NetworkSocket host, bool forceRemove = false)
{
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0) || forceRemove)
{
_ = hosts.RemoveAll(x => x.Equals(host));
}
SaveHosts();
}
public bool Contains(NetworkSocket host)
{
return hosts.Exists(x => x.Equals(host));
}
public int CheckHostsActivity()
{
List<NetworkSocket> hostsToRemove = [];
int activeHostsCount = 0;
foreach (NetworkSocket host in hosts)
{
ApiManager.SendNotificationMessage($"{host.IPAddress}:{host.Port} > ???", NotificationMessageType.Warning);
using TcpClient tcpClient = new TcpClient();
try
{
_ = tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(1000);
Console.CursorLeft = 0;
if (tcpClient.Connected)
{
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Active", NotificationMessageType.Success);
host.LastActive = DateTime.Now;
activeHostsCount++;
}
else
{
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0))
{
hostsToRemove.Add(host);
}
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
}
}
catch
{
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0))
{
hostsToRemove.Add(host);
}
Console.CursorLeft = 0;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
}
}
foreach (NetworkSocket host in hostsToRemove)
{
_ = hosts.Remove(host);
}
SaveHosts();
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()
{
return [.. hosts];
}
public void SaveHosts()
{
hosts = [.. hosts
.OrderByDescending(s => s.DownloadSpeed)
.ThenByDescending(s => s.LastActive)];
File.WriteAllText(ApiConfiguration.HostsFilePath, JsonSerializer.Serialize(hosts));
}
}
@@ -0,0 +1,24 @@
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
internal class MessageRecivedEventArgs<T>(NetworkStream networkStream, IPAddress ipAddress, T data) : EventArgs
{
public T Data { get; set; } = data;
public IPAddress IpAddress { get; set; } = ipAddress;
public async Task SendResponseAsync(object response)
{
byte[] bytesToSend = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(response));
await networkStream.WriteAsync(bytesToSend);
}
public void SendResponse(object response)
{
SendResponseAsync(response).GetAwaiter().GetResult();
}
}
@@ -0,0 +1,334 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using NAudio.Wave;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
internal class NetworkClient(FilesManager filesManager)
{
private readonly Dictionary<string, (Type type, Delegate method)> events = [];
private TcpListener? tcpListener;
public bool IsListening { get; private set; } = false;
public static async Task<T?> SendAsync<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
ArgumentNullException.ThrowIfNull(remoteIp);
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
List<byte> bytes = [];
byte[] bytesToRead = new byte[client.ReceiveBufferSize];
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();
if (string.IsNullOrWhiteSpace(response))
{
return default;
}
else
{
return JsonSerializer.Deserialize<T>(response);
}
}
public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data)
{
ArgumentNullException.ThrowIfNull(remoteIp);
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
client.Close();
}
public static T? Send<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
return SendAsync<T>(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public static void Send(IPAddress remoteIp, int remotePort, string eventName, object data)
{
SendAsync(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public void StartListening(int port)
{
if (IsListening)
{
throw new InvalidOperationException("Already listening!");
}
tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
IsListening = true;
_ = new TaskFactory().StartNew(() =>
{
while (IsListening)
{
try
{
ApiManager.SendNotificationMessageNewLine("Listening for connections...", NotificationMessageType.Debug);
TcpClient client = tcpListener.AcceptTcpClient();
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Connected", NotificationMessageType.Debug);
_ = Task.Run(() => HandleRequest(client));
}
catch (SocketException ex)
{
ApiManager.SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Log);
}
}
tcpListener.Stop();
}, TaskCreationOptions.LongRunning);
}
public void StopListening()
{
tcpListener?.Stop();
IsListening = false;
}
public void ListenTo<T>(string eventName, EventHandler<MessageRecivedEventArgs<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);
}
}
}
@@ -0,0 +1,25 @@
namespace HyperbolicDownloaderApi.Networking;
public class NetworkSocket(string ipAddress, int port, DateTime lastActive)
{
public string IPAddress { get; set; } = ipAddress;
public int Port { get; set; } = port;
public DateTime LastActive { get; set; } = lastActive;
public long DownloadSpeed { get; set; }
public override bool Equals(object? obj)
{
if (obj is not NetworkSocket networkSocket)
{
return false;
}
return networkSocket.IPAddress == IPAddress && networkSocket.Port == Port;
}
public override int GetHashCode()
{
return HashCode.Combine(IPAddress, Port);
}
}
@@ -2,9 +2,9 @@
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
namespace HyperbolicDownloader.Networking; namespace HyperbolicDownloaderApi.Networking;
internal static class NetworkUtilities public static class NetworkUtilities
{ {
public static UnicastIPAddressInformation? GetUnicastIPAddressInformation(IPAddress address) public static UnicastIPAddressInformation? GetUnicastIPAddressInformation(IPAddress address)
{ {
@@ -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";
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Stone_Red
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+9 -13
View File
@@ -9,10 +9,13 @@
1. Wait until the setup process is finished 1. Wait until the setup process is finished
1. Do one of the below 1. Do one of the below
- Add a host to your client using the [add](#add) command and use the [get](#get) command tho retrive a file - Add a host to your client using the [add](#add) command and use the [get](#get) command tho retrive a file
- Use the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrive a file trough a `.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 retrive a file trough 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.
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 a 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.\
@@ -22,7 +25,6 @@ You can generate `.hyper` files with your client using the [generate](https://gi
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.
## Disclaimer ## Disclaimer
Keep in mind that all network traffic is not encrypted. So, do not send sensitive information with HyperbolicDownloader. Keep in mind that all network traffic is not encrypted. So, do not send sensitive information with HyperbolicDownloader.
@@ -38,31 +40,26 @@ Since none of the files are stored anywhere centralized, the quality of the file
**Description:** Exits the application.\ **Description:** Exits the application.\
**Parameter:** `none` **Parameter:** `none`
### `clear` | `cls` ### `clear` | `cls`
**Description:** Clears the console.\ **Description:** Clears the console.\
**Parameter:** `none` **Parameter:** `none`
### `info` | `inf` ### `info` | `inf`
**Description:** Displays the private and public IP address.\ **Description:** Displays the private and public IP address.\
**Parameter:** `none` **Parameter:** `none`
### `discover` | `disc` ### `discover` | `disc`
**Description:** Tries to find other active hosts on the local network.\ **Description:** Tries to find other active hosts on the local network.\
**parameter:** `none` **parameter:** `none`
### `check` | `status` ### `check` | `status`
**Description:** Checks the status of known hosts.\ **Description:** Checks the status of known hosts.\
**parameter:** `none` **parameter:** `none`
### `list` | `ls` ### `list` | `ls`
**Description:** Lists all files\ **Description:** Lists all files\
@@ -70,15 +67,14 @@ Since none of the files are stored anywhere centralized, the quality of the file
#### `<list> files` #### `<list> files`
**Description:** Lists all files\ **Description:** Lists all files.\
**parameter:** `none` **parameter:** `none`
#### `<list> hosts` #### `<list> hosts`
**description:** lists all hosts **description:** Lists all hosts.\
**parameter:** `none` **parameter:** `none`
### `add` ### `add`
**Description:** Adds a file to the tracking list.\ **Description:** Adds a file to the tracking list.\
@@ -94,8 +90,7 @@ Since none of the files are stored anywhere centralized, the quality of the file
**Description:** Adds a host to the list of known hosts.\ **Description:** Adds a host to the list of known hosts.\
**parameter:** `<IpAddress:port>` **parameter:** `<IpAddress:port>`
### `remove` | `rm`
### `remove` `rm`
**Description:** Removes a file from the tracking list.\ **Description:** Removes a file from the tracking list.\
**parameter:** `<FileHash>` **parameter:** `<FileHash>`
@@ -110,19 +105,20 @@ Since none of the files are stored anywhere centralized, the quality of the file
**Description:** Removes a host from the list of known hosts.\ **Description:** Removes a host from the list of known hosts.\
**parameter:** `<IpAddress:Port>` **parameter:** `<IpAddress:Port>`
### `get` ### `get`
**Description:** Attempts to retrieve a file from another host using a hash.\ **Description:** Attempts to retrieve a file from another host using a hash.\
**parameter:** `<FileHash>` **parameter:** `<FileHash>`
<a name="#getfrom"></a> <a name="#getfrom"></a>
#### `<get> from` #### `<get> from`
**Description:** Attempts to retrieve a file from another host using a .hyper file.\ **Description:** Attempts to retrieve a file from another host using a .hyper file.\
**Parameter:** `<FilePathToHyperFile>` **Parameter:** `<FilePathToHyperFile>`
<a name="#generate"></a> <a name="#generate"></a>
### `generate` | `gen` ### `generate` | `gen`
**Description:** Generates a .hyper file from a file hash.\ **Description:** Generates a .hyper file from a file hash.\