diff options
| author | Luke <luke.pulverenti@gmail.com> | 2015-01-04 09:27:54 -0500 |
|---|---|---|
| committer | Luke <luke.pulverenti@gmail.com> | 2015-01-04 09:27:54 -0500 |
| commit | c5ff30f66e368efc2ca7dea7813fba6d9f6a657c (patch) | |
| tree | c5552b898f66b7d510e9257eb8bbeafd6a003676 /MediaBrowser.Server.Implementations/Library/Resolvers | |
| parent | 767590125b27c2498e3ad9544edbede30fb70f45 (diff) | |
| parent | 59b6bc28c332701d5e383fbf99170bdc740fb6cc (diff) | |
Merge pull request #965 from MediaBrowser/dev
3.0.5482.0
Diffstat (limited to 'MediaBrowser.Server.Implementations/Library/Resolvers')
11 files changed, 173 insertions, 690 deletions
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index f32ed2b20..0f703cb22 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -37,7 +37,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// <value>The priority.</value> public override ResolverPriority Priority { - get { return ResolverPriority.Third; } // we need to be ahead of the generic folder resolver but behind the movie one + get + { + // Behind special folder resolver + return ResolverPriority.Second; + } } /// <summary> @@ -49,21 +53,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { if (!args.IsDirectory) return null; - //Avoid mis-identifying top folders - if (args.Parent == null) return null; + // Avoid mis-identifying top folders if (args.Parent.IsRoot) return null; if (args.HasParent<MusicAlbum>()) return null; - // Optimization - if (args.HasParent<BoxSet>() || args.HasParent<Series>() || args.HasParent<Season>()) - { - return null; - } - var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, - StringComparison.OrdinalIgnoreCase); + var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase); // If there's a collection type and it's not music, don't allow it. if (!isMusicMediaFolder) @@ -125,14 +121,28 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio ILibraryManager libraryManager) { var discSubfolderCount = 0; + var notMultiDisc = false; foreach (var fileSystemInfo in list) { if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { - if (allowSubfolders && IsAlbumSubfolder(fileSystemInfo, directoryService, logger, fileSystem, libraryManager)) + if (allowSubfolders) { - discSubfolderCount++; + var path = fileSystemInfo.FullName; + var isMultiDisc = IsMultiDiscFolder(path); + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); + + if (isMultiDisc && hasMusic) + { + logger.Debug("Found multi-disc folder: " + path); + discSubfolderCount++; + } + else if (hasMusic) + { + // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album + notMultiDisc = true; + } } } @@ -144,34 +154,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio } } - return discSubfolderCount > 0; - } - - private static bool IsAlbumSubfolder(FileSystemInfo directory, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager) - { - var path = directory.FullName; - - if (IsMultiDiscFolder(path)) + if (notMultiDisc) { - logger.Debug("Found multi-disc folder: " + path); - - return ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); + return false; } - return false; - } - - public static bool IsMultiDiscFolder(string path) - { - return IsMultiDiscAlbumFolder(path); + return discSubfolderCount > 0 && discSubfolderCount > 10; } - /// <summary> - /// Determines whether [is multi disc album folder] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <returns><c>true</c> if [is multi disc album folder] [the specified path]; otherwise, <c>false</c>.</returns> - private static bool IsMultiDiscAlbumFolder(string path) + private static bool IsMultiDiscFolder(string path) { var parser = new AlbumParser(new ExtendedNamingOptions(), new Naming.Logging.NullLogger()); var result = parser.ParseMultiPart(path); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 71b6c0843..edbc87415 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -34,7 +34,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// <value>The priority.</value> public override ResolverPriority Priority { - get { return ResolverPriority.Third; } // we need to be ahead of the generic folder resolver but behind the movie one + get + { + // Behind special folder resolver + return ResolverPriority.Second; + } } /// <summary> @@ -46,8 +50,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { if (!args.IsDirectory) return null; - //Avoid mis-identifying top folders - if (args.Parent == null) return null; + // Avoid mis-identifying top folders if (args.Parent.IsRoot) return null; // Don't allow nested artists @@ -56,16 +59,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio return null; } - // Optimization - if (args.HasParent<BoxSet>() || args.HasParent<Series>() || args.HasParent<Season>()) - { - return null; - } - var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, - StringComparison.OrdinalIgnoreCase); + var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase); // If there's a collection type and it's not music, it can't be a series if (!isMusicMediaFolder) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs index 166465f72..34237622d 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,10 +1,6 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; -using System; -using System.IO; -using System.Linq; namespace MediaBrowser.Server.Implementations.Library.Resolvers { @@ -13,13 +9,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers /// </summary> public class FolderResolver : FolderResolver<Folder> { - private readonly IFileSystem _fileSystem; - - public FolderResolver(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - /// <summary> /// Gets the priority. /// </summary> @@ -38,48 +27,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers { if (args.IsDirectory) { - if (args.IsPhysicalRoot) - { - return new AggregateFolder(); - } - if (args.IsRoot) - { - return new UserRootFolder(); //if we got here and still a root - must be user root - } - if (args.IsVf) - { - return new CollectionFolder - { - CollectionType = GetCollectionType(args) - }; - } - return new Folder(); } return null; } - - private string GetCollectionType(ItemResolveArgs args) - { - return args.FileSystemChildren - .Where(i => - { - - try - { - return (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && - string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase); - } - catch (IOException) - { - return false; - } - - }) - .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) - .FirstOrDefault(); - } } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index cc261c3e7..67b9d546f 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using System; using System.IO; +using System.Linq; namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { @@ -30,12 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { return null; } - - // This is a bit of a one-off but it's here to combat MCM's over-aggressive placement of collection.xml files where they don't belong, including in series folders. - if (args.ContainsMetaFileByName("series.xml")) - { - return null; - } if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || args.ContainsFileSystemEntryByName("collection.xml")) @@ -51,6 +46,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return null; } + private bool IsInvalid(string collectionType) + { + var validCollectionTypes = new[] + { + CollectionType.Movies, + CollectionType.BoxSets + }; + + return !validCollectionTypes.Contains(collectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + } + /// <summary> /// Sets the initial item values. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 276b99d3a..587c6668c 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,13 +1,9 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; using MediaBrowser.Naming.Common; using MediaBrowser.Naming.IO; using MediaBrowser.Naming.Video; @@ -23,16 +19,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// </summary> public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver { - private readonly IServerApplicationPaths _applicationPaths; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - - public MovieResolver(ILibraryManager libraryManager, IServerApplicationPaths applicationPaths, ILogger logger, IFileSystem fileSystem) - : base(libraryManager) + public MovieResolver(ILibraryManager libraryManager) : base(libraryManager) { - _applicationPaths = applicationPaths; - _logger = logger; - _fileSystem = fileSystem; } /// <summary> @@ -63,12 +51,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType); + return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos<Video>(parent, files, directoryService, collectionType); + return ResolveVideos<Video>(parent, files, directoryService, collectionType, false); } if (string.IsNullOrEmpty(collectionType)) @@ -76,22 +64,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies // Owned items should just use the plain video type if (parent == null) { - return ResolveVideos<Video>(parent, files, directoryService, collectionType); + return ResolveVideos<Video>(parent, files, directoryService, collectionType, false); } - return ResolveVideos<Movie>(parent, files, directoryService, collectionType); + return ResolveVideos<Video>(parent, files, directoryService, collectionType, false); } - if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || - string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { - return ResolveVideos<Movie>(parent, files, directoryService, collectionType); + return ResolveVideos<Movie>(parent, files, directoryService, collectionType, false); } return null; } - private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType) + private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool suppportMultiEditions) where T : Video, new() { var files = new List<FileSystemInfo>(); @@ -117,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies FullName = i.FullName, Type = FileInfoType.File - }).ToList()).ToList(); + }).ToList(), suppportMultiEditions).ToList(); var result = new MultiItemResolverResult { @@ -137,7 +124,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies IsInMixedFolder = isInMixedFolder, ProductionYear = video.Year, Name = video.Name, - AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList() + AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList(), + LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToList() }; SetVideoType(videoItem, firstVideo); @@ -168,12 +156,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType, false); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType, false); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); } if (string.IsNullOrEmpty(collectionType)) @@ -184,17 +172,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); } - // Since the looping is expensive, this is an optimization to help us avoid it - if (args.ContainsMetaFileByName("series.xml")) - { - return null; - } - - return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); } - if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || - string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType); } @@ -216,8 +197,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } // To find a movie file, the collection type must be movies or boxsets - else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || - string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { item = ResolveVideo<Movie>(args, true); } @@ -228,7 +208,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } else if (string.IsNullOrEmpty(collectionType)) { - item = ResolveVideo<Movie>(args, false); + item = ResolveVideo<Video>(args, false); } if (item != null) @@ -277,9 +257,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="fileSystemEntries">The file system entries.</param> /// <param name="directoryService">The directory service.</param> /// <param name="collectionType">Type of the collection.</param> - /// <param name="supportMultiVersion">if set to <c>true</c> [support multi version].</param> /// <returns>Movie.</returns> - private T FindMovie<T>(string path, Folder parent, List<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool supportMultiVersion = true) + private T FindMovie<T>(string path, Folder parent, List<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType) where T : Video, new() { var multiDiscFolders = new List<FileSystemInfo>(); @@ -325,34 +304,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return movie; } } - - var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType); - // Test for multi-editions - if (result.Items.Count > 1 && supportMultiVersion) - { - var filenamePrefix = Path.GetFileName(path); + var supportsMultiVersion = !string.Equals(collectionType, CollectionType.HomeVideos) && + !string.Equals(collectionType, CollectionType.MusicVideos); - if (!string.IsNullOrWhiteSpace(filenamePrefix)) - { - if (result.Items.All(i => _fileSystem.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) - { - var movie = (T)result.Items[0]; - movie.Name = filenamePrefix; - movie.LocalAlternateVersions = result.Items.Skip(1).Select(i => i.Path).ToList(); - movie.IsInMixedFolder = false; - - _logger.Debug("Multi-version video found: " + movie.Path); - - return movie; - } - } - } + var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType, supportsMultiVersion); if (result.Items.Count == 1) { var movie = (T)result.Items[0]; movie.IsInMixedFolder = false; + movie.Name = Path.GetFileName(movie.ContainingFolderPath); return movie; } @@ -453,52 +415,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } } - // Don't do any resolving within a series structure - if (string.IsNullOrEmpty(collectionType)) - { - if (HasParent<Series>(parent) || HasParent<Season>(parent)) - { - return true; - } - - // Since the looping is expensive, this is an optimization to help us avoid it - if (files.Select(i => i.Name).Contains("series.xml", StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } - var validCollectionTypes = new[] { string.Empty, CollectionType.Movies, CollectionType.HomeVideos, CollectionType.MusicVideos, - CollectionType.BoxSets, CollectionType.Movies }; return !validCollectionTypes.Contains(collectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); } - - private bool HasParent<T>(Folder parent) - where T : Folder - { - if (parent != null) - { - var item = parent as T; - - // Just in case the user decided to nest episodes. - // Not officially supported but in some cases we can handle it. - if (item == null) - { - item = parent.Parents.OfType<T>().FirstOrDefault(); - } - - return item != null; - - } - return false; - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 2fcfd7086..acae5b801 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; using System.IO; @@ -17,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers protected override PhotoAlbum Resolve(ItemResolveArgs args) { // Must be an image file within a photo collection - if (!args.IsRoot && args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase)) + if (args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase)) { if (HasPhotos(args)) { @@ -35,5 +36,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers { return args.FileSystemChildren.Any(i => ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) && PhotoResolver.IsImageFile(i.FullName)); } + + public override ResolverPriority Priority + { + get + { + // Behind special folder resolver + return ResolverPriority.Second; + } + } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 5580b442c..02960ea7e 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -17,8 +17,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers protected override Photo Resolve(ItemResolveArgs args) { // Must be an image file within a photo collection - if (!args.IsDirectory && - string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) && + if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) && + !args.IsDirectory && IsImageFile(args.Path)) { return new Photo diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs new file mode 100644 index 000000000..0a41a6c04 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -0,0 +1,79 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Resolvers; +using System; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Library.Resolvers +{ + class SpecialFolderResolver : FolderResolver<Folder> + { + private readonly IFileSystem _fileSystem; + + public SpecialFolderResolver(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override ResolverPriority Priority + { + get { return ResolverPriority.First; } + } + + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Folder.</returns> + protected override Folder Resolve(ItemResolveArgs args) + { + if (args.IsDirectory) + { + if (args.IsPhysicalRoot) + { + return new AggregateFolder(); + } + if (args.IsRoot) + { + return new UserRootFolder(); //if we got here and still a root - must be user root + } + if (args.IsVf) + { + return new CollectionFolder + { + CollectionType = GetCollectionType(args) + }; + } + } + + return null; + } + + private string GetCollectionType(ItemResolveArgs args) + { + return args.FileSystemChildren + .Where(i => + { + + try + { + return (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && + string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase); + } + catch (IOException) + { + return false; + } + + }) + .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) + .FirstOrDefault(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 057425ca9..1a873f01e 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -37,23 +37,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV } // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something - if (season != null || parent is Series || parent.Parents.OfType<Series>().Any()) + if (season != null || args.HasParent<Series>()) { var episode = ResolveVideo<Episode>(args, false); - if (episode != null) - { - if (season != null) - { - episode.ParentIndexNumber = season.IndexNumber; - } - - if (episode.ParentIndexNumber == null) - { - episode.ParentIndexNumber = SeriesResolver.GetSeasonNumberFromEpisodeFile(args.Path); - } - } - return episode; } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 058fb1489..80477e567 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -1,7 +1,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; +using MediaBrowser.Naming.Common; +using MediaBrowser.Naming.TV; namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { @@ -35,7 +36,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { var season = new Season { - IndexNumber = SeriesResolver.GetSeasonNumberFromPath(args.Path, CollectionType.TvShows) + IndexNumber = new SeasonPathParser(new ExtendedNamingOptions(), new RegexProvider()).Parse(args.Path, true).SeasonNumber }; if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 72c0bae53..7f1416ad6 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,18 +1,9 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Linq; -using System.Text.RegularExpressions; namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { @@ -21,17 +12,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV /// </summary> public class SeriesResolver : FolderResolver<Series> { - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - - public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager) - { - _fileSystem = fileSystem; - _logger = logger; - _libraryManager = libraryManager; - } - /// <summary> /// Gets the priority. /// </summary> @@ -53,33 +33,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (args.IsDirectory) { - // Avoid expensive tests against VF's and all their children by not allowing this - if (args.Parent == null || args.Parent.IsRoot) - { - return null; - } - - // Optimization to avoid running these tests against Seasons - if (args.HasParent<Series>() || args.HasParent<Season>() || args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>()) - { - return null; - } - var collectionType = args.GetCollectionType(); - var isTvShowsFolder = string.Equals(collectionType, CollectionType.TvShows, - StringComparison.OrdinalIgnoreCase); - // If there's a collection type and it's not tv, it can't be a series - if (!string.IsNullOrEmpty(collectionType) && - !isTvShowsFolder && - !string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - if (IsSeriesFolder(args.Path, collectionType, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager)) + if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { + if (args.HasParent<Series>()) + { + return null; + } + return new Series { Path = args.Path, @@ -92,434 +55,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV } /// <summary> - /// Determines whether [is series folder] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="collectionType">Type of the collection.</param> - /// <param name="fileSystemChildren">The file system children.</param> - /// <param name="directoryService">The directory service.</param> - /// <param name="fileSystem">The file system.</param> - /// <param name="logger">The logger.</param> - /// <param name="libraryManager">The library manager.</param> - /// <returns><c>true</c> if [is series folder] [the specified path]; otherwise, <c>false</c>.</returns> - public static bool IsSeriesFolder(string path, string collectionType, IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager) - { - foreach (var child in fileSystemChildren) - { - var attributes = child.Attributes; - - if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) - { - //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); - continue; - } - - // Can't enforce this because files saved by Bitcasa are always marked System - //if ((attributes & FileAttributes.System) == FileAttributes.System) - //{ - // logger.Debug("Igoring series subfolder marked system: {0}", child.FullName); - // continue; - //} - - if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) - { - if (IsSeasonFolder(child.FullName, collectionType, directoryService, fileSystem)) - { - //logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName); - return true; - } - } - else - { - var fullName = child.FullName; - - if (libraryManager.IsVideoFile(fullName) || IsVideoPlaceHolder(fullName)) - { - var isTvShowsFolder = string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase); - - if (GetEpisodeNumberFromFile(fullName, isTvShowsFolder).HasValue) - { - return true; - } - } - } - } - - logger.Debug("{0} is not a series folder.", path); - return false; - } - - /// <summary> - /// Determines whether [is place holder] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <returns><c>true</c> if [is place holder] [the specified path]; otherwise, <c>false</c>.</returns> - /// <exception cref="System.ArgumentNullException">path</exception> - private static bool IsVideoPlaceHolder(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - var extension = Path.GetExtension(path); - - return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase); - } - - /// <summary> - /// Determines whether [is season folder] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="collectionType">Type of the collection.</param> - /// <param name="directoryService">The directory service.</param> - /// <param name="fileSystem">The file system.</param> - /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns> - private static bool IsSeasonFolder(string path, string collectionType, IDirectoryService directoryService, IFileSystem fileSystem) - { - var seasonNumber = GetSeasonNumberFromPath(path, collectionType); - var hasSeasonNumber = seasonNumber != null; - - if (!hasSeasonNumber) - { - return false; - } - - //// It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3 - //foreach (var fileSystemInfo in directoryService.GetFileSystemEntries(path)) - //{ - // var attributes = fileSystemInfo.Attributes; - - // if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) - // { - // continue; - // } - - // // Can't enforce this because files saved by Bitcasa are always marked System - // //if ((attributes & FileAttributes.System) == FileAttributes.System) - // //{ - // // continue; - // //} - - // if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) - // { - // //if (IsBadFolder(fileSystemInfo.Name)) - // //{ - // // return false; - // //} - // } - // else - // { - // if (EntityResolutionHelper.IsAudioFile(fileSystemInfo.FullName) && - // !string.Equals(fileSystem.GetFileNameWithoutExtension(fileSystemInfo), BaseItem.ThemeSongFilename)) - // { - // return false; - // } - // } - //} - - return true; - } - - /// <summary> - /// A season folder must contain one of these somewhere in the name - /// </summary> - private static readonly string[] SeasonFolderNames = - { - "season", - "sæson", - "temporada", - "saison", - "staffel", - "series", - "сезон" - }; - - /// <summary> - /// Used to detect paths that represent episodes, need to make sure they don't also - /// match movie titles like "2001 A Space..." - /// Currently we limit the numbers here to 2 digits to try and avoid this - /// </summary> - private static readonly Regex[] EpisodeExpressions = - { - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$", - RegexOptions.Compiled) - }; - private static readonly Regex[] MultipleEpisodeExpressions = - { - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled) - }; - - /// <summary> - /// To avoid the following matching movies they are only valid when contained in a folder which has been matched as a being season, or the media type is TV series - /// </summary> - private static readonly Regex[] EpisodeExpressionsWithoutSeason = - { - new Regex( - @".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$", - RegexOptions.Compiled), - // "01.avi" - new Regex( - @".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$", - RegexOptions.Compiled), - // "01 - blah.avi", "01-blah.avi" - new Regex( - @".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.[^\\\/]+$", - RegexOptions.Compiled), - // "01.blah.avi" - new Regex( - @".*[\\\/][^\\\/]* - (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$", - RegexOptions.Compiled), - // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" - }; - - public static int? GetSeasonNumberFromPath(string path) - { - return GetSeasonNumberFromPath(path, CollectionType.TvShows); - } - - /// <summary> - /// Gets the season number from path. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="collectionType">Type of the collection.</param> - /// <returns>System.Nullable{System.Int32}.</returns> - public static int? GetSeasonNumberFromPath(string path, string collectionType) - { - var filename = Path.GetFileName(path); - - if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase)) - { - return 0; - } - } - - int val; - if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) - { - return val; - } - - if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase)) - { - var testFilename = filename.Substring(1); - - if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) - { - return val; - } - } - - // Look for one of the season folder names - foreach (var name in SeasonFolderNames) - { - var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase); - - if (index != -1) - { - return GetSeasonNumberFromPathSubstring(filename.Substring(index + name.Length)); - } - } - - return null; - } - - /// <summary> - /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel") - /// </summary> - /// <param name="path">The path.</param> - /// <returns>System.Nullable{System.Int32}.</returns> - private static int? GetSeasonNumberFromPathSubstring(string path) - { - var numericStart = -1; - var length = 0; - - // Find out where the numbers start, and then keep going until they end - for (var i = 0; i < path.Length; i++) - { - if (char.IsNumber(path, i)) - { - if (numericStart == -1) - { - numericStart = i; - } - length++; - } - else if (numericStart != -1) - { - break; - } - } - - if (numericStart == -1) - { - return null; - } - - return int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture); - } - - /// <summary> - /// Episodes the number from file. - /// </summary> - /// <param name="fullPath">The full path.</param> - /// <param name="considerSeasonlessNames">if set to <c>true</c> [is in season].</param> - /// <returns>System.String.</returns> - public static int? GetEpisodeNumberFromFile(string fullPath, bool considerSeasonlessNames) - { - string fl = fullPath.ToLower(); - foreach (var r in EpisodeExpressions) - { - Match m = r.Match(fl); - if (m.Success) - return ParseEpisodeNumber(m.Groups["epnumber"].Value); - } - if (considerSeasonlessNames) - { - var match = EpisodeExpressionsWithoutSeason.Select(r => r.Match(fl)) - .FirstOrDefault(m => m.Success); - - if (match != null) - { - return ParseEpisodeNumber(match.Groups["epnumber"].Value); - } - } - - return null; - } - - public static int? GetEndingEpisodeNumberFromFile(string fullPath) - { - var fl = fullPath.ToLower(); - foreach (var r in MultipleEpisodeExpressions) - { - var m = r.Match(fl); - if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value)) - return ParseEpisodeNumber(m.Groups["endingepnumber"].Value); - } - foreach (var r in EpisodeExpressionsWithoutSeason) - { - var m = r.Match(fl); - if (m.Success && !string.IsNullOrEmpty(m.Groups["endingepnumber"].Value)) - return ParseEpisodeNumber(m.Groups["endingepnumber"].Value); - } - return null; - } - - /// <summary> - /// Seasons the number from episode file. - /// </summary> - /// <param name="fullPath">The full path.</param> - /// <returns>System.String.</returns> - public static int? GetSeasonNumberFromEpisodeFile(string fullPath) - { - string fl = fullPath.ToLower(); - foreach (var r in EpisodeExpressions) - { - Match m = r.Match(fl); - if (m.Success) - { - Group g = m.Groups["seasonnumber"]; - if (g != null) - { - var val = g.Value; - - if (!string.IsNullOrWhiteSpace(val)) - { - int num; - - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num)) - { - return num; - } - } - } - return null; - } - } - return null; - } - - public static string GetSeriesNameFromEpisodeFile(string fullPath) - { - var fl = fullPath.ToLower(); - foreach (var r in EpisodeExpressions) - { - var m = r.Match(fl); - if (m.Success) - { - var g = m.Groups["seriesname"]; - if (g != null) - { - var val = g.Value; - - if (!string.IsNullOrWhiteSpace(val)) - { - return val; - } - } - return null; - } - } - return null; - } - - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private static int? ParseEpisodeNumber(string val) - { - int num; - - if (!string.IsNullOrEmpty(val) && int.TryParse(val, NumberStyles.Integer, UsCulture, out num)) - { - return num; - } - - return null; - } - - /// <summary> /// Sets the initial item values. /// </summary> /// <param name="item">The item.</param> |
