Skip to content

Commit

Permalink
fix: use ignore patterns during media folder search and in the ignore…
Browse files Browse the repository at this point in the history
… rule

Use a copy of the ignore patterns taken from the Jellyfin core (since they're not exposed through the abstraction used by plugins) to ignore paths we shouldn't check in the media folder mapping check and in the ignore rule check. The latter check is redundant since the core also has such a check, but in the off-case that the shoko ignore rule is ran before the core ignore rule then we won't attempt to check the paths that would otherwise had been checked because of the run order.
  • Loading branch information
revam committed Nov 5, 2024
1 parent c310bc0 commit e981a58
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 4 deletions.
52 changes: 48 additions & 4 deletions Shokofin/Configuration/MediaFolderConfigurationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,7 @@ private async Task<MediaFolderConfiguration> CreateConfigurationForPath(Guid lib
}
else {
var foundLocations = new List<(int, string)>();
var samplePaths = FileSystem.GetFilePaths(mediaFolder.Path, true)
.Where(path => NamingOptions.VideoFileExtensions.Contains(Path.GetExtension(path)))
.Take(101) // 101 as a tie breaker
.ToList();
var samplePaths = GetSamplePaths(mediaFolder.Path).ToList();

Logger.LogDebug("Asking remote server if it knows any of the {Count} sampled files in {Path}. (Library={LibraryId})", samplePaths.Count > 100 ? 100 : samplePaths.Count, mediaFolder.Path, libraryId);
foreach (var path in samplePaths) {
Expand Down Expand Up @@ -480,5 +477,52 @@ private async Task<MediaFolderConfiguration> CreateConfigurationForPath(Guid lib
return mediaFolderConfig;
}

/// <summary>
/// Max number of sample paths to return. We use an odd number as a tie
/// breaker in case of multiple different matches.
/// </summary>
private const int MaxSamplePaths = 101;

/// <summary>
/// Gets the sample paths for the given media folder.
/// </summary>
/// <param name="mediaFolder">The media folder to get the sample paths
/// for.</param>
/// <returns>The sample paths for the given media folder.</returns>
private IEnumerable<string> GetSamplePaths(string mediaFolder)
{
var count = 0;
var rootFiles = FileSystem.GetFilePaths(mediaFolder, false);
foreach (var filePath in rootFiles)
{
if (IgnorePatterns.ShouldIgnore(filePath))
continue;

yield return filePath;

if (++count == MaxSamplePaths)
yield break;
}

var rootFolders = FileSystem.GetDirectoryPaths(mediaFolder, false);
foreach (var directoryPath in rootFolders)
{
if (IgnorePatterns.ShouldIgnore(directoryPath))
continue;

var files = FileSystem.GetFilePaths(directoryPath, true);
foreach (var filePath in files)
{
if (IgnorePatterns.ShouldIgnore(filePath))
continue;

yield return filePath;

if (++count == MaxSamplePaths)
yield break;
}
}
}

#endregion
}
6 changes: 6 additions & 0 deletions Shokofin/Resolvers/ShokoIgnoreRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public async Task<bool> ShouldFilterItem(Folder? parent, FileSystemMetadata file
if (!Lookup.IsEnabledForItem(parent, out var isSoleProvider))
return false;

// Don't even bother continuing if the file system entry is matching one of the ignore patterns.
if (IgnorePatterns.ShouldIgnore(fileInfo.FullName)) {
Logger.LogTrace("Skipped ignored path {Path}", fileInfo.FullName);
return true;
}

trackerId = Plugin.Instance.Tracker.Add($"Should ignore path \"{fileInfo.FullName}\".");
if (fileInfo.IsDirectory && Plugin.Instance.IgnoredFolders.Contains(Path.GetFileName(fileInfo.FullName).ToLowerInvariant())) {
Logger.LogDebug("Skipped excluded folder at path {Path}", fileInfo.FullName);
Expand Down
1 change: 1 addition & 0 deletions Shokofin/Shokofin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Jellyfin.Controller" Version="10.10.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="$(SignalRVersion)" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.*" />
</ItemGroup>

<Target Name="CopySignalRDLLsToOutputPath" AfterTargets="Build">
Expand Down
134 changes: 134 additions & 0 deletions Shokofin/Utils/IgnorePatterns.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using DotNet.Globbing;

namespace Shokofin.Utils;

/// <summary>
/// Glob patterns for files to ignore.
/// </summary>
/// <remarks>
/// A one-to-one copy of <see href="https://github.com/jellyfin/jellyfin/blob/b92fc7ea9dbf86437a981c3f0477a7b457977b9a/Emby.Server.Implementations/Library/IgnorePatterns.cs"/>
/// since it's not exposed through the abstraction used by plugins and I don't
/// care enough to rise a PR to expose it.
/// </remarks>
public static class IgnorePatterns
{
/// <summary>
/// Files matching these glob patterns will be ignored.
/// </summary>
private static readonly string[] _patterns =
{
"**/small.jpg",
"**/albumart.jpg",

// We have neither non-greedy matching or character group repetitions, working around that here.
// https://github.com/dazinator/DotNet.Glob#patterns
// .*/sample\..{1,5}
"**/sample.?",
"**/sample.??",
"**/sample.???", // Matches sample.mkv
"**/sample.????", // Matches sample.webm
"**/sample.?????",
"**/*.sample.?",
"**/*.sample.??",
"**/*.sample.???",
"**/*.sample.????",
"**/*.sample.?????",
"**/sample/*",

// Directories
"**/metadata/**",
"**/metadata",
"**/ps3_update/**",
"**/ps3_update",
"**/ps3_vprm/**",
"**/ps3_vprm",
"**/extrafanart/**",
"**/extrafanart",
"**/extrathumbs/**",
"**/extrathumbs",
"**/.actors/**",
"**/.actors",
"**/.wd_tv/**",
"**/.wd_tv",
"**/lost+found/**",
"**/lost+found",

// Trickplay files
"**/*.trickplay",
"**/*.trickplay/**",

// WMC temp recording directories that will constantly be written to
"**/TempRec/**",
"**/TempRec",
"**/TempSBE/**",
"**/TempSBE",

// Synology
"**/eaDir/**",
"**/eaDir",
"**/@eaDir/**",
"**/@eaDir",
"**/#recycle/**",
"**/#recycle",

// Qnap
"**/@Recycle/**",
"**/@Recycle",
"**/.@__thumb/**",
"**/.@__thumb",
"**/$RECYCLE.BIN/**",
"**/$RECYCLE.BIN",
"**/System Volume Information/**",
"**/System Volume Information",
"**/.grab/**",
"**/.grab",

// Unix hidden files
"**/.*",

// Mac - if you ever remove the above.
// "**/._*",
// "**/.DS_Store",

// thumbs.db
"**/thumbs.db",

// bts sync files
"**/*.bts",
"**/*.sync",

// zfs
"**/.zfs/**",
"**/.zfs"
};

private static readonly GlobOptions _globOptions = new GlobOptions
{
Evaluation =
{
CaseInsensitive = true
}
};

private static readonly Glob[] _globs = Array.ConvertAll(_patterns, p => Glob.Parse(p, _globOptions));

/// <summary>
/// Returns true if the supplied path should be ignored.
/// </summary>
/// <param name="path">The path to test.</param>
/// <returns>Whether to ignore the path.</returns>
public static bool ShouldIgnore(ReadOnlySpan<char> path)
{
int len = _globs.Length;
for (int i = 0; i < len; i++)
{
if (_globs[i].IsMatch(path))
{
return true;
}
}

return false;
}
}

0 comments on commit e981a58

Please sign in to comment.