This commit is contained in:
parent
949bd7cfd2
commit
367e195ed6
9 changed files with 112 additions and 137 deletions
|
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.0.66] - 2024-04-08
|
||||||
|
### Changed
|
||||||
|
- Symlink fixes and improvements.
|
||||||
|
|
||||||
## [2.0.65] - 2024-04-07
|
## [2.0.65] - 2024-04-07
|
||||||
### Added
|
### Added
|
||||||
- Added option to configure the buffersize for the internal downloader.
|
- Added option to configure the buffersize for the internal downloader.
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
<a class="navbar-item" routerLink="profile"> Profile </a>
|
<a class="navbar-item" routerLink="profile"> Profile </a>
|
||||||
<a class="navbar-item" (click)="logout()"> Logout </a>
|
<a class="navbar-item" (click)="logout()"> Logout </a>
|
||||||
<hr class="navbar-divider" />
|
<hr class="navbar-divider" />
|
||||||
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.65</a>
|
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.66</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "rdt-client",
|
"name": "rdt-client",
|
||||||
"version": "2.0.65",
|
"version": "2.0.66",
|
||||||
"description": "This is a web interface to manage your torrents on Real-Debrid.",
|
"description": "This is a web interface to manage your torrents on Real-Debrid.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -186,24 +186,6 @@ public class DownloadData(DataContext dataContext)
|
||||||
await TorrentData.VoidCache();
|
await TorrentData.VoidCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateErrors(Dictionary<Guid, String> downloadIds)
|
|
||||||
{
|
|
||||||
foreach (var entry in downloadIds)
|
|
||||||
{
|
|
||||||
var dbDownload = await dataContext.Downloads
|
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == entry.Key);
|
|
||||||
|
|
||||||
if (dbDownload == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
dbDownload.Error = entry.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
await dataContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
|
|
@ -236,22 +218,6 @@ public class DownloadData(DataContext dataContext)
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateRemoteIds(Dictionary<Guid, String> remoteIds)
|
|
||||||
{
|
|
||||||
foreach (var entry in remoteIds)
|
|
||||||
{
|
|
||||||
var dbDownload = await dataContext.Downloads.FirstOrDefaultAsync(m => m.DownloadId == entry.Key);
|
|
||||||
if (dbDownload == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
dbDownload.RemoteId = entry.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
await dataContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteForTorrent(Guid torrentId)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
var downloads = await dataContext.Downloads
|
var downloads = await dataContext.Downloads
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
|
||||||
Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath),
|
Data.Enums.DownloadClient.Internal => new InternalDownloader(download.Link, filePath),
|
||||||
Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath),
|
Data.Enums.DownloadClient.Bezzad => new BezzadDownloader(download.Link, filePath),
|
||||||
Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath, downloadPath),
|
Data.Enums.DownloadClient.Aria2c => new Aria2cDownloader(download.RemoteId, download.Link, filePath, downloadPath),
|
||||||
Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath),
|
Data.Enums.DownloadClient.Symlink => new SymlinkDownloader(download.Link, filePath, downloadPath),
|
||||||
_ => throw new($"Unknown download client {Settings.Get.DownloadClient}")
|
_ => throw new($"Unknown download client {Settings.Get.DownloadClient}")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.Downloaders;
|
namespace RdtClient.Service.Services.Downloaders;
|
||||||
|
|
||||||
public class SymlinkDownloader(String uri, String path) : IDownloader
|
public class SymlinkDownloader(String uri, String destinationPath, String path) : IDownloader
|
||||||
{
|
{
|
||||||
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
||||||
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
||||||
|
|
@ -11,83 +11,118 @@ public class SymlinkDownloader(String uri, String path) : IDownloader
|
||||||
|
|
||||||
private readonly ILogger _logger = Log.ForContext<SymlinkDownloader>();
|
private readonly ILogger _logger = Log.ForContext<SymlinkDownloader>();
|
||||||
|
|
||||||
|
private const Int32 MaxRetries = 10;
|
||||||
|
|
||||||
public async Task<String> Download()
|
public async Task<String> Download()
|
||||||
{
|
{
|
||||||
_logger.Debug($"Starting symlink resolving of {uri}, writing to path: {path}");
|
_logger.Debug($"Starting symlink resolving of {uri}, writing to path: {path}");
|
||||||
|
|
||||||
var filePath = new DirectoryInfo(path);
|
try
|
||||||
|
|
||||||
var fileName = filePath.Name;
|
|
||||||
var fileExtension = filePath.Extension;
|
|
||||||
var directoryName = Path.GetDirectoryName(filePath.FullName) ?? throw new($"Cannot get directory name for file {filePath.FullName}");
|
|
||||||
var fileDirectory = Path.GetFileName(directoryName) ?? throw new($"Cannot get directory name for file {directoryName}");
|
|
||||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName) ?? throw new($"Cannot get directory name for file {fileName}");
|
|
||||||
var fileDirectoryWithoutExtension = Path.GetFileNameWithoutExtension(fileDirectory) ?? throw new($"Cannot get directory name for file {fileDirectory}");
|
|
||||||
|
|
||||||
String[] folders =
|
|
||||||
[
|
|
||||||
fileNameWithoutExtension,
|
|
||||||
fileDirectoryWithoutExtension,
|
|
||||||
fileName,
|
|
||||||
fileDirectory
|
|
||||||
];
|
|
||||||
|
|
||||||
List<String> unWantedExtensions =
|
|
||||||
[
|
|
||||||
"zip",
|
|
||||||
"rar",
|
|
||||||
"tar"
|
|
||||||
];
|
|
||||||
|
|
||||||
if (unWantedExtensions.Any(m => fileExtension == m))
|
|
||||||
{
|
{
|
||||||
throw new($"Cant handle compressed files with symlink downloader");
|
var filePath = new FileInfo(path);
|
||||||
}
|
|
||||||
|
|
||||||
DownloadProgress?.Invoke(this, new()
|
var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd(['\\', '/']);
|
||||||
{
|
var fileName = filePath.Name;
|
||||||
BytesDone = 0,
|
var fileExtension = filePath.Extension;
|
||||||
BytesTotal = 0,
|
var pathWithoutFileName = path.Replace(fileName, "").TrimEnd(['\\', '/']);
|
||||||
Speed = 0
|
var searchPath = Path.Combine(rcloneMountPath, pathWithoutFileName);
|
||||||
});
|
|
||||||
|
|
||||||
FileInfo? file = null;
|
List<String> unWantedExtensions =
|
||||||
|
[
|
||||||
|
"zip",
|
||||||
|
"rar",
|
||||||
|
"tar"
|
||||||
|
];
|
||||||
|
|
||||||
var tries = 1;
|
if (unWantedExtensions.Any(m => fileExtension == m))
|
||||||
|
{
|
||||||
|
throw new($"Cant handle compressed files with symlink downloader");
|
||||||
|
}
|
||||||
|
|
||||||
while (file == null && tries <= 10)
|
DownloadProgress?.Invoke(this,
|
||||||
{
|
new()
|
||||||
_logger.Debug($"Searching {Settings.Get.DownloadClient.RcloneMountPath} for {fileName} (attempt #{tries})...");
|
{
|
||||||
|
BytesDone = 0,
|
||||||
|
BytesTotal = 0,
|
||||||
|
Speed = 0
|
||||||
|
});
|
||||||
|
|
||||||
var dirInfo = new DirectoryInfo(Settings.Get.DownloadClient.RcloneMountPath);
|
var potentialFilePaths = new List<String>();
|
||||||
file = dirInfo.EnumerateDirectories().FirstOrDefault(dir => folders.Contains(dir.Name))?.EnumerateFiles().FirstOrDefault(x => x.Name == fileName);
|
|
||||||
|
var directoryInfo = new DirectoryInfo(searchPath);
|
||||||
|
while (directoryInfo.Parent != null)
|
||||||
|
{
|
||||||
|
potentialFilePaths.Add(directoryInfo.FullName + @"\");
|
||||||
|
directoryInfo = directoryInfo.Parent;
|
||||||
|
|
||||||
|
if (directoryInfo.FullName == rcloneMountPath)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfo? file = null;
|
||||||
|
|
||||||
|
for (var retryCount = 0; retryCount < MaxRetries; retryCount++)
|
||||||
|
{
|
||||||
|
DownloadProgress?.Invoke(this,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
BytesDone = retryCount,
|
||||||
|
BytesTotal = 10,
|
||||||
|
Speed = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.Debug($"Searching {Settings.Get.DownloadClient.RcloneMountPath} for {fileName} (attempt #{retryCount})...");
|
||||||
|
|
||||||
|
foreach (var potentialFilePath in potentialFilePaths)
|
||||||
|
{
|
||||||
|
var potentialFilePathWithFileName = Path.Combine(potentialFilePath, fileName);
|
||||||
|
|
||||||
|
if (File.Exists(potentialFilePathWithFileName))
|
||||||
|
{
|
||||||
|
file = new(potentialFilePathWithFileName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file == null)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000 * retryCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (file == null)
|
if (file == null)
|
||||||
{
|
{
|
||||||
await Task.Delay(1000 * tries);
|
throw new("Could not find file from rclone mount!");
|
||||||
|
|
||||||
tries++;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (file == null)
|
_logger.Debug($"Found {file.FullName} at {file.FullName}");
|
||||||
|
|
||||||
|
var result = TryCreateSymbolicLink(file.FullName, destinationPath);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
throw new("Could not find file from rclone mount!");
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadComplete?.Invoke(this, new());
|
||||||
|
|
||||||
|
return file.FullName;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new("Could not find file from rclone mount!");
|
DownloadComplete?.Invoke(this, new()
|
||||||
|
{
|
||||||
|
Error = ex.Message
|
||||||
|
});
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug($"Found {file.FullName} after #{tries} attempts");
|
|
||||||
|
|
||||||
var result = TryCreateSymbolicLink(file.FullName, filePath.FullName);
|
|
||||||
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
throw new("Could not find file from rclone mount!");
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadComplete?.Invoke(this, new());
|
|
||||||
|
|
||||||
return file.FullName;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Cancel()
|
public Task Cancel()
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,6 @@ public class Downloads(DownloadData downloadData)
|
||||||
await downloadData.UpdateError(downloadId, error);
|
await downloadData.UpdateError(downloadId, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateErrors(Dictionary<Guid, String> downloadIds)
|
|
||||||
{
|
|
||||||
await downloadData.UpdateErrors(downloadIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
||||||
{
|
{
|
||||||
await downloadData.UpdateRetryCount(downloadId, retryCount);
|
await downloadData.UpdateRetryCount(downloadId, retryCount);
|
||||||
|
|
@ -80,11 +75,6 @@ public class Downloads(DownloadData downloadData)
|
||||||
await downloadData.UpdateRemoteId(downloadId, remoteId);
|
await downloadData.UpdateRemoteId(downloadId, remoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateRemoteIds(Dictionary<Guid, String> downloadIds)
|
|
||||||
{
|
|
||||||
await downloadData.UpdateRemoteIds(downloadIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteForTorrent(Guid torrentId)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
await downloadData.DeleteForTorrent(torrentId);
|
await downloadData.DeleteForTorrent(torrentId);
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
|
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
|
||||||
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
|
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
|
||||||
|
|
||||||
private readonly Dictionary<Guid, String> _aggregatedDownloadResults = [];
|
|
||||||
private readonly Dictionary<Guid, String> _aggregatedDownloadErrors = [];
|
|
||||||
|
|
||||||
private readonly HttpClient _httpClient = new()
|
private readonly HttpClient _httpClient = new()
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(10)
|
Timeout = TimeSpan.FromSeconds(10)
|
||||||
|
|
@ -334,27 +331,24 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
.OrderBy(m => m.DownloadQueued)
|
.OrderBy(m => m.DownloadQueued)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
_aggregatedDownloadResults.Clear();
|
|
||||||
_aggregatedDownloadErrors.Clear();
|
|
||||||
|
|
||||||
Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent);
|
Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent);
|
||||||
|
|
||||||
foreach (var download in queuedDownloads)
|
foreach (var download in queuedDownloads)
|
||||||
{
|
{
|
||||||
Log($"Processing to download", download, torrent);
|
Log($"Processing to download", download, torrent);
|
||||||
|
|
||||||
if (ActiveDownloadClients.Count >= settingDownloadLimit)
|
if (ActiveDownloadClients.Count >= settingDownloadLimit && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink)
|
||||||
{
|
{
|
||||||
Log($"Not starting download because there are already the max number of downloads active", download, torrent);
|
Log($"Not starting download because there are already the max number of downloads active", download, torrent);
|
||||||
|
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ActiveDownloadClients.ContainsKey(download.DownloadId))
|
if (ActiveDownloadClients.ContainsKey(download.DownloadId))
|
||||||
{
|
{
|
||||||
Log($"Not starting download because this download is already active", download, torrent);
|
Log($"Not starting download because this download is already active", download, torrent);
|
||||||
|
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -376,7 +370,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
download.Error = ex.Message;
|
download.Error = ex.Message;
|
||||||
download.Completed = DateTimeOffset.UtcNow;
|
download.Completed = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log($"Marking download as started", download, torrent);
|
Log($"Marking download as started", download, torrent);
|
||||||
|
|
@ -413,32 +407,18 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
|
|
||||||
if (download.RemoteId != remoteId)
|
if (download.RemoteId != remoteId)
|
||||||
{
|
{
|
||||||
_aggregatedDownloadResults.Add(download.DownloadId, remoteId);
|
await downloads.UpdateRemoteId(download.DownloadId, remoteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogError($"Unable to start download: {ex.Message}", download, torrent);
|
LogError($"Unable to start download: {ex.Message}", download, torrent);
|
||||||
_aggregatedDownloadErrors.Add(download.DownloadId, ex.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log($"Started download", download, torrent);
|
Log($"Started download", download, torrent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_aggregatedDownloadResults.Count > 0)
|
|
||||||
{
|
|
||||||
await downloads.UpdateRemoteIds(_aggregatedDownloadResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_aggregatedDownloadErrors.Count > 0)
|
|
||||||
{
|
|
||||||
await downloads.UpdateErrors(_aggregatedDownloadErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
_aggregatedDownloadResults.Clear();
|
|
||||||
_aggregatedDownloadErrors.Clear();
|
|
||||||
|
|
||||||
// Check if there are any unpacks that are queued and can be started.
|
// Check if there are any unpacks that are queued and can be started.
|
||||||
var queuedUnpacks = torrent.Downloads
|
var queuedUnpacks = torrent.Downloads
|
||||||
.Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null)
|
.Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
||||||
<Version>2.0.65</Version>
|
<Version>2.0.66</Version>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue