using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Reflection; using Aria2NET; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using RdtClient.Data.Data; using RdtClient.Data.Models.Data; using RdtClient.Data.Models.Internal; using RdtClient.Service.Helpers; using RdtClient.Service.Services; using RdtClient.Service.Services.Downloaders; namespace RdtClient.Web.Controllers; /// /// Controller for managing application settings and performing system tests /// [Authorize(Policy = "AuthSetting")] [Route("Api/Settings")] public class SettingsController(Settings settings, Torrents torrents) : Controller { /// /// Retrieves all application settings /// /// A collection of all configured settings /// Returns the list of settings [HttpGet] [Route("")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public ActionResult> Get() { var result = SettingData.GetAll(); return Ok(result); } /// /// Updates multiple application settings /// /// List of setting properties to update /// Success status /// Settings were successfully updated /// Invalid settings data provided [HttpPut] [Route("")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task Update([FromBody] IList? settings1) { if (settings1 == null) { return BadRequest(); } await settings.Update(settings1); return Ok(); } /// /// Retrieves the profile information from the currently configured debrid service /// /// The profile information /// The profile information [HttpGet] [Route("Profile")] [ProducesResponseType(typeof(Profile), StatusCodes.Status200OK)] public async Task> Profile() { var profile = await torrents.GetProfile(); return Ok(profile); } [HttpGet] [Route("Version")] public ActionResult Version() { var version = Assembly.GetExecutingAssembly().GetName().Version!; return Ok(new { Version = version }); } /// /// Tests if a specified path is writable by attempting to create and delete a test file /// /// /// Creates a test file in the specified directory to verify write permissions. /// The test file is automatically deleted after the test completes. /// /// The path testing request containing the directory to test /// Success status if the path is writable /// The path is valid and writable /// Invalid or empty path provided /// Path does not exist or is not accessible [HttpPost] [Route("TestPath")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(String), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(String), StatusCodes.Status500InternalServerError)] public async Task TestPath([FromBody] SettingsControllerTestPathRequest? request) { if (request == null) { return BadRequest(); } if (String.IsNullOrEmpty(request.Path)) { return BadRequest("Invalid path"); } var path = request.Path.TrimEnd('/').TrimEnd('\\'); if (!Directory.Exists(path)) { throw new($"Path {path} does not exist"); } var testFile = $"{path}/test.txt"; await System.IO.File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file."); await FileHelper.Delete(testFile); return Ok(); } /// /// Tests download speed by downloading a sample file and measuring throughput /// /// Token to cancel the operation /// The measured download speed in bytes per second /// Returns the measured download speed /// /// The test downloads a file up to 50MB and measures the download speed. /// [HttpGet] [Route("TestDownloadSpeed")] [ProducesResponseType(typeof(Int64), StatusCodes.Status200OK)] public async Task TestDownloadSpeed(CancellationToken cancellationToken) { var downloadPath = Settings.Get.DownloadClient.DownloadPath; var testFilePath = Path.Combine(downloadPath, "testDefault.rar"); await FileHelper.Delete(testFilePath); var download = new Download { Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar", Torrent = new() { DownloadClient = Settings.Get.DownloadClient.Client == Data.Enums.DownloadClient.Symlink ? Data.Enums.DownloadClient.Internal : Settings.Get.DownloadClient.Client, RdName = "testDefault.rar" } }; var downloadClient = new DownloadClient(download, download.Torrent, downloadPath, null); await downloadClient.Start(); var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; while (!downloadClient.Finished) { await Task.Delay(1000, CancellationToken.None); if (cancellationToken.IsCancellationRequested) { await downloadClient.Cancel(); } if (downloadClient.Downloader is Aria2cDownloader aria2Downloader) { var aria2NetClient = new Aria2NetClient(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 1); var allDownloads = await aria2NetClient.TellAllAsync(cancellationToken); await aria2Downloader.Update(allDownloads); } if (downloadClient.BytesDone > 1024 * 1024 * 50) { await downloadClient.Cancel(); break; } } await FileHelper.Delete(testFilePath); // ReSharper disable once SuggestVarOrType_BuiltInTypes return Ok(downloadClient.Speed); } /// /// Tests write speed to the configured download directory /// /// The measured write speed in bytes per second /// Returns the measured write speed /// /// Creates a 64MB test file with random data to measure disk write performance. /// The test file is automatically deleted after the test completes. /// [HttpGet] [Route("TestWriteSpeed")] [ProducesResponseType(typeof(Double), StatusCodes.Status200OK)] public async Task TestWriteSpeed() { var downloadPath = Settings.Get.DownloadClient.DownloadPath; var testFilePath = Path.Combine(downloadPath, "test.tmp"); await FileHelper.Delete(testFilePath); const Int32 testFileSize = 64 * 1024 * 1024; var watch = new Stopwatch(); watch.Start(); var rnd = new Random(); await using var fileStream = new FileStream(testFilePath, FileMode.Create, FileAccess.Write, FileShare.Write); var buffer = new Byte[64 * 1024]; while (fileStream.Length < testFileSize) { rnd.NextBytes(buffer); await fileStream.WriteAsync(buffer.AsMemory(0, buffer.Length)); } watch.Stop(); var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds; fileStream.Close(); await FileHelper.Delete(testFilePath); return Ok(writeSpeed); } /// /// Tests the connection to an Aria2c instance /// /// /// Attempts to connect to an Aria2c RPC endpoint and retrieve its version information. /// This verifies both connectivity and authentication with the Aria2c server. /// /// The connection details for the Aria2c instance /// The version information of the Aria2c server if connection is successful /// Returns the Aria2c version information /// Invalid or missing connection details /// Connection to Aria2c failed [HttpPost] [Route("TestAria2cConnection")] [ProducesResponseType(typeof(String), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> TestAria2cConnection([FromBody] SettingsControllerTestAria2cConnectionRequest? request) { if (request == null) { return BadRequest(); } if (String.IsNullOrEmpty(request.Url)) { return BadRequest("Invalid Url"); } var client = new Aria2NetClient(request.Url, request.Secret); var version = await client.GetVersionAsync(); return Ok(version); } } /// /// Request model for testing path accessibility /// public class SettingsControllerTestPathRequest { /// /// The directory path to test for write access /// /// /path/to/downloads [Required] public String? Path { get; set; } } /// /// Request model for testing Aria2c connection /// public class SettingsControllerTestAria2cConnectionRequest { /// /// The URL of the Aria2c RPC endpoint /// /// http://localhost:6800/jsonrpc [Required] public String? Url { get; set; } /// /// The secret token for authenticating with the Aria2c server /// /// your-secret-token [Required] public String? Secret { get; set; } }