From e7c3d6ea44f013d4d740245b946ba2aed4df8b72 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 14 Dec 2023 17:55:20 +0100 Subject: [PATCH] Add directory watcher functionality --- HyperbolicDownloader/InputHandler.cs | 6 +- HyperbolicDownloader/Program.cs | 15 ++- .../Commands/DirectoryCommands.cs | 88 +++++++++++++++ .../Commands/DownloadCommands.cs | 1 + .../Commands/FileCommands.cs | 10 +- .../FileProcessing/DirectoryWatcher.cs | 100 ++++++++++++++++++ .../FileProcessing/FilesManager.cs | 21 ++++ .../Managment/ApiConfiguration.cs | 7 +- .../Managment/ApiManager.cs | 2 + 9 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 HyperbolicDownloaderApi/Commands/DirectoryCommands.cs create mode 100644 HyperbolicDownloaderApi/FileProcessing/DirectoryWatcher.cs diff --git a/HyperbolicDownloader/InputHandler.cs b/HyperbolicDownloader/InputHandler.cs index ef72b58..89be2e7 100644 --- a/HyperbolicDownloader/InputHandler.cs +++ b/HyperbolicDownloader/InputHandler.cs @@ -13,10 +13,11 @@ internal class InputHandler private readonly Commander commander = new Commander(); private bool exit = false; - public InputHandler(HostsManager hostsManager, FilesManager filesManager) + 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); ClientCommands clientCommands = new ClientCommands(); LogCommands logCommands = new LogCommands(); @@ -37,15 +38,18 @@ internal class InputHandler 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"); diff --git a/HyperbolicDownloader/Program.cs b/HyperbolicDownloader/Program.cs index 0f634ab..f5b2855 100644 --- a/HyperbolicDownloader/Program.cs +++ b/HyperbolicDownloader/Program.cs @@ -20,6 +20,11 @@ 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); @@ -32,11 +37,17 @@ internal static class Program apiManager.FilesManager.AddRange(JsonSerializer.Deserialize>(filesJson) ?? new()); } + if (File.Exists(ApiConfiguration.DirectoriesInfoPath)) + { + string directoriesJson = await File.ReadAllTextAsync(ApiConfiguration.DirectoriesInfoPath); + apiManager.DirectoryWatcher.AddRange(JsonSerializer.Deserialize>(directoriesJson) ?? new()); + } + 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); + InputHandler inputHandler = new InputHandler(apiManager.HostsManager, apiManager.FilesManager, apiManager.DirectoryWatcher); if (args.Length > 0 && File.Exists(args[0])) { @@ -58,6 +69,8 @@ internal static class Program private static async Task Initialize() { + apiManager.DirectoryWatcher.Start(); + Console.WriteLine("Searching for a UPnP/NAT-PMP device..."); _ = await ApiManager.OpenPorts(); diff --git a/HyperbolicDownloaderApi/Commands/DirectoryCommands.cs b/HyperbolicDownloaderApi/Commands/DirectoryCommands.cs new file mode 100644 index 0000000..8531381 --- /dev/null +++ b/HyperbolicDownloaderApi/Commands/DirectoryCommands.cs @@ -0,0 +1,88 @@ +using HyperbolicDownloaderApi.FileProcessing; +using HyperbolicDownloaderApi.Managment; + +namespace HyperbolicDownloaderApi.Commands; + +public class DirectoryCommands +{ + private readonly DirectoryWatcher directoryWatcher; + + public DirectoryCommands(DirectoryWatcher directoryWatcher) + { + this.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 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 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--; + } + } +} \ No newline at end of file diff --git a/HyperbolicDownloaderApi/Commands/DownloadCommands.cs b/HyperbolicDownloaderApi/Commands/DownloadCommands.cs index 1cfbc4d..c5027c6 100644 --- a/HyperbolicDownloaderApi/Commands/DownloadCommands.cs +++ b/HyperbolicDownloaderApi/Commands/DownloadCommands.cs @@ -37,6 +37,7 @@ public class DownloadCommands if (!File.Exists(fullPath)) { ApiManager.SendNotificationMessageNewLine("Invalid file path!", NotificationMessageType.Error); + return; } string json = File.ReadAllText(fullPath); diff --git a/HyperbolicDownloaderApi/Commands/FileCommands.cs b/HyperbolicDownloaderApi/Commands/FileCommands.cs index ec22427..c6c8fc0 100644 --- a/HyperbolicDownloaderApi/Commands/FileCommands.cs +++ b/HyperbolicDownloaderApi/Commands/FileCommands.cs @@ -161,6 +161,8 @@ public class FileCommands return; } + ApiManager.SendNotificationMessageNewLine($"Requesting file list from {ipAddress}:{port}..."); + Task?> sendTask = NetworkClient.SendAsync>(ipAddress, port, "GetFilesList", searchString); _ = sendTask.Wait(1000); @@ -183,6 +185,8 @@ public class FileCommands return; } + index = 0; + foreach (HyperFileDto fileInfo in fileInfos) { index++; @@ -227,7 +231,7 @@ public class FileCommands string fileName = Path.GetFileName(localHyperFileInfo!.FilePath); string filePath = Path.Combine(directoryPath, $"{fileName}.hyper"); - PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(args); + PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash); NetworkSocket? localHost = ApiManager.GetLocalSocket(); if (localHost is null) @@ -280,7 +284,7 @@ public class FileCommands string fileName = Path.GetFileName(localHyperFileInfo!.FilePath); string filePath = Path.Combine(directoryPath, $"{fileName}.hyper"); - PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(args); + PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(localHyperFileInfo.Hash); NetworkSocket? localHost = ApiManager.GetLocalSocket(); if (localHost is null) @@ -303,7 +307,7 @@ public class FileCommands Console.CursorLeft = 0; - Task sendTask = NetworkClient.SendAsync(ipAddress!, host.Port, "HasFile", args); + Task sendTask = NetworkClient.SendAsync(ipAddress!, host.Port, "HasFile", localHyperFileInfo.Hash); _ = sendTask.Wait(1000); diff --git a/HyperbolicDownloaderApi/FileProcessing/DirectoryWatcher.cs b/HyperbolicDownloaderApi/FileProcessing/DirectoryWatcher.cs new file mode 100644 index 0000000..df58a91 --- /dev/null +++ b/HyperbolicDownloaderApi/FileProcessing/DirectoryWatcher.cs @@ -0,0 +1,100 @@ +using HyperbolicDownloaderApi.Managment; + +using System.Text.Json; +using System.Timers; + +namespace HyperbolicDownloaderApi.FileProcessing; + +public class DirectoryWatcher +{ + private readonly FilesManager filesManager; + + private readonly List directories = new(); + 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 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 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; + + foreach (string directory in directories) + { + ApiManager.SendNotificationMessageNewLine($"Checking directory: {directory}", NotificationMessageType.Log); + string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); + + foreach (string file in files) + { + _ = filesManager.TryAdd(file, out _, out _); + } + } + + ApiManager.SendNotificationMessageNewLine("Finished checking for new files.", NotificationMessageType.Log); + + filesManager.RemoveFilesThatDontExist(); + } +} \ No newline at end of file diff --git a/HyperbolicDownloaderApi/FileProcessing/FilesManager.cs b/HyperbolicDownloaderApi/FileProcessing/FilesManager.cs index 12d9f83..93d37f7 100644 --- a/HyperbolicDownloaderApi/FileProcessing/FilesManager.cs +++ b/HyperbolicDownloaderApi/FileProcessing/FilesManager.cs @@ -1,5 +1,6 @@ using HyperbolicDownloaderApi.Managment; +using System.Diagnostics; using System.Text.Json; namespace HyperbolicDownloaderApi.FileProcessing; @@ -31,10 +32,16 @@ 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); @@ -91,6 +98,20 @@ public class FilesManager return files.ToList(); } + internal void RemoveFilesThatDontExist() + { + List filesToRemove = files + .Where(f => !File.Exists(f.FilePath)) + .ToList(); + + foreach (PrivateHyperFileInfo fileToRemove in filesToRemove) + { + _ = files.Remove(fileToRemove); + } + + SaveFiles(); + } + private void SaveFiles() { File.WriteAllText(ApiConfiguration.FilesInfoPath, JsonSerializer.Serialize(files)); diff --git a/HyperbolicDownloaderApi/Managment/ApiConfiguration.cs b/HyperbolicDownloaderApi/Managment/ApiConfiguration.cs index 66a5cc6..d0e4e27 100644 --- a/HyperbolicDownloaderApi/Managment/ApiConfiguration.cs +++ b/HyperbolicDownloaderApi/Managment/ApiConfiguration.cs @@ -1,13 +1,12 @@ -using System.Reflection; - -namespace HyperbolicDownloaderApi.Managment; +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.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) ?? string.Empty; + public static string BasePath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "StoneRed", "HyperbolicDownloader"); public static string HostsFilePath { get; } = Path.Combine(BasePath, "Hosts.json"); public static string FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json"); + public static string DirectoriesInfoPath { get; } = Path.Combine(BasePath, "Directories.json"); } \ No newline at end of file diff --git a/HyperbolicDownloaderApi/Managment/ApiManager.cs b/HyperbolicDownloaderApi/Managment/ApiManager.cs index e12ad9c..448c90a 100644 --- a/HyperbolicDownloaderApi/Managment/ApiManager.cs +++ b/HyperbolicDownloaderApi/Managment/ApiManager.cs @@ -21,10 +21,12 @@ public class ApiManager 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()