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; }
}