Tim Eisele 1 месяц назад
Родитель
Сommit
a0b3b7335f

+ 2 - 1
Directory.Packages.props

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

+ 4 - 0
Emby.Server.Implementations/Emby.Server.Implementations.csproj

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

+ 2 - 2
Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs

@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library
             {
                 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)
                         && parent is not AggregateFolder
                         && parent is not UserRootFolder)
@@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Library
             {
                 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)
                         && AudioFileParser.IsAudioFile(filename, _namingOptions))
                     {

+ 77 - 0
Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs

@@ -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);
+    }
+}

+ 0 - 1
Emby.Server.Implementations/Library/IgnorePatterns.cs

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

+ 8 - 16
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -649,10 +649,11 @@ namespace Emby.Server.Implementations.Library
                 args.FileSystemChildren = files;
             }
 
-            // Check to see if we should resolve based on our contents
-            if (args.IsDirectory && !ShouldResolvePathContents(args))
+            // Filter content based on ignore rules
+            if (args.IsDirectory)
             {
-                return null;
+                var filtered = args.GetActualFileSystemChildren().ToArray();
+                args.FileSystemChildren = filtered ?? [];
             }
 
             return ResolveItem(args, resolvers);
@@ -683,17 +684,6 @@ namespace Emby.Server.Implementations.Library
             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)
         {
             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)
         {
+            // 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);
             if (ownerVideoInfo is null)
             {
                 yield break;
             }
 
-            var count = fileSystemChildren.Count;
+            var count = filtered.Count;
             for (var i = 0; i < count; i++)
             {
-                var current = fileSystemChildren[i];
+                var current = filtered[i];
                 if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
                 {
                     var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);

+ 30 - 0
tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs

@@ -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"));
+    }
+}