66 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
58 changed files with 2518 additions and 1987 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
+10 -5
View File
@@ -5,7 +5,12 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HyperbolicDownloaderGUI", "HyperbolicDownloaderGUI\HyperbolicDownloaderGUI.csproj", "{E1CE6E07-F8A6-447F-837D-9D1943A42E96}" 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,10 +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
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Debug|Any CPU.Build.0 = Debug|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Release|Any CPU.ActiveCfg = Release|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Release|Any CPU.Build.0 = 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,149 +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();
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; set; } = new(); public List<NetworkSocket> Hosts { get; set; } = [];
public PublicHyperFileInfo() public PublicHyperFileInfo()
{ {
@@ -0,0 +1,9 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Major Code Smell", "S6561:Avoid using \"DateTime.Now\" for benchmarking or timing operations", Justification = "Stop")]
[assembly: SuppressMessage("Minor Code Smell", "S3604:Member initializer values should not be redundant", Justification = "False positive")]
@@ -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";
}
}
-454
View File
@@ -1,454 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
-12
View File
@@ -1,12 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HyperbolicDownloaderGUI"
x:Class="HyperbolicDownloaderGUI.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>
-30
View File
@@ -1,30 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using HyperbolicDownloaderGUI.ViewModels;
using HyperbolicDownloaderGUI.Views;
namespace HyperbolicDownloaderGUI
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
</ItemGroup>
</Project>
-28
View File
@@ -1,28 +0,0 @@
using Avalonia;
using Avalonia.ReactiveUI;
using System;
namespace HyperbolicDownloaderGUI
{
internal static class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
}
}
}
-32
View File
@@ -1,32 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using HyperbolicDownloaderGUI.ViewModels;
using System;
namespace HyperbolicDownloaderGUI
{
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
}
@@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HyperbolicDownloaderGUI.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
public string Greeting => "Welcome to Avalonia!";
}
}
@@ -1,12 +0,0 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace HyperbolicDownloaderGUI.ViewModels
{
public class ViewModelBase : ReactiveObject
{
}
}
@@ -1,17 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:HyperbolicDownloaderGUI.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="HyperbolicDownloaderGUI.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="HyperbolicDownloaderGUI">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Window>
@@ -1,22 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace HyperbolicDownloaderGUI.Views
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
+5 -12
View File
@@ -25,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.
@@ -41,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\
@@ -73,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.\
@@ -97,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>`
@@ -113,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.\