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/),
|
||||
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
|
||||
### Added
|
||||
- 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" (click)="logout()"> Logout </a>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rdt-client",
|
||||
"version": "2.0.65",
|
||||
"version": "2.0.66",
|
||||
"description": "This is a web interface to manage your torrents on Real-Debrid.",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -185,25 +185,7 @@ public class DownloadData(DataContext dataContext)
|
|||
|
||||
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)
|
||||
{
|
||||
var dbDownload = await dataContext.Downloads
|
||||
|
|
@ -236,22 +218,6 @@ public class DownloadData(DataContext dataContext)
|
|||
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)
|
||||
{
|
||||
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.Bezzad => new BezzadDownloader(download.Link, filePath),
|
||||
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}")
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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<DownloadProgressEventArgs>? DownloadProgress;
|
||||
|
|
@ -11,83 +11,118 @@ public class SymlinkDownloader(String uri, String path) : IDownloader
|
|||
|
||||
private readonly ILogger _logger = Log.ForContext<SymlinkDownloader>();
|
||||
|
||||
private const Int32 MaxRetries = 10;
|
||||
|
||||
public async Task<String> Download()
|
||||
{
|
||||
_logger.Debug($"Starting symlink resolving of {uri}, writing to path: {path}");
|
||||
|
||||
var filePath = new DirectoryInfo(path);
|
||||
|
||||
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))
|
||||
try
|
||||
{
|
||||
throw new($"Cant handle compressed files with symlink downloader");
|
||||
}
|
||||
var filePath = new FileInfo(path);
|
||||
|
||||
DownloadProgress?.Invoke(this, new()
|
||||
{
|
||||
BytesDone = 0,
|
||||
BytesTotal = 0,
|
||||
Speed = 0
|
||||
});
|
||||
var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd(['\\', '/']);
|
||||
var fileName = filePath.Name;
|
||||
var fileExtension = filePath.Extension;
|
||||
var pathWithoutFileName = path.Replace(fileName, "").TrimEnd(['\\', '/']);
|
||||
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)
|
||||
{
|
||||
_logger.Debug($"Searching {Settings.Get.DownloadClient.RcloneMountPath} for {fileName} (attempt #{tries})...");
|
||||
DownloadProgress?.Invoke(this,
|
||||
new()
|
||||
{
|
||||
BytesDone = 0,
|
||||
BytesTotal = 0,
|
||||
Speed = 0
|
||||
});
|
||||
|
||||
var dirInfo = new DirectoryInfo(Settings.Get.DownloadClient.RcloneMountPath);
|
||||
file = dirInfo.EnumerateDirectories().FirstOrDefault(dir => folders.Contains(dir.Name))?.EnumerateFiles().FirstOrDefault(x => x.Name == fileName);
|
||||
var potentialFilePaths = new List<String>();
|
||||
|
||||
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)
|
||||
{
|
||||
await Task.Delay(1000 * tries);
|
||||
|
||||
tries++;
|
||||
throw new("Could not find file from rclone mount!");
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -64,12 +64,7 @@ public class Downloads(DownloadData downloadData)
|
|||
{
|
||||
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)
|
||||
{
|
||||
await downloadData.UpdateRetryCount(downloadId, retryCount);
|
||||
|
|
@ -79,12 +74,7 @@ public class Downloads(DownloadData downloadData)
|
|||
{
|
||||
await downloadData.UpdateRemoteId(downloadId, remoteId);
|
||||
}
|
||||
|
||||
public async Task UpdateRemoteIds(Dictionary<Guid, String> downloadIds)
|
||||
{
|
||||
await downloadData.UpdateRemoteIds(downloadIds);
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteForTorrent(Guid 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, UnpackClient> ActiveUnpackClients = new();
|
||||
|
||||
private readonly Dictionary<Guid, String> _aggregatedDownloadResults = [];
|
||||
private readonly Dictionary<Guid, String> _aggregatedDownloadErrors = [];
|
||||
|
||||
private readonly HttpClient _httpClient = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
|
|
@ -334,27 +331,24 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
|||
.OrderBy(m => m.DownloadQueued)
|
||||
.ToList();
|
||||
|
||||
_aggregatedDownloadResults.Clear();
|
||||
_aggregatedDownloadErrors.Clear();
|
||||
|
||||
Log($"Currently {queuedDownloads.Count} queued downloads and {ActiveDownloadClients.Count} total active downloads", torrent);
|
||||
|
||||
foreach (var download in queuedDownloads)
|
||||
{
|
||||
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);
|
||||
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveDownloadClients.ContainsKey(download.DownloadId))
|
||||
{
|
||||
Log($"Not starting download because this download is already active", download, torrent);
|
||||
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -376,7 +370,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
|||
download.Error = ex.Message;
|
||||
download.Completed = DateTimeOffset.UtcNow;
|
||||
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"Marking download as started", download, torrent);
|
||||
|
|
@ -413,32 +407,18 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
|||
|
||||
if (download.RemoteId != remoteId)
|
||||
{
|
||||
_aggregatedDownloadResults.Add(download.DownloadId, remoteId);
|
||||
await downloads.UpdateRemoteId(download.DownloadId, remoteId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError($"Unable to start download: {ex.Message}", download, torrent);
|
||||
_aggregatedDownloadErrors.Add(download.DownloadId, ex.Message);
|
||||
}
|
||||
|
||||
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.
|
||||
var queuedUnpacks = torrent.Downloads
|
||||
.Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
||||
<Version>2.0.65</Version>
|
||||
<Version>2.0.66</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
|
|
|||
Loading…
Reference in a new issue