Add DownloadableFileFilter + unit tests
`DownloadableFileFilter` is an abstraction of the filtering we do in most of the `TorrentClient`s. It operates on a single file at a time, not the entire torrent.
This commit is contained in:
parent
85d6b820f3
commit
b95dbcc824
2 changed files with 340 additions and 0 deletions
|
|
@ -0,0 +1,261 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Service.Services;
|
||||
|
||||
namespace RdtClient.Service.Test.Services;
|
||||
|
||||
public class DownloadableFileFilterTest
|
||||
{
|
||||
[Fact]
|
||||
public void IsDownloadable_WhenNoFilterSpecified_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1"
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, "file.txt", 10000);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// downloadMinSize is in MB, fileSize is in B
|
||||
[InlineData(100, 20 * 1024 * 1024)]
|
||||
[InlineData(2, 2 * 1024 * 1024)]
|
||||
[InlineData(2, 2 * (1000 * 1000 + 1))] // mostly to show we use 1024 not 1000 for conversion
|
||||
public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadBelowSize_ReturnsFalse(Int32 downloadMinSize, Int64 fileSize)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
DownloadMinSize = downloadMinSize
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, "file.txt", fileSize);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(100, 110 * 1024 * 1024)]
|
||||
[InlineData(2, 2 * 1024 * 1024 + 1)]
|
||||
public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadAboveSize_ReturnsTrue(Int32 downloadMinSize, Int64 fileSize)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
DownloadMinSize = downloadMinSize
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, "file.txt", fileSize);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("file", "no-match")]
|
||||
[InlineData("file", "even/in/a/subdirectory.txt")]
|
||||
[InlineData("ch[aA]racter c[lL]asses", "nope.txt")]
|
||||
[InlineData("digits\\d+", "123 not matching.txt")]
|
||||
public void IsDownloadable_WhenIncludeRegexSpecified_AndPathDoesNotMatchRegex_ReturnsFalse(String includeRegex, String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
IncludeRegex = includeRegex
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, Int64.MaxValue);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("file", "file.txt")]
|
||||
[InlineData("file", "file/in/a/subdirectory.txt")]
|
||||
[InlineData("ch[aA]racter c[lL]asses", "character cLasses")]
|
||||
[InlineData("digits\\d+", "digits123456.txt")]
|
||||
public void IsDownloadable_WhenIncludeRegexSpecified_AndPathMatchesRegex_ReturnsTrue(String includeRegex, String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
IncludeRegex = includeRegex
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, Int64.MaxValue);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("file", "no-match")]
|
||||
[InlineData("file", "even/in/a/subdirectory.txt")]
|
||||
[InlineData("ch[aA]racter c[lL]asses", "nope.txt")]
|
||||
[InlineData("digits\\d+", "123 not matching.txt")]
|
||||
public void IsDownloadable_WhenExcludeRegexSpecified_AndPathDoesNotMatchRegex_ReturnsTrue(String excludeRegex, String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
ExcludeRegex = excludeRegex
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, Int64.MaxValue);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("file", "file.txt")]
|
||||
[InlineData("file", "file/in/a/subdirectory.txt")]
|
||||
[InlineData("ch[aA]racter c[lL]asses", "character cLasses")]
|
||||
[InlineData("digits\\d+", "digits123456.txt")]
|
||||
public void IsDownloadable_WhenExcludeRegexSpecified_AndPathMatchesRegex_ReturnsFalse(String excludeRegex, String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
ExcludeRegex = excludeRegex
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, Int64.MaxValue);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("file", "file", "file.txt")]
|
||||
[InlineData("file", "in/a", "file/in/a/subdirectory.txt")]
|
||||
[InlineData("ch[aA]racter c[lL]asses", "character", "character cLasses")]
|
||||
[InlineData("digits\\d+", "123456", "digits123456.txt")]
|
||||
public void IsDownloadable_WhenBothIncludeAndExcludeRegexSpecified_AndPathMatchesIncludeAndExcludeRegex_ReturnsTrue(String includeRegex, String excludeRegex, String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
IncludeRegex = includeRegex,
|
||||
ExcludeRegex = excludeRegex
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, Int64.MaxValue);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(10, "file", 10 * 1024 * 1024 + 1, "no-match.txt")]
|
||||
public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadAboveSizeAndDoesNotMatchRegex_ReturnsFalse(
|
||||
Int32 minSize,
|
||||
String includeRegex,
|
||||
Int64 fileSize,
|
||||
String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
IncludeRegex = includeRegex,
|
||||
DownloadMinSize = minSize
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, fileSize);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(10, "file", 10 * 1024 * 1024 - 1, "file.txt")]
|
||||
public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadBelowSizeAndMatchesRegex_ReturnsFalse(
|
||||
Int32 minSize,
|
||||
String includeRegex,
|
||||
Int64 fileSize,
|
||||
String filePath)
|
||||
{
|
||||
// Arrange
|
||||
var mocks = new Mocks();
|
||||
|
||||
var torrent = new Torrent
|
||||
{
|
||||
RdId = "1",
|
||||
IncludeRegex = includeRegex,
|
||||
DownloadMinSize = minSize
|
||||
};
|
||||
|
||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||
|
||||
// Act
|
||||
var result = fileFilter.IsDownloadable(torrent, filePath, fileSize);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
private class Mocks
|
||||
{
|
||||
public readonly Mock<ILogger<DownloadableFileFilter>> LoggerMock = new();
|
||||
}
|
||||
}
|
||||
79
server/RdtClient.Service/Services/DownloadableFileFilter.cs
Normal file
79
server/RdtClient.Service/Services/DownloadableFileFilter.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public interface IDownloadableFileFilter
|
||||
{
|
||||
public Boolean IsDownloadable(Torrent torrent, String filePath, Int64 fileSize);
|
||||
}
|
||||
|
||||
public class DownloadableFileFilter(ILogger<DownloadableFileFilter> logger) : IDownloadableFileFilter
|
||||
{
|
||||
public Boolean IsDownloadable(Torrent torrent, String filePath, Int64 fileSize)
|
||||
{
|
||||
var isDownloadable = SatisfiesMinSize(torrent, filePath, fileSize) &&
|
||||
SatisfiesFileNameRegex(torrent, filePath);
|
||||
|
||||
if (isDownloadable)
|
||||
{
|
||||
logger.LogDebug("File {filePath} was included after filtering", filePath);
|
||||
}
|
||||
|
||||
return isDownloadable;
|
||||
}
|
||||
|
||||
private Boolean SatisfiesMinSize(Torrent torrent, String filePath, Int64 fileSize)
|
||||
{
|
||||
if (torrent is { ClientKind: Provider.RealDebrid, DownloadAction: TorrentDownloadAction.DownloadManual })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (torrent.DownloadMinSize <= 0 || fileSize > torrent.DownloadMinSize * 1024 * 1024)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.LogDebug("Not downloading file {filePath} file size {fileSize} smaller than minimum {downloadMinSize}", filePath, fileSize, torrent.DownloadMinSize);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean SatisfiesFileNameRegex(Torrent torrent, String filePath)
|
||||
{
|
||||
return IncludeFileName(torrent, filePath) && ExcludeFileName(torrent, filePath);
|
||||
}
|
||||
|
||||
private Boolean IncludeFileName(Torrent torrent, String filePath)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(torrent.IncludeRegex) || Regex.IsMatch(filePath, torrent.IncludeRegex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.LogDebug("Not downloading file {filePath} does not match regex {includeRegex}", filePath, torrent.IncludeRegex);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Boolean ExcludeFileName(Torrent torrent, String filePath)
|
||||
{
|
||||
// If the IncludeRegex is set, ignore the ExcludeRegex
|
||||
if (!String.IsNullOrWhiteSpace(torrent.IncludeRegex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(torrent.ExcludeRegex) || !Regex.IsMatch(filePath, torrent.ExcludeRegex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.LogDebug("Not downloading file {filePath} matches regex {excludeRegex}", filePath, torrent.ExcludeRegex);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue