6 Commits
Author SHA1 Message Date
Stone_Red 17941d4829 Update README.md 2023-12-14 19:52:00 +01:00
Stone_Red 7393dc9f13 Update usage information 2023-12-14 19:44:01 +01:00
Stone_Red e06ba635de Update README.md 2023-02-06 15:59:55 +01:00
Stone_Red 9f9771cf0d Update README.md 2023-02-06 15:59:26 +01:00
Stone_Red 10e02cdb37 Fix spelling mistakes 2023-02-06 15:57:50 +01:00
Stone_Red f7aaa407e7 Merge pull request #2 from Stone-Red-Code/develop
Develop
2023-02-06 15:41:19 +01:00
40 changed files with 988 additions and 1402 deletions
@@ -1,8 +0,0 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Critical Code Smell", "S3998:Threads should not lock on objects with weak identity", Justification = "Not a problem in this case", Scope = "member", Target = "~M:HyperbolicDownloader.LogCommands.OnNotificationMessageRecived(System.Object,HyperbolicDownloaderApi.Managment.NotificationMessageEventArgs)")]
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -12,7 +12,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.2" />
<PackageReference Include="Console.Commander.NET" Version="1.0.1" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup>
<ItemGroup>
+9 -28
View File
@@ -1,62 +1,47 @@
using Commander;
using CuteUtils.Misc;
using Commander_Net;
using HyperbolicDownloaderApi.Commands;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader;
internal class InputHandler
{
private readonly Commander.Commander commander = new Commander.Commander();
private readonly Commander commander = new Commander();
private bool exit = false;
public InputHandler(HostsManager hostsManager, FilesManager filesManager, DirectoryWatcher directoryWatcher)
public InputHandler(HostsManager hostsManager, FilesManager filesManager)
{
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(input => commander.PrintHelp(input), "help");
_ = 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(fileCommands.ListFiles, (HelpText)"Lists all files.", "files");
_ = listCommand.Register(hostCommands.ListHosts, (HelpText)"lists all hosts.", "hosts");
_ = commander.Register(hostCommands.CheckActiveHosts, (HelpText)"Checks the status of known hosts.", "status", "check");
@@ -71,18 +56,13 @@ internal class InputHandler
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);
ConsoleExt.WriteLine("Unknown command!", ConsoleColor.Red);
}
}
catch (Exception ex)
@@ -91,3 +71,4 @@ internal class InputHandler
}
}
}
}
-102
View File
@@ -1,102 +0,0 @@
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);
}
}
}
}
}
+12 -39
View File
@@ -1,10 +1,9 @@
using CuteUtils.Misc;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using System.Reflection;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Text.Json;
namespace HyperbolicDownloader;
@@ -20,48 +19,30 @@ internal static class Program
ApiManager.OnNotificationMessageRecived += ApiManager_OnNotificationMessageRecived;
if (!Directory.Exists(ApiConfiguration.BasePath))
{
_ = Directory.CreateDirectory(ApiConfiguration.BasePath);
}
if (File.Exists(ApiConfiguration.HostsFilePath))
{
string hostsJson = await File.ReadAllTextAsync(ApiConfiguration.HostsFilePath);
_ = apiManager.HostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? []);
apiManager.HostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? new());
}
if (File.Exists(ApiConfiguration.FilesInfoPath))
{
string filesJson = await File.ReadAllTextAsync(ApiConfiguration.FilesInfoPath);
apiManager.FilesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? []);
apiManager.FilesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? new());
}
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);
InputHandler inputHandler = new InputHandler(apiManager.HostsManager, apiManager.FilesManager);
if (args.Length > 0 && File.Exists(args[0]))
{
HyperbolicDownloaderApi.Commands.DownloadCommands downloadCommands = new HyperbolicDownloaderApi.Commands.DownloadCommands(apiManager.HostsManager, apiManager.FilesManager);
downloadCommands.GetFileFrom(args[0]);
_ = Console.ReadLine();
Console.WriteLine("Do you want to continue using this instance? [y/N]");
if (char.ToLower(Console.ReadKey().KeyChar) != 'y')
{
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();
inputHandler.ExecuteInput(string.Join(' ', args));
return;
}
await Initialize();
@@ -72,8 +53,6 @@ internal static class Program
private static async Task Initialize()
{
apiManager.DirectoryWatcher.Start();
Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
_ = await ApiManager.OpenPorts();
@@ -84,15 +63,10 @@ internal static class Program
if (!apiManager.StartTcpListener())
{
_ = Console.ReadLine();
Environment.Exit(-1);
}
Console.WriteLine("Starting UDP listener...");
if (!apiManager.StartBroadcastListener())
{
_ = Console.ReadLine();
Environment.Exit(-2);
}
Console.WriteLine("Starting broadcast listener...");
apiManager.StartBroadcastListener();
int activeHostsCount = 0;
if (apiManager.HostsManager.Count > 0)
@@ -104,7 +78,7 @@ internal static class Program
if (activeHostsCount == 0)
{
ConsoleExt.WriteLine("No active hosts found!", 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);
ConsoleExt.WriteLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host or use 'discover' to find hosts in the local network.", ConsoleColor.Red);
}
Console.WriteLine($"{apiManager.HostsManager.Count} known host(s).");
@@ -128,6 +102,5 @@ internal static class Program
{
ApiManager.ClosePorts();
apiManager.HostsManager.SaveHosts();
Environment.Exit(0);
}
}
@@ -1,81 +0,0 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
namespace HyperbolicDownloaderApi.Commands;
public class DirectoryCommands(DirectoryWatcher directoryWatcher)
{
public void AddDirectory(string directoryPath)
{
if (directoryWatcher.TryAdd(directoryPath, out string? message))
{
ApiManager.SendNotificationMessageNewLine($"Added directory: {directoryPath}", NotificationMessageType.Success);
}
else
{
ApiManager.SendNotificationMessageNewLine(message!, NotificationMessageType.Error);
}
}
public void RemoveDirectory(string args)
{
if (int.TryParse(args, out int index))
{
List<string> fileInfos = directoryWatcher.ToList();
if (index < 1 || index > fileInfos.Count)
{
ApiManager.SendNotificationMessageNewLine("Invalid index!", NotificationMessageType.Error);
return;
}
args = fileInfos[index - 1];
}
if (directoryWatcher.TryRemove(args))
{
ApiManager.SendNotificationMessageNewLine($"Removed directory: {args}", NotificationMessageType.Success);
}
else
{
ApiManager.SendNotificationMessageNewLine("Directory is not tracked!", NotificationMessageType.Error);
}
}
public void ListDirectories(string searchString)
{
int index = 0;
int directoryCount = 0;
List<string> directoryInfos = directoryWatcher.ToList();
if (directoryInfos.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No tracked directories!", NotificationMessageType.Warning);
return;
}
foreach (string directoryInfo in directoryInfos)
{
index++;
if (!string.IsNullOrWhiteSpace(searchString) && !directoryInfo.Contains(searchString, StringComparison.OrdinalIgnoreCase))
{
continue;
}
directoryCount++;
ApiManager.SendNotificationMessageNewLine($"{index}) {directoryInfo}");
ApiManager.SendNotificationMessageNewLine(string.Empty);
}
if (directoryCount == 0)
{
ApiManager.SendNotificationMessage($"No directories found containing \"{searchString}\".", NotificationMessageType.Warning);
}
else
{
Console.CursorTop--;
}
}
}
@@ -1,9 +1,8 @@
using CuteUtils;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using HyperbolicDownloaderApi.Utilities;
using Stone_Red_Utilities.StringExtentions;
using System.Diagnostics;
using System.Net;
@@ -13,8 +12,17 @@ using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands;
public class DownloadCommands(HostsManager hostsManager, FilesManager filesManager)
public 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))
@@ -28,7 +36,6 @@ public class DownloadCommands(HostsManager hostsManager, FilesManager filesManag
if (!File.Exists(fullPath))
{
ApiManager.SendNotificationMessageNewLine("Invalid file path!", NotificationMessageType.Error);
return;
}
string json = File.ReadAllText(fullPath);
@@ -40,7 +47,7 @@ public class DownloadCommands(HostsManager hostsManager, FilesManager filesManag
return;
}
_ = hostsManager.AddRange(publicHyperFileInfo.Hosts);
hostsManager.AddRange(publicHyperFileInfo.Hosts);
GetFile(publicHyperFileInfo.Hash);
}
@@ -102,7 +109,7 @@ public class DownloadCommands(HostsManager hostsManager, FilesManager filesManag
byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}");
nwStream.Write(bytesToSend);
nwStream.ReadTimeout = 5000;
nwStream.ReadTimeout = 30000;
int bytesRead;
try
@@ -141,6 +148,8 @@ public class DownloadCommands(HostsManager hostsManager, FilesManager filesManag
ApiManager.SendNotificationMessageNewLine($"File name: {fileName}");
ApiManager.SendNotificationMessageNewLine($"Starting download...");
int totalBytesRead = 0;
if (!Directory.Exists(directoryPath))
{
_ = Directory.CreateDirectory(directoryPath);
@@ -148,14 +157,12 @@ public class DownloadCommands(HostsManager hostsManager, FilesManager filesManag
using FileStream? fileStream = new FileStream(filePath, FileMode.Create);
int totalBytesRead = 0;
int bytesPerSecond = 0;
int transferRate = 0;
TimeSpan timeRemaining = TimeSpan.Zero;
int bytesInOneSecond = 0;
int unitsPerSecond = 0;
string unit = "Kb";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (totalBytesRead < fileSize)
{
try
@@ -175,18 +182,26 @@ public class DownloadCommands(HostsManager hostsManager, FilesManager filesManag
fileStream.Write(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
bytesPerSecond += bytesRead;
bytesInOneSecond += bytesRead;
if (stopWatch.Elapsed.TotalSeconds >= 1)
{
transferRate = bytesPerSecond;
host.DownloadSpeed = (host.DownloadSpeed + bytesPerSecond) / 2;
timeRemaining = TimeSpan.FromSeconds((fileSize - totalBytesRead) / (double)host.DownloadSpeed);
bytesPerSecond = 0;
unitsPerSecond = (int)(bytesInOneSecond * stopWatch.Elapsed.TotalSeconds);
if (unitsPerSecond > 125000)
{
unitsPerSecond /= 125000;
unit = "Mb";
}
else
{
unitsPerSecond /= 125;
unit = "Kb";
}
bytesInOneSecond = 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}] ");
ApiManager.SendNotificationMessage($"\rDownloading: {Math.Clamp(Math.Ceiling(100d / fileSize * totalBytesRead), 0, 100)}% {totalBytesRead / 1000}/{fileSize / 1000}KB [{unitsPerSecond}{unit}/s] ");
}
fileStream.Close();
+27 -176
View File
@@ -2,16 +2,22 @@
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)
public class FileCommands
{
private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
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)
{
@@ -26,24 +32,11 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
}
}
public void RemoveFile(string args)
public void RemoveFile(string hash)
{
args = args.Trim().ToLower();
hash = hash.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))
if (filesManager.TryRemove(hash))
{
ApiManager.SendNotificationMessageNewLine($"Successfully removed file!", NotificationMessageType.Success);
}
@@ -53,10 +46,9 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
}
}
public void ListFiles(string searchString)
public void ListFiles(string _)
{
int index = 0;
int fileCount = 0;
List<PrivateHyperFileInfo> fileInfos = filesManager.ToList();
if (fileInfos.Count == 0)
@@ -68,129 +60,14 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
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)
public void GenerateFileSingle(string hash)
{
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath))
@@ -198,23 +75,9 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
_ = Directory.CreateDirectory(directoryPath);
}
args = args.Trim().ToLower();
hash = hash.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))
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo))
{
ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
return;
@@ -223,7 +86,7 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash);
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash);
NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null)
@@ -234,7 +97,8 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
publicHyperFileInfo.Hosts.Add(localHost);
string json = JsonSerializer.Serialize(publicHyperFileInfo, jsonSerializerOptions);
JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(publicHyperFileInfo, options);
File.WriteAllText(filePath, json);
@@ -242,7 +106,7 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
}
public void GenerateFileFull(string args)
public void GenerateFileFull(string hash)
{
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath))
@@ -250,23 +114,9 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
_ = Directory.CreateDirectory(directoryPath);
}
args = args.Trim().ToLower();
hash = hash.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))
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo))
{
ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
return;
@@ -275,7 +125,7 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
string fileName = Path.GetFileName(localHyperFileInfo!.FilePath);
string filePath = Path.Combine(directoryPath, $"{fileName}.hyper");
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash);
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash);
NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null)
@@ -298,7 +148,7 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", localHyperFileInfo.Hash);
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash);
_ = sendTask.Wait(1000);
@@ -325,7 +175,8 @@ public class FileCommands(HostsManager hostsManager, FilesManager filesManager)
publicHyperFileInfo.Hosts.Add(localHost);
string json = JsonSerializer.Serialize(publicHyperFileInfo, jsonSerializerOptions);
JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(publicHyperFileInfo, options);
File.WriteAllText(filePath, json);
@@ -1,6 +1,5 @@
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using HyperbolicDownloaderApi.Utilities;
using System.Diagnostics;
using System.Net;
@@ -8,8 +7,15 @@ using System.Net.Sockets;
namespace HyperbolicDownloaderApi.Commands;
public class HostCommands(HostsManager hostsManager)
public class HostCommands
{
private readonly HostsManager hostsManager;
public HostCommands(HostsManager hostsManager)
{
this.hostsManager = hostsManager;
}
public void Discover(string _)
{
ApiManager.SendNotificationMessageNewLine("Running local discovery routine...", NotificationMessageType.Info);
@@ -19,16 +25,7 @@ public class HostCommands(HostsManager hostsManager)
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);
}
ApiManager.SendNotificationMessageNewLine($"Found {hostsManager.Count - hostsCountBefore} host(s)", NotificationMessageType.Info);
}
public void CheckActiveHosts(string _)
@@ -37,7 +34,7 @@ public class HostCommands(HostsManager hostsManager)
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("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host.", NotificationMessageType.Error);
}
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
@@ -52,7 +49,7 @@ public class HostCommands(HostsManager hostsManager)
if (hosts.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No known hosts.", NotificationMessageType.Warning);
ApiManager.SendNotificationMessageNewLine("No known hosts", NotificationMessageType.Warning);
return;
}
@@ -61,7 +58,6 @@ public class HostCommands(HostsManager hostsManager)
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--;
@@ -71,23 +67,9 @@ public class HostCommands(HostsManager hostsManager)
{
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);
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", NotificationMessageType.Error);
return;
}
@@ -111,7 +93,7 @@ public class HostCommands(HostsManager hostsManager)
if (!hostsManager.Contains(hostToRemove))
{
ApiManager.SendNotificationMessageNewLine("Host not in list!", NotificationMessageType.Error);
ApiManager.SendNotificationMessageNewLine("Host not in list", NotificationMessageType.Error);
return;
}
@@ -125,7 +107,7 @@ public class HostCommands(HostsManager hostsManager)
if (parts.Length != 2)
{
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: <ip address>:<port>", NotificationMessageType.Error);
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", NotificationMessageType.Error);
return;
}
@@ -162,60 +144,25 @@ public class HostCommands(HostsManager hostsManager)
{
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;
List<NetworkSocket>? recivedHosts = NetworkClient.Send<List<NetworkSocket>>(ipAddress, port, "GetHostsList", localSocket);
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);
}
ApiManager.SendNotificationMessageNewLine($"Success! Added {recivedHosts.Count} new host(s).", NotificationMessageType.Success);
hostsManager.AddRange(recivedHosts);
}
else
{
ApiManager.SendNotificationMessageNewLine($"Invalid response!", NotificationMessageType.Error);
}
}
catch (Exception ex) when (ex is SocketException or IOException or AggregateException)
catch (SocketException ex)
{
ApiManager.SendNotificationMessageNewLine($"Invalid host! Error message: {ex.Message}", NotificationMessageType.Error);
}
catch (IOException ex)
{
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);
}
}
}
@@ -1,247 +0,0 @@
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;
}
}
@@ -1,105 +0,0 @@
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,13 +1,12 @@
using HyperbolicDownloaderApi.Managment;
using System.Diagnostics;
using System.Text.Json;
namespace HyperbolicDownloaderApi.FileProcessing;
public class FilesManager
{
private readonly List<PrivateHyperFileInfo> files = [];
private readonly List<PrivateHyperFileInfo> files = new List<PrivateHyperFileInfo>();
public bool TryAdd(string filePath, out PrivateHyperFileInfo? fileInfo, out string? errorMessage)
{
@@ -32,16 +31,10 @@ public class FilesManager
if (Contains(hash))
{
fileInfo = null;
Debug.WriteLine(filePath + "-.-" + hash);
errorMessage = "File already tracked!";
return false;
}
if (files.Exists(f => f.FilePath == fullPath))
{
_ = files.RemoveAll(f => f.FilePath == fullPath);
}
fileInfo = new PrivateHyperFileInfo(hash, fullPath);
files.Add(fileInfo);
@@ -90,26 +83,12 @@ public class FilesManager
public bool Contains(string? hash)
{
return files.Exists(f => f.Hash == hash);
return files.Any(f => f.Hash == hash);
}
public List<PrivateHyperFileInfo> 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();
return files.ToList();
}
private void SaveFiles()
@@ -1,23 +0,0 @@
using System.Text.Json.Serialization;
namespace HyperbolicDownloaderApi.FileProcessing;
internal class HyperFileDto
{
public string Hash { get; set; }
public string Name { get; set; }
[JsonConstructor]
public HyperFileDto(string hash, string name)
{
Hash = hash;
Name = name;
}
public HyperFileDto(PrivateHyperFileInfo privateHyperFileInfo)
{
Hash = privateHyperFileInfo.Hash;
Name = Path.GetFileName(privateHyperFileInfo.FilePath);
}
}
@@ -1,7 +1,13 @@
namespace HyperbolicDownloaderApi.FileProcessing;
public class PrivateHyperFileInfo(string hash, string filePath)
public class PrivateHyperFileInfo
{
public string Hash { get; set; } = hash;
public string FilePath { get; set; } = filePath;
public string Hash { get; set; }
public string FilePath { get; set; }
public PrivateHyperFileInfo(string hash, string filePath)
{
Hash = hash;
FilePath = filePath;
}
}
@@ -5,7 +5,7 @@ namespace HyperbolicDownloaderApi.FileProcessing;
public class PublicHyperFileInfo
{
public string Hash { get; set; } = string.Empty;
public List<NetworkSocket> Hosts { get; set; } = [];
public List<NetworkSocket> Hosts { get; set; } = new();
public PublicHyperFileInfo()
{
@@ -1,9 +0,0 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Major Code Smell", "S6561:Avoid using \"DateTime.Now\" for benchmarking or timing operations", Justification = "Stop")]
[assembly: SuppressMessage("Minor Code Smell", "S3604:Member initializer values should not be redundant", Justification = "False positive")]
@@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.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" />
<PackageReference Include="Open.Nat" Version="2.1.0" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup>
</Project>
@@ -1,12 +1,13 @@
namespace HyperbolicDownloaderApi.Managment;
using System.Reflection;
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 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 string DirectoriesInfoPath { get; } = Path.Combine(BasePath, "Directories.json");
}
+34 -59
View File
@@ -1,7 +1,7 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking;
using SharpOpenNat;
using Open.Nat;
using System.Diagnostics;
using System.Net;
@@ -13,20 +13,19 @@ 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; }
private static readonly Random random = new();
private static NatDevice? device;
private static Mapping? portMapping;
private readonly BroadcastClient broadcastClient = new BroadcastClient();
private readonly NetworkClient networkClient;
public ApiManager()
{
networkClient = new(FilesManager);
DirectoryWatcher = new(FilesManager);
}
public static NetworkSocket? GetLocalSocket()
@@ -59,7 +58,8 @@ public class ApiManager
{
ApiConfiguration.PublicPort = random.Next(1000, 6000);
device = await OpenNat.Discoverer.DiscoverDeviceAsync();
NatDiscoverer? discoverer = new NatDiscoverer();
device = await discoverer.DiscoverDeviceAsync();
IPAddress? ip = await device.GetExternalIPAsync();
SendNotificationMessageNewLine($"The public IP address is: {ip} ", NotificationMessageType.Success);
@@ -81,46 +81,39 @@ public class ApiManager
SendNotificationMessageNewLine($"An error occurred while mapping the private port ({ApiConfiguration.PrivatePort}) to the public port ({ApiConfiguration.PublicPort})! Error message: {ex.Message}", NotificationMessageType.Error);
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)
if (device is not null)
{
try
{
device.DeletePortMapAsync(portMapping).GetAwaiter().GetResult();
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)
{
SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Error);
SendNotificationMessageNewLine(ex.ToString(), NotificationMessageType.Error);
}
}
SendNotificationMessageNewLine("Ports closed!", NotificationMessageType.Warning);
Environment.Exit(0);
}
public bool StartBroadcastListener()
{
try
public void StartBroadcastListener()
{
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()
{
@@ -130,7 +123,6 @@ public class ApiManager
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)
@@ -142,21 +134,6 @@ public class ApiManager
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;
@@ -179,7 +156,7 @@ public class ApiManager
if (success)
{
_ = HostsManager.Add(new NetworkSocket(remoteIpAddress.ToString(), remotePort, DateTime.Now));
HostsManager.Add(new NetworkSocket(remoteIpAddress.ToString(), remotePort, DateTime.Now));
try
{
await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend);
@@ -194,14 +171,11 @@ public class ApiManager
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);
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();
@@ -216,7 +190,7 @@ public class ApiManager
if (recivedEventArgs.Data.Port != 0)
{
_ = HostsManager.Add(recivedEventArgs.Data);
HostsManager.Add(recivedEventArgs.Data);
}
await recivedEventArgs.SendResponseAsync(hostsToSend);
@@ -224,20 +198,21 @@ public class ApiManager
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)
private void ReciveMessage(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting file list", NotificationMessageType.Log);
SendNotificationMessageNewLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.", NotificationMessageType.Info);
}
List<HyperFileDto> files = [.. FilesManager
.ToList()
.Select(f => new HyperFileDto(f))
.Where(f => f.Name.Contains(recivedEventArgs.Data))];
internal static void SendNotificationMessage(string message, NotificationMessageType messageType = NotificationMessageType.Info)
{
OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message));
}
await recivedEventArgs.SendResponseAsync(files);
internal static void SendNotificationMessageNewLine(string message, NotificationMessageType messageType = NotificationMessageType.Info)
{
OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message + Environment.NewLine));
}
}
@@ -1,16 +1,20 @@
namespace HyperbolicDownloaderApi.Managment;
public class NotificationMessageEventArgs(NotificationMessageType notificationMessageType, string? message) : EventArgs
public class NotificationMessageEventArgs : EventArgs
{
public NotificationMessageType NotificationMessageType { get; } = notificationMessageType;
public string? Message { get; } = message;
public NotificationMessageType NotificationMessageType { get; }
public string? Message { get; }
public NotificationMessageEventArgs(NotificationMessageType notificationMessageType, string? message)
{
NotificationMessageType = notificationMessageType;
Message = message;
}
}
public enum NotificationMessageType
{
Info,
Log,
Debug,
Success,
Warning,
Error
@@ -11,9 +11,10 @@ internal class BroadcastClient
{
public event EventHandler<BroadcastRecivedEventArgs>? OnBroadcastRecived;
private UdpClient? udpListener;
public bool IsListening { get; private set; } = false;
private UdpClient? udpListener;
public static void Send(int port, string message)
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
@@ -57,7 +58,7 @@ internal class BroadcastClient
udpListener = new UdpClient(port);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, port);
_ = new TaskFactory().StartNew(() =>
_ = Task.Run(() =>
{
while (IsListening)
{
@@ -66,7 +67,7 @@ internal class BroadcastClient
OnBroadcastRecived?.Invoke(this, new BroadcastRecivedEventArgs(groupEP, message));
}
}, TaskCreationOptions.LongRunning);
});
}
public void StopListening()
@@ -2,8 +2,14 @@
namespace HyperbolicDownloaderApi.Networking;
internal class BroadcastRecivedEventArgs(IPEndPoint ipEndPoint, string message) : EventArgs
internal class BroadcastRecivedEventArgs : EventArgs
{
public IPEndPoint IPEndPoint { get; } = ipEndPoint;
public string Message { get; } = message;
public BroadcastRecivedEventArgs(IPEndPoint iPEndPoint, string message)
{
IPEndPoint = iPEndPoint;
Message = message;
}
public IPEndPoint IPEndPoint { get; }
public string Message { get; }
}
@@ -1,7 +1,13 @@
namespace HyperbolicDownloaderApi.Networking;
internal class DataContainer(string eventName, string jsonData)
internal class DataContainer
{
public string EventName { get; set; } = eventName;
public string JsonData { get; set; } = jsonData;
public string EventName { get; set; }
public string JsonData { get; set; }
public DataContainer(string eventName, string jsonData)
{
JsonData = jsonData;
EventName = eventName;
}
}
@@ -1,6 +1,5 @@
using HyperbolicDownloaderApi.Managment;
using System.Net;
using System.Net.Sockets;
using System.Text.Json;
@@ -8,47 +7,45 @@ namespace HyperbolicDownloaderApi.Networking;
public class HostsManager
{
private List<NetworkSocket> hosts = [];
private List<NetworkSocket> hosts = new List<NetworkSocket>();
public int Count => hosts.Count;
public int AddRange(IEnumerable<NetworkSocket> hosts)
public void AddRange(IEnumerable<NetworkSocket> hosts)
{
int newHosts = hosts.Count(Add);
SaveHosts();
return newHosts;
foreach (NetworkSocket host in hosts)
{
Add(host);
}
public bool Add(NetworkSocket host)
SaveHosts();
}
public void Add(NetworkSocket host)
{
if (!Contains(host) && !host.Equals(ApiManager.GetLocalSocket()))
if (!Contains(host))
{
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));
_ = hosts.RemoveAll(x => x.IPAddress == host.IPAddress && x.Port == host.Port);
}
SaveHosts();
}
public bool Contains(NetworkSocket host)
{
return hosts.Exists(x => x.Equals(host));
return hosts.Any(x => x.IPAddress == host.IPAddress && x.Port == host.Port);
}
public int CheckHostsActivity()
{
List<NetworkSocket> hostsToRemove = [];
List<NetworkSocket> hostsToRemove = new List<NetworkSocket>();
int activeHostsCount = 0;
foreach (NetworkSocket host in hosts)
{
@@ -57,7 +54,7 @@ public class HostsManager
try
{
_ = tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(1000);
_ = tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(500);
Console.CursorLeft = 0;
if (tcpClient.Connected)
{
@@ -94,65 +91,14 @@ public class HostsManager
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];
return hosts.ToList();
}
public void SaveHosts()
{
hosts = [.. hosts
.OrderByDescending(s => s.DownloadSpeed)
.ThenByDescending(s => s.LastActive)];
hosts = hosts.OrderByDescending(h => h.LastActive).ToList();
File.WriteAllText(ApiConfiguration.HostsFilePath, JsonSerializer.Serialize(hosts));
}
}
@@ -5,11 +5,20 @@ using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
internal class MessageRecivedEventArgs<T>(NetworkStream networkStream, IPAddress ipAddress, T data) : EventArgs
internal class MessageRecivedEventArgs<T> : EventArgs
{
public T Data { get; set; } = data;
private readonly NetworkStream networkStream;
public IPAddress IpAddress { get; set; } = ipAddress;
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)
{
@@ -1,7 +1,4 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using NAudio.Wave;
using System.Diagnostics;
using System.Net;
@@ -11,15 +8,25 @@ using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
internal class NetworkClient(FilesManager filesManager)
internal class NetworkClient
{
private readonly Dictionary<string, (Type type, Delegate method)> events = [];
private TcpListener? tcpListener;
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)
{
ArgumentNullException.ThrowIfNull(remoteIp);
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
@@ -32,23 +39,9 @@ internal class NetworkClient(FilesManager filesManager)
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());
string response = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead);
client.Close();
@@ -64,7 +57,10 @@ internal class NetworkClient(FilesManager filesManager)
public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data)
{
ArgumentNullException.ThrowIfNull(remoteIp);
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
@@ -102,27 +98,98 @@ internal class NetworkClient(FilesManager filesManager)
tcpListener.Start();
IsListening = true;
_ = new TaskFactory().StartNew(() =>
_ = Task.Run(async () =>
{
while (IsListening)
{
try
{
ApiManager.SendNotificationMessageNewLine("Listening for connections...", NotificationMessageType.Debug);
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Connected", NotificationMessageType.Debug);
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize));
_ = Task.Run(() => HandleRequest(client));
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)
{
ApiManager.SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Log);
if (ex.SocketErrorCode != SocketError.Interrupted)
{
throw;
}
}
}
tcpListener.Stop();
}, TaskCreationOptions.LongRunning);
});
}
private async Task Upload(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 64000;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
FileInfo fileInfo = new FileInfo(hyperFileInfo.FilePath);
bytesToSend = Encoding.ASCII.GetBytes($"{fileInfo.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}");
Array.Resize(ref bytesToSend, 1000);
await nwStream.WriteAsync(bytesToSend);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000).Where(chunk => chunk is not null))
{
await nwStream.WriteAsync(chunk);
}
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
client.Close();
}
}
public void StopListening()
@@ -135,200 +202,4 @@ internal class NetworkClient(FilesManager filesManager)
{
events.Add(eventName, (typeof(T), eventHandler));
}
private async Task HandleRequest(TcpClient client)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Handling request", NotificationMessageType.Debug);
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Reading data", NotificationMessageType.Debug);
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize));
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Data read", NotificationMessageType.Debug);
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Data: {dataReceived}", NotificationMessageType.Debug);
if (dataReceived.StartsWith("Download"))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Download request", NotificationMessageType.Debug);
_ = Upload(client, dataReceived[8..]);
return;
}
if (dataReceived.StartsWith("StreamWav"))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Download request", NotificationMessageType.Debug);
_ = StreamWav(client, dataReceived[9..]);
return;
}
if (string.IsNullOrWhiteSpace(dataReceived))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Disconnected", NotificationMessageType.Debug);
client.Close();
return;
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Deserializing data", NotificationMessageType.Debug);
DataContainer? dataContainer = JsonSerializer.Deserialize<DataContainer>(dataReceived);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Data deserialized", NotificationMessageType.Debug);
if (dataContainer is not null && events.TryGetValue(dataContainer.EventName, out (Type type, Delegate method) value))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Found event", NotificationMessageType.Debug);
(Type type, Delegate method) = value;
Type eventArgsType = typeof(MessageRecivedEventArgs<>).MakeGenericType(type);
object? eventArgs = Activator.CreateInstance(
eventArgsType,
nwStream,
(client.Client.RemoteEndPoint as IPEndPoint)?.Address,
JsonSerializer.Deserialize(dataContainer.JsonData, type));
_ = (method?.DynamicInvoke(this, eventArgs));
}
else
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Event not found", NotificationMessageType.Debug);
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Closing connection", NotificationMessageType.Debug);
client.Close();
ApiManager.SendNotificationMessageNewLine($"{(client.Client?.RemoteEndPoint as IPEndPoint)?.Address} > Connection closed", NotificationMessageType.Debug);
}
private async Task Upload(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Requesting file download [{hash}]", NotificationMessageType.Log);
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 64000;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Accepting file download [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
FileInfo fileInfo = new FileInfo(hyperFileInfo.FilePath);
bytesToSend = Encoding.ASCII.GetBytes($"{fileInfo.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}");
await nwStream.WriteAsync(bytesToSend);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Starting file download of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000).Where(chunk => chunk is not null))
{
await nwStream.WriteAsync(chunk);
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Completed download of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > File not found [{hash}]", NotificationMessageType.Log);
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Error downloading file {ex.Message} [{hash}]", NotificationMessageType.Log);
Debug.WriteLine(ex);
}
finally
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Closing connection", NotificationMessageType.Debug);
client.Close();
ApiManager.SendNotificationMessageNewLine($"{(client.Client?.RemoteEndPoint as IPEndPoint)?.Address} > Connection closed", NotificationMessageType.Debug);
}
}
private async Task StreamWav(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Requesting file stream [{hash}]", NotificationMessageType.Log);
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 6400;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Accepting file stream [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
WaveFileReader reader;
try
{
reader = new WaveFileReader(hyperFileInfo.FilePath);
}
catch (FormatException ex)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Error streaming file {ex.Message} [{hash}]", NotificationMessageType.Log);
Debug.WriteLine(ex);
bytesToSend = Encoding.ASCII.GetBytes("Invalid file format!");
await nwStream.WriteAsync(bytesToSend);
return;
}
bytesToSend = Encoding.ASCII.GetBytes($"{reader.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}/{reader.WaveFormat.SampleRate}/{reader.WaveFormat.BitsPerSample}/{reader.WaveFormat.Channels}");
await nwStream.WriteAsync(bytesToSend);
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Starting file stream of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
byte[]? buffer = new byte[64000];
while (reader.Position < reader.Length)
{
int bytesRead = reader.Read(buffer, 0, 6400);
await nwStream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Completed stream of file [{Path.GetFileName(hyperFileInfo.FilePath)}] [{hash}]", NotificationMessageType.Log);
reader.Close();
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > File not found [{hash}]", NotificationMessageType.Log);
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Error streaming file {ex.Message} [{hash}]", NotificationMessageType.Log);
Debug.WriteLine(ex);
}
finally
{
ApiManager.SendNotificationMessageNewLine($"{(client.Client.RemoteEndPoint as IPEndPoint)?.Address} > Closing connection", NotificationMessageType.Debug);
client.Close();
ApiManager.SendNotificationMessageNewLine($"{(client.Client?.RemoteEndPoint as IPEndPoint)?.Address} > Connection closed", NotificationMessageType.Debug);
}
}
}
@@ -1,25 +1,15 @@
namespace HyperbolicDownloaderApi.Networking;
public class NetworkSocket(string ipAddress, int port, DateTime lastActive)
public class NetworkSocket
{
public string IPAddress { get; set; } = ipAddress;
public int Port { get; set; } = port;
public DateTime LastActive { get; set; } = lastActive;
public string IPAddress { get; set; }
public int Port { get; set; }
public DateTime LastActive { get; set; }
public long DownloadSpeed { get; set; }
public override bool Equals(object? obj)
public NetworkSocket(string ipAddress, int port, DateTime lastActive)
{
if (obj is not NetworkSocket networkSocket)
{
return false;
}
return networkSocket.IPAddress == IPAddress && networkSocket.Port == Port;
}
public override int GetHashCode()
{
return HashCode.Combine(IPAddress, Port);
IPAddress = ipAddress;
Port = port;
LastActive = lastActive;
}
}
@@ -1,38 +0,0 @@
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
@@ -0,0 +1,454 @@
## 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
@@ -0,0 +1,12 @@
<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
@@ -0,0 +1,30 @@
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.

After

Width:  |  Height:  |  Size: 172 KiB

@@ -0,0 +1,19 @@
<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
@@ -0,0 +1,28 @@
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
@@ -0,0 +1,32 @@
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;
}
}
}
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HyperbolicDownloaderGUI.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
public string Greeting => "Welcome to Avalonia!";
}
}
@@ -0,0 +1,12 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace HyperbolicDownloaderGUI.ViewModels
{
public class ViewModelBase : ReactiveObject
{
}
}
@@ -0,0 +1,17 @@
<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>
@@ -0,0 +1,22 @@
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);
}
}
}
+11 -10
View File
@@ -1,27 +1,28 @@
# HyperbolicDownloader
> A cross-platform P2P file sharing CLI client
> A cross-platform P2P file sharing CLI tool & protocol
## Usage
1. Download one of the [releases](https://github.com/Stone-Red-Code/HyperbolicDownloader/releases)
1. Exacute the `HyperbolicDownloader` file
1. Wait until the setup process is finished
1. Do one of the below
- Add a host to your client using the [add](#add) command and use the [get](#get) command tho retrive a file
- Use the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate) command to generate a `.hyper` file and the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrive a file trough the generated `.hyper` file
1. Download one of the [releases](https://github.com/Stone-Red-Code/HyperbolicDownloader/releases).
1. Execute the `HyperbolicDownloader` file.
1. Wait until the setup process is finished.
1. Next steps:
- Add a host to your client using the [add](#add) command and use the [get](#get) command to retrieve a file.
- Add the host `hyper.stone-red.net:3055` (`add host hyper.stone-red.net:3055`) to the hosts list to obtain a few existing hosts and immediately gain access to all of my files hosted on the HyperbolicDownloader network.\
- Use the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate) command to generate a `.hyper` file and the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrieve a file through the generated `.hyper` file.
## How it works
When you start the program, it first tries to open a public port via UPnP/NAT-PMP. If it succeeds, you can communicate with the client using the public IP address and port.
If it is unable to find a UPnP/NAT-PMP device, you will need to manually set up port forwarding on your client's IP address to port "3055". The public port has to be between 1000 and 6000 here.
HyperbolicDownloader can retrieve files from other computers (aka hosts) using a SHA 512 hash.
HyperbolicDownloader can retrieve files from other computers (aka hosts) using an SHA 512 hash.
The client checks all known hosts to see if it could find the requested file. If one of the hosts has the requested file, it immediately downloads it.
After the file is completely downloaded, it is validated by comparing the hash entered with that of the file received. If the hash does not match, you will receive a warning message and you can download the file again if needed.\
After the file is completely downloaded, it is validated by comparing the hash entered with that of the file received. If the hash does not match, you will receive a warning message, and you can download the file again if needed.\
This makes it very difficult to tamper with requested files, as long as the source from which you obtain the hash/`.hpyer` file is trusted.
You can generate `.hyper` files with your client using the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate). command\
You can generate `.hyper` files with your client using the [generate](https://github.com/Stone-Red-Code/HyperbolicDownloader#generate). Command\
These files contain the hash value of the actual file and the hosts that should have the requested file.
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.