Add .gitignore style ignoring (#13906)

This commit is contained in:
Tim Eisele 2025-04-26 17:35:57 +02:00 committed by GitHub
parent 5d65cfcd99
commit a0b3b7335f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 123 additions and 20 deletions

View File

@ -21,6 +21,7 @@
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.3" /> <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.3" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" /> <PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" /> <PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Ignore" Version="0.2.1" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="4.0.12" /> <PackageVersion Include="libse" Version="4.0.12" />
<PackageVersion Include="LrcParser" Version="2025.228.1" /> <PackageVersion Include="LrcParser" Version="2025.228.1" />
@ -88,4 +89,4 @@
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" /> <PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
<PackageVersion Include="xunit" Version="2.9.3" /> <PackageVersion Include="xunit" Version="2.9.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -64,6 +64,10 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Ignore" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Localization\iso6392.txt" /> <EmbeddedResource Include="Localization\iso6392.txt" />
<EmbeddedResource Include="Localization\countries.json" /> <EmbeddedResource Include="Localization\countries.json" />

View File

@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library
{ {
if (parent is not null) if (parent is not null)
{ {
// Ignore extras folders but allow it at the collection level // Ignore extras for unsupported types
if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename) if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
&& parent is not AggregateFolder && parent is not AggregateFolder
&& parent is not UserRootFolder) && parent is not UserRootFolder)
@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Library
{ {
if (parent is not null) if (parent is not null)
{ {
// Don't resolve these into audio files // Don't resolve theme songs
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
&& AudioFileParser.IsAudioFile(filename, _namingOptions)) && AudioFileParser.IsAudioFile(filename, _namingOptions))
{ {

View File

@ -0,0 +1,77 @@
using System;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library;
/// <summary>
/// Resolver rule class for ignoring files via .ignore.
/// </summary>
public class DotIgnoreIgnoreRule : IResolverIgnoreRule
{
private static FileInfo? FindIgnoreFile(DirectoryInfo directory)
{
var ignoreFile = new FileInfo(Path.Join(directory.FullName, ".ignore"));
if (ignoreFile.Exists)
{
return ignoreFile;
}
var parentDir = directory.Parent;
if (parentDir == null || parentDir.FullName == directory.FullName)
{
return null;
}
return FindIgnoreFile(parentDir);
}
/// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent)
{
return IsIgnored(fileInfo, parent);
}
/// <summary>
/// Checks whether or not the file is ignored.
/// </summary>
/// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent BaseItem.</param>
/// <returns>True if the file should be ignored.</returns>
public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent)
{
var parentDirPath = Path.GetDirectoryName(fileInfo.FullName);
if (string.IsNullOrEmpty(parentDirPath))
{
return false;
}
var folder = new DirectoryInfo(parentDirPath);
var ignoreFile = FindIgnoreFile(folder);
if (ignoreFile is null)
{
return false;
}
string ignoreFileString;
using (var reader = ignoreFile.OpenText())
{
ignoreFileString = reader.ReadToEnd();
}
if (string.IsNullOrEmpty(ignoreFileString))
{
// Ignore directory if we just have the file
return true;
}
// If file has content, base ignoring off the content .gitignore-style rules
var ignoreRules = ignoreFileString.Split('\n', StringSplitOptions.RemoveEmptyEntries);
var ignore = new Ignore.Ignore();
ignore.Add(ignoreRules);
return ignore.IsIgnored(fileInfo.FullName);
}
}

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using DotNet.Globbing; using DotNet.Globbing;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library

View File

@ -649,10 +649,11 @@ namespace Emby.Server.Implementations.Library
args.FileSystemChildren = files; args.FileSystemChildren = files;
} }
// Check to see if we should resolve based on our contents // Filter content based on ignore rules
if (args.IsDirectory && !ShouldResolvePathContents(args)) if (args.IsDirectory)
{ {
return null; var filtered = args.GetActualFileSystemChildren().ToArray();
args.FileSystemChildren = filtered ?? [];
} }
return ResolveItem(args, resolvers); return ResolveItem(args, resolvers);
@ -683,17 +684,6 @@ namespace Emby.Server.Implementations.Library
return newList; return newList;
} }
/// <summary>
/// Determines whether a path should be ignored based on its contents - called after the contents have been read.
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
private static bool ShouldResolvePathContents(ItemResolveArgs args)
{
// Ignore any folders containing a file called .ignore
return !args.ContainsFileSystemEntryByName(".ignore");
}
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, CollectionType? collectionType = null) public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, CollectionType? collectionType = null)
{ {
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
@ -2724,16 +2714,18 @@ namespace Emby.Server.Implementations.Library
public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{ {
// Apply .ignore rules
var filtered = fileSystemChildren.Where(c => !DotIgnoreIgnoreRule.IsIgnored(c, owner)).ToList();
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath); var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath);
if (ownerVideoInfo is null) if (ownerVideoInfo is null)
{ {
yield break; yield break;
} }
var count = fileSystemChildren.Count; var count = filtered.Count;
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
var current = fileSystemChildren[i]; var current = filtered[i];
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name)) if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
{ {
var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false); var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);

View File

@ -0,0 +1,30 @@
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.Library;
public class DotIgnoreIgnoreRuleTest
{
[Fact]
public void Test()
{
var ignore = new Ignore.Ignore();
ignore.Add("SPs");
Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
}
[Fact]
public void TestNegatePattern()
{
var ignore = new Ignore.Ignore();
ignore.Add("SPs");
ignore.Add("!thebestshot.mkv");
Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
Assert.True(!ignore.IsIgnored("f:/cd/sps/thebestshot.mkv"));
Assert.True(!ignore.IsIgnored("cd/sps/thebestshot.mkv"));
Assert.True(!ignore.IsIgnored("/cd/sps/thebestshot.mkv"));
}
}