diff options
| author | 7illusions <z@7illusions.com> | 2014-08-30 19:06:58 +0200 |
|---|---|---|
| committer | 7illusions <z@7illusions.com> | 2014-08-30 19:06:58 +0200 |
| commit | 66ad1699e22029b605e17735e8d9450285d8748a (patch) | |
| tree | ffc92c88d24850b2f82b6b3a8bdd904a2ccc77a5 /MediaBrowser.Server.Implementations/Library | |
| parent | 34bc54263e886aae777a3537dc50a6535b51330a (diff) | |
| parent | 9d36f518182bc075c19d78084870f5115fa62d1e (diff) | |
Merge pull request #1 from MediaBrowser/master
Update to latest
Diffstat (limited to 'MediaBrowser.Server.Implementations/Library')
17 files changed, 593 insertions, 166 deletions
diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 17118f4b4..7b58dd7c4 100644 --- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -14,21 +14,6 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> public class CoreResolutionIgnoreRule : IResolverIgnoreRule { - /// <summary> - /// Any folder named in this list will be ignored - can be added to at runtime for extensibility - /// </summary> - private static readonly Dictionary<string, string> IgnoreFolders = new List<string> - { - "metadata", - "ps3_update", - "ps3_vprm", - "extrafanart", - "extrathumbs", - ".actors", - ".wd_tv" - - }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - private readonly IFileSystem _fileSystem; public CoreResolutionIgnoreRule(IFileSystem fileSystem) @@ -78,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.Library if (args.IsDirectory) { // Ignore any folders in our list - if (IgnoreFolders.ContainsKey(filename)) + if (EntityResolutionHelper.IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase)) { return true; } @@ -105,7 +90,7 @@ namespace MediaBrowser.Server.Implementations.Library if (args.Parent != null) { // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(filename)) + if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(filename)) { return true; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 64e0a6662..297d5e032 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -906,7 +906,7 @@ namespace MediaBrowser.Server.Implementations.Library // Ensure the location is available. Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); - return new PeopleValidator(this, _logger).ValidatePeople(cancellationToken, new MetadataRefreshOptions(), progress); + return new PeopleValidator(this, _logger, ConfigurationManager).ValidatePeople(cancellationToken, progress); } /// <summary> @@ -1157,7 +1157,7 @@ namespace MediaBrowser.Server.Implementations.Library private string GetCollectionType(string path) { return new DirectoryInfo(path).EnumerateFiles("*.collection", SearchOption.TopDirectoryOnly) - .Select(i => Path.GetFileNameWithoutExtension(i.FullName)) + .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) .FirstOrDefault(); } @@ -1486,23 +1486,22 @@ namespace MediaBrowser.Server.Implementations.Library public async Task<UserView> GetNamedView(string name, string type, string sortName, CancellationToken cancellationToken) { - var id = "namedview_3_" + name; - var guid = id.GetMD5(); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, + "views", + _fileSystem.GetValidFilename(type)); - var item = GetItemById(guid) as UserView; + var id = (path + "_namedview_" + name).GetMBId(typeof(UserView)); + + var item = GetItemById(id) as UserView; if (item == null) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, - "views", - _fileSystem.GetValidFilename(type)); - Directory.CreateDirectory(Path.GetDirectoryName(path)); item = new UserView { Path = path, - Id = guid, + Id = id, DateCreated = DateTime.UtcNow, Name = name, ViewType = type, diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs index 9d5826454..7ffbab860 100644 --- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs @@ -18,15 +18,20 @@ namespace MediaBrowser.Server.Implementations.Library public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user) { - return GetInstantMixFromGenres(item.Genres, user); + var list = new List<Audio> + { + item + }; + + return list.Concat(GetInstantMixFromGenres(item.Genres, user)); } public IEnumerable<Audio> GetInstantMixFromArtist(string name, User user) { var artist = _libraryManager.GetArtist(name); - var genres = _libraryManager.RootFolder - .RecursiveChildren + var genres = user.RootFolder + .GetRecursiveChildren(user) .OfType<Audio>() .Where(i => i.HasArtist(name)) .SelectMany(i => i.Genres) @@ -39,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.Library public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user) { var genres = item - .RecursiveChildren + .GetRecursiveChildren(user, true) .OfType<Audio>() .SelectMany(i => i.Genres) .Concat(item.Genres) @@ -57,6 +62,7 @@ namespace MediaBrowser.Server.Implementations.Library return inputItems .OfType<Audio>() .Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey))) + .Where(i => i.Item2 > 0) .OrderByDescending(i => i.Item2) .ThenBy(i => Guid.NewGuid()) .Select(i => i.Item1) diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 28d476971..1f9dc56f9 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -6,6 +7,7 @@ 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.IO; @@ -17,6 +19,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// </summary> public class MusicAlbumResolver : ItemResolver<MusicAlbum> { + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + /// <summary> /// Gets the priority. /// </summary> @@ -45,17 +56,19 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { return null; } - + var collectionType = args.GetCollectionType(); + 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 (!string.IsNullOrEmpty(collectionType) && - !string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) + if (!isMusicMediaFolder) { return null; } - - return IsMusicAlbum(args) ? new MusicAlbum() : null; + + return IsMusicAlbum(args, isMusicMediaFolder) ? new MusicAlbum() : null; } @@ -63,50 +76,29 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// Determine if the supplied file data points to a music album /// </summary> /// <param name="path">The path.</param> + /// <param name="isMusicMediaFolder">if set to <c>true</c> [is music media folder].</param> /// <param name="directoryService">The directory service.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">The file system.</param> /// <returns><c>true</c> if [is music album] [the specified data]; otherwise, <c>false</c>.</returns> - public static bool IsMusicAlbum(string path, IDirectoryService directoryService) + public static bool IsMusicAlbum(string path, bool isMusicMediaFolder, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem) { - // If list contains at least 2 audio files or at least one and no video files consider it to contain music - var foundAudio = 0; - - foreach (var file in directoryService.GetFiles(path)) - { - var fullName = file.FullName; - - if (EntityResolutionHelper.IsAudioFile(fullName)) - { - // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) - { - continue; - } - - foundAudio++; - } - if (foundAudio >= 2) - { - return true; - } - if (EntityResolutionHelper.IsVideoFile(fullName)) return false; - } - - // or a single audio file and no video files - return foundAudio > 0; + return ContainsMusic(directoryService.GetFileSystemEntries(path), isMusicMediaFolder, true, directoryService, logger, fileSystem); } /// <summary> /// Determine if the supplied resolve args should be considered a music album /// </summary> /// <param name="args">The args.</param> + /// <param name="isMusicMediaFolder">if set to <c>true</c> [is music media folder].</param> /// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns> - public static bool IsMusicAlbum(ItemResolveArgs args) + private bool IsMusicAlbum(ItemResolveArgs args, bool isMusicMediaFolder) { // Args points to an album if parent is an Artist folder or it directly contains music if (args.IsDirectory) { //if (args.Parent is MusicArtist) return true; //saves us from testing children twice - if (ContainsMusic(args.FileSystemChildren)) return true; + if (ContainsMusic(args.FileSystemChildren, isMusicMediaFolder, true, args.DirectoryService, _logger, _fileSystem)) return true; } return false; @@ -116,27 +108,86 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// Determine if the supplied list contains what we should consider music /// </summary> /// <param name="list">The list.</param> + /// <param name="isMusicMediaFolder">if set to <c>true</c> [is music media folder].</param> + /// <param name="allowSubfolders">if set to <c>true</c> [allow subfolders].</param> + /// <param name="directoryService">The directory service.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">The file system.</param> /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns> - private static bool ContainsMusic(IEnumerable<FileSystemInfo> list) + private static bool ContainsMusic(IEnumerable<FileSystemInfo> list, + bool isMusicMediaFolder, + bool allowSubfolders, + IDirectoryService directoryService, + ILogger logger, + IFileSystem fileSystem) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; - foreach (var file in list) + var discSubfolderCount = 0; + + foreach (var fileSystemInfo in list) { - var fullName = file.FullName; + if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + if (isMusicMediaFolder && allowSubfolders && IsAlbumSubfolder(fileSystemInfo, true, directoryService, logger, fileSystem)) + { + discSubfolderCount++; + } + if (!IsAdditionalSubfolderAllowed(fileSystemInfo)) + { + return false; + } + } + + var fullName = fileSystemInfo.FullName; + + if (EntityResolutionHelper.IsAudioFile(fullName)) + { + // Don't resolve these into audio files + if (string.Equals(fileSystem.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename)) + { + continue; + } + + foundAudio++; + } + else if (EntityResolutionHelper.IsVideoFile(fullName)) return false; + else if (EntityResolutionHelper.IsVideoPlaceHolder(fullName)) return false; - if (EntityResolutionHelper.IsAudioFile(fullName)) foundAudio++; if (foundAudio >= 2) { return true; } - if (EntityResolutionHelper.IsVideoFile(fullName)) return false; - if (EntityResolutionHelper.IsVideoPlaceHolder(fullName)) return false; } // or a single audio file and no video files - return foundAudio > 0; + return foundAudio > 0 || discSubfolderCount > 0; + } + + private static bool IsAlbumSubfolder(FileSystemInfo directory, bool isMusicMediaFolder, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem) + { + var path = directory.FullName; + + if (IsMultiDiscFolder(path)) + { + logger.Debug("Found multi-disc folder: " + path); + + return ContainsMusic(directoryService.GetFileSystemEntries(path), isMusicMediaFolder, false, directoryService, logger, fileSystem); + } + + return false; + } + + public static bool IsMultiDiscFolder(string path) + { + return EntityResolutionHelper.IsMultiDiscAlbumFolder(path); + } + + private static bool IsAdditionalSubfolderAllowed(FileSystemInfo directory) + { + // Resolver will ignore them based on rules engine + return true; } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 4fa97fc9d..2417d5dcb 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.IO; using System.Linq; @@ -15,6 +17,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// </summary> public class MusicArtistResolver : ItemResolver<MusicArtist> { + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public MusicArtistResolver(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + /// <summary> /// Gets the priority. /// </summary> @@ -51,9 +62,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); + 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 (!string.IsNullOrEmpty(collectionType) && - !string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) + if (!isMusicMediaFolder) { return null; } @@ -61,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var directoryService = args.DirectoryService; // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => MusicAlbumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; + return args.FileSystemChildren.Where(i => (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory).Any(i => MusicAlbumResolver.IsMusicAlbum(i.FullName, isMusicMediaFolder, directoryService, _logger, _fileSystem)) ? new MusicArtist() : null; } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs index 594277ef7..166465f72 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using System; @@ -12,6 +13,13 @@ 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> @@ -69,7 +77,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers } }) - .Select(i => Path.GetFileNameWithoutExtension(i.FullName)) + .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) .FirstOrDefault(); } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs index 10ee3586d..b483f7c42 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/LocalTrailerResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using System; @@ -11,6 +12,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers /// </summary> public class LocalTrailerResolver : BaseVideoResolver<Trailer> { + private readonly IFileSystem _fileSystem; + + public LocalTrailerResolver(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + /// <summary> /// Resolves the specified args. /// </summary> @@ -33,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers } // Support xbmc local trailer convention, but only when looking for local trailers (hence the parent == null check) - if (args.Parent == null && Path.GetFileNameWithoutExtension(args.Path).EndsWith(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase)) + if (args.Parent == null && _fileSystem.GetFileNameWithoutExtension(args.Path).EndsWith(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase)) { return base.Resolve(args); } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 905e6e676..215cff22f 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -6,11 +7,11 @@ 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.IO; using System.Linq; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { @@ -22,12 +23,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies private readonly IServerApplicationPaths _applicationPaths; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; - public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager, ILogger logger) + public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) { _applicationPaths = appPaths; _libraryManager = libraryManager; _logger = logger; + _fileSystem = fileSystem; } /// <summary> @@ -79,29 +82,29 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false, false); + return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, false, false); } if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false, false); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, false, false); } if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, false); + return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, true, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, false); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, true, false); } if (string.IsNullOrEmpty(collectionType) || string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, true); + return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, true, true); } return null; @@ -187,7 +190,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="directoryService">The directory service.</param> /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param> /// <returns>Movie.</returns> - private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems, bool supportsMultipleSources) + private T FindMovie<T>(string path, Folder parent, List<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems, bool supportsMultipleSources) where T : Video, new() { var movies = new List<T>(); @@ -407,7 +410,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrWhiteSpace(filenamePrefix)) { - if (sortedMovies.All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) + if (sortedMovies.All(i => _fileSystem.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) { firstMovie.HasLocalAlternateVersions = true; diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs new file mode 100644 index 000000000..2fcfd7086 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -0,0 +1,39 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using System; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Library.Resolvers +{ + public class PhotoAlbumResolver : FolderResolver<PhotoAlbum> + { + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>Trailer.</returns> + 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 (HasPhotos(args)) + { + return new PhotoAlbum + { + Path = args.Path + }; + } + } + + return null; + } + + private static bool HasPhotos(ItemResolveArgs args) + { + return args.FileSystemChildren.Any(i => ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) && PhotoResolver.IsImageFile(i.FullName)); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index cba7aba9a..60e7edfdd 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,24 +1,14 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; using System; +using System.IO; using System.Linq; namespace MediaBrowser.Server.Implementations.Library.Resolvers { public class PhotoResolver : ItemResolver<Photo> { - private readonly IServerApplicationPaths _applicationPaths; - - /// <summary> - /// Initializes a new instance of the <see cref="PhotoResolver" /> class. - /// </summary> - /// <param name="applicationPaths">The application paths.</param> - public PhotoResolver(IServerApplicationPaths applicationPaths) - { - _applicationPaths = applicationPaths; - } - /// <summary> /// Resolves the specified args. /// </summary> @@ -27,7 +17,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers protected override Photo Resolve(ItemResolveArgs args) { // Must be an image file within a photo collection - if (!args.IsDirectory && IsImageFile(args.Path) && string.Equals(args.GetCollectionType(), "photos", StringComparison.OrdinalIgnoreCase)) + if (!args.IsDirectory && IsImageFile(args.Path) && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase)) { return new Photo { @@ -39,10 +29,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers } protected static string[] ImageExtensions = { ".tiff", ".jpeg", ".jpg", ".png", ".aiff" }; - protected bool IsImageFile(string path) + internal static bool IsImageFile(string path) { - return !path.EndsWith("folder.jpg", StringComparison.OrdinalIgnoreCase) - && ImageExtensions.Any(p => path.EndsWith(p, StringComparison.OrdinalIgnoreCase)); + var filename = Path.GetFileName(path); + + return !string.Equals(filename, "folder.jpg", StringComparison.OrdinalIgnoreCase) + && ImageExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase); } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs new file mode 100644 index 000000000..7eff53ce1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -0,0 +1,38 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using System; +using System.IO; + +namespace MediaBrowser.Server.Implementations.Library.Resolvers +{ + public class PlaylistResolver : FolderResolver<Playlist> + { + /// <summary> + /// Resolves the specified args. + /// </summary> + /// <param name="args">The args.</param> + /// <returns>BoxSet.</returns> + protected override Playlist Resolve(ItemResolveArgs args) + { + // It's a boxset if all of the following conditions are met: + // Is a Directory + // Contains [playlist] in the path + if (args.IsDirectory) + { + var filename = Path.GetFileName(args.Path); + + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1) + { + return new Playlist { Path = args.Path }; + } + } + + return null; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 6b376d3b4..d3aad582a 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,9 +1,11 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.IO; @@ -14,6 +16,15 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV /// </summary> public class SeriesResolver : FolderResolver<Series> { + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + + public SeriesResolver(IFileSystem fileSystem, ILogger logger) + { + _fileSystem = fileSystem; + _logger = logger; + } + /// <summary> /// Gets the priority. /// </summary> @@ -49,32 +60,18 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV 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) && - !string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) && + !isTvShowsFolder && !string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) { return null; } - - // It's a Series if any of the following conditions are met: - // series.xml exists - // [tvdbid= is present in the path - // TVUtils.IsSeriesFolder returns true - var filename = Path.GetFileName(args.Path); - - if (string.IsNullOrEmpty(filename)) - { - return null; - } - // Without these movies that have the name season in them could cause the parent folder to be resolved as a series - if (filename.IndexOf("[tmdbid=", StringComparison.OrdinalIgnoreCase) != -1) - { - return null; - } - - if (args.ContainsMetaFileByName("series.xml") || filename.IndexOf("[tvdbid=", StringComparison.OrdinalIgnoreCase) != -1 || TVUtils.IsSeriesFolder(args.Path, collectionType == CollectionType.TvShows, args.FileSystemChildren, args.DirectoryService)) + if (TVUtils.IsSeriesFolder(args.Path, isTvShowsFolder, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger)) { return new Series(); } diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index 4c65fad68..6faa72b81 100644 --- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Library { var user = _userManager.GetUserById(new Guid(query.UserId)); - inputItems = user.RootFolder.GetRecursiveChildren(user, null); + inputItems = user.RootFolder.GetRecursiveChildren(user, true); } diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs index 79f126511..d3030f31f 100644 --- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -125,5 +126,41 @@ namespace MediaBrowser.Server.Implementations.Library { return userId + key; } + + public UserItemDataDto GetUserDataDto(IHasUserData item, User user) + { + var userData = GetUserData(user.Id, item.GetUserDataKey()); + var dto = GetUserItemDataDto(userData); + + item.FillUserDataDtoValues(dto, userData, user); + + return dto; + } + + /// <summary> + /// Converts a UserItemData to a DTOUserItemData + /// </summary> + /// <param name="data">The data.</param> + /// <returns>DtoUserItemData.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + private UserItemDataDto GetUserItemDataDto(UserItemData data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + Key = data.Key + }; + } } } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index f9d7479ce..94cc61240 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -1,14 +1,20 @@ using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using MediaBrowser.Server.Implementations.Security; using System; using System.Collections.Generic; using System.IO; @@ -47,20 +53,29 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> /// <value>The user repository.</value> private IUserRepository UserRepository { get; set; } + public event EventHandler<GenericEventArgs<User>> UserPasswordChanged; private readonly IXmlSerializer _xmlSerializer; + private readonly INetworkManager _networkManager; + + private readonly Func<IImageProcessor> _imageProcessorFactory; + private readonly Func<IDtoService> _dtoServiceFactory; + /// <summary> /// Initializes a new instance of the <see cref="UserManager" /> class. /// </summary> /// <param name="logger">The logger.</param> /// <param name="configurationManager">The configuration manager.</param> /// <param name="userRepository">The user repository.</param> - public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer) + public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory) { _logger = logger; UserRepository = userRepository; _xmlSerializer = xmlSerializer; + _networkManager = networkManager; + _imageProcessorFactory = imageProcessorFactory; + _dtoServiceFactory = dtoServiceFactory; ConfigurationManager = configurationManager; Users = new List<User>(); } @@ -118,28 +133,26 @@ namespace MediaBrowser.Server.Implementations.Library Users = await LoadUsers().ConfigureAwait(false); } - /// <summary> - /// Authenticates a User and returns a result indicating whether or not it succeeded - /// </summary> - /// <param name="user">The user.</param> - /// <param name="password">The password.</param> - /// <returns>Task{System.Boolean}.</returns> - /// <exception cref="System.ArgumentNullException">user</exception> - public async Task<bool> AuthenticateUser(User user, string password) + public async Task<bool> AuthenticateUser(string username, string password, string remoteEndPoint) { - if (user == null) + if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException("username"); } + var user = Users.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + if (user.Configuration.IsDisabled) { - throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + throw new AuthenticationException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); } - var existingPasswordString = string.IsNullOrEmpty(user.Password) ? GetSha1String(string.Empty) : user.Password; + var success = string.Equals(GetPasswordHash(user), password.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - var success = string.Equals(existingPasswordString, password.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + success = string.Equals(GetLocalPasswordHash(user), password.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } // Update LastActivityDate and LastLoginDate, then save if (success) @@ -153,6 +166,25 @@ namespace MediaBrowser.Server.Implementations.Library return success; } + private string GetPasswordHash(User user) + { + return string.IsNullOrEmpty(user.Password) + ? GetSha1String(string.Empty) + : user.Password; + } + + private string GetLocalPasswordHash(User user) + { + return string.IsNullOrEmpty(user.LocalPassword) + ? GetSha1String(string.Empty) + : user.LocalPassword; + } + + private bool IsPasswordEmpty(string passwordHash) + { + return string.Equals(passwordHash, GetSha1String(string.Empty), StringComparison.OrdinalIgnoreCase); + } + /// <summary> /// Gets the sha1 string. /// </summary> @@ -192,6 +224,65 @@ namespace MediaBrowser.Server.Implementations.Library return users; } + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + var passwordHash = GetPasswordHash(user); + + var hasConfiguredDefaultPassword = !IsPasswordEmpty(passwordHash); + + var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + !IsPasswordEmpty(GetLocalPasswordHash(user)) : + hasConfiguredDefaultPassword; + + var dto = new UserDto + { + Id = user.Id.ToString("N"), + Name = user.Name, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredDefaultPassword, + LastActivityDate = user.LastActivityDate, + LastLoginDate = user.LastLoginDate, + Configuration = user.Configuration + }; + + var image = user.GetImageInfo(ImageType.Primary, 0); + + if (image != null) + { + dto.PrimaryImageTag = GetImageCacheTag(user, image); + + try + { + _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, user.Name); + } + } + + return dto; + } + + private string GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessorFactory().GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting {0} image info for {1}", ex, image.Type, image.Path); + return null; + } + } + /// <summary> /// Refreshes metadata for each user /// </summary> @@ -278,7 +369,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <exception cref="System.ArgumentException"></exception> public async Task<User> CreateUser(string name) { - if (string.IsNullOrEmpty(name)) + if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException("name"); } @@ -386,16 +477,18 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="user">The user.</param> /// <param name="newPassword">The new password.</param> /// <returns>Task.</returns> - public Task ChangePassword(User user, string newPassword) + public async Task ChangePassword(User user, string newPassword) { if (user == null) { throw new ArgumentNullException("user"); } - user.Password = string.IsNullOrEmpty(newPassword) ? string.Empty : GetSha1String(newPassword); + user.Password = string.IsNullOrEmpty(newPassword) ? GetSha1String(string.Empty) : GetSha1String(newPassword); + + await UpdateUser(user).ConfigureAwait(false); - return UpdateUser(user); + EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index fc4b9eb4c..63aa3764c 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -7,11 +9,14 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; +using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -27,8 +32,10 @@ namespace MediaBrowser.Server.Implementations.Library private readonly IChannelManager _channelManager; private readonly ILiveTvManager _liveTvManager; + private readonly IServerApplicationPaths _appPaths; + private readonly IPlaylistManager _playlists; - public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IFileSystem fileSystem, IUserManager userManager, IChannelManager channelManager, ILiveTvManager liveTvManager) + public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IFileSystem fileSystem, IUserManager userManager, IChannelManager channelManager, ILiveTvManager liveTvManager, IServerApplicationPaths appPaths, IPlaylistManager playlists) { _libraryManager = libraryManager; _localizationManager = localizationManager; @@ -36,6 +43,8 @@ namespace MediaBrowser.Server.Implementations.Library _userManager = userManager; _channelManager = channelManager; _liveTvManager = liveTvManager; + _appPaths = appPaths; + _playlists = playlists; } public async Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken) @@ -62,40 +71,58 @@ namespace MediaBrowser.Server.Implementations.Library if (recursiveChildren.OfType<Series>().Any()) { - list.Add(await GetUserView(CollectionType.TvShows, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.TvShows, user, string.Empty, cancellationToken).ConfigureAwait(false)); } if (recursiveChildren.OfType<MusicAlbum>().Any() || recursiveChildren.OfType<MusicVideo>().Any()) { - list.Add(await GetUserView(CollectionType.Music, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.Music, user, string.Empty, cancellationToken).ConfigureAwait(false)); } if (recursiveChildren.OfType<Movie>().Any()) { - list.Add(await GetUserView(CollectionType.Movies, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.Movies, user, string.Empty, cancellationToken).ConfigureAwait(false)); } if (recursiveChildren.OfType<Game>().Any()) { - list.Add(await GetUserView(CollectionType.Games, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.Games, user, string.Empty, cancellationToken).ConfigureAwait(false)); } - if (recursiveChildren.OfType<BoxSet>().Any()) + if (user.Configuration.DisplayCollectionsView && + recursiveChildren.OfType<BoxSet>().Any()) { - list.Add(await GetUserView(CollectionType.BoxSets, user, cancellationToken).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.BoxSets, user, string.Empty, cancellationToken).ConfigureAwait(false)); + } + + if (recursiveChildren.OfType<Playlist>().Any()) + { + list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N"))); + } + + if (user.Configuration.DisplayFoldersView) + { + list.Add(await GetUserView(CollectionType.Folders, user, "zz_" + CollectionType.Folders, cancellationToken).ConfigureAwait(false)); } if (query.IncludeExternalContent) { var channelResult = await _channelManager.GetChannels(new ChannelQuery { - Limit = 0, UserId = query.UserId }, cancellationToken).ConfigureAwait(false); - if (channelResult.TotalRecordCount > 0) + var channels = channelResult.Items; + + var embeddedChannels = channels + .Where(i => user.Configuration.DisplayChannelsWithinViews.Contains(i.Id)) + .ToList(); + + list.AddRange(embeddedChannels.Select(i => _channelManager.GetChannel(i.Id))); + + if (channels.Length > embeddedChannels.Count) { list.Add(await _channelManager.GetInternalChannelFolder(query.UserId, cancellationToken).ConfigureAwait(false)); } @@ -106,14 +133,58 @@ namespace MediaBrowser.Server.Implementations.Library } } - return list.OrderBy(i => i.SortName); + var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); + + var orders = user.Configuration.OrderedViews.ToList(); + + return list + .OrderBy(i => + { + var index = orders.IndexOf(i.Id.ToString("N")); + + return index == -1 ? int.MaxValue : index; + }) + .ThenBy(sorted.IndexOf) + .ThenBy(i => i.SortName); } - private Task<UserView> GetUserView(string type, User user, CancellationToken cancellationToken) + public Task<UserView> GetUserView(string type, User user, string sortName, CancellationToken cancellationToken) { var name = _localizationManager.GetLocalizedString("ViewType" + type); - return _libraryManager.GetNamedView(name, type, string.Empty, cancellationToken); + return _libraryManager.GetNamedView(name, type, sortName, cancellationToken); + } + + public async Task<SpecialFolder> GetSpecialFolder(string name, SpecialFolderType type, string itemType, CancellationToken cancellationToken) + { + var path = Path.Combine(_appPaths.ItemsByNamePath, + "specialfolders", + _fileSystem.GetValidFilename(name)); + + var id = (path + "_specialfolder_" + name).GetMBId(typeof(SpecialFolder)); + + var item = _libraryManager.GetItemById(id) as SpecialFolder; + + if (item == null) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + item = new SpecialFolder + { + Path = path, + Id = id, + DateCreated = DateTime.UtcNow, + Name = name, + SpecialFolderType = type, + ItemTypeName = itemType + }; + + await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + + return item; } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs index 05c972a4e..059ad2481 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -1,8 +1,13 @@ using MediaBrowser.Common.Progress; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -23,46 +28,128 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILogger _logger; + private readonly IServerConfigurationManager _config; + /// <summary> /// Initializes a new instance of the <see cref="PeopleValidator" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> - public PeopleValidator(ILibraryManager libraryManager, ILogger logger) + public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config) { _libraryManager = libraryManager; _logger = logger; + _config = config; + } + + private bool DownloadMetadata(PersonInfo i, PeopleMetadataOptions options) + { + if (i.IsType(PersonType.Actor)) + { + return options.DownloadActorMetadata; + } + if (i.IsType(PersonType.Director)) + { + return options.DownloadDirectorMetadata; + } + if (i.IsType(PersonType.Composer)) + { + return options.DownloadComposerMetadata; + } + if (i.IsType(PersonType.Writer)) + { + return options.DownloadWriterMetadata; + } + if (i.IsType(PersonType.Producer)) + { + return options.DownloadProducerMetadata; + } + if (i.IsType(PersonType.GuestStar)) + { + return options.DownloadGuestStarMetadata; + } + + return options.DownloadOtherPeopleMetadata; + } + + private IEnumerable<PersonInfo> GetPeopleToValidate(BaseItem item, PeopleMetadataOptions options) + { + return item.People.Where(i => + { + if (i.IsType(PersonType.Actor)) + { + return options.DownloadActorMetadata; + } + if (i.IsType(PersonType.Director)) + { + return options.DownloadDirectorMetadata; + } + if (i.IsType(PersonType.Composer)) + { + return options.DownloadComposerMetadata; + } + if (i.IsType(PersonType.Writer)) + { + return options.DownloadWriterMetadata; + } + if (i.IsType(PersonType.Producer)) + { + return options.DownloadProducerMetadata; + } + if (i.IsType(PersonType.GuestStar)) + { + return options.DownloadGuestStarMetadata; + } + + return options.DownloadOtherPeopleMetadata; + }); } /// <summary> /// Validates the people. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="options">The options.</param> /// <param name="progress">The progress.</param> /// <returns>Task.</returns> - public async Task ValidatePeople(CancellationToken cancellationToken, MetadataRefreshOptions options, IProgress<double> progress) + public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { var innerProgress = new ActionableProgress<double>(); innerProgress.RegisterAction(pct => progress.Report(pct * .15)); + var peopleOptions = _config.Configuration.PeopleMetadataOptions; + var people = _libraryManager.RootFolder.GetRecursiveChildren() - .SelectMany(c => c.People) + .SelectMany(i => i.People) .Where(i => !string.IsNullOrWhiteSpace(i.Name)) - .Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var numComplete = 0; + var dict = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); foreach (var person in people) { + bool current; + if (!dict.TryGetValue(person.Name, out current) || !current) + { + dict[person.Name] = DownloadMetadata(person, peopleOptions); + } + } + + var numComplete = 0; + + foreach (var person in dict) + { cancellationToken.ThrowIfCancellationRequested(); try { - var item = _libraryManager.GetPerson(person); + var item = _libraryManager.GetPerson(person.Key); + + var options = new MetadataRefreshOptions + { + MetadataRefreshMode = person.Value ? MetadataRefreshMode.Default : MetadataRefreshMode.ValidationOnly, + ImageRefreshMode = person.Value ? ImageRefreshMode.Default : ImageRefreshMode.ValidationOnly + }; await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } |
