diff options
| author | Tim Hobbs <jesus.tesh@gmail.com> | 2014-03-13 06:26:20 -0700 |
|---|---|---|
| committer | Tim Hobbs <jesus.tesh@gmail.com> | 2014-03-13 06:26:20 -0700 |
| commit | 9e33966ee120134d89f7aa21204b7ffc814dd29a (patch) | |
| tree | 297982b6cd4269ada15d93cec859b7cba27c0c1a /MediaBrowser.Server.Implementations | |
| parent | dd4a1ff4b50f58c27af223202454f80d45de4af1 (diff) | |
| parent | b7bcc2450694105de9f9fc8cc07d2cfc4d9d7c96 (diff) | |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'MediaBrowser.Server.Implementations')
37 files changed, 827 insertions, 1014 deletions
diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs new file mode 100644 index 000000000..9a196cc47 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -0,0 +1,201 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Collections +{ + public class CollectionManager : ICollectionManager + { + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryMonitor _iLibraryMonitor; + + public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor) + { + _libraryManager = libraryManager; + _fileSystem = fileSystem; + _iLibraryMonitor = iLibraryMonitor; + } + + public async Task CreateCollection(CollectionCreationOptions options) + { + var name = options.Name; + + // Need to use the [boxset] suffix + // If internet metadata is not found, or if xml saving is off there will be no collection.xml + // This could cause it to get re-resolved as a plain folder + var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; + + var parentFolder = GetParentFolder(options.ParentId); + + if (parentFolder == null) + { + throw new ArgumentException(); + } + + var path = Path.Combine(parentFolder.Path, folderName); + + _iLibraryMonitor.ReportFileSystemChangeBeginning(path); + + try + { + Directory.CreateDirectory(path); + + var collection = new BoxSet + { + Name = name, + Parent = parentFolder, + DisplayMediaType = "Collection", + Path = path, + DontFetchMeta = options.IsLocked, + ProviderIds = options.ProviderIds + }; + + await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) + .ConfigureAwait(false); + } + finally + { + // Refresh handled internally + _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); + } + } + + private Folder GetParentFolder(Guid? parentId) + { + if (parentId.HasValue) + { + if (parentId.Value == Guid.Empty) + { + throw new ArgumentNullException("parentId"); + } + + var folder = _libraryManager.GetItemById(parentId.Value) as Folder; + + // Find an actual physical folder + if (folder is CollectionFolder) + { + return _libraryManager.RootFolder.Children.OfType<Folder>().First(i => folder.PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase)); + } + } + + return _libraryManager.RootFolder.Children.OfType<ManualCollectionsFolder>().FirstOrDefault() ?? + _libraryManager.RootFolder.GetHiddenChildren().OfType<ManualCollectionsFolder>().FirstOrDefault(); + } + + public async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids) + { + var collection = _libraryManager.GetItemById(collectionId) as BoxSet; + + if (collection == null) + { + throw new ArgumentException("No collection exists with the supplied Id"); + } + + var list = new List<LinkedChild>(); + + foreach (var itemId in ids) + { + var item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + throw new ArgumentException("No item exists with the supplied Id"); + } + + if (collection.LinkedChildren.Any(i => i.ItemId.HasValue && i.ItemId == itemId)) + { + throw new ArgumentException("Item already exists in collection"); + } + + list.Add(new LinkedChild + { + ItemName = item.Name, + ItemYear = item.ProductionYear, + ItemType = item.GetType().Name, + Type = LinkedChildType.Manual + }); + } + + collection.LinkedChildren.AddRange(list); + + await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + } + + public async Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds) + { + var collection = _libraryManager.GetItemById(collectionId) as BoxSet; + + if (collection == null) + { + throw new ArgumentException("No collection exists with the supplied Id"); + } + + var list = new List<LinkedChild>(); + + foreach (var itemId in itemIds) + { + var child = collection.LinkedChildren.FirstOrDefault(i => i.ItemId.HasValue && i.ItemId.Value == itemId); + + if (child == null) + { + throw new ArgumentException("No collection title exists with the supplied Id"); + } + + list.Add(child); + } + + var shortcutFiles = Directory + .EnumerateFiles(collection.Path, "*", SearchOption.TopDirectoryOnly) + .Where(i => _fileSystem.IsShortcut(i)) + .ToList(); + + var shortcutFilesToDelete = list.Where(child => !string.IsNullOrWhiteSpace(child.Path) && child.Type == LinkedChildType.Shortcut) + .Select(child => shortcutFiles.FirstOrDefault(i => string.Equals(child.Path, _fileSystem.ResolveShortcut(i), StringComparison.OrdinalIgnoreCase))) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + foreach (var file in shortcutFilesToDelete) + { + _iLibraryMonitor.ReportFileSystemChangeBeginning(file); + } + + try + { + foreach (var file in shortcutFilesToDelete) + { + File.Delete(file); + } + + foreach (var child in list) + { + collection.LinkedChildren.Remove(child); + } + } + finally + { + foreach (var file in shortcutFilesToDelete) + { + _iLibraryMonitor.ReportFileSystemChangeComplete(file, false); + } + } + + await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs new file mode 100644 index 000000000..834fbcd31 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs @@ -0,0 +1,55 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Collections +{ + public class CollectionsDynamicFolder : IVirtualFolderCreator + { + private readonly IApplicationPaths _appPaths; + + public CollectionsDynamicFolder(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + + public BasePluginFolder GetFolder() + { + var path = Path.Combine(_appPaths.DataPath, "collections"); + + Directory.CreateDirectory(path); + + return new ManualCollectionsFolder + { + Path = path + }; + } + } + + public class ManualCollectionsFolder : BasePluginFolder + { + public ManualCollectionsFolder() + { + Name = "Collections"; + } + + public override bool IsVisible(User user) + { + if (!GetChildren(user, true).Any()) + { + return false; + } + + return base.IsVisible(user); + } + + public override bool IsHidden + { + get + { + return !ActualChildren.Any() || base.IsHidden; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 7bf87875e..fadf4c900 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -121,41 +121,51 @@ namespace MediaBrowser.Server.Implementations.Dto } } - var itemByName = item as IItemByName; - if (itemByName != null) - { - AttachItemByNameCounts(dto, itemByName, user); - } - return dto; } - /// <summary> - /// Attaches the item by name counts. - /// </summary> - /// <param name="dto">The dto.</param> - /// <param name="item">The item.</param> - /// <param name="user">The user.</param> - private void AttachItemByNameCounts(BaseItemDto dto, IItemByName item, User user) + public BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, User user = null) + where T : BaseItem, IItemByName { - if (user == null) + var libraryItems = user != null ? user.RootFolder.GetRecursiveChildren(user) : + _libraryManager.RootFolder.RecursiveChildren; + + return GetItemByNameDto(item, fields, item.GetTaggedItems(libraryItems).ToList(), user); + } + + public BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, List<BaseItem> taggedItems, User user = null) + where T : BaseItem, IItemByName + { + var dto = GetBaseItemDto(item, fields, user); + + if (item is MusicArtist || item is MusicGenre) { - return; + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + else if (item is GameGenre) + { + dto.GameCount = taggedItems.Count(i => i is Game); } + else + { + // This populates them all and covers Genre, Person, Studio, Year - var counts = item.GetItemByNameCounts(user.Id) ?? new ItemByNameCounts(); + dto.AdultVideoCount = taggedItems.Count(i => i is AdultVideo); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.EpisodeCount = taggedItems.Count(i => i is Episode); + dto.GameCount = taggedItems.Count(i => i is Game); + dto.MovieCount = taggedItems.Count(i => i is Movie); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SeriesCount = taggedItems.Count(i => i is Series); + dto.SongCount = taggedItems.Count(i => i is Audio); + dto.TrailerCount = taggedItems.Count(i => i is Trailer); + } - dto.ChildCount = counts.TotalCount; + dto.ChildCount = taggedItems.Count; - dto.AdultVideoCount = counts.AdultVideoCount; - dto.AlbumCount = counts.AlbumCount; - dto.EpisodeCount = counts.EpisodeCount; - dto.GameCount = counts.GameCount; - dto.MovieCount = counts.MovieCount; - dto.MusicVideoCount = counts.MusicVideoCount; - dto.SeriesCount = counts.SeriesCount; - dto.SongCount = counts.SongCount; - dto.TrailerCount = counts.TrailerCount; + return dto; } /// <summary> @@ -297,6 +307,57 @@ namespace MediaBrowser.Server.Implementations.Dto info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary); + var backropItem = item.HasImage(ImageType.Backdrop) ? item : null; + + var thumbItem = item.HasImage(ImageType.Thumb) ? item : null; + + if (thumbItem == null) + { + var episode = item as Episode; + + if (episode != null) + { + var series = episode.Series; + + if (series != null && series.HasImage(ImageType.Thumb)) + { + thumbItem = series; + } + } + } + + if (backropItem == null) + { + var episode = item as Episode; + + if (episode != null) + { + var series = episode.Series; + + if (series != null && series.HasImage(ImageType.Backdrop)) + { + backropItem = series; + } + } + } + + if (thumbItem == null) + { + thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb)); + } + + if (thumbItem != null) + { + info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb); + info.ThumbItemId = GetDtoId(thumbItem); + } + + if (thumbItem != null) + { + info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop); + info.BackdropItemId = GetDtoId(backropItem); + } + return info; } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index c0d784fcc..ad2852a91 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -3,6 +3,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; using Mono.Nat; +using Mono.Nat.Enums; +using Mono.Nat.EventArgs; using System; using System.IO; using System.Text; diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index b91067dd7..5d326f1ca 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -171,12 +171,23 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var fileExists = File.Exists(result.TargetPath); var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); - if (!overwriteExisting && (fileExists || otherDuplicatePaths.Count > 0)) + if (!overwriteExisting) { - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = string.Empty; - result.DuplicatePaths = otherDuplicatePaths; - return; + if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) + { + _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath); + result.Status = FileSortingStatus.SkippedExisting; + result.StatusMessage = string.Empty; + return; + } + + if (fileExists || otherDuplicatePaths.Count > 0) + { + result.Status = FileSortingStatus.SkippedExisting; + result.StatusMessage = string.Empty; + result.DuplicatePaths = otherDuplicatePaths; + return; + } } PerformFileSorting(options, result); @@ -266,7 +277,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization try { - if (copy) + if (copy || options.CopyOriginalFile) { File.Copy(result.OriginalPath, result.TargetPath, true); } @@ -293,7 +304,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); } - if (copy) + if (copy && !options.CopyOriginalFile) { try { @@ -439,5 +450,27 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("%0e", episodeNumber.ToString("00", _usCulture)) .Replace("%00e", episodeNumber.ToString("000", _usCulture)); } + + private bool IsSameEpisode(string sourcePath, string newPath) + { + + FileInfo sourceFileInfo = new FileInfo(sourcePath); + FileInfo destinationFileInfo = new FileInfo(newPath); + + try + { + if (sourceFileInfo.Length == destinationFileInfo.Length) + { + return true; + } + } + catch (FileNotFoundException) + { + return false; + } + + return false; + + } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 7edcf9739..82bb9862c 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false, cancellationToken).ConfigureAwait(false); + var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false); if (result.Status == FileSortingStatus.Success) { diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 8455d9f58..925ef8050 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -306,6 +306,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw new ArgumentNullException("path"); } + return GetStaticFileResult(requestContext, path, MimeTypes.GetMimeType(path), fileShare, responseHeaders, isHeadRequest); + } + + public object GetStaticFileResult(IRequest requestContext, string path, string contentType, + FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, + bool isHeadRequest = false) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite) { throw new ArgumentException("FileShare must be either Read or ReadWrite"); @@ -315,7 +327,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer var cacheKey = path + dateModified.Ticks; - return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest); + return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, contentType, () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index cbc4a8c24..b2f0a2769 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -449,7 +449,14 @@ namespace MediaBrowser.Server.Implementations.IO var paths = _affectedPaths.Keys.ToList(); _affectedPaths.Clear(); - await ProcessPathChanges(paths).ConfigureAwait(false); + try + { + await ProcessPathChanges(paths).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error processing directory changes", ex); + } } private void DisposeTimer() diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 6e9d803bf..0b1947d4c 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -5,6 +5,7 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; @@ -484,6 +485,9 @@ namespace MediaBrowser.Server.Implementations.Library await ItemRepository.DeleteItem(child.Id, CancellationToken.None).ConfigureAwait(false); } + BaseItem removed; + _libraryItemsCache.TryRemove(item.Id, out removed); + ReportItemRemoved(item); } @@ -922,10 +926,10 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { - // Ensure the location is unavailable. + // Ensure the location is available. Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); - return new PeopleValidator(this, _logger).ValidatePeople(cancellationToken, progress); + return new PeopleValidator(this, _logger).ValidatePeople(cancellationToken, new MetadataRefreshOptions(), progress); } /// <summary> @@ -953,7 +957,7 @@ namespace MediaBrowser.Server.Implementations.Library // Ensure the location is unavailable. Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.MusicGenrePath); - return new MusicGenresValidator(this, _userManager, _logger).Run(progress, cancellationToken); + return new MusicGenresValidator(this, _logger).Run(progress, cancellationToken); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 871171541..ac1927931 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - // If there's a collection type and it's not music, it can't be a series + // 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)) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index ac9b42efb..16c0d1a27 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -56,21 +56,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { return null; } - - // If the parent is not a boxset, the only other allowed parent type is Folder - if (!(args.Parent is BoxSet)) - { - if (args.Parent.GetType() != typeof(Folder)) - { - return null; - } - } - } - - // Since the looping is expensive, this is an optimization to help us avoid it - if (args.Path.IndexOf("[tvdbid", StringComparison.OrdinalIgnoreCase) != -1) - { - return null; } var isDirectory = args.IsDirectory; @@ -89,34 +74,31 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies // Find movies with their own folders if (isDirectory) { - if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); } - if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); } - if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } 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); + return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } return null; @@ -202,8 +184,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="path">The path.</param> /// <param name="parent">The parent.</param> /// <param name="fileSystemEntries">The file system entries.</param> + /// <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) + private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems) where T : Video, new() { var movies = new List<T>(); @@ -264,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } } - if (movies.Count > 1) + if (movies.Count > 1 && supportMultiFileItems) { return GetMultiFileMovie(movies); } diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index 12686f542..aaafd35a9 100644 --- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs @@ -37,6 +37,12 @@ namespace MediaBrowser.Server.Implementations.Library var results = await GetSearchHints(inputItems, query).ConfigureAwait(false); + // Include item types + if (query.IncludeItemTypes.Length > 0) + { + results = results.Where(f => query.IncludeItemTypes.Contains(f.Item.GetType().Name, StringComparer.OrdinalIgnoreCase)); + } + var searchResultArray = results.ToArray(); results = searchResultArray; @@ -96,7 +102,9 @@ namespace MediaBrowser.Server.Implementations.Library if (query.IncludeArtists) { // Find artists - var artists = _libraryManager.GetAllArtists(items) + var artists = items.OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in artists) diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 8654a26ce..06028d37e 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -189,15 +189,10 @@ namespace MediaBrowser.Server.Implementations.Library /// Refreshes metadata for each user /// </summary> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="force">if set to <c>true</c> [force].</param> /// <returns>Task.</returns> - public Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false) + public Task RefreshUsersMetadata(CancellationToken cancellationToken) { - var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = force - - }, cancellationToken)).ToList(); + var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions(), cancellationToken)).ToList(); return Task.WhenAll(tasks); } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 1d9eea866..5968d847e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -1,8 +1,6 @@ using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -69,10 +67,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var numComplete = 0; - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, List<IHasArtist>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<IHasArtist>().ToList())) - .ToList(); - var numArtists = allArtists.Count; foreach (var artist in allArtists) @@ -91,11 +85,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators .ToList(); } - foreach (var lib in userLibraries) - { - SetItemCounts(artist, lib.Item1, lib.Item2); - } - numComplete++; double percent = numComplete; percent /= numArtists; @@ -108,37 +97,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } /// <summary> - /// Sets the item counts. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="userId">The user id.</param> - /// <param name="allItems">All items.</param> - private void SetItemCounts(MusicArtist artist, Guid? userId, IEnumerable<IHasArtist> allItems) - { - var name = artist.Name; - - var items = allItems - .Where(i => i.HasArtist(name)) - .ToList(); - - var counts = new ItemByNameCounts - { - TotalCount = items.Count, - - SongCount = items.OfType<Audio>().Count(), - - AlbumCount = items.OfType<MusicAlbum>().Count(), - - MusicVideoCount = items.OfType<MusicVideo>().Count() - }; - - if (userId.HasValue) - { - artist.SetItemByNameCounts(userId.Value, counts); - } - } - - /// <summary> /// Gets all artists. /// </summary> /// <param name="allSongs">All songs.</param> @@ -147,7 +105,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task{Artist[]}.</returns> private async Task<List<MusicArtist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) { - var allArtists = _libraryManager.GetAllArtists(allSongs) + var allArtists = allSongs.SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); var returnArtists = new List<MusicArtist>(allArtists.Count); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index 9e64c7810..6b658e175 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -41,38 +40,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, List<Game>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<Game>().ToList())) + var items = _libraryManager.RootFolder.RecursiveChildren.Where(i => (i is Game)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } + cancellationToken.ThrowIfCancellationRequested(); - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetGameGenre(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -81,7 +66,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -94,32 +79,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetGameGenre(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Genres - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index e0a9e2ce8..b0dee9aaf 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -42,38 +41,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, m => !(m is IHasMusicGenres) && !(m is Game)))) + var items = _libraryManager.RootFolder.RecursiveChildren.Where(i => !(i is IHasMusicGenres) && !(i is Game)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } + cancellationToken.ThrowIfCancellationRequested(); - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetGenre(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -82,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -95,32 +80,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetGenre(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Genres - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index b55ab1cbe..aa6c6281e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -1,9 +1,7 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,19 +16,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public MusicGenresValidator(ILibraryManager libraryManager, IUserManager userManager, ILogger logger) + public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; - _userManager = userManager; _logger = logger; } @@ -42,38 +34,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, m => m is IHasMusicGenres))) + var items = _libraryManager.RootFolder.RecursiveChildren.Where(i => (i is IHasMusicGenres)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } - - progress.Report(10); + cancellationToken.ThrowIfCancellationRequested(); - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetMusicGenre(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -82,7 +60,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -95,32 +73,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetMusicGenre(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Genres - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index 86c5dbc4c..d11e62a1a 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -1,10 +1,7 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,19 +15,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public PeoplePostScanTask(ILibraryManager libraryManager, IUserManager userManager, ILogger logger) + public PeoplePostScanTask(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; - _userManager = userManager; _logger = logger; } @@ -42,94 +33,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return RunInternal(progress, cancellationToken); - } - - private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken) - { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, null))) - .ToList(); - - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - - progress.Report(2); - - var numComplete = 0; - - foreach (var lib in userLibraries) + return new PeopleValidator(_libraryManager, _logger).ValidatePeople(cancellationToken, new MetadataRefreshOptions { - cancellationToken.ThrowIfCancellationRequested(); - - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; + ImageRefreshMode = ImageRefreshMode.ValidationOnly, + MetadataRefreshMode = MetadataRefreshMode.None - progress.Report(percent); - } - - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var counts = masterDictionary[name]; - - var itemByName = _libraryManager.GetPerson(name); - - // The only purpose here is to be able to react to image changes without running the people task. - // All other metadata can wait for that. - await itemByName.RefreshMetadata(new MetadataRefreshOptions - { - ImageRefreshMode = ImageRefreshMode.ValidationOnly, - MetadataRefreshMode = MetadataRefreshMode.None - - }, cancellationToken).ConfigureAwait(false); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error updating counts for {0}", ex, name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 90; - - progress.Report(percent + 10); - } - - progress.Report(100); + }, progress); } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .People.Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } - } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs index 268bccd7a..722c24a10 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using MoreLinq; using System; @@ -38,9 +39,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// 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, IProgress<double> progress) + public async Task ValidatePeople(CancellationToken cancellationToken, MetadataRefreshOptions options, IProgress<double> progress) { var innerProgress = new ActionableProgress<double>(); @@ -61,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person.Name); - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index 54fadfb77..a2ec9788c 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -1,8 +1,6 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -41,38 +39,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, null))) + var items = _libraryManager.RootFolder.RecursiveChildren + .SelectMany(i => i.Studios) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } + cancellationToken.ThrowIfCancellationRequested(); - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetStudio(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -81,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -94,32 +78,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetStudio(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Studios - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index 9a1373460..83d8e4b73 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -105,7 +105,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1; + var liveTvItem = item as LiveTvChannel; + + if (liveTvItem != null) + { + return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.HasProviderImage ?? true) && (DateTime.UtcNow - date).TotalHours >= 6; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index bd315530e..e4446895f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -47,6 +47,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private List<Guid> _channelIdList = new List<Guid>(); private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>(); + private readonly ConcurrentDictionary<Guid, bool> _refreshedPrograms = new ConcurrentDictionary<Guid, bool>(); private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1); @@ -106,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); } - public Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken) + public async Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); @@ -161,17 +162,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv allEnumerable = allEnumerable.Take(query.Limit.Value); } - var returnChannels = allEnumerable - .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ExternalId), user)) - .ToArray(); + var returnList = new List<ChannelInfoDto>(); + + foreach (var channel in allEnumerable) + { + var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false); + + returnList.Add(_tvDtoService.GetChannelInfoDto(channel, currentProgram, user)); + } var result = new QueryResult<ChannelInfoDto> { - Items = returnChannels, + Items = returnList.ToArray(), TotalRecordCount = allChannels.Count }; - return Task.FromResult(result); + return result; } public LiveTvChannel GetInternalChannel(string id) @@ -184,16 +190,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv return _libraryManager.GetItemById(id) as LiveTvChannel; } - public LiveTvProgram GetInternalProgram(string id) + public async Task<LiveTvProgram> GetInternalProgram(string id, CancellationToken cancellationToken) { var guid = new Guid(id); LiveTvProgram obj = null; _programs.TryGetValue(guid, out obj); + + if (obj != null) + { + await RefreshIfNeeded(obj, cancellationToken).ConfigureAwait(false); + } return obj; } + private async Task RefreshIfNeeded(IEnumerable<LiveTvProgram> programs, CancellationToken cancellationToken) + { + foreach (var program in programs) + { + await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false); + } + } + + private async Task RefreshIfNeeded(LiveTvProgram program, CancellationToken cancellationToken) + { + if (_refreshedPrograms.ContainsKey(program.Id)) + { + return; + } + + await program.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + _refreshedPrograms.TryAdd(program.Id, true); + } + public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken) { var service = ActiveService; @@ -336,10 +367,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv return item; } - private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken) + private LiveTvProgram GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken) { - var isNew = false; - var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id); var item = _itemRepo.RetrieveItem(id) as LiveTvProgram; @@ -353,8 +382,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - - isNew = true; } item.ChannelType = channelType; @@ -386,12 +413,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.StartDate = info.StartDate; - await item.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = isNew - - }, cancellationToken); - return item; } @@ -464,7 +485,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null) { - var program = GetInternalProgram(id); + var program = await GetInternalProgram(id, cancellationToken).ConfigureAwait(false); var channel = GetChannel(program); @@ -531,7 +552,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(i => i.IsParentalAllowed(currentUser)); } - var returnArray = programs + var programList = programs.ToList(); + + var returnArray = programList .Select(i => { var channel = GetChannel(i); @@ -540,6 +563,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv }) .ToArray(); + await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false); + await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false); var result = new QueryResult<ProgramInfoDto> @@ -582,7 +607,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv .Select(i => _libraryManager.GetGenre(i)) .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); - programs = programList.OrderByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres)) + programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1) + .ThenByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres)) .ThenBy(i => i.StartDate); if (query.Limit.HasValue) @@ -591,7 +617,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv .OrderBy(i => i.StartDate); } - var returnArray = programs + programList = programs.ToList(); + + await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false); + + var returnArray = programList .Select(i => { var channel = GetChannel(i); @@ -790,8 +820,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false); - var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken)); - var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false); + var programEntities = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken)); programs.AddRange(programEntities); } @@ -812,6 +841,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } _programs = programs.ToDictionary(i => i.Id); + _refreshedPrograms.Clear(); progress.Report(90); // Load these now which will prefetch metadata @@ -955,6 +985,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv entities = entities.Where(i => i.IsParentalAllowed(currentUser)); } + var entityList = entities.ToList(); + entities = entityList; + if (query.StartIndex.HasValue) { entities = entities.Skip(query.StartIndex.Value); @@ -976,7 +1009,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return new QueryResult<RecordingInfoDto> { Items = returnArray, - TotalRecordCount = returnArray.Length + TotalRecordCount = entityList.Count }; } @@ -1030,14 +1063,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid); } - var returnArray = timers - .Select(i => - { - var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N")); - var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId)); + var returnList = new List<TimerInfoDto>(); - return _tvDtoService.GetTimerInfoDto(i, service, program, channel); - }) + foreach (var i in timers) + { + var program = string.IsNullOrEmpty(i.ProgramId) ? + null : + await GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"), cancellationToken).ConfigureAwait(false); + + var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId)); + + returnList.Add(_tvDtoService.GetTimerInfoDto(i, service, program, channel)); + } + + var returnArray = returnList .OrderBy(i => i.StartDate) .ToArray(); @@ -1162,24 +1201,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } - public Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null) + public async Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null) { var channel = GetInternalChannel(id); - var dto = _tvDtoService.GetChannelInfoDto(channel, GetCurrentProgram(channel.ExternalId), user); + var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false); - return Task.FromResult(dto); + var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user); + + return dto; } - private LiveTvProgram GetCurrentProgram(string externalChannelId) + private async Task<LiveTvProgram> GetCurrentProgram(string externalChannelId, CancellationToken cancellationToken) { var now = DateTime.UtcNow; - return _programs.Values + var program = _programs.Values .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => i.StartDate) .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue)) .FirstOrDefault(); + + if (program != null) + { + await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false); + } + + return program; } private async Task<SeriesTimerInfo> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null) @@ -1235,7 +1283,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken) { - var program = GetInternalProgram(programId); + var program = await GetInternalProgram(programId, cancellationToken).ConfigureAwait(false); var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false); var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index cb7635b45..081722bb2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -105,7 +105,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 6; + var liveTvItem = item as LiveTvProgram; + + if (liveTvItem != null) + { + return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.HasProviderImage ?? true) && (DateTime.UtcNow - date).TotalHours >= 6; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs index be8955d16..7aa5dcebd 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs @@ -105,7 +105,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 3; + var liveTvItem = item as ILiveTvRecording; + + if (liveTvItem != null) + { + return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.RecordingInfo.HasImage ?? true) && (DateTime.UtcNow - date).TotalHours >= 6; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index f0b08b923..c44b60845 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -56,20 +56,18 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\DvdLib.dll</HintPath> </Reference> - <Reference Include="Mono.Nat"> - <HintPath>..\packages\Mono.Nat.1.1.13\lib\Net40\Mono.Nat.dll</HintPath> + <Reference Include="Mono.Nat, Version=1.2.3.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Mono.Nat.1.2.3\lib\Net40\Mono.Nat.dll</HintPath> </Reference> <Reference Include="ServiceStack.Api.Swagger"> <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> - <Reference Include="System.Data.SQLite, Version=1.0.90.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86" Condition=" '$(ConfigurationName)' != 'Release Mono' "> + <Reference Include="System.Data.SQLite, Version=1.0.91.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.dll</HintPath> - </Reference> - <Reference Include="System.Data.SQLite.Linq" Condition=" '$(ConfigurationName)' != 'Release Mono' "> - <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath> + <HintPath>..\packages\System.Data.SQLite.Core.1.0.91.3\lib\net45\System.Data.SQLite.dll</HintPath> </Reference> <Reference Include="System.Drawing" /> <Reference Include="Microsoft.CSharp" /> @@ -108,6 +106,8 @@ <Link>Properties\SharedVersion.cs</Link> </Compile> <Compile Include="BdInfo\BdInfoExaminer.cs" /> + <Compile Include="Collections\CollectionManager.cs" /> + <Compile Include="Collections\CollectionsDynamicFolder.cs" /> <Compile Include="Configuration\ServerConfigurationManager.cs" /> <Compile Include="Drawing\ImageHeader.cs" /> <Compile Include="Drawing\PercentPlayedDrawer.cs" /> @@ -216,7 +216,6 @@ <Compile Include="Sorting\AirTimeComparer.cs" /> <Compile Include="Sorting\AlbumArtistComparer.cs" /> <Compile Include="Sorting\AlbumComparer.cs" /> - <Compile Include="Sorting\AlbumCountComparer.cs" /> <Compile Include="Sorting\AlphanumComparator.cs" /> <Compile Include="Sorting\ArtistComparer.cs" /> <Compile Include="Sorting\BudgetComparer.cs" /> @@ -224,29 +223,26 @@ <Compile Include="Sorting\CriticRatingComparer.cs" /> <Compile Include="Sorting\DateCreatedComparer.cs" /> <Compile Include="Sorting\DatePlayedComparer.cs" /> - <Compile Include="Sorting\EpisodeCountComparer.cs" /> + <Compile Include="Sorting\GameSystemComparer.cs" /> <Compile Include="Sorting\IsFolderComparer.cs" /> <Compile Include="Sorting\IsUnplayedComparer.cs" /> <Compile Include="Sorting\MetascoreComparer.cs" /> - <Compile Include="Sorting\MovieCountComparer.cs" /> - <Compile Include="Sorting\MusicVideoCountComparer.cs" /> <Compile Include="Sorting\NameComparer.cs" /> <Compile Include="Sorting\OfficialRatingComparer.cs" /> <Compile Include="Sorting\PlayCountComparer.cs" /> + <Compile Include="Sorting\PlayersComparer.cs" /> <Compile Include="Sorting\PremiereDateComparer.cs" /> <Compile Include="Sorting\ProductionYearComparer.cs" /> <Compile Include="Sorting\RandomComparer.cs" /> <Compile Include="Sorting\RevenueComparer.cs" /> <Compile Include="Sorting\RuntimeComparer.cs" /> - <Compile Include="Sorting\SeriesCountComparer.cs" /> <Compile Include="Sorting\SeriesSortNameComparer.cs" /> - <Compile Include="Sorting\SongCountComparer.cs" /> <Compile Include="Sorting\SortNameComparer.cs" /> <Compile Include="Persistence\SqliteDisplayPreferencesRepository.cs" /> <Compile Include="Persistence\SqliteItemRepository.cs" /> <Compile Include="Persistence\SqliteUserDataRepository.cs" /> <Compile Include="Persistence\SqliteUserRepository.cs" /> - <Compile Include="Sorting\TrailerCountComparer.cs" /> + <Compile Include="Sorting\StudioComparer.cs" /> <Compile Include="Sorting\VideoBitRateComparer.cs" /> <Compile Include="Themes\AppThemeManager.cs" /> <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" /> @@ -376,6 +372,12 @@ <Link>swagger-ui\swagger-ui.min.js</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="x64\SQLite.Interop.dll" Condition=" '$(ConfigurationName)' != 'Release Mono' "> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + <Content Include="x86\SQLite.Interop.dll" Condition=" '$(ConfigurationName)' != 'Release Mono' "> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> <EmbeddedResource Include="Localization\Ratings\be.txt" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index bfa23b997..71d95b97b 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -69,6 +69,8 @@ namespace MediaBrowser.Server.Implementations.Session private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>(); + private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); + /// <summary> /// Initializes a new instance of the <see cref="SessionManager" /> class. /// </summary> @@ -146,7 +148,7 @@ namespace MediaBrowser.Server.Implementations.Session var userId = user == null ? (Guid?)null : user.Id; var username = user == null ? null : user.Name; - var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, userId, username); + var session = await GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, userId, username).ConfigureAwait(false); session.LastActivityDate = activityDate; @@ -171,6 +173,46 @@ namespace MediaBrowser.Server.Implementations.Session return session; } + public async Task ReportSessionEnded(Guid sessionId) + { + await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + try + { + var session = GetSession(sessionId); + + if (session == null) + { + throw new ArgumentException("Session not found"); + } + + var key = GetSessionKey(session.Client, session.ApplicationVersion, session.DeviceId); + + SessionInfo removed; + + if (_activeConnections.TryRemove(key, out removed)) + { + var disposable = removed.SessionController as IDisposable; + + if (disposable != null) + { + try + { + disposable.Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing session controller", ex); + } + } + } + } + finally + { + _sessionLock.Release(); + } + } + /// <summary> /// Updates the now playing item id. /// </summary> @@ -207,6 +249,11 @@ namespace MediaBrowser.Server.Implementations.Session } } + private string GetSessionKey(string clientType, string appVersion, string deviceId) + { + return clientType + deviceId + appVersion; + } + /// <summary> /// Gets the connection. /// </summary> @@ -218,36 +265,45 @@ namespace MediaBrowser.Server.Implementations.Session /// <param name="userId">The user identifier.</param> /// <param name="username">The username.</param> /// <returns>SessionInfo.</returns> - private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Guid? userId, string username) + private async Task<SessionInfo> GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Guid? userId, string username) { - var key = clientType + deviceId + appVersion; + var key = GetSessionKey(clientType, appVersion, deviceId); - var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo + await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + try { - Client = clientType, - DeviceId = deviceId, - ApplicationVersion = appVersion, - Id = Guid.NewGuid() - }); + var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo + { + Client = clientType, + DeviceId = deviceId, + ApplicationVersion = appVersion, + Id = Guid.NewGuid() + }); - connection.DeviceName = deviceName; - connection.UserId = userId; - connection.UserName = username; - connection.RemoteEndPoint = remoteEndPoint; + connection.DeviceName = deviceName; + connection.UserId = userId; + connection.UserName = username; + connection.RemoteEndPoint = remoteEndPoint; - if (!userId.HasValue) - { - connection.AdditionalUsers.Clear(); - } + if (!userId.HasValue) + { + connection.AdditionalUsers.Clear(); + } + + if (connection.SessionController == null) + { + connection.SessionController = _sessionFactories + .Select(i => i.GetSessionController(connection)) + .FirstOrDefault(i => i != null); + } - if (connection.SessionController == null) + return connection; + } + finally { - connection.SessionController = _sessionFactories - .Select(i => i.GetSessionController(connection)) - .FirstOrDefault(i => i != null); + _sessionLock.Release(); } - - return connection; } private List<User> GetUsers(SessionInfo session) diff --git a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs deleted file mode 100644 index e35ba00f2..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class AlbumCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.AlbumCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.AlbumCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs deleted file mode 100644 index b3fd8a023..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class EpisodeCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.EpisodeCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.EpisodeCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs b/MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs new file mode 100644 index 000000000..eb83b98e9 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs @@ -0,0 +1,54 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class GameSystemComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private string GetValue(BaseItem x) + { + var game = x as Game; + + if (game != null) + { + return game.GameSystem; + } + + var system = x as GameSystem; + + if (system != null) + { + return system.GameSystemName; + } + + return string.Empty; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.GameSystem; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs deleted file mode 100644 index 605f4d1af..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - public class MovieCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.MovieCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.MovieCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs deleted file mode 100644 index 6c9c5534d..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class MusicVideoCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.MusicVideoCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.MusicVideoCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs new file mode 100644 index 000000000..5bcd080d7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class PlayersComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private int GetValue(BaseItem x) + { + var game = x as Game; + + if (game != null) + { + return game.PlayersSupported ?? 0; + } + + return 0; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Players; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs deleted file mode 100644 index 8567e400c..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class SeriesCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.SeriesCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.SeriesCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs deleted file mode 100644 index 85b849a21..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class SongCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.SongCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.SongCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs b/MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs new file mode 100644 index 000000000..83ab4dfc2 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class StudioComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Studio; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs deleted file mode 100644 index a13875674..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - public class TrailerCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.TrailerCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.TrailerCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 258e0639d..f04536190 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -2,7 +2,7 @@ <packages>
<package id="Alchemy" version="2.2.1" targetFramework="net45" />
<package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
- <package id="Mono.Nat" version="1.1.13" targetFramework="net45" />
+ <package id="Mono.Nat" version="1.2.3" targetFramework="net45" />
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
- <package id="System.Data.SQLite.x86" version="1.0.90.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.Core" version="1.0.91.3" targetFramework="net45" />
</packages>
\ No newline at end of file |
