renamed vars; added root dir; fixed root dir file counts; fixed qbit flow
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using Common.Exceptions;
|
using Common.Exceptions;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Common.Configuration.DownloadCleaner;
|
namespace Common.Configuration.DownloadCleaner;
|
||||||
@@ -17,14 +17,14 @@ public sealed record DownloadCleanerConfig : IJobConfig, IIgnoredDownloadsConfig
|
|||||||
[ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")]
|
[ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")]
|
||||||
public string? IgnoredDownloadsPath { get; init; }
|
public string? IgnoredDownloadsPath { get; init; }
|
||||||
|
|
||||||
[ConfigurationKeyName("NO_HL_CATEGORY")]
|
[ConfigurationKeyName("UNLINKED_TARGET_CATEGORY")]
|
||||||
public string NoHardLinksCategory { get; init; } = "";
|
public string UnlinkedTargetCategory { get; init; } = "cleanuperr-unlinked";
|
||||||
|
|
||||||
[ConfigurationKeyName("NO_HL_IGNORE_ROOT_DIR")]
|
[ConfigurationKeyName("UNLINKED_IGNORED_ROOT_DIR")]
|
||||||
public bool NoHardLinksIgnoreRootDir { get; init; }
|
public string UnlinkedIgnoredRootDir { get; init; } = string.Empty;
|
||||||
|
|
||||||
[ConfigurationKeyName("NO_HL_CATEGORIES")]
|
[ConfigurationKeyName("UNLINKED_CATEGORIES")]
|
||||||
public List<string>? NoHardLinksCategories { get; init; }
|
public List<string>? UnlinkedCategories { get; init; }
|
||||||
|
|
||||||
public void Validate()
|
public void Validate()
|
||||||
{
|
{
|
||||||
@@ -45,24 +45,29 @@ public sealed record DownloadCleanerConfig : IJobConfig, IIgnoredDownloadsConfig
|
|||||||
|
|
||||||
Categories?.ForEach(x => x.Validate());
|
Categories?.ForEach(x => x.Validate());
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(NoHardLinksCategory))
|
if (string.IsNullOrEmpty(UnlinkedTargetCategory))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NoHardLinksCategories?.Count is null or 0)
|
if (UnlinkedCategories?.Count is null or 0)
|
||||||
{
|
{
|
||||||
throw new ValidationException("no categories configured");
|
throw new ValidationException("no unlinked categories configured");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NoHardLinksCategories.Contains(NoHardLinksCategory))
|
if (UnlinkedCategories.Contains(UnlinkedTargetCategory))
|
||||||
{
|
{
|
||||||
throw new ValidationException("NO_HARDLINKS_CATEGORY is present in NO_HARDLINKS_CATEGORIES");
|
throw new ValidationException($"{SectionName.ToUpperInvariant()}__UNLINKED_TARGET_CATEGORY should not be present in {SectionName.ToUpperInvariant()}__UNLINKED_CATEGORIES");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NoHardLinksCategories.Any(string.IsNullOrEmpty))
|
if (UnlinkedCategories.Any(string.IsNullOrEmpty))
|
||||||
{
|
{
|
||||||
throw new ValidationException("empty hardlink filter category found");
|
throw new ValidationException("empty unlinked category filter found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(UnlinkedIgnoredRootDir) && !Directory.Exists(UnlinkedIgnoredRootDir))
|
||||||
|
{
|
||||||
|
throw new ValidationException($"{UnlinkedIgnoredRootDir} root directory does not exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,16 +44,16 @@
|
|||||||
"Name": "tv-sonarr",
|
"Name": "tv-sonarr",
|
||||||
"MAX_RATIO": -1,
|
"MAX_RATIO": -1,
|
||||||
"MIN_SEED_TIME": 0,
|
"MIN_SEED_TIME": 0,
|
||||||
"MAX_SEED_TIME": -1
|
"MAX_SEED_TIME": 240
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"IGNORED_DOWNLOADS_PATH": "../test/data/cleanuperr/ignored_downloads",
|
"UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked",
|
||||||
"NO_HL_CATEGORY": "nohardlinks",
|
"UNLINKED_IGNORED_ROOT_DIR": "../test/data/qbit-win",
|
||||||
"NO_HL_IGNORE_ROOT_DIR": false,
|
"UNLINKED_CATEGORIES": [
|
||||||
"NO_HL_CATEGORIES": [
|
|
||||||
"tv-sonarr",
|
"tv-sonarr",
|
||||||
"radarr"
|
"radarr"
|
||||||
]
|
],
|
||||||
|
"IGNORED_DOWNLOADS_PATH": "../test/data/cleanuperr/ignored_downloads"
|
||||||
},
|
},
|
||||||
"DOWNLOAD_CLIENT": "qbittorrent",
|
"DOWNLOAD_CLIENT": "qbittorrent",
|
||||||
"qBittorrent": {
|
"qBittorrent": {
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
"Enabled": false,
|
"Enabled": false,
|
||||||
"DELETE_PRIVATE": false,
|
"DELETE_PRIVATE": false,
|
||||||
"CATEGORIES": [],
|
"CATEGORIES": [],
|
||||||
|
"UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked",
|
||||||
|
"UNLINKED_IGNORED_ROOT_DIR": "",
|
||||||
|
"UNLINKED_CATEGORIES": [],
|
||||||
"IGNORED_DOWNLOADS_PATH": ""
|
"IGNORED_DOWNLOADS_PATH": ""
|
||||||
},
|
},
|
||||||
"DOWNLOAD_CLIENT": "none",
|
"DOWNLOAD_CLIENT": "none",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class TestDownloadService : DownloadService
|
|||||||
public override Task<List<object>?> GetSeedingDownloads() => Task.FromResult<List<object>?>(null);
|
public override Task<List<object>?> GetSeedingDownloads() => Task.FromResult<List<object>?>(null);
|
||||||
public override List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories) => null;
|
public override List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories) => null;
|
||||||
public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories) => null;
|
public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories) => null;
|
||||||
public override Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) => Task.CompletedTask;
|
public override Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads) => Task.CompletedTask;
|
||||||
public override Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads) => Task.CompletedTask;
|
public override Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads) => Task.CompletedTask;
|
||||||
// Expose protected methods for testing
|
// Expose protected methods for testing
|
||||||
public new void ResetStrikesOnProgress(string hash, long downloaded) => base.ResetStrikesOnProgress(hash, downloaded);
|
public new void ResetStrikesOnProgress(string hash, long downloaded) => base.ResetStrikesOnProgress(hash, downloaded);
|
||||||
|
|||||||
@@ -70,17 +70,17 @@ public sealed class DownloadCleaner : GenericHandler
|
|||||||
List<object>? downloads = await _downloadService.GetSeedingDownloads();
|
List<object>? downloads = await _downloadService.GetSeedingDownloads();
|
||||||
List<object>? downloadsToChangeCategory = null;
|
List<object>? downloadsToChangeCategory = null;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_config.NoHardLinksCategory) && _config.NoHardLinksCategories?.Count > 0)
|
if (!string.IsNullOrEmpty(_config.UnlinkedTargetCategory) && _config.UnlinkedCategories?.Count > 0)
|
||||||
{
|
{
|
||||||
if (!_hardLinkCategoryCreated)
|
if (!_hardLinkCategoryCreated)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("creating category {cat}", _config.NoHardLinksCategory);
|
_logger.LogDebug("creating category {cat}", _config.UnlinkedTargetCategory);
|
||||||
|
|
||||||
await _downloadService.CreateCategoryAsync(_config.NoHardLinksCategory);
|
await _downloadService.CreateCategoryAsync(_config.UnlinkedTargetCategory);
|
||||||
_hardLinkCategoryCreated = true;
|
_hardLinkCategoryCreated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadsToChangeCategory = _downloadService.FilterDownloadsToChangeCategoryAsync(downloads, _config.NoHardLinksCategories);
|
downloadsToChangeCategory = _downloadService.FilterDownloadsToChangeCategoryAsync(downloads, _config.UnlinkedCategories);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for the downloads to appear in the arr queue
|
// wait for the downloads to appear in the arr queue
|
||||||
@@ -91,7 +91,7 @@ public sealed class DownloadCleaner : GenericHandler
|
|||||||
await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr, true);
|
await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr, true);
|
||||||
|
|
||||||
_logger.LogTrace("looking for downloads to change category");
|
_logger.LogTrace("looking for downloads to change category");
|
||||||
await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes);
|
await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes, ignoredDownloads);
|
||||||
|
|
||||||
List<object>? downloadsToClean = _downloadService.FilterDownloadsToBeCleanedAsync(downloads, _config.Categories);
|
List<object>? downloadsToClean = _downloadService.FilterDownloadsToBeCleanedAsync(downloads, _config.Categories);
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public sealed class DownloadCleaner : GenericHandler
|
|||||||
downloads = null;
|
downloads = null;
|
||||||
|
|
||||||
_logger.LogTrace("looking for downloads to clean");
|
_logger.LogTrace("looking for downloads to clean");
|
||||||
await _downloadService.CleanDownloadsAsync(downloadsToClean, _config.Categories, _excludedHashes);
|
await _downloadService.CleanDownloadsAsync(downloadsToClean, _config.Categories, _excludedHashes, ignoredDownloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
|
||||||
|
|||||||
@@ -193,4 +193,10 @@ public sealed class DelugeClient
|
|||||||
|
|
||||||
return webResponse.Result;
|
return webResponse.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetTorrentLabel(string hash, string newLabel)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -301,110 +301,112 @@ public class DelugeService : DownloadService, IDelugeService
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes)
|
public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads)
|
||||||
{
|
{
|
||||||
if (downloads?.Count is null or 0)
|
if (downloads?.Count is null or 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir)
|
throw new NotImplementedException();
|
||||||
{
|
|
||||||
downloads
|
|
||||||
.Cast<TorrentStatus>()
|
|
||||||
.Select(x =>
|
|
||||||
{
|
|
||||||
string? firstDir = GetRootWithFirstDirectory(x.DownloadPath);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(firstDir))
|
// if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir)
|
||||||
{
|
// {
|
||||||
return string.Empty;
|
// downloads
|
||||||
}
|
// .Cast<TorrentStatus>()
|
||||||
|
// .Select(x =>
|
||||||
if (firstDir == Path.GetPathRoot(x.DownloadPath))
|
// {
|
||||||
{
|
// string? firstDir = GetRootWithFirstDirectory(x.DownloadPath);
|
||||||
return string.Empty;
|
//
|
||||||
}
|
// if (string.IsNullOrEmpty(firstDir))
|
||||||
|
// {
|
||||||
return firstDir;
|
// return string.Empty;
|
||||||
})
|
// }
|
||||||
.Where(x => !string.IsNullOrEmpty(x))
|
//
|
||||||
.Distinct()
|
// if (firstDir == Path.GetPathRoot(x.DownloadPath))
|
||||||
.ToList()
|
// {
|
||||||
.ForEach(x =>
|
// return string.Empty;
|
||||||
{
|
// }
|
||||||
_logger.LogTrace("populating file counts from {dir}", x);
|
//
|
||||||
|
// return firstDir;
|
||||||
if (!Directory.Exists(x))
|
// })
|
||||||
{
|
// .Where(x => !string.IsNullOrEmpty(x))
|
||||||
throw new ValidationException($"directory \"{x}\" does not exist");
|
// .Distinct()
|
||||||
}
|
// .ToList()
|
||||||
|
// .ForEach(x =>
|
||||||
_hardLinkFileService.PopulateFileCounts(x);
|
// {
|
||||||
});
|
// _logger.LogTrace("populating file counts from {dir}", x);
|
||||||
}
|
//
|
||||||
|
// if (!Directory.Exists(x))
|
||||||
foreach (TorrentStatus download in downloads.Cast<TorrentStatus>())
|
// {
|
||||||
{
|
// throw new ValidationException($"directory \"{x}\" does not exist");
|
||||||
if (string.IsNullOrEmpty(download.Hash))
|
// }
|
||||||
{
|
//
|
||||||
_logger.LogDebug("skip | download hash is null for {name}", download.Name);
|
// _hardLinkFileService.PopulateFileCounts(x);
|
||||||
continue;
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase)))
|
// foreach (TorrentStatus download in downloads.Cast<TorrentStatus>())
|
||||||
{
|
// {
|
||||||
_logger.LogDebug("skip | download is used by an arr | {name}", download.Name);
|
// if (string.IsNullOrEmpty(download.Hash))
|
||||||
continue;
|
// {
|
||||||
}
|
// _logger.LogDebug("skip | download hash is null for {name}", download.Name);
|
||||||
|
// continue;
|
||||||
ContextProvider.Set("downloadName", download.Name);
|
// }
|
||||||
ContextProvider.Set("hash", download.Hash);
|
//
|
||||||
|
// if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
DelugeContents? contents = null;
|
// {
|
||||||
try
|
// _logger.LogDebug("skip | download is used by an arr | {name}", download.Name);
|
||||||
{
|
// continue;
|
||||||
contents = await _client.GetTorrentFiles(download.Hash);
|
// }
|
||||||
}
|
//
|
||||||
catch (Exception exception)
|
// ContextProvider.Set("downloadName", download.Name);
|
||||||
{
|
// ContextProvider.Set("hash", download.Hash);
|
||||||
_logger.LogDebug(exception, "failed to find torrent files for {name}", download.Name);
|
//
|
||||||
continue;
|
// DelugeContents? contents = null;
|
||||||
}
|
// try
|
||||||
|
// {
|
||||||
bool hasHardlinks = false;
|
// contents = await _client.GetTorrentFiles(download.Hash);
|
||||||
|
// }
|
||||||
ProcessFiles(contents?.Contents, (name, file) =>
|
// catch (Exception exception)
|
||||||
{
|
// {
|
||||||
string filePath = Path.Combine(download.DownloadPath, file.Path);
|
// _logger.LogDebug(exception, "failed to find torrent files for {name}", download.Name);
|
||||||
|
// continue;
|
||||||
long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir);
|
// }
|
||||||
|
//
|
||||||
if (hardlinkCount < 0)
|
// bool hasHardlinks = false;
|
||||||
{
|
//
|
||||||
_logger.LogDebug("skip | could not get file properties | {name}", download.Name);
|
// ProcessFiles(contents?.Contents, (name, file) =>
|
||||||
hasHardlinks = true;
|
// {
|
||||||
return;
|
// string filePath = Path.Combine(download.DownloadPath, file.Path);
|
||||||
}
|
//
|
||||||
|
// long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir);
|
||||||
if (hardlinkCount > 0)
|
//
|
||||||
{
|
// if (hardlinkCount < 0)
|
||||||
hasHardlinks = true;
|
// {
|
||||||
}
|
// _logger.LogDebug("skip | could not get file properties | {name}", download.Name);
|
||||||
});
|
// hasHardlinks = true;
|
||||||
|
// return;
|
||||||
if (hasHardlinks)
|
// }
|
||||||
{
|
//
|
||||||
_logger.LogDebug("skip | download has hardlinks | {name}", download.Name);
|
// if (hardlinkCount > 0)
|
||||||
continue;
|
// {
|
||||||
}
|
// hasHardlinks = true;
|
||||||
|
// }
|
||||||
await _dryRunInterceptor.InterceptAsync(ChangeLabel, download.Hash, _downloadCleanerConfig.NoHardLinksCategory);
|
// });
|
||||||
|
//
|
||||||
_logger.LogInformation("category changed for {name}", download.Name);
|
// if (hasHardlinks)
|
||||||
|
// {
|
||||||
await _notifier.NotifyCategoryChanged(download.Label, _downloadCleanerConfig.NoHardLinksCategory);
|
// _logger.LogDebug("skip | download has hardlinks | {name}", download.Name);
|
||||||
}
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// await _dryRunInterceptor.InterceptAsync(ChangeLabel, download.Hash, _downloadCleanerConfig.NoHardLinksCategory);
|
||||||
|
//
|
||||||
|
// _logger.LogInformation("category changed for {name}", download.Name);
|
||||||
|
//
|
||||||
|
// await _notifier.NotifyCategoryChanged(download.Label, _downloadCleanerConfig.NoHardLinksCategory);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public abstract class DownloadService : IDownloadService
|
|||||||
public abstract Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads);
|
public abstract Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public abstract Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes);
|
public abstract Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public abstract Task CreateCategoryAsync(string name);
|
public abstract Task CreateCategoryAsync(string name);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class DummyDownloadService : DownloadService
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes)
|
public override Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,16 @@ public interface IDownloadService : IDisposable
|
|||||||
/// <param name="downloads">The downloads to clean.</param>
|
/// <param name="downloads">The downloads to clean.</param>
|
||||||
/// <param name="categoriesToClean">The categories that should be cleaned.</param>
|
/// <param name="categoriesToClean">The categories that should be cleaned.</param>
|
||||||
/// <param name="excludedHashes">The hashes that should not be cleaned.</param>
|
/// <param name="excludedHashes">The hashes that should not be cleaned.</param>
|
||||||
|
/// <param name="ignoredDownloads">The downloads to ignore from processing.</param>
|
||||||
Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads);
|
Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the category for downloads that have no hardlinks.
|
/// Changes the category for downloads that have no hardlinks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="downloads">The downloads to change.</param>
|
/// <param name="downloads">The downloads to change.</param>
|
||||||
/// <param name="excludedHashes"></param>
|
/// <param name="excludedHashes">The hashes that should not be cleaned.</param>
|
||||||
Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes);
|
/// <param name="ignoredDownloads">The downloads to ignore from processing.</param>
|
||||||
|
Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a download item.
|
/// Deletes a download item.
|
||||||
|
|||||||
@@ -271,10 +271,16 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("skip | download is used by an arr | {name}", download.Name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
IReadOnlyList<TorrentTracker> trackers = await GetTrackersAsync(download.Hash);
|
IReadOnlyList<TorrentTracker> trackers = await GetTrackersAsync(download.Hash);
|
||||||
|
|
||||||
if (ignoredDownloads.Count > 0 &&
|
if (ignoredDownloads.Count > 0 &&
|
||||||
(download.ShouldIgnore(ignoredDownloads) || trackers.Any(x => x.ShouldIgnore(ignoredDownloads)) is true))
|
(download.ShouldIgnore(ignoredDownloads) || trackers.Any(x => x.ShouldIgnore(ignoredDownloads))))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("skip | download is ignored | {name}", download.Name);
|
_logger.LogInformation("skip | download is ignored | {name}", download.Name);
|
||||||
continue;
|
continue;
|
||||||
@@ -288,12 +294,6 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase)))
|
|
||||||
{
|
|
||||||
_logger.LogDebug("skip | download is used by an arr | {name}", download.Name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_downloadCleanerConfig.DeletePrivate)
|
if (!_downloadCleanerConfig.DeletePrivate)
|
||||||
{
|
{
|
||||||
TorrentProperties? torrentProperties = await _client.GetTorrentPropertiesAsync(download.Hash);
|
TorrentProperties? torrentProperties = await _client.GetTorrentPropertiesAsync(download.Hash);
|
||||||
@@ -351,57 +351,23 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
await _client.AddCategoryAsync(name);
|
await _client.AddCategoryAsync(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes)
|
public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads)
|
||||||
{
|
{
|
||||||
if (downloads?.Count is null or 0)
|
if (downloads?.Count is null or 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir)
|
if (!string.IsNullOrEmpty(_downloadCleanerConfig.UnlinkedIgnoredRootDir))
|
||||||
{
|
{
|
||||||
downloads
|
_hardLinkFileService.PopulateFileCounts(_downloadCleanerConfig.UnlinkedIgnoredRootDir);
|
||||||
.Cast<TorrentInfo>()
|
|
||||||
.Select(x =>
|
|
||||||
{
|
|
||||||
string? firstDir = GetRootWithFirstDirectory(x.SavePath);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(firstDir))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstDir == Path.GetPathRoot(x.SavePath))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return firstDir;
|
|
||||||
})
|
|
||||||
.Where(x => !string.IsNullOrEmpty(x))
|
|
||||||
.Distinct()
|
|
||||||
.ToList()
|
|
||||||
.ForEach(x =>
|
|
||||||
{
|
|
||||||
_logger.LogTrace("populating file counts from {dir}", x);
|
|
||||||
|
|
||||||
if (!Directory.Exists(x))
|
|
||||||
{
|
|
||||||
throw new ValidationException($"directory \"{x}\" does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
_hardLinkFileService.PopulateFileCounts(x);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (TorrentInfo download in downloads)
|
foreach (TorrentInfo download in downloads)
|
||||||
{
|
{
|
||||||
IReadOnlyList<TorrentContent>? files = await _client.GetTorrentContentsAsync(download.Hash);
|
if (string.IsNullOrEmpty(download.Hash))
|
||||||
|
|
||||||
if (files is null)
|
|
||||||
{
|
{
|
||||||
_logger.LogDebug("failed to find files for {name}", download.Name);
|
continue;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase)))
|
if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
@@ -410,6 +376,23 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<TorrentTracker> trackers = await GetTrackersAsync(download.Hash);
|
||||||
|
|
||||||
|
if (ignoredDownloads.Count > 0 &&
|
||||||
|
(download.ShouldIgnore(ignoredDownloads) || trackers.Any(x => x.ShouldIgnore(ignoredDownloads))))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("skip | download is ignored | {name}", download.Name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<TorrentContent>? files = await _client.GetTorrentContentsAsync(download.Hash);
|
||||||
|
|
||||||
|
if (files is null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("failed to find files for {name}", download.Name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ContextProvider.Set("downloadName", download.Name);
|
ContextProvider.Set("downloadName", download.Name);
|
||||||
ContextProvider.Set("hash", download.Hash);
|
ContextProvider.Set("hash", download.Hash);
|
||||||
bool hasHardlinks = false;
|
bool hasHardlinks = false;
|
||||||
@@ -419,20 +402,17 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
if (!file.Index.HasValue)
|
if (!file.Index.HasValue)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("skip | file index is null for {name}", download.Name);
|
_logger.LogDebug("skip | file index is null for {name}", download.Name);
|
||||||
return;
|
hasHardlinks = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ceva = Path.Combine(download.ContentPath, file.Name);
|
// string filePath = Path.Combine(Directory.Exists(download.ContentPath)
|
||||||
var ceva2 = Path.Combine(download.SavePath, file.Name);
|
// ? download.ContentPath
|
||||||
|
// : download.SavePath, file.Name
|
||||||
|
// );
|
||||||
|
string filePath = string.Join(Path.DirectorySeparatorChar, Path.Combine(download.SavePath, file.Name).Split(['\\', '/'])); // TODO
|
||||||
|
|
||||||
string filePath = Path.Combine(Directory.Exists(download.ContentPath)
|
long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, !string.IsNullOrEmpty(_downloadCleanerConfig.UnlinkedIgnoredRootDir));
|
||||||
? download.ContentPath
|
|
||||||
: download.SavePath, file.Name
|
|
||||||
);
|
|
||||||
filePath = string.Join(Path.DirectorySeparatorChar, Path.Combine(download.SavePath, file.Name).Split(['\\', '/'])); // TODO
|
|
||||||
|
|
||||||
// TODO add config for root directory
|
|
||||||
long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir);
|
|
||||||
|
|
||||||
if (hardlinkCount < 0)
|
if (hardlinkCount < 0)
|
||||||
{
|
{
|
||||||
@@ -444,6 +424,7 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
if (hardlinkCount > 0)
|
if (hardlinkCount > 0)
|
||||||
{
|
{
|
||||||
hasHardlinks = true;
|
hasHardlinks = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,13 +434,13 @@ public class QBitService : DownloadService, IQBitService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.NoHardLinksCategory);
|
await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.UnlinkedTargetCategory);
|
||||||
|
|
||||||
_logger.LogInformation("category changed for {name}", download.Name);
|
_logger.LogInformation("category changed for {name}", download.Name);
|
||||||
|
|
||||||
await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.NoHardLinksCategory);
|
await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.UnlinkedTargetCategory);
|
||||||
|
|
||||||
download.Category = _downloadCleanerConfig.NoHardLinksCategory;
|
download.Category = _downloadCleanerConfig.UnlinkedTargetCategory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+112
-104
@@ -269,6 +269,11 @@ public class TransmissionService : DownloadService, ITransmissionService
|
|||||||
public override async Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean,
|
public override async Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean,
|
||||||
HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads)
|
HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads)
|
||||||
{
|
{
|
||||||
|
if (downloads?.Count is null or 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (TorrentInfo download in downloads)
|
foreach (TorrentInfo download in downloads)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(download.HashString))
|
if (string.IsNullOrEmpty(download.HashString))
|
||||||
@@ -341,117 +346,120 @@ public class TransmissionService : DownloadService, ITransmissionService
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes)
|
public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes, IReadOnlyList<string> ignoredDownloads)
|
||||||
{
|
{
|
||||||
if (downloads?.Count is null or 0)
|
if (downloads?.Count is null or 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir)
|
// TODO ignored downloads
|
||||||
{
|
throw new NotImplementedException();
|
||||||
downloads
|
|
||||||
.Cast<TorrentInfo>()
|
|
||||||
.Select(x =>
|
|
||||||
{
|
|
||||||
if (x.DownloadDir == null)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? firstDir = GetRootWithFirstDirectory(x.DownloadDir);
|
// if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir)
|
||||||
|
// {
|
||||||
if (string.IsNullOrEmpty(firstDir))
|
// downloads
|
||||||
{
|
// .Cast<TorrentInfo>()
|
||||||
return string.Empty;
|
// .Select(x =>
|
||||||
}
|
// {
|
||||||
|
// if (x.DownloadDir == null)
|
||||||
if (firstDir == Path.GetPathRoot(x.DownloadDir))
|
// {
|
||||||
{
|
// return string.Empty;
|
||||||
return string.Empty;
|
// }
|
||||||
}
|
//
|
||||||
|
// string? firstDir = GetRootWithFirstDirectory(x.DownloadDir);
|
||||||
return firstDir;
|
//
|
||||||
})
|
// if (string.IsNullOrEmpty(firstDir))
|
||||||
.Where(x => !string.IsNullOrEmpty(x))
|
// {
|
||||||
.Distinct()
|
// return string.Empty;
|
||||||
.ToList()
|
// }
|
||||||
.ForEach(x =>
|
//
|
||||||
{
|
// if (firstDir == Path.GetPathRoot(x.DownloadDir))
|
||||||
_logger.LogTrace("populating file counts from {dir}", x);
|
// {
|
||||||
|
// return string.Empty;
|
||||||
if (!Directory.Exists(x))
|
// }
|
||||||
{
|
//
|
||||||
throw new ValidationException($"directory \"{x}\" does not exist");
|
// return firstDir;
|
||||||
}
|
// })
|
||||||
|
// .Where(x => !string.IsNullOrEmpty(x))
|
||||||
_hardLinkFileService.PopulateFileCounts(x);
|
// .Distinct()
|
||||||
});
|
// .ToList()
|
||||||
}
|
// .ForEach(x =>
|
||||||
|
// {
|
||||||
foreach (TorrentInfo download in downloads.Cast<TorrentInfo>())
|
// _logger.LogTrace("populating file counts from {dir}", x);
|
||||||
{
|
//
|
||||||
if (string.IsNullOrEmpty(download.HashString) || download.DownloadDir == null)
|
// if (!Directory.Exists(x))
|
||||||
{
|
// {
|
||||||
_logger.LogDebug("skip | download hash or download directory is null for {name}", download.Name);
|
// throw new ValidationException($"directory \"{x}\" does not exist");
|
||||||
continue;
|
// }
|
||||||
}
|
//
|
||||||
|
// _hardLinkFileService.PopulateFileCounts(x);
|
||||||
if (excludedHashes.Any(x => x.Equals(download.HashString, StringComparison.InvariantCultureIgnoreCase)))
|
// });
|
||||||
{
|
// }
|
||||||
_logger.LogDebug("skip | download is used by an arr | {name}", download.Name);
|
//
|
||||||
continue;
|
// foreach (TorrentInfo download in downloads.Cast<TorrentInfo>())
|
||||||
}
|
// {
|
||||||
|
// if (string.IsNullOrEmpty(download.HashString) || download.DownloadDir == null)
|
||||||
ContextProvider.Set("downloadName", download.Name);
|
// {
|
||||||
ContextProvider.Set("hash", download.HashString);
|
// _logger.LogDebug("skip | download hash or download directory is null for {name}", download.Name);
|
||||||
|
// continue;
|
||||||
bool hasHardlinks = false;
|
// }
|
||||||
|
//
|
||||||
if (download.Files != null)
|
// if (excludedHashes.Any(x => x.Equals(download.HashString, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
{
|
// {
|
||||||
foreach (TransmissionTorrentFiles file in download.Files)
|
// _logger.LogDebug("skip | download is used by an arr | {name}", download.Name);
|
||||||
{
|
// continue;
|
||||||
string filePath = Path.Combine(download.DownloadDir, file.Name);
|
// }
|
||||||
|
//
|
||||||
long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir);
|
// ContextProvider.Set("downloadName", download.Name);
|
||||||
|
// ContextProvider.Set("hash", download.HashString);
|
||||||
if (hardlinkCount < 0)
|
//
|
||||||
{
|
// bool hasHardlinks = false;
|
||||||
_logger.LogDebug("skip | could not get file properties | {name}", download.Name);
|
//
|
||||||
hasHardlinks = true;
|
// if (download.Files != null)
|
||||||
break;
|
// {
|
||||||
}
|
// foreach (TransmissionTorrentFiles file in download.Files)
|
||||||
|
// {
|
||||||
if (hardlinkCount > 0)
|
// string filePath = Path.Combine(download.DownloadDir, file.Name);
|
||||||
{
|
//
|
||||||
hasHardlinks = true;
|
// long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir);
|
||||||
break;
|
//
|
||||||
}
|
// if (hardlinkCount < 0)
|
||||||
}
|
// {
|
||||||
}
|
// _logger.LogDebug("skip | could not get file properties | {name}", download.Name);
|
||||||
|
// hasHardlinks = true;
|
||||||
if (hasHardlinks)
|
// break;
|
||||||
{
|
// }
|
||||||
_logger.LogDebug("skip | download has hardlinks | {name}", download.Name);
|
//
|
||||||
continue;
|
// if (hardlinkCount > 0)
|
||||||
}
|
// {
|
||||||
|
// hasHardlinks = true;
|
||||||
// Get the current category (directory name)
|
// break;
|
||||||
string currentCategory = Path.GetFileName(Path.TrimEndingDirectorySeparator(download.DownloadDir));
|
// }
|
||||||
|
// }
|
||||||
// Create the new location path
|
// }
|
||||||
string newLocation = Path.Combine(
|
//
|
||||||
Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(download.DownloadDir)) ?? string.Empty,
|
// if (hasHardlinks)
|
||||||
_downloadCleanerConfig.NoHardLinksCategory
|
// {
|
||||||
);
|
// _logger.LogDebug("skip | download has hardlinks | {name}", download.Name);
|
||||||
|
// continue;
|
||||||
await _dryRunInterceptor.InterceptAsync(MoveDownload, download.Id, newLocation);
|
// }
|
||||||
|
//
|
||||||
_logger.LogInformation("category changed for {name}", download.Name);
|
// // Get the current category (directory name)
|
||||||
|
// string currentCategory = Path.GetFileName(Path.TrimEndingDirectorySeparator(download.DownloadDir));
|
||||||
await _notifier.NotifyCategoryChanged(currentCategory, _downloadCleanerConfig.NoHardLinksCategory);
|
//
|
||||||
}
|
// // Create the new location path
|
||||||
|
// string newLocation = Path.Combine(
|
||||||
|
// Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(download.DownloadDir)) ?? string.Empty,
|
||||||
|
// _downloadCleanerConfig.NoHardLinksCategory
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// await _dryRunInterceptor.InterceptAsync(MoveDownload, download.Id, newLocation);
|
||||||
|
//
|
||||||
|
// _logger.LogInformation("category changed for {name}", download.Name);
|
||||||
|
//
|
||||||
|
// await _notifier.NotifyCategoryChanged(currentCategory, _downloadCleanerConfig.NoHardLinksCategory);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
[DryRunSafeguard]
|
[DryRunSafeguard]
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public class HardLinkFileService : IHardLinkFileService
|
|||||||
|
|
||||||
public void PopulateFileCounts(string directoryPath)
|
public void PopulateFileCounts(string directoryPath)
|
||||||
{
|
{
|
||||||
|
_logger.LogTrace("populating file counts from {dir}", directoryPath);
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
_windowsHardLinkFileService.PopulateFileCounts(directoryPath);
|
_windowsHardLinkFileService.PopulateFileCounts(directoryPath);
|
||||||
|
|||||||
@@ -51,15 +51,11 @@ public class UnixHardLinkFileService : IHardLinkFileService, IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories))
|
// traverse all files in the ignored path and subdirectories
|
||||||
|
foreach (string file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
AddInodeToCount(file);
|
AddInodeToCount(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dir in Directory.EnumerateDirectories(directoryPath, "*", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
AddInodeToCount(dir);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,16 +57,11 @@ public class WindowsHardLinkFileService : IHardLinkFileService, IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Traverse all files and directories in the ignored path
|
// traverse all files in the ignored path and subdirectories
|
||||||
foreach (var file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories))
|
foreach (string file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
AddFileIndexToCount(file);
|
AddFileIndexToCount(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dir in Directory.EnumerateDirectories(directoryPath, "*", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
AddFileIndexToCount(dir);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user