#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Audio;
using Emby.Naming.Common;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
///
/// The music album resolver.
///
public class MusicAlbumResolver : ItemResolver
{
private readonly ILogger _logger;
private readonly NamingOptions _namingOptions;
private readonly IDirectoryService _directoryService;
///
/// Initializes a new instance of the class.
///
/// The logger.
/// The naming options.
/// The directory service.
public MusicAlbumResolver(ILogger logger, NamingOptions namingOptions, IDirectoryService directoryService)
{
_logger = logger;
_namingOptions = namingOptions;
_directoryService = directoryService;
}
///
/// Gets the priority.
///
/// The priority.
public override ResolverPriority Priority => ResolverPriority.Third;
///
/// Resolves the specified args.
///
/// The args.
/// MusicAlbum.
protected override MusicAlbum Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
var isMusicMediaFolder = collectionType == CollectionType.Music;
// If there's a collection type and it's not music, don't allow it.
if (!isMusicMediaFolder)
{
return null;
}
if (!args.IsDirectory)
{
return null;
}
// Avoid mis-identifying top folders
if (args.HasParent())
{
return null;
}
if (args.Parent.IsRoot)
{
return null;
}
return IsMusicAlbum(args) ? new MusicAlbum() : null;
}
///
/// Determine if the supplied file data points to a music album.
///
/// The path to check.
/// The directory service.
/// true if the provided path points to a music album; otherwise, false.
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
}
///
/// Determine if the supplied resolve args should be considered a music album.
///
/// The args.
/// true if [is music album] [the specified args]; otherwise, false.
private bool IsMusicAlbum(ItemResolveArgs args)
{
if (args.IsDirectory)
{
// If args is a artist subfolder it's not a music album
foreach (var subfolder in _namingOptions.ArtistSubfolders)
{
if (Path.GetDirectoryName(args.Path.AsSpan()).Equals(subfolder, StringComparison.OrdinalIgnoreCase))
{
_logger.LogDebug("Found release folder: {Path}", args.Path);
return false;
}
}
// If args contains music it's a music album
if (ContainsMusic(args.FileSystemChildren, true, _directoryService))
{
return true;
}
}
return false;
}
///
/// Determine if the supplied list contains what we should consider music.
///
/// true if the provided path list contains music; otherwise, false.
private bool ContainsMusic(
ICollection list,
bool allowSubfolders,
IDirectoryService directoryService)
{
// Check for audio files before digging down into directories
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
if (foundAudioFile)
{
// At least one audio file exists
return true;
}
if (!allowSubfolders)
{
// Not music since no audio file exists and we're not looking into subfolders
return false;
}
var discSubfolderCount = 0;
var parser = new AlbumParser(_namingOptions);
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{
var path = fileSystemInfo.FullName;
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService);
if (hasMusic)
{
if (parser.IsMultiPart(path))
{
_logger.LogDebug("Found multi-disc folder: {Path}", path);
Interlocked.Increment(ref discSubfolderCount);
}
else
{
// If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
state.Stop();
}
}
});
if (!result.IsCompleted)
{
return false;
}
return discSubfolderCount > 0;
}
}
}