diff options
240 files changed, 5589 insertions, 3168 deletions
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 2dd1c77e1..bea1ed505 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Api protected object ToOptimizedSerializedResultUsingCache<T>(T result) where T : class { - return ResultFactory.GetOptimizedSerializedResultUsingCache(Request, result); + return ToOptimizedResult(result); } /// <summary> diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index b2a5fa7a7..d8ecc3685 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -1,10 +1,15 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; 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.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; using ServiceStack; +using System; using System.Collections.Generic; using System.Linq; @@ -43,6 +48,13 @@ namespace MediaBrowser.Api } + [Route("/System/Configuration/SaveLocalMetadata", "POST")] + [Api(("Updates saving of local metadata and images for all types"))] + public class UpdateSaveLocalMetadata : IReturnVoid + { + public bool Enabled { get; set; } + } + public class ConfigurationService : BaseApiService { /// <summary> @@ -106,5 +118,82 @@ namespace MediaBrowser.Api { return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins().ToList()); } + + /// <summary> + /// This is a temporary method used until image settings get broken out. + /// </summary> + /// <param name="request"></param> + public void Post(UpdateSaveLocalMetadata request) + { + var config = _configurationManager.Configuration; + + if (request.Enabled) + { + config.SaveLocalMeta = true; + + foreach (var options in config.MetadataOptions) + { + options.DisabledMetadataSavers = new string[] { }; + } + } + else + { + config.SaveLocalMeta = false; + + DisableSaversForType(typeof(Game), config); + DisableSaversForType(typeof(GameSystem), config); + DisableSaversForType(typeof(Movie), config); + DisableSaversForType(typeof(BoxSet), config); + DisableSaversForType(typeof(Book), config); + DisableSaversForType(typeof(Series), config); + DisableSaversForType(typeof(Season), config); + DisableSaversForType(typeof(Episode), config); + DisableSaversForType(typeof(MusicAlbum), config); + DisableSaversForType(typeof(MusicArtist), config); + DisableSaversForType(typeof(AdultVideo), config); + DisableSaversForType(typeof(MusicVideo), config); + DisableSaversForType(typeof(Video), config); + } + + _configurationManager.SaveConfiguration(); + } + + private void DisableSaversForType(Type type, ServerConfiguration config) + { + var options = GetMetadataOptions(type, config); + + const string mediabrowserSaverName = "Media Browser Xml"; + + if (!options.DisabledMetadataSavers.Contains(mediabrowserSaverName, StringComparer.OrdinalIgnoreCase)) + { + var list = options.DisabledMetadataSavers.ToList(); + + list.Add(mediabrowserSaverName); + + options.DisabledMetadataSavers = list.ToArray(); + } + } + + private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config) + { + var options = config.MetadataOptions + .FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase)); + + if (options == null) + { + var list = config.MetadataOptions.ToList(); + + options = new MetadataOptions + { + ItemType = type.Name + }; + + list.Add(options); + + config.MetadataOptions = list.ToArray(); + } + + return options; + } } } diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index 6a91897f2..afba8c360 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -115,7 +115,7 @@ namespace MediaBrowser.Api.DefaultTheme var itemsWithImages = allFavoriteItems.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)) .ToList(); - var itemsWithBackdrops = allFavoriteItems.Where(i => i.BackdropImagePaths.Count > 0) + var itemsWithBackdrops = allFavoriteItems.Where(i => i.GetImages(ImageType.Backdrop).Any()) .ToList(); var view = new FavoritesView(); @@ -227,7 +227,7 @@ namespace MediaBrowser.Api.DefaultTheme var gamesWithImages = items.OfType<Game>().Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)).ToList(); - var itemsWithBackdrops = FilterItemsForBackdropDisplay(items.Where(i => i.BackdropImagePaths.Count > 0)).ToList(); + var itemsWithBackdrops = FilterItemsForBackdropDisplay(items.Where(i => i.GetImages(ImageType.Backdrop).Any())).ToList(); var gamesWithBackdrops = itemsWithBackdrops.OfType<Game>().ToList(); @@ -282,7 +282,7 @@ namespace MediaBrowser.Api.DefaultTheme .OfType<Series>() .ToList(); - var seriesWithBackdrops = series.Where(i => i.BackdropImagePaths.Count > 0).ToList(); + var seriesWithBackdrops = series.Where(i => i.GetImages(ImageType.Backdrop).Any()).ToList(); var view = new TvView(); @@ -298,7 +298,7 @@ namespace MediaBrowser.Api.DefaultTheme .ToList(); view.ShowsItems = series - .Where(i => i.BackdropImagePaths.Count > 0) + .Where(i => i.GetImages(ImageType.Backdrop).Any()) .Randomize("all") .Select(i => GetItemStub(i, ImageType.Backdrop)) .Where(i => i != null) @@ -425,7 +425,7 @@ namespace MediaBrowser.Api.DefaultTheme view.FamilyMoviePercentage /= movies.Count; var moviesWithBackdrops = movies - .Where(i => i.BackdropImagePaths.Count > 0) + .Where(i => i.GetImages(ImageType.Backdrop).Any()) .ToList(); var fields = new List<ItemFields>(); @@ -456,7 +456,7 @@ namespace MediaBrowser.Api.DefaultTheme view.BoxSetItems = items .OfType<BoxSet>() - .Where(i => i.BackdropImagePaths.Count > 0) + .Where(i => i.GetImages(ImageType.Backdrop).Any()) .Randomize() .Select(i => GetItemStub(i, ImageType.Backdrop)) .Where(i => i != null) @@ -491,7 +491,7 @@ namespace MediaBrowser.Api.DefaultTheme .ToList(); view.HDItems = hdMovies - .Where(i => i.BackdropImagePaths.Count > 0) + .Where(i => i.GetImages(ImageType.Backdrop).Any()) .Randomize("hd") .Select(i => GetItemStub(i, ImageType.Backdrop)) .Where(i => i != null) @@ -499,7 +499,7 @@ namespace MediaBrowser.Api.DefaultTheme .ToList(); view.FamilyMovies = familyMovies - .Where(i => i.BackdropImagePaths.Count > 0) + .Where(i => i.GetImages(ImageType.Backdrop).Any()) .Randomize("family") .Select(i => GetItemStub(i, ImageType.Backdrop)) .Where(i => i != null) @@ -575,7 +575,7 @@ namespace MediaBrowser.Api.DefaultTheme private IEnumerable<BaseItem> FilterItemsForBackdropDisplay(IEnumerable<BaseItem> items) { var tuples = items - .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, i.BackdropImagePaths[0]))) + .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, ImageType.Backdrop, 0))) .Where(i => i.Item2 > 0) .ToList(); @@ -591,13 +591,13 @@ namespace MediaBrowser.Api.DefaultTheme return tuples.Select(i => i.Item1); } - private double GetResolution(BaseItem item, string path) + private double GetResolution(BaseItem item, ImageType type, int index) { try { - var date = item.GetImageDateModified(path); + var info = item.GetImageInfo(type, index); - var size = _imageProcessor.GetImageSize(path, date); + var size = _imageProcessor.GetImageSize(info.Path, info.DateModified); return size.Width; } @@ -618,9 +618,12 @@ namespace MediaBrowser.Api.DefaultTheme try { - var imagePath = item.GetImagePath(imageType, 0); + var tag = _imageProcessor.GetImageCacheTag(item, imageType); - stub.ImageTag = _imageProcessor.GetImageCacheTag(item, imageType, imagePath); + if (tag.HasValue) + { + stub.ImageTag = tag.Value; + } } catch (Exception ex) { diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 514e85655..8dfecceec 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -334,12 +335,12 @@ namespace MediaBrowser.Api.Images private readonly IItemRepository _itemRepo; private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; - + private readonly IFileSystem _fileSystem; /// <summary> /// Initializes a new instance of the <see cref="ImageService" /> class. /// </summary> - public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor) + public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) { _userManager = userManager; _libraryManager = libraryManager; @@ -348,6 +349,7 @@ namespace MediaBrowser.Api.Images _itemRepo = itemRepo; _dtoService = dtoService; _imageProcessor = imageProcessor; + _fileSystem = fileSystem; } /// <summary> @@ -395,9 +397,9 @@ namespace MediaBrowser.Api.Images { var list = new List<ImageInfo>(); - foreach (var image in item.Images) + foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type))) { - var info = GetImageInfo(image.Value, item, null, image.Key); + var info = GetImageInfo(item, image, null); if (info != null) { @@ -405,28 +407,16 @@ namespace MediaBrowser.Api.Images } } - var index = 0; - - foreach (var image in item.BackdropImagePaths) + foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages)) { - var info = GetImageInfo(image, item, index, ImageType.Backdrop); - - if (info != null) - { - list.Add(info); - } - - index++; - } + var index = 0; - index = 0; + // Prevent implicitly captured closure + var currentImageType = imageType; - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots != null) - { - foreach (var image in hasScreenshots.ScreenshotImagePaths) + foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType)) { - var info = GetImageInfo(image, item, index, ImageType.Screenshot); + var info = GetImageInfo(item, image, index); if (info != null) { @@ -441,7 +431,7 @@ namespace MediaBrowser.Api.Images if (video != null) { - index = 0; + var index = 0; foreach (var chapter in _itemRepo.GetChapters(video.Id)) { @@ -449,7 +439,13 @@ namespace MediaBrowser.Api.Images { var image = chapter.ImagePath; - var info = GetImageInfo(image, item, index, ImageType.Chapter); + var info = GetImageInfo(item, new ItemImageInfo + { + Path = image, + Type = ImageType.Chapter, + DateModified = _fileSystem.GetLastWriteTimeUtc(image) + + }, index); if (info != null) { @@ -464,20 +460,20 @@ namespace MediaBrowser.Api.Images return list; } - private ImageInfo GetImageInfo(string path, IHasImages item, int? imageIndex, ImageType type) + private ImageInfo GetImageInfo(IHasImages item, ItemImageInfo info, int? imageIndex) { try { - var fileInfo = new FileInfo(path); + var fileInfo = new FileInfo(info.Path); - var size = _imageProcessor.GetImageSize(path); + var size = _imageProcessor.GetImageSize(info.Path); return new ImageInfo { - Path = path, + Path = info.Path, ImageIndex = imageIndex, - ImageType = type, - ImageTag = _imageProcessor.GetImageCacheTag(item, type, path), + ImageType = info.Type, + ImageTag = _imageProcessor.GetImageCacheTag(item, info), Size = fileInfo.Length, Width = Convert.ToInt32(size.Width), Height = Convert.ToInt32(size.Height) @@ -485,7 +481,7 @@ namespace MediaBrowser.Api.Images } catch (Exception ex) { - Logger.ErrorException("Error getting image information for {0}", ex, path); + Logger.ErrorException("Error getting image information for {0}", ex, info.Path); return null; } @@ -584,7 +580,7 @@ namespace MediaBrowser.Api.Images { var item = _userManager.Users.First(i => i.Id == request.Id); - var task = item.DeleteImage(request.Type, request.Index); + var task = item.DeleteImage(request.Type, request.Index ?? 0); Task.WaitAll(task); } @@ -597,7 +593,7 @@ namespace MediaBrowser.Api.Images { var item = _libraryManager.GetItemById(request.Id); - var task = item.DeleteImage(request.Type, request.Index); + var task = item.DeleteImage(request.Type, request.Index ?? 0); Task.WaitAll(task); } @@ -613,7 +609,7 @@ namespace MediaBrowser.Api.Images var item = GetItemByName(request.Name, type, _libraryManager); - var task = item.DeleteImage(request.Type, request.Index); + var task = item.DeleteImage(request.Type, request.Index ?? 0); Task.WaitAll(task); } @@ -671,15 +667,15 @@ namespace MediaBrowser.Api.Images /// </exception> public object GetImage(ImageRequest request, IHasImages item) { - var imagePath = GetImagePath(request, item); + var imageInfo = GetImageInfo(request, item); - if (string.IsNullOrEmpty(imagePath)) + if (imageInfo == null) { throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type)); } // See if we can avoid a file system lookup by looking for the file in ResolveArgs - var originalFileImageDateModified = item.GetImageDateModified(imagePath); + var originalFileImageDateModified = imageInfo.DateModified; var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.ImageEnhancers.Where(i => { @@ -696,16 +692,9 @@ namespace MediaBrowser.Api.Images }).ToList() : new List<IImageEnhancer>(); - // If the file does not exist GetLastWriteTimeUtc will return jan 1, 1601 as opposed to throwing an exception - // http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx - if (originalFileImageDateModified.Year == 1601 && !File.Exists(imagePath)) - { - throw new ResourceNotFoundException(string.Format("File not found: {0}", imagePath)); - } - - var contentType = GetMimeType(request.Format, imagePath); + var contentType = GetMimeType(request.Format, imageInfo.Path); - var cacheGuid = _imageProcessor.GetImageCacheTag(item, request.Type, imagePath, originalFileImageDateModified, supportedImageEnhancers); + var cacheGuid = _imageProcessor.GetImageCacheTag(item, request.Type, imageInfo.Path, originalFileImageDateModified, supportedImageEnhancers); TimeSpan? cacheDuration = null; @@ -724,7 +713,7 @@ namespace MediaBrowser.Api.Images Request = currentRequest, OriginalImageDateModified = originalFileImageDateModified, Enhancers = supportedImageEnhancers, - OriginalImagePath = imagePath, + OriginalImagePath = imageInfo.Path, ImageProcessor = _imageProcessor }, contentType); @@ -758,11 +747,11 @@ namespace MediaBrowser.Api.Images /// <param name="request">The request.</param> /// <param name="item">The item.</param> /// <returns>System.String.</returns> - private string GetImagePath(ImageRequest request, IHasImages item) + private ItemImageInfo GetImageInfo(ImageRequest request, IHasImages item) { var index = request.Index ?? 0; - return item.GetImagePath(request.Type, index); + return item.GetImageInfo(request.Type, index); } /// <summary> @@ -800,12 +789,7 @@ namespace MediaBrowser.Api.Images await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); - await entity.RefreshMetadata(new MetadataRefreshOptions - { - ImageRefreshMode = ImageRefreshMode.ValidationOnly, - ForceSave = true - - }, CancellationToken.None).ConfigureAwait(false); + await entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index deba146e8..36b509c74 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -40,6 +40,9 @@ namespace MediaBrowser.Api.Images [ApiMember(Name = "ProviderName", Description = "Optional. The image provider to use", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ProviderName { get; set; } + + [ApiMember(Name = "IncludeAllLanguages", Description = "Optional.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool IncludeAllLanguages { get; set; } } [Route("/Items/{Id}/RemoteImages", "GET")] @@ -145,8 +148,8 @@ namespace MediaBrowser.Api.Images [Api(Description = "Gets a remote image")] public class GetRemoteImage { - [ApiMember(Name = "Url", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Url { get; set; } + [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ImageUrl { get; set; } } public class RemoteImageService : BaseApiService @@ -193,7 +196,7 @@ namespace MediaBrowser.Api.Images private List<ImageProviderInfo> GetImageProviders(BaseItem item) { - return _providerManager.GetImageProviderInfo(item).ToList(); + return _providerManager.GetRemoteImageProviderInfo(item).ToList(); } public object Get(GetRemoteImages request) @@ -217,14 +220,28 @@ namespace MediaBrowser.Api.Images private RemoteImageResult GetRemoteImageResult(BaseItem item, BaseRemoteImageRequest request) { - var images = _providerManager.GetAvailableRemoteImages(item, CancellationToken.None, request.ProviderName, request.Type).Result; + var images = _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery + { + ProviderName = request.ProviderName, + IncludeAllLanguages = request.IncludeAllLanguages, + IncludeDisabledProviders = true, + ImageType = request.Type + + }, CancellationToken.None).Result; var imagesList = images.ToList(); + var allProviders = _providerManager.GetRemoteImageProviderInfo(item); + + if (request.Type.HasValue) + { + allProviders = allProviders.Where(i => i.SupportedImages.Contains(request.Type.Value)); + } + var result = new RemoteImageResult { TotalRecordCount = imagesList.Count, - Providers = images.Select(i => i.ProviderName) + Providers = allProviders.Select(i => i.Name) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList() }; @@ -281,13 +298,7 @@ namespace MediaBrowser.Api.Images { await _providerManager.SaveImage(item, request.ImageUrl, null, request.Type, null, CancellationToken.None).ConfigureAwait(false); - await item.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = true, - ImageRefreshMode = ImageRefreshMode.ValidationOnly, - MetadataRefreshMode = MetadataRefreshMode.None - - }, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); } /// <summary> @@ -309,7 +320,7 @@ namespace MediaBrowser.Api.Images /// <returns>Task{System.Object}.</returns> private async Task<object> GetRemoteImage(GetRemoteImage request) { - var urlHash = request.Url.GetMD5(); + var urlHash = request.ImageUrl.GetMD5(); var pointerCachePath = GetFullCachePath(urlHash.ToString()); string contentPath; @@ -326,12 +337,16 @@ namespace MediaBrowser.Api.Images return ToStaticFileResult(contentPath); } } + catch (DirectoryNotFoundException) + { + // Means the file isn't cached yet + } catch (FileNotFoundException) { // Means the file isn't cached yet } - await DownloadImage(request.Url, urlHash, pointerCachePath).ConfigureAwait(false); + await DownloadImage(request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); // Read the pointer file again using (var reader = new StreamReader(pointerCachePath)) @@ -362,7 +377,6 @@ namespace MediaBrowser.Api.Images var fullCachePath = GetFullCachePath(urlHash + "." + ext); Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) { using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) @@ -371,6 +385,7 @@ namespace MediaBrowser.Api.Images } } + Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); using (var writer = new StreamWriter(pointerCachePath)) { await writer.WriteAsync(fullCachePath).ConfigureAwait(false); @@ -384,7 +399,7 @@ namespace MediaBrowser.Api.Images /// <returns>System.String.</returns> private string GetFullCachePath(string filename) { - return Path.Combine(_appPaths.DownloadedImagesDataPath, filename.Substring(0, 1), filename); + return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); } } } diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index ca56e40af..97e43f018 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -111,13 +111,15 @@ namespace MediaBrowser.Api .OfType<MusicArtist>() .ToList(); - var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), cancellationToken, true, request.Forced)); + var options = GetRefreshOptions(request); + + var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), cancellationToken, options, true)); await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false); try { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -250,9 +252,11 @@ namespace MediaBrowser.Api { var item = _dtoService.GetItemByDtoId(request.Id); + var options = GetRefreshOptions(request); + try { - await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false); + await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); if (item.IsFolder) { @@ -267,7 +271,7 @@ namespace MediaBrowser.Api { var folder = (Folder)item; - await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced).ConfigureAwait(false); + await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, options, request.Recursive).ConfigureAwait(false); } } } @@ -295,7 +299,7 @@ namespace MediaBrowser.Api { var folder = (Folder)child; - await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced).ConfigureAwait(false); + await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, options, request.Recursive).ConfigureAwait(false); } } } @@ -304,8 +308,8 @@ namespace MediaBrowser.Api { return new MetadataRefreshOptions { - MetadataRefreshMode = request.Forced ? MetadataRefreshMode.FullRefresh : MetadataRefreshMode.EnsureMetadata, - ImageRefreshMode = request.Forced ? ImageRefreshMode.FullRefresh : ImageRefreshMode.Default, + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = ImageRefreshMode.FullRefresh, ReplaceAllMetadata = request.Forced }; } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 17520ba1c..6ffa10191 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -69,17 +69,7 @@ namespace MediaBrowser.Api.Library public object Get(GetPhyscialPaths request) { var result = _libraryManager.RootFolder.Children - .SelectMany(c => - { - var locationType = c.LocationType; - - if (locationType != LocationType.Remote && locationType != LocationType.Virtual) - { - return c.PhysicalLocations; - } - - return new List<string>(); - }) + .SelectMany(c => c.PhysicalLocations) .ToList(); return ToOptimizedSerializedResultUsingCache(result); diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index f08d79c0d..ed086398f 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -167,15 +167,32 @@ namespace MediaBrowser.Api.Library public bool RefreshLibrary { get; set; } } - [Route("/Library/Changes/Path", "POST")] + [Route("/Library/Changes/New", "POST")] public class ReportChangedPath : IReturnVoid { /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> - [ApiMember(Name = "Path", Description = "The path that was changed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "Path", Description = "The path that was changed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Path { get; set; } + + [ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ImageUrl { get; set; } + } + + [Route("/Library/Episodes/New", "POST")] + public class ReportNewEpisode : IReturnVoid + { + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "TvdbId", Description = "The tvdb id of the new episode.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string TvdbId { get; set; } + + [ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ImageUrl { get; set; } } /// <summary> diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index a7a58dbfb..aeb795a78 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -243,8 +243,8 @@ namespace MediaBrowser.Api public object Get(GetFile request) { var item = _dtoService.GetItemByDtoId(request.Id); - - if (item.LocationType == LocationType.Remote || item.LocationType == LocationType.Virtual) + var locationType = item.LocationType; + if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { throw new ArgumentException("This command cannot be used for remote or virtual items."); } @@ -331,8 +331,7 @@ namespace MediaBrowser.Api { if (item.Parent is AggregateFolder) { - return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.LocationType == LocationType.FileSystem && - i.PhysicalLocations.Contains(item.Path)); + return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)); } return item; @@ -442,12 +441,9 @@ namespace MediaBrowser.Api var parent = item.Parent; - if (item.LocationType == LocationType.Offline) - { - throw new InvalidOperationException(string.Format("{0} is currently offline.", item.Name)); - } + var locationType = item.LocationType; - if (item.LocationType == LocationType.FileSystem) + if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) { foreach (var path in item.GetDeletePaths().ToList()) { diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 4fdcc8472..f8a674f05 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -38,12 +38,6 @@ namespace MediaBrowser.Api.Playback.Hls [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] public class GetHlsVideoSegment : VideoStreamRequest { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public string Id { get; set; } - public string PlaylistId { get; set; } /// <summary> diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 330d7c46f..18bd8c695 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -165,9 +165,11 @@ namespace MediaBrowser.Api ProductionYear = item.ProductionYear }; - if (item.HasImage(ImageType.Primary)) + var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); + + if (primaryImageTag.HasValue) { - result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImagePath(ImageType.Primary)); + result.PrimaryImageTag = primaryImageTag.Value; } SetThumbImageInfo(result, item); @@ -241,8 +243,13 @@ namespace MediaBrowser.Api if (itemWithImage != null) { - hint.ThumbImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb, itemWithImage.GetImagePath(ImageType.Thumb)); - hint.ThumbImageItemId = itemWithImage.Id.ToString("N"); + var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb); + + if (tag.HasValue) + { + hint.ThumbImageTag = tag.Value; + hint.ThumbImageItemId = itemWithImage.Id.ToString("N"); + } } } @@ -257,8 +264,13 @@ namespace MediaBrowser.Api if (itemWithImage != null) { - hint.BackdropImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop, itemWithImage.GetImagePath(ImageType.Backdrop, 0)); - hint.BackdropImageItemId = itemWithImage.Id.ToString("N"); + var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop); + + if (tag.HasValue) + { + hint.BackdropImageTag = tag.Value; + hint.BackdropImageItemId = itemWithImage.Id.ToString("N"); + } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 101a379ea..39cccf28a 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1181,21 +1181,6 @@ namespace MediaBrowser.Api.UserLibrary /// <returns><c>true</c> if the specified item has image; otherwise, <c>false</c>.</returns> internal static bool HasImage(BaseItem item, ImageType imageType) { - if (imageType == ImageType.Backdrop) - { - return item.BackdropImagePaths.Count > 0; - } - - if (imageType == ImageType.Screenshot) - { - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots == null) - { - return false; - } - return hasScreenshots.ScreenshotImagePaths.Count > 0; - } - return item.HasImage(imageType); } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 80260c83c..740ac7930 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -68,7 +68,7 @@ namespace MediaBrowser.Api /// </summary> [Route("/Users/{Id}/Authenticate", "POST")] [Api(Description = "Authenticates a user")] - public class AuthenticateUser : IReturnVoid + public class AuthenticateUser : IReturn<AuthenticationResult> { /// <summary> /// Gets or sets the id. diff --git a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs index 4193cc797..7fbea2f40 100644 --- a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs +++ b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs @@ -111,9 +111,9 @@ namespace MediaBrowser.Api.WebSocket { var line = await reader.ReadLineAsync().ConfigureAwait(false); - if (line.IndexOf("Info", StringComparison.OrdinalIgnoreCase) != -1 || - line.IndexOf("Warn", StringComparison.OrdinalIgnoreCase) != -1 || - line.IndexOf("Error", StringComparison.OrdinalIgnoreCase) != -1) + if (line.IndexOf("Info -", StringComparison.OrdinalIgnoreCase) != -1 || + line.IndexOf("Warn -", StringComparison.OrdinalIgnoreCase) != -1 || + line.IndexOf("Error -", StringComparison.OrdinalIgnoreCase) != -1) { lines.Add(line); } diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index a3d90cb0d..e583c6b26 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -65,6 +65,14 @@ namespace MediaBrowser.Common.Net Task<Stream> Post(string url, Dictionary<string, string> postData, CancellationToken cancellationToken); /// <summary> + /// Posts the specified options with post data + /// </summary> + /// <param name="options">The options</param> + /// <param name="postData">The post data</param> + /// <returns>Task{Stream}</returns> + Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData); + + /// <summary> /// Posts the specified options. /// </summary> /// <param name="options">The options.</param> diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 2ecf3ec9a..ad5e622fc 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -53,10 +53,9 @@ namespace MediaBrowser.Controller.Drawing /// Gets the image cache tag. /// </summary> /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imagePath">The image path.</param> + /// <param name="image">The image.</param> /// <returns>Guid.</returns> - Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath); + Guid GetImageCacheTag(IHasImages item, ItemImageInfo image); /// <summary> /// Gets the image cache tag. @@ -87,4 +86,24 @@ namespace MediaBrowser.Controller.Drawing /// <returns>Task{System.String}.</returns> Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex); } + + public static class ImageProcessorExtensions + { + public static Guid? GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType) + { + return processor.GetImageCacheTag(item, imageType, 0); + } + + public static Guid? GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType, int imageIndex) + { + var imageInfo = item.GetImageInfo(imageType, imageIndex); + + if (imageInfo == null) + { + return null; + } + + return processor.GetImageCacheTag(item, imageInfo); + } + } } diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs index 475d7bc54..fc7632152 100644 --- a/MediaBrowser.Controller/Entities/AdultVideo.cs +++ b/MediaBrowser.Controller/Entities/AdultVideo.cs @@ -1,7 +1,8 @@ - +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { - public class AdultVideo : Video, IHasPreferredMetadataLanguage + public class AdultVideo : Video, IHasPreferredMetadataLanguage, IHasTaglines { /// <summary> /// Gets or sets the preferred metadata language. @@ -14,5 +15,12 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The preferred metadata country code.</value> public string PreferredMetadataCountryCode { get; set; } + + public List<string> Taglines { get; set; } + + public AdultVideo() + { + Taglines = new List<string>(); + } } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index ef455846e..5cabe1cfe 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; +using MediaBrowser.Controller.Providers; namespace MediaBrowser.Controller.Entities { @@ -56,12 +57,12 @@ namespace MediaBrowser.Controller.Entities public List<string> PhysicalLocationsList { get; set; } - protected override IEnumerable<FileSystemInfo> GetFileSystemChildren() + protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService) { - return CreateResolveArgs().FileSystemChildren; + return CreateResolveArgs(directoryService).FileSystemChildren; } - private ItemResolveArgs CreateResolveArgs() + private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService) { var path = ContainingFolderPath; @@ -80,7 +81,7 @@ namespace MediaBrowser.Controller.Entities // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action @@ -118,9 +119,9 @@ namespace MediaBrowser.Controller.Entities /// Get the children of this folder from the actual file system /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - protected override IEnumerable<BaseItem> GetNonCachedChildren() + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { - return base.GetNonCachedChildren().Concat(_virtualChildren); + return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 6f4a0c4d2..836874db9 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class Audio /// </summary> - public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres + public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>, IHasSeries { public Audio() { @@ -50,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.Audio } } + [IgnoreDataMember] + public string SeriesName + { + get + { + return Album; + } + } + /// <summary> /// Gets or sets the artist. /// </summary> @@ -127,5 +137,16 @@ namespace MediaBrowser.Controller.Entities.Audio { return config.BlockUnratedMusic; } + + public SongInfo GetLookupInfo() + { + var info = GetItemLookupInfo<SongInfo>(); + + info.AlbumArtist = AlbumArtist; + info.Album = Album; + info.Artists = Artists; + + return info; + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index b3bf0d2b6..51c8a8727 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -10,10 +11,10 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class MusicAlbum /// </summary> - public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasTags + public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasTags, IHasLookupInfo<AlbumInfo>, IHasSeries { public List<Guid> SoundtrackIds { get; set; } - + public MusicAlbum() { Artists = new List<string>(); @@ -21,6 +22,15 @@ namespace MediaBrowser.Controller.Entities.Audio Tags = new List<string>(); } + [IgnoreDataMember] + public MusicArtist MusicArtist + { + get + { + return Parents.OfType<MusicArtist>().FirstOrDefault(); + } + } + /// <summary> /// Gets or sets the tags. /// </summary> @@ -40,6 +50,15 @@ namespace MediaBrowser.Controller.Entities.Audio } } + [IgnoreDataMember] + public string SeriesName + { + get + { + return AlbumArtist; + } + } + /// <summary> /// Override this to true if class should be grouped under a container in indicies /// The container class should be defined via IndexContainer @@ -98,7 +117,7 @@ namespace MediaBrowser.Controller.Entities.Audio return "MusicAlbum-MusicBrainzReleaseGroup-" + id; } - id = this.GetProviderId(MetadataProviders.Musicbrainz); + id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum); if (!string.IsNullOrEmpty(id)) { @@ -112,6 +131,26 @@ namespace MediaBrowser.Controller.Entities.Audio { return config.BlockUnratedMusic; } + + public AlbumInfo GetLookupInfo() + { + var id = GetItemLookupInfo<AlbumInfo>(); + + id.AlbumArtist = AlbumArtist; + + var artist = Parents.OfType<MusicArtist>().FirstOrDefault(); + + if (artist != null) + { + id.ArtistProviderIds = artist.ProviderIds; + } + + id.SongInfos = RecursiveChildren.OfType<Audio>() + .Select(i => i.GetLookupInfo()) + .ToList(); + + return id; + } } public class MusicAlbumDisc : Folder diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 9b4e3a736..2b5570a80 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,8 +1,11 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -12,7 +15,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class MusicArtist /// </summary> - public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasTags, IHasProductionLocations + public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasTags, IHasProductionLocations, IHasLookupInfo<ArtistInfo> { [IgnoreDataMember] public List<ItemByNameCounts> UserItemCountList { get; set; } @@ -49,7 +52,7 @@ namespace MediaBrowser.Controller.Entities.Audio } private readonly Task _cachedTask = Task.FromResult(true); - protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { if (IsAccessedByName) { @@ -57,17 +60,7 @@ namespace MediaBrowser.Controller.Entities.Audio return _cachedTask; } - return base.ValidateChildrenInternal(progress, cancellationToken, recursive, forceRefreshMetadata); - } - - public override string GetClientTypeName() - { - if (IsAccessedByName) - { - //return "Artist"; - } - - return base.GetClientTypeName(); + return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService); } public MusicArtist() @@ -87,13 +80,38 @@ namespace MediaBrowser.Controller.Entities.Audio } /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } + + /// <summary> /// Gets the user data key. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> private static string GetUserDataKey(MusicArtist item) { - var id = item.GetProviderId(MetadataProviders.Musicbrainz); + var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!string.IsNullOrEmpty(id)) { @@ -107,5 +125,107 @@ namespace MediaBrowser.Controller.Entities.Audio { return config.BlockUnratedMusic; } + + public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) + { + var items = RecursiveChildren.ToList(); + + var songs = items.OfType<Audio>().ToList(); + + var others = items.Except(songs).ToList(); + + var totalItems = songs.Count + others.Count; + var percentages = new Dictionary<Guid, double>(totalItems); + + var tasks = new List<Task>(); + + // Refresh songs + foreach (var item in songs) + { + if (tasks.Count >= 2) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); + } + + cancellationToken.ThrowIfCancellationRequested(); + var innerProgress = new ActionableProgress<double>(); + + // Avoid implicitly captured closure + var currentChild = item; + innerProgress.RegisterAction(p => + { + lock (percentages) + { + percentages[currentChild.Id] = p / 100; + + var percent = percentages.Values.Sum(); + percent /= totalItems; + percent *= 100; + progress.Report(percent); + } + }); + + tasks.Add(RefreshItem(item, refreshOptions, innerProgress, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); + + // Refresh current item + await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + + // Refresh all non-songs + foreach (var item in others) + { + if (tasks.Count > 3) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); + } + + cancellationToken.ThrowIfCancellationRequested(); + var innerProgress = new ActionableProgress<double>(); + + // Avoid implicitly captured closure + var currentChild = item; + innerProgress.RegisterAction(p => + { + lock (percentages) + { + percentages[currentChild.Id] = p / 100; + + var percent = percentages.Values.Sum(); + percent /= totalItems; + percent *= 100; + progress.Report(percent); + } + }); + + tasks.Add(RefreshItem(item, refreshOptions, innerProgress, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + progress.Report(100); + } + + private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) + { + await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + + progress.Report(100); + } + + public ArtistInfo GetLookupInfo() + { + var info = GetItemLookupInfo<ArtistInfo>(); + + info.SongInfos = RecursiveChildren.OfType<Audio>() + .Select(i => i.GetLookupInfo()) + .ToList(); + + return info; + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index b54e14f2d..5e1d4c3c9 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -26,5 +26,30 @@ namespace MediaBrowser.Controller.Entities.Audio [IgnoreDataMember] public List<ItemByNameCounts> UserItemCountList { get; set; } + + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index de5516e29..8dcf08642 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -21,17 +21,16 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class BaseItem /// </summary> - public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData, IHasMetadata + public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData, IHasMetadata, IHasLookupInfo<ItemLookupInfo> { protected BaseItem() { Genres = new List<string>(); Studios = new List<string>(); People = new List<PersonInfo>(); - BackdropImagePaths = new List<string>(); - Images = new Dictionary<ImageType, string>(); ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); LockedFields = new List<MetadataFields>(); + ImageInfos = new List<ItemImageInfo>(); } /// <summary> @@ -48,6 +47,12 @@ namespace MediaBrowser.Controller.Entities public const string ThemeVideosFolderName = "backdrops"; public const string XbmcTrailerFileSuffix = "-trailer"; + public List<ItemImageInfo> ImageInfos { get; set; } + + /// <summary> + /// Gets a value indicating whether this instance is in mixed folder. + /// </summary> + /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value> public bool IsInMixedFolder { get; set; } private string _name; @@ -119,7 +124,7 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public bool IsOwnedItem + public virtual bool IsOwnedItem { get { @@ -152,6 +157,16 @@ namespace MediaBrowser.Controller.Entities } } + public virtual bool SupportsLocalMetadata + { + get + { + var locationType = LocationType; + + return locationType == LocationType.FileSystem || locationType == LocationType.Offline; + } + } + /// <summary> /// This is just a helper for convenience /// </summary> @@ -160,16 +175,9 @@ namespace MediaBrowser.Controller.Entities public string PrimaryImagePath { get { return this.GetImagePath(ImageType.Primary); } - set { this.SetImagePath(ImageType.Primary, value); } } /// <summary> - /// Gets or sets the images. - /// </summary> - /// <value>The images.</value> - public Dictionary<ImageType, string> Images { get; set; } - - /// <summary> /// Gets or sets the date created. /// </summary> /// <value>The date created.</value> @@ -236,7 +244,7 @@ namespace MediaBrowser.Controller.Entities { var locationType = LocationType; - if (locationType != LocationType.Remote && locationType != LocationType.Virtual) + if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { return new string[] { }; } @@ -258,7 +266,7 @@ namespace MediaBrowser.Controller.Entities private string _sortName; /// <summary> - /// Gets or sets the name of the sort. + /// Gets the name of the sort. /// </summary> /// <value>The name of the sort.</value> [IgnoreDataMember] @@ -350,12 +358,6 @@ namespace MediaBrowser.Controller.Entities public string DisplayMediaType { get; set; } /// <summary> - /// Gets or sets the backdrop image paths. - /// </summary> - /// <value>The backdrop image paths.</value> - public List<string> BackdropImagePaths { get; set; } - - /// <summary> /// Gets or sets the official rating. /// </summary> /// <value>The official rating.</value> @@ -447,9 +449,23 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public virtual string CustomRatingForComparison + public string CustomRatingForComparison { - get { return CustomRating; } + get + { + if (!string.IsNullOrEmpty(CustomRating)) + { + return CustomRating; + } + + var parent = Parent; + if (parent != null) + { + return parent.CustomRatingForComparison; + } + + return null; + } } /// <summary> @@ -458,75 +474,30 @@ namespace MediaBrowser.Controller.Entities /// <returns>List{Video}.</returns> private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren) { - return new List<Trailer>(); - //ItemResolveArgs resolveArgs; - - //try - //{ - // resolveArgs = ResolveArgs; - - // if (!resolveArgs.IsDirectory) - // { - // return new List<Trailer>(); - // } - //} - //catch (IOException ex) - //{ - // Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - // return new List<Trailer>(); - //} - - //var files = new List<FileSystemInfo>(); - - //var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName); - - //// Path doesn't exist. No biggie - //if (folder != null) - //{ - // try - // { - // files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles()); - // } - // catch (IOException ex) - // { - // Logger.ErrorException("Error loading trailers for {0}", ex, Name); - // } - //} - - //// Support xbmc trailers (-trailer suffix on video file names) - //files.AddRange(resolveArgs.FileSystemChildren.Where(i => - //{ - // try - // { - // if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) - // { - // if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) - // { - // return true; - // } - // } - // } - // catch (IOException ex) - // { - // Logger.ErrorException("Error accessing path {0}", ex, i.FullName); - // } - - // return false; - //})); - - //return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => - //{ - // // Try to retrieve it from the db. If we don't find it, use the resolved version - // var dbItem = LibraryManager.GetItemById(video.Id) as Trailer; - - // if (dbItem != null) - // { - // video = dbItem; - // } - - // return video; - - //}).ToList(); + var files = fileSystemChildren.OfType<DirectoryInfo>() + .Where(i => string.Equals(i.Name, TrailerFolderName, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) + .ToList(); + + // Support plex/xbmc convention + files.AddRange(fileSystemChildren.OfType<FileInfo>() + .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase)) + ); + + return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video => + { + // Try to retrieve it from the db. If we don't find it, use the resolved version + var dbItem = LibraryManager.GetItemById(video.Id) as Trailer; + + if (dbItem != null) + { + video = dbItem; + } + + return video; + + // Sort them so that the list can be easily compared for changes + }).OrderBy(i => i.Path).ToList(); } /// <summary> @@ -556,7 +527,9 @@ namespace MediaBrowser.Controller.Entities } return audio; - }).ToList(); + + // Sort them so that the list can be easily compared for changes + }).OrderBy(i => i.Path).ToList(); } /// <summary> @@ -580,7 +553,9 @@ namespace MediaBrowser.Controller.Entities } return item; - }).ToList(); + + // Sort them so that the list can be easily compared for changes + }).OrderBy(i => i.Path).ToList(); } public Task RefreshMetadata(CancellationToken cancellationToken) @@ -598,19 +573,44 @@ namespace MediaBrowser.Controller.Entities { var locationType = LocationType; + var requiresSave = false; + if (IsFolder || Parent != null) { + options.DirectoryService = options.DirectoryService ?? new DirectoryService(Logger); + var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? - GetFileSystemChildren().ToList() : + GetFileSystemChildren(options.DirectoryService).ToList() : new List<FileSystemInfo>(); - await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false); + var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); + + if (ownedItemsChanged) + { + requiresSave = true; + } } + var dateLastSaved = DateLastSaved; + await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false); + + // If it wasn't saved by the provider process, save now + if (requiresSave && dateLastSaved == DateLastSaved) + { + await UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + } } - protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + /// <summary> + /// Refreshes owned items such as trailers, theme videos, special features, etc. + /// Returns true or false indicating if changes were found. + /// </summary> + /// <param name="options"></param> + /// <param name="fileSystemChildren"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { var themeSongsChanged = false; @@ -637,18 +637,15 @@ namespace MediaBrowser.Controller.Entities localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); } } - - if (themeSongsChanged || themeVideosChanged || localTrailersChanged) - { - options.ForceSave = true; - } + + return themeSongsChanged || themeVideosChanged || localTrailersChanged; } - protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren() + protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService) { var path = ContainingFolderPath; - return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly); + return directoryService.GetFileSystemEntries(path); } private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) @@ -670,6 +667,7 @@ namespace MediaBrowser.Controller.Entities private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList(); + var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList(); var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); @@ -885,29 +883,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Finds the particular item by searching through our parents and, if not found there, loading from repo - /// </summary> - /// <param name="id">The id.</param> - /// <returns>BaseItem.</returns> - /// <exception cref="System.ArgumentException"></exception> - protected BaseItem FindParentItem(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentException(); - } - - var parent = Parent; - while (parent != null && !parent.IsRoot) - { - if (parent.Id == id) return parent; - parent = parent.Parent; - } - - return null; - } - - /// <summary> /// Gets a value indicating whether this instance is folder. /// </summary> /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> @@ -1188,43 +1163,31 @@ namespace MediaBrowser.Controller.Entities /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> public bool HasImage(ImageType type, int imageIndex) { - if (type == ImageType.Backdrop) - { - return BackdropImagePaths.Count > imageIndex; - } - if (type == ImageType.Screenshot) - { - var hasScreenshots = this as IHasScreenshots; - return hasScreenshots != null && hasScreenshots.ScreenshotImagePaths.Count > imageIndex; - } - - return !string.IsNullOrEmpty(this.GetImagePath(type)); + return GetImageInfo(type, imageIndex) != null; } - public void SetImagePath(ImageType type, int index, string path) + public void SetImagePath(ImageType type, int index, FileInfo file) { - if (type == ImageType.Backdrop) - { - throw new ArgumentException("Backdrops should be accessed using Item.Backdrops"); - } - if (type == ImageType.Screenshot) + if (type == ImageType.Chapter) { - throw new ArgumentException("Screenshots should be accessed using Item.Screenshots"); + throw new ArgumentException("Cannot set chapter images using SetImagePath"); } - var typeKey = type; + var image = GetImageInfo(type, index); - // If it's null remove the key from the dictionary - if (string.IsNullOrEmpty(path)) + if (image == null) { - if (Images.ContainsKey(typeKey)) + ImageInfos.Add(new ItemImageInfo { - Images.Remove(typeKey); - } + Path = file.FullName, + Type = type, + DateModified = FileSystem.GetLastWriteTimeUtc(file) + }); } else { - Images[typeKey] = path; + image.Path = file.FullName; + image.DateModified = FileSystem.GetLastWriteTimeUtc(file); } } @@ -1234,66 +1197,23 @@ namespace MediaBrowser.Controller.Entities /// <param name="type">The type.</param> /// <param name="index">The index.</param> /// <returns>Task.</returns> - public Task DeleteImage(ImageType type, int? index) + public Task DeleteImage(ImageType type, int index) { - if (type == ImageType.Backdrop) - { - if (!index.HasValue) - { - throw new ArgumentException("Please specify a backdrop image index to delete."); - } - - var file = BackdropImagePaths[index.Value]; + var info = GetImageInfo(type, index); - BackdropImagePaths.Remove(file); - - // Delete the source file - DeleteImagePath(file); - } - else if (type == ImageType.Screenshot) + if (info == null) { - if (!index.HasValue) - { - throw new ArgumentException("Please specify a screenshot image index to delete."); - } - - var hasScreenshots = (IHasScreenshots)this; - var file = hasScreenshots.ScreenshotImagePaths[index.Value]; - - hasScreenshots.ScreenshotImagePaths.Remove(file); - - // Delete the source file - DeleteImagePath(file); + // Nothing to do + return Task.FromResult(true); } - else - { - // Delete the source file - DeleteImagePath(this.GetImagePath(type)); - // Remove it from the item - this.SetImagePath(type, null); - } - - // Refresh metadata - // Need to disable slow providers or the image might get re-downloaded - return RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = true, - ImageRefreshMode = ImageRefreshMode.ValidationOnly, - MetadataRefreshMode = MetadataRefreshMode.None + // Remove it from the item + ImageInfos.Remove(info); - }, CancellationToken.None); - } - - /// <summary> - /// Deletes the image path. - /// </summary> - /// <param name="path">The path.</param> - private void DeleteImagePath(string path) - { - var currentFile = new FileInfo(path); + // Delete the source file + var currentFile = new FileInfo(info.Path); - // This will fail if the file is hidden + // Deletion will fail if the file is hidden so remove the attribute first if (currentFile.Exists) { if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) @@ -1303,90 +1223,33 @@ namespace MediaBrowser.Controller.Entities currentFile.Delete(); } - } - - /// <summary> - /// Validates that images within the item are still on the file system - /// </summary> - public bool ValidateImages() - { - var changed = false; - - // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedKeys = Images - .Where(image => !File.Exists(image.Value)) - .Select(i => i.Key) - .ToList(); - - // Now remove them from the dictionary - foreach (var key in deletedKeys) - { - Images.Remove(key); - changed = true; - } - - if (ValidateBackdrops()) - { - changed = true; - } - if (ValidateScreenshots()) - { - changed = true; - } - return changed; + return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } - /// <summary> - /// Validates that backdrops within the item are still on the file system - /// </summary> - private bool ValidateBackdrops() + public virtual Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) { - var changed = false; - - // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedImages = BackdropImagePaths - .Where(path => !File.Exists(path)) - .ToList(); - - // Now remove them from the dictionary - foreach (var path in deletedImages) - { - BackdropImagePaths.Remove(path); - - changed = true; - } - - return changed; + return LibraryManager.UpdateItem(this, ItemUpdateType.ImageUpdate, cancellationToken); } /// <summary> - /// Validates the screenshots. + /// Validates that images within the item are still on the file system /// </summary> - private bool ValidateScreenshots() + public bool ValidateImages(IDirectoryService directoryService) { - var changed = false; + var allDirectories = ImageInfos.Select(i => System.IO.Path.GetDirectoryName(i.Path)).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + var allFiles = allDirectories.SelectMany(directoryService.GetFiles).Select(i => i.FullName).ToList(); - var hasScreenshots = this as IHasScreenshots; - - if (hasScreenshots == null) - { - return changed; - } - - // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedImages = hasScreenshots.ScreenshotImagePaths - .Where(path => !File.Exists(path)) + var deletedImages = ImageInfos + .Where(image => !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase)) .ToList(); - // Now remove them from the dictionary - foreach (var path in deletedImages) + if (deletedImages.Count > 0) { - hasScreenshots.ScreenshotImagePaths.Remove(path); - changed = true; + ImageInfos = ImageInfos.Except(deletedImages).ToList(); } - return changed; + return deletedImages.Count > 0; } /// <summary> @@ -1400,42 +1263,87 @@ namespace MediaBrowser.Controller.Entities /// <exception cref="System.ArgumentNullException">item</exception> public string GetImagePath(ImageType imageType, int imageIndex) { - if (imageType == ImageType.Backdrop) - { - return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null; - } + var info = GetImageInfo(imageType, imageIndex); + + return info == null ? null : info.Path; + } - if (imageType == ImageType.Screenshot) + /// <summary> + /// Gets the image information. + /// </summary> + /// <param name="imageType">Type of the image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>ItemImageInfo.</returns> + public ItemImageInfo GetImageInfo(ImageType imageType, int imageIndex) + { + if (imageType == ImageType.Chapter) { - var hasScreenshots = (IHasScreenshots)this; - return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null; + var chapter = ItemRepository.GetChapter(Id, imageIndex); + + if (chapter == null) + { + return null; + } + + var path = chapter.ImagePath; + + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + return new ItemImageInfo + { + Path = path, + DateModified = FileSystem.GetLastWriteTimeUtc(path), + Type = imageType + }; } + return GetImages(imageType) + .ElementAtOrDefault(imageIndex); + } + + public IEnumerable<ItemImageInfo> GetImages(ImageType imageType) + { if (imageType == ImageType.Chapter) { - return ItemRepository.GetChapter(Id, imageIndex).ImagePath; + throw new ArgumentException("No image info for chapter images"); } - string val; - Images.TryGetValue(imageType, out val); - return val; + return ImageInfos.Where(i => i.Type == imageType); } /// <summary> - /// Gets the image date modified. + /// Adds the images. /// </summary> - /// <param name="imagePath">The image path.</param> - /// <returns>DateTime.</returns> - /// <exception cref="System.ArgumentNullException">item</exception> - public DateTime GetImageDateModified(string imagePath) + /// <param name="imageType">Type of the image.</param> + /// <param name="images">The images.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + /// <exception cref="System.ArgumentException">Cannot call AddImages with chapter images</exception> + public bool AddImages(ImageType imageType, IEnumerable<FileInfo> images) { - if (string.IsNullOrEmpty(imagePath)) + if (imageType == ImageType.Chapter) { - throw new ArgumentNullException("imagePath"); + throw new ArgumentException("Cannot call AddImages with chapter images"); } - // See if we can avoid a file system lookup by looking for the file in ResolveArgs - return FileSystem.GetLastWriteTimeUtc(imagePath); + var existingImagePaths = GetImages(imageType) + .Select(i => i.Path) + .ToList(); + + var newImages = images + .Where(i => !existingImagePaths.Contains(i.FullName, StringComparer.OrdinalIgnoreCase)) + .ToList(); + + ImageInfos.AddRange(newImages.Select(i => new ItemImageInfo + { + Path = i.FullName, + Type = imageType, + DateModified = FileSystem.GetLastWriteTimeUtc(i) + })); + + return newImages.Count > 0; } /// <summary> @@ -1447,25 +1355,37 @@ namespace MediaBrowser.Controller.Entities return new[] { Path }; } + public bool AllowsMultipleImages(ImageType type) + { + return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter; + } + public Task SwapImages(ImageType type, int index1, int index2) { - if (type != ImageType.Screenshot && type != ImageType.Backdrop) + if (!AllowsMultipleImages(type)) { throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots"); } - var file1 = GetImagePath(type, index1); - var file2 = GetImagePath(type, index2); - - FileSystem.SwapFiles(file1, file2); + var info1 = GetImageInfo(type, index1); + var info2 = GetImageInfo(type, index2); - // Directory watchers should repeat this, but do a quick refresh first - return RefreshMetadata(new MetadataRefreshOptions + if (info1 == null || info2 == null) { - ForceSave = true, - MetadataRefreshMode = MetadataRefreshMode.None + // Nothing to do + return Task.FromResult(true); + } - }, CancellationToken.None); + var path1 = info1.Path; + var path2 = info2.Path; + + FileSystem.SwapFiles(path1, path2); + + // Refresh these values + info1.DateModified = FileSystem.GetLastWriteTimeUtc(info1.Path); + info2.DateModified = FileSystem.GetLastWriteTimeUtc(info2.Path); + + return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } public virtual bool IsPlayed(User user) @@ -1481,5 +1401,41 @@ namespace MediaBrowser.Controller.Entities return userdata == null || !userdata.Played; } + + ItemLookupInfo IHasLookupInfo<ItemLookupInfo>.GetLookupInfo() + { + return GetItemLookupInfo<ItemLookupInfo>(); + } + + protected T GetItemLookupInfo<T>() + where T : ItemLookupInfo, new() + { + return new T + { + MetadataCountryCode = GetPreferredMetadataCountryCode(), + MetadataLanguage = GetPreferredMetadataLanguage(), + Name = Name, + ProviderIds = ProviderIds, + IndexNumber = IndexNumber, + ParentIndexNumber = ParentIndexNumber + }; + } + + /// <summary> + /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what. + /// </summary> + /// <returns>ItemUpdateType.</returns> + public virtual ItemUpdateType BeforeMetadataRefresh() + { + var updateType = ItemUpdateType.None; + + if (string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Path)) + { + Name = System.IO.Path.GetFileNameWithoutExtension(Path); + updateType = updateType | ItemUpdateType.MetadataEdit; + } + + return updateType; + } } } diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 28ccf687c..0405fc484 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Model.Configuration; +using System.Linq; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { - public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage + public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage, IHasLookupInfo<BookInfo>, IHasSeries { public override string MediaType { @@ -38,5 +40,21 @@ namespace MediaBrowser.Controller.Entities { return config.BlockUnratedBooks; } + + public BookInfo GetLookupInfo() + { + var info = GetItemLookupInfo<BookInfo>(); + + if (string.IsNullOrEmpty(SeriesName)) + { + info.SeriesName = Parents.Select(i => i.Name).FirstOrDefault(); + } + else + { + info.SeriesName = SeriesName; + } + + return info; + } } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 9c6b60969..416796b69 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using System; using System.Collections.Generic; using System.IO; @@ -60,12 +61,12 @@ namespace MediaBrowser.Controller.Entities public List<string> PhysicalLocationsList { get; set; } - protected override IEnumerable<FileSystemInfo> GetFileSystemChildren() + protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService) { - return CreateResolveArgs().FileSystemChildren; + return CreateResolveArgs(directoryService).FileSystemChildren; } - private ItemResolveArgs CreateResolveArgs() + private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService) { var path = ContainingFolderPath; @@ -84,7 +85,7 @@ namespace MediaBrowser.Controller.Entities // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action @@ -116,11 +117,13 @@ namespace MediaBrowser.Controller.Entities /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> + /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param> + /// <param name="refreshOptions">The refresh options.</param> + /// <param name="directoryService">The directory service.</param> /// <returns>Task.</returns> - protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { - CreateResolveArgs(); + CreateResolveArgs(directoryService); ResetDynamicChildren(); return NullTaskResult; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 63a1c2bab..cb14ed099 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,11 +1,9 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MoreLinq; using System; @@ -301,15 +299,27 @@ namespace MediaBrowser.Controller.Entities /// <value>The current validation cancellation token source.</value> private CancellationTokenSource CurrentValidationCancellationTokenSource { get; set; } + public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken) + { + return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions()); + } + /// <summary> /// Validates that the children of the folder still exist /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="metadataRefreshOptions">The metadata refresh options.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> /// <returns>Task.</returns> - public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, MetadataRefreshOptions metadataRefreshOptions, bool recursive = true) + { + metadataRefreshOptions.DirectoryService = metadataRefreshOptions.DirectoryService ?? new DirectoryService(Logger); + + return ValidateChildrenWithCancellationSupport(progress, cancellationToken, recursive, true, metadataRefreshOptions, metadataRefreshOptions.DirectoryService); + } + + private async Task ValidateChildrenWithCancellationSupport(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { cancellationToken.ThrowIfCancellationRequested(); @@ -329,7 +339,7 @@ namespace MediaBrowser.Controller.Entities var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken); - await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false); + await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, refreshChildMetadata, refreshOptions, directoryService).ConfigureAwait(false); } catch (OperationCanceledException ex) { @@ -354,21 +364,22 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes - /// ***Currently does not contain logic to maintain items that are unavailable in the file system*** + /// Validates the children internal. /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> + /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param> + /// <param name="refreshOptions">The refresh options.</param> + /// <param name="directoryService">The directory service.</param> /// <returns>Task.</returns> - protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { var locationType = LocationType; cancellationToken.ThrowIfCancellationRequested(); - var validChildren = new List<Tuple<BaseItem, bool>>(); + var validChildren = new List<BaseItem>(); if (locationType != LocationType.Remote && locationType != LocationType.Virtual) { @@ -376,7 +387,7 @@ namespace MediaBrowser.Controller.Entities try { - nonCachedChildren = GetNonCachedChildren(); + nonCachedChildren = GetNonCachedChildren(directoryService); } catch (IOException ex) { @@ -403,43 +414,30 @@ namespace MediaBrowser.Controller.Entities if (currentChildren.TryGetValue(child.Id, out currentChild)) { - //existing item - check if it has changed - if (currentChild.HasChanged(child)) - { - var currentChildLocationType = currentChild.LocationType; - if (currentChildLocationType != LocationType.Remote && - currentChildLocationType != LocationType.Virtual) - { - currentChild.DateModified = child.DateModified; - } - - currentChild.IsInMixedFolder = child.IsInMixedFolder; - validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true)); - } - else + var currentChildLocationType = currentChild.LocationType; + if (currentChildLocationType != LocationType.Remote && + currentChildLocationType != LocationType.Virtual) { - validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false)); + currentChild.DateModified = child.DateModified; } + currentChild.IsInMixedFolder = child.IsInMixedFolder; currentChild.IsOffline = false; } else { //brand new item - needs to be added newItems.Add(child); - - validChildren.Add(new Tuple<BaseItem, bool>(child, true)); } + + validChildren.Add(currentChild); } // If any items were added or removed.... if (newItems.Count > 0 || currentChildren.Count != validChildren.Count) { - var newChildren = validChildren.Select(c => c.Item1).ToList(); - // That's all the new and changed ones - now see if there are any that are missing - var itemsRemoved = currentChildren.Values.Except(newChildren).ToList(); - + var itemsRemoved = currentChildren.Values.Except(validChildren).ToList(); var actualRemovals = new List<BaseItem>(); foreach (var item in itemsRemoved) @@ -448,14 +446,13 @@ namespace MediaBrowser.Controller.Entities item.LocationType == LocationType.Remote) { // Don't remove these because there's no way to accurately validate them. - validChildren.Add(new Tuple<BaseItem, bool>(item, false)); + validChildren.Add(item); } else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path)) { item.IsOffline = true; - - validChildren.Add(new Tuple<BaseItem, bool>(item, false)); + validChildren.Add(item); } else { @@ -481,88 +478,134 @@ namespace MediaBrowser.Controller.Entities await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); } } - else - { - validChildren.AddRange(ActualChildren.Select(i => new Tuple<BaseItem, bool>(i, false))); - } progress.Report(10); cancellationToken.ThrowIfCancellationRequested(); - await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); + if (recursive) + { + await ValidateSubFolders(ActualChildren.OfType<Folder>().ToList(), directoryService, progress, cancellationToken).ConfigureAwait(false); + } + + progress.Report(20); + + if (refreshChildMetadata) + { + var container = this as IMetadataContainer; + + var innerProgress = new ActionableProgress<double>(); + + innerProgress.RegisterAction(p => progress.Report((.80 * p) + 20)); + + if (container != null) + { + await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false); + } + else + { + await RefreshMetadataRecursive(refreshOptions, recursive, innerProgress, cancellationToken); + } + } progress.Report(100); } - /// <summary> - /// Refreshes the children. - /// </summary> - /// <param name="children">The children.</param> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> - /// <returns>Task.</returns> - private async Task RefreshChildren(IList<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false) + private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - var list = children; + var children = ActualChildren.ToList(); - var percentages = new Dictionary<Guid, double>(list.Count); + var percentages = new Dictionary<Guid, double>(children.Count); var tasks = new List<Task>(); - foreach (var tuple in list) + foreach (var child in children) { - if (tasks.Count > 10) + if (tasks.Count >= 8) { await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); } - tasks.Add(RefreshChild(tuple, progress, percentages, list.Count, cancellationToken, recursive, forceRefreshMetadata)); - } + cancellationToken.ThrowIfCancellationRequested(); + var innerProgress = new ActionableProgress<double>(); - cancellationToken.ThrowIfCancellationRequested(); + // Avoid implicitly captured closure + var currentChild = child; + innerProgress.RegisterAction(p => + { + lock (percentages) + { + percentages[currentChild.Id] = p / 100; + + var percent = percentages.Values.Sum(); + percent /= children.Count; + percent *= 100; + progress.Report(percent); + } + }); + + if (child.IsFolder) + { + await RefreshChildMetadata(child, refreshOptions, recursive, innerProgress, cancellationToken) + .ConfigureAwait(false); + } + else + { + tasks.Add(RefreshChildMetadata(child, refreshOptions, false, innerProgress, cancellationToken)); + } + } await Task.WhenAll(tasks).ConfigureAwait(false); + progress.Report(100); } - private async Task RefreshChild(Tuple<BaseItem, bool> currentTuple, IProgress<double> progress, Dictionary<Guid, double> percentages, int childCount, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false) + private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + var container = child as IMetadataContainer; - var child = currentTuple.Item1; - try + if (container != null) { - //refresh it - await child.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = currentTuple.Item2, - ReplaceAllMetadata = forceRefreshMetadata - - }, cancellationToken).ConfigureAwait(false); + await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false); } - catch (IOException ex) + else { - Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); - } - - // Refresh children if a folder and the item changed or recursive is set to true - var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value)); + await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - if (refreshChildren) - { - // Don't refresh children if explicitly set to false - if (recursive.HasValue && recursive.Value == false) + if (recursive) { - refreshChildren = false; + var folder = child as Folder; + + if (folder != null) + { + await folder.RefreshMetadataRecursive(refreshOptions, true, progress, cancellationToken); + } } } + progress.Report(100); + } + + /// <summary> + /// Refreshes the children. + /// </summary> + /// <param name="children">The children.</param> + /// <param name="directoryService">The directory service.</param> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) + { + var list = children; + var childCount = list.Count; + + var percentages = new Dictionary<Guid, double>(list.Count); - if (refreshChildren) + foreach (var item in list) { cancellationToken.ThrowIfCancellationRequested(); + var child = item; + var innerProgress = new ActionableProgress<double>(); innerProgress.RegisterAction(p => @@ -574,23 +617,12 @@ namespace MediaBrowser.Controller.Entities var percent = percentages.Values.Sum(); percent /= childCount; - progress.Report((90 * percent) + 10); + progress.Report((10 * percent) + 10); } }); - await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); - } - else - { - lock (percentages) - { - percentages[child.Id] = 1; - - var percent = percentages.Values.Sum(); - percent /= childCount; - - progress.Report((90 * percent) + 10); - } + await child.ValidateChildrenWithCancellationSupport(innerProgress, cancellationToken, true, false, null, directoryService) + .ConfigureAwait(false); } } @@ -647,9 +679,9 @@ namespace MediaBrowser.Controller.Entities /// Get the children of this folder from the actual file system /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - protected virtual IEnumerable<BaseItem> GetNonCachedChildren() + protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { - return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this); + return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(directoryService), this); } /// <summary> @@ -895,17 +927,21 @@ namespace MediaBrowser.Controller.Entities return item; } - protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { + var changesFound = false; + if (SupportsShortcutChildren && LocationType == LocationType.FileSystem) { if (RefreshLinkedChildren(fileSystemChildren)) { - options.ForceSave = true; + changesFound = true; } } - return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken); + var baseHasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + + return baseHasChanges || changesFound; } /// <summary> @@ -967,11 +1003,11 @@ namespace MediaBrowser.Controller.Entities /// <returns>Task.</returns> public override async Task ChangedExternally() { - await base.ChangedExternally().ConfigureAwait(false); - var progress = new Progress<double>(); await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false); + + await base.ChangedExternally().ConfigureAwait(false); } /// <summary> @@ -1016,50 +1052,17 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException(); } - try - { - var locationType = LocationType; - - if (locationType == LocationType.Remote && string.Equals(Path, path, StringComparison.OrdinalIgnoreCase)) - { - return this; - } - - if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - return this; - } - } - catch (IOException ex) + if (string.Equals(Path, path, StringComparison.OrdinalIgnoreCase)) { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); + return this; } - return RecursiveChildren.Where(i => i.LocationType != LocationType.Virtual).FirstOrDefault(i => + if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) { - try - { - if (string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (i.LocationType != LocationType.Remote) - { - if (i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } + return this; + } - return false; - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return false; - } - }); + return RecursiveChildren.FirstOrDefault(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)); } public override bool IsPlayed(User user) diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index 1b5176362..cc9d9a1a4 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -1,11 +1,12 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { - public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage + public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage, IHasLookupInfo<GameInfo> { public List<Guid> SoundtrackIds { get; set; } @@ -29,18 +30,11 @@ namespace MediaBrowser.Controller.Entities ThemeSongIds = new List<Guid>(); ThemeVideoIds = new List<Guid>(); Tags = new List<string>(); - ScreenshotImagePaths = new List<string>(); } public List<Guid> LocalTrailerIds { get; set; } /// <summary> - /// Gets or sets the screenshot image paths. - /// </summary> - /// <value>The screenshot image paths.</value> - public List<string> ScreenshotImagePaths { get; set; } - - /// <summary> /// Gets or sets the tags. /// </summary> /// <value>The tags.</value> @@ -115,5 +109,14 @@ namespace MediaBrowser.Controller.Entities { return config.BlockUnratedGames; } + + public GameInfo GetLookupInfo() + { + var id = GetItemLookupInfo<GameInfo>(); + + id.GameSystem = GameSystem; + + return id; + } } } diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index ffe62ba03..3a3c575cd 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -1,5 +1,4 @@ using MediaBrowser.Model.Dto; -using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -23,5 +22,30 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public List<ItemByNameCounts> UserItemCountList { get; set; } + + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs index 69cb5e974..f2fec4397 100644 --- a/MediaBrowser.Controller/Entities/GameSystem.cs +++ b/MediaBrowser.Controller/Entities/GameSystem.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using System; @@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class GameSystem /// </summary> - public class GameSystem : Folder + public class GameSystem : Folder, IHasLookupInfo<GameSystemInfo> { /// <summary> /// Return the id that should be used to key display prefs for this item. @@ -47,5 +48,14 @@ namespace MediaBrowser.Controller.Entities // Don't block. Determine by game return false; } + + public GameSystemInfo GetLookupInfo() + { + var id = GetItemLookupInfo<GameSystemInfo>(); + + id.Path = Path; + + return id; + } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 53bc64194..c15ca0aa2 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -25,5 +25,30 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public List<ItemByNameCounts> UserItemCountList { get; set; } + + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index dd6194bc7..d53eba11a 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -1,6 +1,8 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities @@ -32,6 +34,13 @@ namespace MediaBrowser.Controller.Entities LocationType LocationType { get; } /// <summary> + /// Gets the images. + /// </summary> + /// <param name="imageType">Type of the image.</param> + /// <returns>IEnumerable{ItemImageInfo}.</returns> + IEnumerable<ItemImageInfo> GetImages(ImageType imageType); + + /// <summary> /// Gets the image path. /// </summary> /// <param name="imageType">Type of the image.</param> @@ -40,19 +49,20 @@ namespace MediaBrowser.Controller.Entities string GetImagePath(ImageType imageType, int imageIndex); /// <summary> - /// Gets the image date modified. + /// Gets the image information. /// </summary> - /// <param name="imagePath">The image path.</param> - /// <returns>DateTime.</returns> - DateTime GetImageDateModified(string imagePath); + /// <param name="imageType">Type of the image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>ItemImageInfo.</returns> + ItemImageInfo GetImageInfo(ImageType imageType, int imageIndex); /// <summary> /// Sets the image. /// </summary> /// <param name="type">The type.</param> /// <param name="index">The index.</param> - /// <param name="path">The path.</param> - void SetImagePath(ImageType type, int index, string path); + /// <param name="file">The file.</param> + void SetImagePath(ImageType type, int index, FileInfo file); /// <summary> /// Determines whether the specified type has image. @@ -63,6 +73,13 @@ namespace MediaBrowser.Controller.Entities bool HasImage(ImageType type, int imageIndex); /// <summary> + /// Allowses the multiple images. + /// </summary> + /// <param name="type">The type.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + bool AllowsMultipleImages(ImageType type); + + /// <summary> /// Swaps the images. /// </summary> /// <param name="type">The type.</param> @@ -92,13 +109,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Validates the images and returns true or false indicating if any were removed. /// </summary> - bool ValidateImages(); - - /// <summary> - /// Gets or sets the backdrop image paths. - /// </summary> - /// <value>The backdrop image paths.</value> - List<string> BackdropImagePaths { get; set; } + bool ValidateImages(IDirectoryService directoryService); /// <summary> /// Gets a value indicating whether this instance is owned item. @@ -111,6 +122,26 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The containing folder path.</value> string ContainingFolderPath { get; } + + /// <summary> + /// Adds the images. + /// </summary> + /// <param name="imageType">Type of the image.</param> + /// <param name="images">The images.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + bool AddImages(ImageType imageType, IEnumerable<FileInfo> images); + + /// <summary> + /// Determines whether [is save local metadata enabled]. + /// </summary> + /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns> + bool IsSaveLocalMetadataEnabled(); + + /// <summary> + /// Gets a value indicating whether [supports local metadata]. + /// </summary> + /// <value><c>true</c> if [supports local metadata]; otherwise, <c>false</c>.</value> + bool SupportsLocalMetadata { get; } } public static class HasImagesExtensions @@ -136,10 +167,21 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> - /// <param name="path">The path.</param> - public static void SetImagePath(this IHasImages item, ImageType imageType, string path) + /// <param name="file">The file.</param> + public static void SetImagePath(this IHasImages item, ImageType imageType, FileInfo file) + { + item.SetImagePath(imageType, 0, file); + } + + /// <summary> + /// Sets the image path. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <param name="file">The file.</param> + public static void SetImagePath(this IHasImages item, ImageType imageType, string file) { - item.SetImagePath(imageType, 0, path); + item.SetImagePath(imageType, new FileInfo(file)); } } } diff --git a/MediaBrowser.Controller/Providers/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index 19ab00e68..0285b6749 100644 --- a/MediaBrowser.Controller/Providers/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; -namespace MediaBrowser.Controller.Providers +namespace MediaBrowser.Controller.Entities { /// <summary> /// Interface IHasMetadata @@ -35,15 +37,23 @@ namespace MediaBrowser.Controller.Providers DateTime DateLastSaved { get; set; } /// <summary> - /// Determines whether [is save local metadata enabled]. - /// </summary> - /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns> - bool IsSaveLocalMetadataEnabled(); - - /// <summary> /// Gets a value indicating whether this instance is in mixed folder. /// </summary> /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value> bool IsInMixedFolder { get; } + + /// <summary> + /// Updates to repository. + /// </summary> + /// <param name="updateReason">The update reason.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken); + + /// <summary> + /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what. + /// </summary> + /// <returns>ItemUpdateType.</returns> + ItemUpdateType BeforeMetadataRefresh(); } } diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs index 341d6403f..2fd402bc2 100644 --- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs +++ b/MediaBrowser.Controller/Entities/IHasScreenshots.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; - + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -7,10 +6,5 @@ namespace MediaBrowser.Controller.Entities /// </summary> public interface IHasScreenshots { - /// <summary> - /// Gets or sets the screenshot image paths. - /// </summary> - /// <value>The screenshot image paths.</value> - List<string> ScreenshotImagePaths { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs new file mode 100644 index 000000000..64c33a376 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasSeries + { + /// <summary> + /// Gets the name of the series. + /// </summary> + /// <value>The name of the series.</value> + string SeriesName { get; } + } +} diff --git a/MediaBrowser.Controller/Entities/IMetadataContainer.cs b/MediaBrowser.Controller/Entities/IMetadataContainer.cs new file mode 100644 index 000000000..33aa08425 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IMetadataContainer.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Controller.Providers; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Entities +{ + public interface IMetadataContainer + { + /// <summary> + /// Refreshes all metadata. + /// </summary> + /// <param name="refreshOptions">The refresh options.</param> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs new file mode 100644 index 000000000..80aec6482 --- /dev/null +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -0,0 +1,14 @@ +using MediaBrowser.Model.Entities; +using System; + +namespace MediaBrowser.Controller.Entities +{ + public class ItemImageInfo + { + public string Path { get; set; } + + public ImageType Type { get; set; } + + public DateTime DateModified { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 19d0d6682..a00bda772 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,15 +1,20 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities.Movies { /// <summary> /// Class BoxSet /// </summary> - public class BoxSet : Folder, IHasTrailers, IHasTags, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder + public class BoxSet : Folder, IHasTrailers, IHasTags, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IMetadataContainer { public BoxSet() { @@ -74,5 +79,67 @@ namespace MediaBrowser.Controller.Entities.Movies // Default sorting return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending); } + + public BoxSetInfo GetLookupInfo() + { + return GetItemLookupInfo<BoxSetInfo>(); + } + + public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) + { + // Refresh bottom up, children first, then the boxset + // By then hopefully the movies within will have Tmdb collection values + var items = RecursiveChildren.ToList(); + + var totalItems = items.Count; + var percentages = new Dictionary<Guid, double>(totalItems); + + var tasks = new List<Task>(); + + // Refresh songs + foreach (var item in items) + { + if (tasks.Count >= 2) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); + } + + cancellationToken.ThrowIfCancellationRequested(); + var innerProgress = new ActionableProgress<double>(); + + // Avoid implicitly captured closure + var currentChild = item; + innerProgress.RegisterAction(p => + { + lock (percentages) + { + percentages[currentChild.Id] = p / 100; + + var percent = percentages.Values.Sum(); + percent /= totalItems; + percent *= 100; + progress.Report(percent); + } + }); + + tasks.Add(RefreshItem(item, refreshOptions, innerProgress, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); + + // Refresh current item + await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + + progress.Report(100); + } + + private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) + { + await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + + progress.Report(100); + } } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 41a1969d6..8eba21df0 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// <summary> /// Class Movie /// </summary> - public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore + public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo> { public List<Guid> SpecialFeatureIds { get; set; } @@ -39,7 +39,6 @@ namespace MediaBrowser.Controller.Entities.Movies ThemeSongIds = new List<Guid>(); ThemeVideoIds = new List<Guid>(); Taglines = new List<string>(); - Tags = new List<string>(); Keywords = new List<string>(); } @@ -53,12 +52,6 @@ namespace MediaBrowser.Controller.Entities.Movies public List<MediaUrl> RemoteTrailers { get; set; } /// <summary> - /// Gets or sets the tags. - /// </summary> - /// <value>The tags.</value> - public List<string> Tags { get; set; } - - /// <summary> /// Gets or sets the taglines. /// </summary> /// <value>The taglines.</value> @@ -103,9 +96,9 @@ namespace MediaBrowser.Controller.Entities.Movies return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey(); } - protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); // Must have a parent to have special features // In other words, it must be part of the Parent/Child tree @@ -115,12 +108,14 @@ namespace MediaBrowser.Controller.Entities.Movies if (specialFeaturesChanged) { - options.ForceSave = true; + hasChanges = true; } } + + return hasChanges; } - private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { var newItems = LoadSpecialFeatures(fileSystemChildren).ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); @@ -157,12 +152,19 @@ namespace MediaBrowser.Controller.Entities.Movies } return video; - }); + + // Sort them so that the list can be easily compared for changes + }).OrderBy(i => i.Path).ToList(); } protected override bool GetBlockUnratedValue(UserConfiguration config) { return config.BlockUnratedMovies; } + + public MovieInfo GetLookupInfo() + { + return GetItemLookupInfo<MovieInfo>(); + } } } diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index d9eff8fbe..56cd71d49 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,11 +1,12 @@ using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; namespace MediaBrowser.Controller.Entities { - public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget + public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget, IHasLookupInfo<MusicVideoInfo> { /// <summary> /// Gets or sets the artist. @@ -54,5 +55,10 @@ namespace MediaBrowser.Controller.Entities { return config.BlockUnratedMusic; } + + public MusicVideoInfo GetLookupInfo() + { + return GetItemLookupInfo<MusicVideoInfo>(); + } } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 832586ab9..c1dc81136 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Dto; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; using System.Collections.Generic; using System.Runtime.Serialization; @@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// This is the full Person object that can be retrieved with all of it's data. /// </summary> - public class Person : BaseItem, IItemByName + public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo> { public Person() { @@ -31,6 +32,36 @@ namespace MediaBrowser.Controller.Entities { return "Person-" + Name; } + + public PersonLookupInfo GetLookupInfo() + { + return GetItemLookupInfo<PersonLookupInfo>(); + } + + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 7bc17549f..5c3946f9b 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -26,5 +26,30 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public List<ItemByNameCounts> UserItemCountList { get; set; } + + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 73726a4e2..daff3dd6c 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -1,4 +1,7 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Class Episode /// </summary> - public class Episode : Video + public class Episode : Video, IHasLookupInfo<EpisodeInfo>, IHasSeries { /// <summary> /// Gets the season in which it aired. @@ -41,7 +44,7 @@ namespace MediaBrowser.Controller.Entities.TV /// </summary> /// <value>The index number.</value> public int? IndexNumberEnd { get; set; } - + /// <summary> /// We want to group into series not show individually in an index /// </summary> @@ -98,9 +101,11 @@ namespace MediaBrowser.Controller.Entities.TV /// <returns>System.String.</returns> public override string GetUserDataKey() { - if (Series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) + var series = Series; + + if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue) { - return Series.GetUserDataKey() + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000"); + return series.GetUserDataKey() + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000"); } return base.GetUserDataKey(); @@ -112,16 +117,11 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public override string OfficialRatingForComparison { - get { return Series != null ? Series.OfficialRatingForComparison : base.OfficialRatingForComparison; } - } - - /// <summary> - /// Our rating comes from our series - /// </summary> - [IgnoreDataMember] - public override string CustomRatingForComparison - { - get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; } + get + { + var series = Series; + return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison; + } } /// <summary> @@ -140,6 +140,16 @@ namespace MediaBrowser.Controller.Entities.TV get { return FindParent<Season>(); } } + [IgnoreDataMember] + public string SeriesName + { + get + { + var series = Series; + return series == null ? null : series.Name; + } + } + /// <summary> /// Creates the name of the sort. /// </summary> @@ -175,7 +185,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return LocationType == Model.Entities.LocationType.Virtual && PremiereDate.HasValue && PremiereDate.Value < DateTime.UtcNow; + return LocationType == LocationType.Virtual && PremiereDate.HasValue && PremiereDate.Value < DateTime.UtcNow; } } @@ -188,7 +198,7 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public bool IsVirtualUnaired { - get { return LocationType == Model.Entities.LocationType.Virtual && IsUnaired; } + get { return LocationType == LocationType.Virtual && IsUnaired; } } [IgnoreDataMember] @@ -236,5 +246,70 @@ namespace MediaBrowser.Controller.Entities.TV { return config.BlockUnratedSeries; } + + public EpisodeInfo GetLookupInfo() + { + var id = GetItemLookupInfo<EpisodeInfo>(); + + var series = Series; + + if (series != null) + { + id.SeriesProviderIds = series.ProviderIds; + } + + id.IndexNumberEnd = IndexNumberEnd; + + return id; + } + + public override ItemUpdateType BeforeMetadataRefresh() + { + var updateType = base.BeforeMetadataRefresh(); + + var locationType = LocationType; + if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) + { + if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) + { + IndexNumber = IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(Path, Parent is Season); + + // If a change was made record it + if (IndexNumber.HasValue) + { + updateType = updateType | ItemUpdateType.MetadataImport; + } + } + + if (!IndexNumberEnd.HasValue && !string.IsNullOrEmpty(Path)) + { + IndexNumberEnd = IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(Path); + + // If a change was made record it + if (IndexNumberEnd.HasValue) + { + updateType = updateType | ItemUpdateType.MetadataImport; + } + } + } + + if (!ParentIndexNumber.HasValue) + { + var season = Season; + + if (season != null) + { + ParentIndexNumber = season.IndexNumber; + } + + // If a change was made record it + if (ParentIndexNumber.HasValue) + { + updateType = updateType | ItemUpdateType.MetadataImport; + } + } + + return updateType; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 744416560..830ccb8a2 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -1,11 +1,10 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.Serialization; @@ -14,7 +13,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Class Season /// </summary> - public class Season : Folder + public class Season : Folder, IHasSeries, IHasLookupInfo<SeasonInfo> { /// <summary> @@ -119,16 +118,11 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public override string OfficialRatingForComparison { - get { return Series != null ? Series.OfficialRatingForComparison : base.OfficialRatingForComparison; } - } - - /// <summary> - /// Our rating comes from our series - /// </summary> - [IgnoreDataMember] - public override string CustomRatingForComparison - { - get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; } + get + { + var series = Series; + return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison; + } } /// <summary> @@ -223,7 +217,7 @@ namespace MediaBrowser.Controller.Entities.TV { episodes = episodes.Where(i => !i.IsVirtualUnaired); } - + return LibraryManager .Sort(episodes, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending) .Cast<Episode>(); @@ -239,5 +233,51 @@ namespace MediaBrowser.Controller.Entities.TV // Don't block. Let either the entire series rating or episode rating determine it return false; } + + [IgnoreDataMember] + public string SeriesName + { + get + { + var series = Series; + return series == null ? null : series.Name; + } + } + + /// <summary> + /// Gets the lookup information. + /// </summary> + /// <returns>SeasonInfo.</returns> + public SeasonInfo GetLookupInfo() + { + return GetItemLookupInfo<SeasonInfo>(); + } + + /// <summary> + /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what. + /// </summary> + /// <returns>ItemUpdateType.</returns> + public override ItemUpdateType BeforeMetadataRefresh() + { + var updateType = base.BeforeMetadataRefresh(); + + var locationType = LocationType; + + if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) + { + if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) + { + IndexNumber = IndexNumber ?? TVUtils.GetSeasonNumberFromPath(Path); + + // If a change was made record it + if (IndexNumber.HasValue) + { + updateType = updateType | ItemUpdateType.MetadataImport; + } + } + } + + return updateType; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index efb3c393b..0e07654d6 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -14,7 +15,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Class Series /// </summary> - public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage, IHasDisplayOrder + public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo<SeriesInfo> { public List<Guid> SpecialFeatureIds { get; set; } public List<Guid> SoundtrackIds { get; set; } @@ -222,5 +223,10 @@ namespace MediaBrowser.Controller.Entities.TV } public string PreferredMetadataLanguage { get; set; } + + public SeriesInfo GetLookupInfo() + { + return GetItemLookupInfo<SeriesInfo>(); + } } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index d6d193442..d655c275d 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Trailer /// </summary> - public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasKeywords, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage, IHasMetascore + public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasKeywords, IHasTaglines, IHasPreferredMetadataLanguage, IHasMetascore, IHasLookupInfo<TrailerInfo> { public List<Guid> SoundtrackIds { get; set; } @@ -27,7 +28,6 @@ namespace MediaBrowser.Controller.Entities Taglines = new List<string>(); SoundtrackIds = new List<Guid>(); LocalTrailerIds = new List<Guid>(); - Tags = new List<string>(); Keywords = new List<string>(); } @@ -40,12 +40,6 @@ namespace MediaBrowser.Controller.Entities public List<string> Keywords { get; set; } /// <summary> - /// Gets or sets the tags. - /// </summary> - /// <value>The tags.</value> - public List<string> Tags { get; set; } - - /// <summary> /// Gets or sets the taglines. /// </summary> /// <value>The taglines.</value> @@ -105,5 +99,10 @@ namespace MediaBrowser.Controller.Entities { return config.BlockUnratedTrailers; } + + public TrailerInfo GetLookupInfo() + { + return GetItemLookupInfo<TrailerInfo>(); + } } } diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 5feb000af..66ef8c7dc 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -73,6 +73,31 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } + + /// <summary> /// The _root folder /// </summary> private UserRootFolder _rootFolder; @@ -215,12 +240,18 @@ namespace MediaBrowser.Controller.Entities return RefreshMetadata(new MetadataRefreshOptions { - ForceSave = true, - ReplaceAllMetadata = true + ReplaceAllMetadata = true, + ImageRefreshMode = ImageRefreshMode.FullRefresh, + MetadataRefreshMode = MetadataRefreshMode.FullRefresh }, CancellationToken.None); } + public override Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) + { + return UserManager.UpdateUser(this); + } + /// <summary> /// Gets the path to the user's configuration directory /// </summary> diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 8fe5f43f1..dc3d4c384 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using System.Collections.Generic; using System.Linq; namespace MediaBrowser.Controller.Entities @@ -13,9 +15,22 @@ namespace MediaBrowser.Controller.Entities /// Get the children of this folder from the actual file system /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - protected override IEnumerable<BaseItem> GetNonCachedChildren() + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { - return base.GetNonCachedChildren().Concat(LibraryManager.RootFolder.VirtualChildren); + return base.GetNonCachedChildren(directoryService).Concat(LibraryManager.RootFolder.VirtualChildren); + } + + public override ItemUpdateType BeforeMetadataRefresh() + { + var updateType = base.BeforeMetadataRefresh(); + + if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase)) + { + Name = "Default Media Library"; + updateType = updateType | ItemUpdateType.MetadataEdit; + } + + return updateType; } } } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index de78068b3..e778b38bd 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Video /// </summary> - public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio + public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio, IHasTags { public bool IsMultiPart { get; set; } @@ -26,15 +26,29 @@ namespace MediaBrowser.Controller.Entities { PlayableStreamFileNames = new List<string>(); AdditionalPartIds = new List<Guid>(); + Tags = new List<string>(); + SubtitleFiles = new List<string>(); } /// <summary> + /// Gets or sets the subtitle paths. + /// </summary> + /// <value>The subtitle paths.</value> + public List<string> SubtitleFiles { get; set; } + + /// <summary> /// Gets or sets a value indicating whether this instance has subtitles. /// </summary> /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value> public bool HasSubtitles { get; set; } /// <summary> + /// Gets or sets the tags. + /// </summary> + /// <value>The tags.</value> + public List<string> Tags { get; set; } + + /// <summary> /// Gets or sets the video bit rate. /// </summary> /// <value>The video bit rate.</value> @@ -149,9 +163,9 @@ namespace MediaBrowser.Controller.Entities } } - protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); // Must have a parent to have additional parts // In other words, it must be part of the Parent/Child tree @@ -162,9 +176,11 @@ namespace MediaBrowser.Controller.Entities if (additionalPartsChanged) { - options.ForceSave = true; + hasChanges = true; } } + + return hasChanges; } /// <summary> @@ -174,7 +190,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="fileSystemChildren">The file system children.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> - private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { var newItems = LoadAdditionalParts(fileSystemChildren).ToList(); @@ -238,7 +254,8 @@ namespace MediaBrowser.Controller.Entities return video; - }).ToList(); + // Sort them so that the list can be easily compared for changes + }).OrderBy(i => i.Path).ToList(); } public override IEnumerable<string> GetDeletePaths() diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index cd50a1c60..c6ca028ae 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -26,5 +26,30 @@ namespace MediaBrowser.Controller.Entities { return "Year-" + Name; } + + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index 270afd89a..4ee8a810b 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -15,16 +16,16 @@ namespace MediaBrowser.Controller.IO /// <summary> /// Gets the filtered file system entries. /// </summary> + /// <param name="directoryService">The directory service.</param> /// <param name="path">The path.</param> /// <param name="fileSystem">The file system.</param> /// <param name="logger">The logger.</param> /// <param name="args">The args.</param> - /// <param name="searchPattern">The search pattern.</param> /// <param name="flattenFolderDepth">The flatten folder depth.</param> /// <param name="resolveShortcuts">if set to <c>true</c> [resolve shortcuts].</param> /// <returns>Dictionary{System.StringFileSystemInfo}.</returns> /// <exception cref="System.ArgumentNullException">path</exception> - public static Dictionary<string, FileSystemInfo> GetFilteredFileSystemEntries(string path, IFileSystem fileSystem, ILogger logger, ItemResolveArgs args, string searchPattern = "*", int flattenFolderDepth = 0, bool resolveShortcuts = true) + public static Dictionary<string, FileSystemInfo> GetFilteredFileSystemEntries(IDirectoryService directoryService, string path, IFileSystem fileSystem, ILogger logger, ItemResolveArgs args, int flattenFolderDepth = 0, bool resolveShortcuts = true) { if (string.IsNullOrEmpty(path)) { @@ -35,7 +36,7 @@ namespace MediaBrowser.Controller.IO throw new ArgumentNullException("args"); } - var entries = new DirectoryInfo(path).EnumerateFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly); + var entries = directoryService.GetFileSystemEntries(path); if (!resolveShortcuts && flattenFolderDepth == 0) { @@ -79,7 +80,7 @@ namespace MediaBrowser.Controller.IO } else if (flattenFolderDepth > 0 && isDirectory) { - foreach (var child in GetFilteredFileSystemEntries(fullName, fileSystem, logger, args, flattenFolderDepth: flattenFolderDepth - 1, resolveShortcuts: resolveShortcuts)) + foreach (var child in GetFilteredFileSystemEntries(directoryService, fullName, fileSystem, logger, args, flattenFolderDepth: flattenFolderDepth - 1, resolveShortcuts: resolveShortcuts)) { dict[child.Key] = child.Value; } diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index e163ac31d..05f681597 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Configuration; +using System; +using MediaBrowser.Common.Configuration; namespace MediaBrowser.Controller { @@ -101,9 +102,16 @@ namespace MediaBrowser.Controller string TranscodingTempPath { get; } /// <summary> - /// Gets the downloaded images data path. + /// Gets the internal metadata path. /// </summary> - /// <value>The downloaded images data path.</value> - string DownloadedImagesDataPath { get; } + /// <value>The internal metadata path.</value> + string InternalMetadataPath { get; } + + /// <summary> + /// Gets the internal metadata path. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>System.String.</returns> + string GetInternalMetadataPath(Guid id); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index d8ba019db..7c803e651 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -162,17 +162,13 @@ namespace MediaBrowser.Controller.Library /// <param name="resolvers">The resolvers.</param> /// <param name="introProviders">The intro providers.</param> /// <param name="itemComparers">The item comparers.</param> - /// <param name="prescanTasks">The prescan tasks.</param> /// <param name="postscanTasks">The postscan tasks.</param> - /// <param name="peoplePrescanTasks">The people prescan tasks.</param> void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, - IEnumerable<ILibraryPrescanTask> prescanTasks, - IEnumerable<ILibraryPostScanTask> postscanTasks, - IEnumerable<IPeoplePrescanTask> peoplePrescanTasks); + IEnumerable<ILibraryPostScanTask> postscanTasks); /// <summary> /// Sorts the specified items. diff --git a/MediaBrowser.Controller/Library/ILibraryPrescanTask.cs b/MediaBrowser.Controller/Library/ILibraryPrescanTask.cs deleted file mode 100644 index 6a48ba777..000000000 --- a/MediaBrowser.Controller/Library/ILibraryPrescanTask.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Library -{ - /// <summary> - /// An interface for tasks that run prior to the media library scan - /// </summary> - public interface ILibraryPrescanTask - { - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task Run(IProgress<double> progress, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index 75d318057..cfee9d206 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using System.Threading; namespace MediaBrowser.Controller.Library diff --git a/MediaBrowser.Controller/Library/IPeoplePrescanTask.cs b/MediaBrowser.Controller/Library/IPeoplePrescanTask.cs deleted file mode 100644 index 04d179bae..000000000 --- a/MediaBrowser.Controller/Library/IPeoplePrescanTask.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Library -{ - /// <summary> - /// Interface IPeoplePrescanTask - /// </summary> - public interface IPeoplePrescanTask - { - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task Run(IProgress<double> progress, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 5d6d850f0..d84e7aa8c 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -97,18 +97,6 @@ namespace MediaBrowser.Controller.Library } /// <summary> - /// Gets a value indicating whether this instance is system file. - /// </summary> - /// <value><c>true</c> if this instance is system file; otherwise, <c>false</c>.</value> - public bool IsSystemFile - { - get - { - return (FileInfo.Attributes & FileAttributes.System) == FileAttributes.System; - } - } - - /// <summary> /// Gets a value indicating whether this instance is vf. /// </summary> /// <value><c>true</c> if this instance is vf; otherwise, <c>false</c>.</value> @@ -238,22 +226,6 @@ namespace MediaBrowser.Controller.Library } /// <summary> - /// Gets the meta file by path. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>FileSystemInfo.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - public FileSystemInfo GetMetaFileByPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(); - } - - return GetFileSystemEntryByPath(path); - } - - /// <summary> /// Gets the name of the meta file by. /// </summary> /// <param name="name">The name.</param> diff --git a/MediaBrowser.Controller/Library/ItemUpdateType.cs b/MediaBrowser.Controller/Library/ItemUpdateType.cs index 31a00d7b4..cf6263356 100644 --- a/MediaBrowser.Controller/Library/ItemUpdateType.cs +++ b/MediaBrowser.Controller/Library/ItemUpdateType.cs @@ -5,7 +5,7 @@ namespace MediaBrowser.Controller.Library [Flags] public enum ItemUpdateType { - Unspecified = 1, + None = 1, MetadataImport = 2, ImageUpdate = 4, MetadataDownload = 8, diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs index 93de9d5c3..a4a85ec04 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -25,6 +25,18 @@ namespace MediaBrowser.Controller.LiveTv public string ServiceName { get; set; } + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } + public override string MediaType { get @@ -55,5 +67,13 @@ namespace MediaBrowser.Controller.LiveTv { return false; } + + public override bool SupportsLocalMetadata + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index f37e94714..583b90fd4 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -26,6 +26,31 @@ namespace MediaBrowser.Controller.LiveTv public List<ItemByNameCounts> UserItemCountList { get; set; } /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } + + /// <summary> /// Gets or sets the number. /// </summary> /// <value>The number.</value> diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 6a00607e4..e7a501479 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,6 +1,9 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.LiveTv; using System; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.LiveTv { @@ -127,6 +130,31 @@ namespace MediaBrowser.Controller.LiveTv /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> public bool IsPremiere { get; set; } + /// <summary> + /// Returns the folder containing the item. + /// If the item is a folder, it returns the folder itself + /// </summary> + /// <value>The containing folder path.</value> + public override string ContainingFolderPath + { + get + { + return Path; + } + } + + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } + public override string MediaType { get @@ -159,5 +187,14 @@ namespace MediaBrowser.Controller.LiveTv { return "Program"; } + + public override Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) + { + DateLastSaved = DateTime.UtcNow; + + // Avoid library manager and keep out of in-memory cache + // Not great that this class has to know about that, but we'll improve that later. + return ItemRepository.SaveItem(this, cancellationToken); + } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs index bc4ed5493..6e3644b4a 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs @@ -46,6 +46,18 @@ namespace MediaBrowser.Controller.LiveTv } } + /// <summary> + /// Gets a value indicating whether this instance is owned item. + /// </summary> + /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value> + public override bool IsOwnedItem + { + get + { + return false; + } + } + public override string GetClientTypeName() { return "Recording"; @@ -55,5 +67,13 @@ namespace MediaBrowser.Controller.LiveTv { return false; } + + public override bool SupportsLocalMetadata + { + get + { + return false; + } + } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8a11cc9a0..18ac01c8b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -94,6 +94,7 @@ <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" /> <Compile Include="Entities\IHasProductionLocations.cs" /> <Compile Include="Entities\IHasScreenshots.cs" /> + <Compile Include="Entities\IHasSeries.cs" /> <Compile Include="Entities\IHasSoundtracks.cs" /> <Compile Include="Entities\IHasTaglines.cs" /> <Compile Include="Entities\IHasTags.cs" /> @@ -103,14 +104,14 @@ <Compile Include="Entities\IItemByName.cs" /> <Compile Include="Entities\ILibraryItem.cs" /> <Compile Include="Entities\ImageSourceInfo.cs" /> + <Compile Include="Entities\IMetadataContainer.cs" /> + <Compile Include="Entities\ItemImageInfo.cs" /> <Compile Include="Entities\LinkedChild.cs" /> <Compile Include="Entities\MusicVideo.cs" /> <Compile Include="Entities\IHasAwards.cs" /> <Compile Include="FileOrganization\IFileOrganizationService.cs" /> <Compile Include="Library\ILibraryPostScanTask.cs" /> - <Compile Include="Library\ILibraryPrescanTask.cs" /> <Compile Include="Library\IMetadataSaver.cs" /> - <Compile Include="Library\IPeoplePrescanTask.cs" /> <Compile Include="Library\ItemUpdateType.cs" /> <Compile Include="Library\IUserDataManager.cs" /> <Compile Include="Library\UserDataSaveEventArgs.cs" /> @@ -143,9 +144,10 @@ <Compile Include="Notifications\NotificationUpdateEventArgs.cs" /> <Compile Include="Persistence\IFileOrganizationRepository.cs" /> <Compile Include="Persistence\MediaStreamQuery.cs" /> + <Compile Include="Providers\DirectoryService.cs" /> <Compile Include="Providers\ICustomMetadataProvider.cs" /> <Compile Include="Providers\IHasChangeMonitor.cs" /> - <Compile Include="Providers\IHasMetadata.cs" /> + <Compile Include="Entities\IHasMetadata.cs" /> <Compile Include="Providers\IImageProvider.cs" /> <Compile Include="Providers\ILocalMetadataProvider.cs" /> <Compile Include="Providers\IProviderRepository.cs" /> @@ -154,7 +156,7 @@ <Compile Include="Providers\IMetadataProvider.cs" /> <Compile Include="Providers\IMetadataService.cs" /> <Compile Include="Providers\IRemoteMetadataProvider.cs" /> - <Compile Include="Providers\ItemId.cs" /> + <Compile Include="Providers\ItemLookupInfo.cs" /> <Compile Include="Providers\MetadataRefreshOptions.cs" /> <Compile Include="Providers\NameParser.cs" /> <Compile Include="Providers\MetadataStatus.cs" /> diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index 746157bb3..e3604eb0e 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -177,7 +177,14 @@ namespace MediaBrowser.Controller.MediaInfo Directory.CreateDirectory(parentPath); - await _encoder.ExtractImage(inputPath, type, false, video.Video3DFormat, time, path, cancellationToken).ConfigureAwait(false); + using (var stream = await _encoder.ExtractImage(inputPath, type, false, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false)) + { + using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await stream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } + chapter.ImagePath = path; changesMade = true; } diff --git a/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs b/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs index e77cd14d1..d8cad48b3 100644 --- a/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaInfo/IMediaEncoder.cs @@ -31,19 +31,6 @@ namespace MediaBrowser.Controller.MediaInfo /// <param name="isAudio">if set to <c>true</c> [is audio].</param> /// <param name="threedFormat">The threed format.</param> /// <param name="offset">The offset.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken); - - /// <summary> - /// Extracts the image. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="type">The type.</param> - /// <param name="isAudio">if set to <c>true</c> [is audio].</param> - /// <param name="threedFormat">The threed format.</param> - /// <param name="offset">The offset.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{Stream}.</returns> Task<Stream> ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 5d3d9dbca..59b7cd8db 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -247,7 +247,10 @@ namespace MediaBrowser.Controller.Providers { var val = reader.ReadElementContentAsString(); - + if (!string.IsNullOrWhiteSpace(val)) + { + item.ForcedSortName = val; + } break; } @@ -479,7 +482,7 @@ namespace MediaBrowser.Controller.Providers case "Director": { - foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Director })) + foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new Entities.PersonInfo { Name = v.Trim(), Type = PersonType.Director })) { if (string.IsNullOrWhiteSpace(p.Name)) { @@ -491,7 +494,7 @@ namespace MediaBrowser.Controller.Providers } case "Writer": { - foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Writer })) + foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new Entities.PersonInfo { Name = v.Trim(), Type = PersonType.Writer })) { if (string.IsNullOrWhiteSpace(p.Name)) { @@ -516,7 +519,7 @@ namespace MediaBrowser.Controller.Providers else { // Old-style piped string - foreach (var p in SplitNames(actors).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Actor })) + foreach (var p in SplitNames(actors).Select(v => new Entities.PersonInfo { Name = v.Trim(), Type = PersonType.Actor })) { if (string.IsNullOrWhiteSpace(p.Name)) { @@ -530,7 +533,7 @@ namespace MediaBrowser.Controller.Providers case "GuestStars": { - foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar })) + foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new Entities.PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar })) { if (string.IsNullOrWhiteSpace(p.Name)) { @@ -680,12 +683,30 @@ namespace MediaBrowser.Controller.Providers } break; } - case "MusicbrainzId": + case "MusicBrainzAlbumId": { var mbz = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(mbz)) { - item.SetProviderId(MetadataProviders.Musicbrainz, mbz); + item.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbz); + } + break; + } + case "MusicBrainzAlbumArtistId": + { + var mbz = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(mbz)) + { + item.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, mbz); + } + break; + } + case "MusicBrainzArtistId": + { + var mbz = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(mbz)) + { + item.SetProviderId(MetadataProviders.MusicBrainzArtist, mbz); } break; } @@ -698,6 +719,33 @@ namespace MediaBrowser.Controller.Providers } break; } + case "TvRageId": + { + var id = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(id)) + { + item.SetProviderId(MetadataProviders.TvRage, id); + } + break; + } + case "AudioDbArtistId": + { + var id = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(id)) + { + item.SetProviderId(MetadataProviders.AudioDbArtist, id); + } + break; + } + case "AudioDbAlbumId": + { + var id = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(id)) + { + item.SetProviderId(MetadataProviders.AudioDbAlbum, id); + } + break; + } case "RottenTomatoesId": var rtId = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(rtId)) @@ -1195,7 +1243,7 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <param name="reader">The reader.</param> /// <returns>IEnumerable{PersonInfo}.</returns> - private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader) + private IEnumerable<Entities.PersonInfo> GetPersonsFromXmlNode(XmlReader reader) { var name = string.Empty; var type = "Actor"; // If type is not specified assume actor @@ -1257,7 +1305,7 @@ namespace MediaBrowser.Controller.Providers } } - var personInfo = new PersonInfo + var personInfo = new Entities.PersonInfo { Name = name.Trim(), Role = role, diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index 6818fa52b..f8580244a 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -40,8 +40,6 @@ namespace MediaBrowser.Controller.Providers protected static readonly Task<bool> FalseTaskResult = Task.FromResult(false); - protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4); - /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Controller/Providers/BaseProviderInfo.cs b/MediaBrowser.Controller/Providers/BaseProviderInfo.cs index 829dd34c8..3a33924f0 100644 --- a/MediaBrowser.Controller/Providers/BaseProviderInfo.cs +++ b/MediaBrowser.Controller/Providers/BaseProviderInfo.cs @@ -38,14 +38,14 @@ namespace MediaBrowser.Controller.Providers /// <summary> /// The success /// </summary> - Success, - /// <summary> - /// The failure - /// </summary> - Failure, + Success = 0, /// <summary> /// The completed with errors /// </summary> - CompletedWithErrors - } + CompletedWithErrors = 1, + /// <summary> + /// The failure + /// </summary> + Failure = 2 + } } diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs new file mode 100644 index 000000000..e17ae76ff --- /dev/null +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -0,0 +1,79 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Controller.Providers +{ + public interface IDirectoryService + { + List<FileSystemInfo> GetFileSystemEntries(string path); + IEnumerable<FileInfo> GetFiles(string path); + IEnumerable<DirectoryInfo> GetDirectories(string path); + FileInfo GetFile(string path); + DirectoryInfo GetDirectory(string path); + } + + public class DirectoryService : IDirectoryService + { + private readonly ILogger _logger; + + private readonly Dictionary<string, List<FileSystemInfo>> _cache = new Dictionary<string, List<FileSystemInfo>>(StringComparer.OrdinalIgnoreCase); + + public DirectoryService(ILogger logger) + { + _logger = logger; + } + + public List<FileSystemInfo> GetFileSystemEntries(string path) + { + List<FileSystemInfo> entries; + + if (!_cache.TryGetValue(path, out entries)) + { + //_logger.Debug("Getting files for " + path); + + try + { + entries = new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly).ToList(); + } + catch (DirectoryNotFoundException) + { + entries = new List<FileSystemInfo>(); + } + + _cache.Add(path, entries); + } + + return entries; + } + + public IEnumerable<FileInfo> GetFiles(string path) + { + return GetFileSystemEntries(path).OfType<FileInfo>(); + } + + public IEnumerable<DirectoryInfo> GetDirectories(string path) + { + return GetFileSystemEntries(path).OfType<DirectoryInfo>(); + } + + public FileInfo GetFile(string path) + { + var directory = Path.GetDirectoryName(path); + var filename = Path.GetFileName(path); + + return GetFiles(directory).FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)); + } + + + public DirectoryInfo GetDirectory(string path) + { + var directory = Path.GetDirectoryName(path); + var name = Path.GetFileName(path); + + return GetDirectories(directory).FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs index de75c62e9..c98810cbc 100644 --- a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using System.Threading; using System.Threading.Tasks; @@ -11,6 +12,6 @@ namespace MediaBrowser.Controller.Providers public interface ICustomMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ICustomMetadataProvider where TItemType : IHasMetadata { - Task<ItemUpdateType> FetchAsync(TItemType item, CancellationToken cancellationToken); + Task<ItemUpdateType> FetchAsync(TItemType item, IDirectoryService directoryService, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/IHasChangeMonitor.cs b/MediaBrowser.Controller/Providers/IHasChangeMonitor.cs index 41313da3d..aa0b0e3c9 100644 --- a/MediaBrowser.Controller/Providers/IHasChangeMonitor.cs +++ b/MediaBrowser.Controller/Providers/IHasChangeMonitor.cs @@ -1,4 +1,5 @@ -using System; +using MediaBrowser.Controller.Entities; +using System; namespace MediaBrowser.Controller.Providers { @@ -8,8 +9,9 @@ namespace MediaBrowser.Controller.Providers /// Determines whether the specified item has changed. /// </summary> /// <param name="item">The item.</param> + /// <param name="directoryService">The directory service.</param> /// <param name="date">The date.</param> /// <returns><c>true</c> if the specified item has changed; otherwise, <c>false</c>.</returns> - bool HasChanged(IHasMetadata item, DateTime date); + bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date); } } diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs index ed7cdc8b2..ec24e1d60 100644 --- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -15,14 +16,14 @@ namespace MediaBrowser.Controller.Providers { } - public interface IImageFileProvider : ILocalImageProvider + public interface ILocalImageFileProvider : ILocalImageProvider { - List<LocalImageInfo> GetImages(IHasImages item); + List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService); } public class LocalImageInfo { - public string Path { get; set; } + public FileInfo FileInfo { get; set; } public ImageType Type { get; set; } } @@ -60,7 +61,22 @@ namespace MediaBrowser.Controller.Providers public void SetFormatFromMimeType(string mimeType) { - + if (mimeType.EndsWith("gif", StringComparison.OrdinalIgnoreCase)) + { + Format = ImageFormat.Gif; + } + else if (mimeType.EndsWith("bmp", StringComparison.OrdinalIgnoreCase)) + { + Format = ImageFormat.Bmp; + } + else if (mimeType.EndsWith("png", StringComparison.OrdinalIgnoreCase)) + { + Format = ImageFormat.Png; + } + else + { + Format = ImageFormat.Jpg; + } } } } diff --git a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs index 91ef22b2c..a7c1e6e1b 100644 --- a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs @@ -1,4 +1,6 @@ -using System.Threading; +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers @@ -16,7 +18,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="info">The information.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{MetadataResult{`0}}.</returns> - Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken); + Task<LocalMetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken); } public class ItemInfo @@ -25,4 +27,18 @@ namespace MediaBrowser.Controller.Providers public bool IsInMixedFolder { get; set; } } + + public class LocalMetadataResult<T> + where T : IHasMetadata + { + public bool HasMetadata { get; set; } + public T Item { get; set; } + + public List<LocalImageInfo> Images { get; set; } + + public LocalMetadataResult() + { + Images = new List<LocalImageInfo>(); + } + } } diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index 910c88e53..70bc06059 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -1,4 +1,5 @@ using System; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs index c4314c15f..786a7147c 100644 --- a/MediaBrowser.Controller/Providers/IMetadataService.cs +++ b/MediaBrowser.Controller/Providers/IMetadataService.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 369b58256..691a5add6 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -62,18 +62,17 @@ namespace MediaBrowser.Controller.Providers /// Gets the available remote images. /// </summary> /// <param name="item">The item.</param> + /// <param name="query">The query.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="providerName">Name of the provider.</param> - /// <param name="type">The type.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null); + Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, RemoteImageQuery query, CancellationToken cancellationToken); /// <summary> /// Gets the image providers. /// </summary> /// <param name="item">The item.</param> /// <returns>IEnumerable{ImageProviderInfo}.</returns> - IEnumerable<ImageProviderInfo> GetImageProviderInfo(IHasImages item); + IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(IHasImages item); /// <summary> /// Gets all metadata plugins. @@ -88,5 +87,12 @@ namespace MediaBrowser.Controller.Providers /// <param name="updateType">Type of the update.</param> /// <returns>Task.</returns> Task SaveMetadata(IHasMetadata item, ItemUpdateType updateType); + + /// <summary> + /// Gets the metadata options. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>MetadataOptions.</returns> + MetadataOptions GetMetadataOptions(IHasImages item); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IProviderRepository.cs b/MediaBrowser.Controller/Providers/IProviderRepository.cs index 1c0ad2cd7..3cd2c3f31 100644 --- a/MediaBrowser.Controller/Providers/IProviderRepository.cs +++ b/MediaBrowser.Controller/Providers/IProviderRepository.cs @@ -9,22 +9,6 @@ namespace MediaBrowser.Controller.Providers public interface IProviderRepository : IRepository { /// <summary> - /// Gets the provider history. - /// </summary> - /// <param name="itemId">The item identifier.</param> - /// <returns>IEnumerable{BaseProviderInfo}.</returns> - IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId); - - /// <summary> - /// Saves the provider history. - /// </summary> - /// <param name="id">The identifier.</param> - /// <param name="history">The history.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> history, CancellationToken cancellationToken); - - /// <summary> /// Gets the metadata status. /// </summary> /// <param name="itemId">The item identifier.</param> diff --git a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs index 6007a5af6..cbbd62557 100644 --- a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers { @@ -7,15 +8,10 @@ namespace MediaBrowser.Controller.Providers { } - public interface IRemoteMetadataProvider<TItemType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider - where TItemType : IHasMetadata + public interface IRemoteMetadataProvider<TItemType, in TLookupInfoType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider + where TItemType : IHasMetadata, IHasLookupInfo<TLookupInfoType> + where TLookupInfoType : ItemLookupInfo, new() { - /// <summary> - /// Gets the metadata. - /// </summary> - /// <param name="id">The identifier.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{MetadataResult{`0}}.</returns> - Task<MetadataResult<TItemType>> GetMetadata(ItemId id, CancellationToken cancellationToken); + Task<MetadataResult<TItemType>> GetMetadata(TLookupInfoType info, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/ItemId.cs b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs index 9be6b783c..b43654005 100644 --- a/MediaBrowser.Controller/Providers/ItemId.cs +++ b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace MediaBrowser.Controller.Providers { - public class ItemId : IHasProviderIds + public class ItemLookupInfo : IHasProviderIds { /// <summary> /// Gets or sets the name. @@ -34,13 +34,29 @@ namespace MediaBrowser.Controller.Providers public int? IndexNumber { get; set; } public int? ParentIndexNumber { get; set; } - public ItemId() + public ItemLookupInfo() { ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } } - public class AlbumId : ItemId + public interface IHasLookupInfo<out TLookupInfoType> + where TLookupInfoType : ItemLookupInfo, new() + { + TLookupInfoType GetLookupInfo(); + } + + public class ArtistInfo : ItemLookupInfo + { + public List<SongInfo> SongInfos { get; set; } + + public ArtistInfo() + { + SongInfos = new List<SongInfo>(); + } + } + + public class AlbumInfo : ItemLookupInfo { /// <summary> /// Gets or sets the album artist. @@ -53,14 +69,16 @@ namespace MediaBrowser.Controller.Providers /// </summary> /// <value>The artist provider ids.</value> public Dictionary<string, string> ArtistProviderIds { get; set; } + public List<SongInfo> SongInfos { get; set; } - public AlbumId() + public AlbumInfo() { ArtistProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + SongInfos = new List<SongInfo>(); } } - public class GameId : ItemId + public class GameInfo : ItemLookupInfo { /// <summary> /// Gets or sets the game system. @@ -69,7 +87,7 @@ namespace MediaBrowser.Controller.Providers public string GameSystem { get; set; } } - public class GameSystemId : ItemId + public class GameSystemInfo : ItemLookupInfo { /// <summary> /// Gets or sets the path. @@ -78,15 +96,62 @@ namespace MediaBrowser.Controller.Providers public string Path { get; set; } } - public class EpisodeId : ItemId + public class EpisodeInfo : ItemLookupInfo { public Dictionary<string, string> SeriesProviderIds { get; set; } public int? IndexNumberEnd { get; set; } - public EpisodeId() + public EpisodeInfo() { SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } } + + public class SongInfo : ItemLookupInfo + { + public string AlbumArtist { get; set; } + public string Album { get; set; } + public List<string> Artists { get; set; } + } + + public class SeriesInfo : ItemLookupInfo + { + + } + + public class PersonLookupInfo : ItemLookupInfo + { + + } + + public class MovieInfo : ItemLookupInfo + { + + } + + public class BoxSetInfo : ItemLookupInfo + { + + } + + public class MusicVideoInfo : ItemLookupInfo + { + + } + + public class TrailerInfo : ItemLookupInfo + { + + } + + public class BookInfo : ItemLookupInfo + { + public string SeriesName { get; set; } + } + + public class SeasonInfo : ItemLookupInfo + { + + } } diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 83f1a12d9..780aa6a56 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -21,6 +21,7 @@ namespace MediaBrowser.Controller.Providers public class ImageRefreshOptions { public ImageRefreshMode ImageRefreshMode { get; set; } + public IDirectoryService DirectoryService { get; set; } public ImageRefreshOptions() { diff --git a/MediaBrowser.Controller/Providers/MetadataStatus.cs b/MediaBrowser.Controller/Providers/MetadataStatus.cs index 834d8ec35..1e84e5880 100644 --- a/MediaBrowser.Controller/Providers/MetadataStatus.cs +++ b/MediaBrowser.Controller/Providers/MetadataStatus.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using MediaBrowser.Common.Extensions; namespace MediaBrowser.Controller.Providers { @@ -14,6 +13,24 @@ namespace MediaBrowser.Controller.Providers public Guid ItemId { get; set; } /// <summary> + /// Gets or sets the name of the item. + /// </summary> + /// <value>The name of the item.</value> + public string ItemName { get; set; } + + /// <summary> + /// Gets or sets the type of the item. + /// </summary> + /// <value>The type of the item.</value> + public string ItemType { get; set; } + + /// <summary> + /// Gets or sets the name of the series. + /// </summary> + /// <value>The name of the series.</value> + public string SeriesName { get; set; } + + /// <summary> /// Gets or sets the date last metadata refresh. /// </summary> /// <value>The date last metadata refresh.</value> diff --git a/MediaBrowser.Model/Configuration/MetadataOptions.cs b/MediaBrowser.Model/Configuration/MetadataOptions.cs index f914a2d21..d666f6cce 100644 --- a/MediaBrowser.Model/Configuration/MetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/MetadataOptions.cs @@ -15,7 +15,14 @@ namespace MediaBrowser.Model.Configuration public ImageOption[] ImageOptions { get; set; } public string[] DisabledMetadataSavers { get; set; } + public string[] LocalMetadataReaderOrder { get; set; } + public string[] DisabledMetadataFetchers { get; set; } + public string[] MetadataFetcherOrder { get; set; } + + public string[] DisabledImageFetchers { get; set; } + public string[] ImageFetcherOrder { get; set; } + public MetadataOptions() : this(3, 1280) { @@ -35,6 +42,12 @@ namespace MediaBrowser.Model.Configuration ImageOptions = imageOptions.ToArray(); DisabledMetadataSavers = new string[] { }; + LocalMetadataReaderOrder = new string[] { }; + + DisabledMetadataFetchers = new string[] { }; + MetadataFetcherOrder = new string[] { }; + DisabledImageFetchers = new string[] { }; + ImageFetcherOrder = new string[] { }; } public int GetLimit(ImageType type) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index df80b465f..0e93246c9 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -265,11 +265,6 @@ namespace MediaBrowser.Model.Configuration MetadataOptions = options.ToArray(); } - - public MetadataOptions GetMetadataOptions(string type) - { - return MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)); - } } public enum ImageSavingConvention diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index af3c62396..c6bb6a7e1 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -32,6 +32,8 @@ namespace MediaBrowser.Model.Dto /// <value>The date created.</value> public DateTime? DateCreated { get; set; } + public DateTime? DateLastMediaAdded { get; set; } + public int? AirsBeforeSeasonNumber { get; set; } public int? AirsAfterSeasonNumber { get; set; } public int? AirsBeforeEpisodeNumber { get; set; } diff --git a/MediaBrowser.Model/Entities/MetadataProviders.cs b/MediaBrowser.Model/Entities/MetadataProviders.cs index 5ef449317..e86773789 100644 --- a/MediaBrowser.Model/Entities/MetadataProviders.cs +++ b/MediaBrowser.Model/Entities/MetadataProviders.cs @@ -6,38 +6,40 @@ namespace MediaBrowser.Model.Entities /// </summary> public enum MetadataProviders { - Gamesdb, + Gamesdb = 1, /// <summary> /// The imdb /// </summary> - Imdb, + Imdb = 2, /// <summary> /// The TMDB /// </summary> - Tmdb, + Tmdb = 3, /// <summary> /// The TVDB /// </summary> - Tvdb, + Tvdb = 4, /// <summary> /// The tvcom /// </summary> - Tvcom, - /// <summary> - /// MusicBrainz - /// </summary> - Musicbrainz, + Tvcom = 5, /// <summary> /// The rotten tomatoes /// </summary> - RottenTomatoes, + RottenTomatoes = 6, /// <summary> /// Tmdb Collection Id /// </summary> - TmdbCollection, - MusicBrainzReleaseGroup, - Zap2It, - NesBox, - NesBoxRom + TmdbCollection = 7, + MusicBrainzAlbum = 8, + MusicBrainzAlbumArtist = 9, + MusicBrainzArtist = 10, + MusicBrainzReleaseGroup = 11, + Zap2It = 12, + NesBox = 13, + NesBoxRom = 14, + TvRage = 15, + AudioDbArtist = 16, + AudioDbAlbum = 17 } } diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index 1b8a2816a..c519d66cb 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -1,4 +1,7 @@ -namespace MediaBrowser.Model.Providers +using System.Collections.Generic; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.Providers { /// <summary> /// Class ImageProviderInfo. @@ -11,10 +14,11 @@ /// <value>The name.</value> public string Name { get; set; } - /// <summary> - /// Gets or sets the order. - /// </summary> - /// <value>The order.</value> - public int Order { get; set; } + public List<ImageType> SupportedImages { get; set; } + + public ImageProviderInfo() + { + SupportedImages = new List<ImageType>(); + } } } diff --git a/MediaBrowser.Model/Providers/RemoteImageResult.cs b/MediaBrowser.Model/Providers/RemoteImageResult.cs index 1c60db6ae..ed2788c0b 100644 --- a/MediaBrowser.Model/Providers/RemoteImageResult.cs +++ b/MediaBrowser.Model/Providers/RemoteImageResult.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Providers { @@ -25,4 +26,15 @@ namespace MediaBrowser.Model.Providers /// <value>The providers.</value> public List<string> Providers { get; set; } } + + public class RemoteImageQuery + { + public string ProviderName { get; set; } + + public ImageType? ImageType { get; set; } + + public bool IncludeDisabledProviders { get; set; } + + public bool IncludeAllLanguages { get; set; } + } } diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index e16da857b..f5640227e 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -42,6 +42,11 @@ namespace MediaBrowser.Model.Querying DateCreated, /// <summary> + /// The date last media added + /// </summary> + DateLastMediaAdded, + + /// <summary> /// Item display preferences /// </summary> DisplayPreferencesId, diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs index 23f370974..b0d6af887 100644 --- a/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs +++ b/MediaBrowser.Providers/AdultVideos/AdultVideoMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.AdultVideos { - class AdultVideoMetadataService : MetadataService<AdultVideo, ItemId> + class AdultVideoMetadataService : MetadataService<AdultVideo, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.AdultVideos { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(AdultVideo item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs index 40f9fded5..fcdaf29fd 100644 --- a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs +++ b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs @@ -18,12 +18,12 @@ namespace MediaBrowser.Providers.AdultVideos _logger = logger; } - protected override void Fetch(AdultVideo item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<AdultVideo> result, string path, CancellationToken cancellationToken) { - new MovieXmlParser(_logger).Fetch(item, path, cancellationToken); + new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/All/InternalMetadataFolderImageProvider.cs b/MediaBrowser.Providers/All/InternalMetadataFolderImageProvider.cs new file mode 100644 index 000000000..edaa5edaf --- /dev/null +++ b/MediaBrowser.Providers/All/InternalMetadataFolderImageProvider.cs @@ -0,0 +1,68 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using System.Collections.Generic; +using System.IO; + +namespace MediaBrowser.Providers.All +{ + public class InternalMetadataFolderImageProvider : ILocalImageFileProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + + public InternalMetadataFolderImageProvider(IServerConfigurationManager config) + { + _config = config; + } + + public string Name + { + get { return "Internal Images"; } + } + + public bool Supports(IHasImages item) + { + if (!item.IsSaveLocalMetadataEnabled()) + { + return true; + } + + // Extracted images will be saved in here + if (item is Audio) + { + return true; + } + + if (item.SupportsLocalMetadata) + { + return false; + } + + return true; + } + + public int Order + { + get + { + // Make sure this is last so that all other locations are scanned first + return 1000; + } + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var path = _config.ApplicationPaths.GetInternalMetadataPath(item.Id); + + try + { + return new LocalImageProvider().GetImages(item, path, directoryService); + } + catch (DirectoryNotFoundException) + { + return new List<LocalImageInfo>(); + } + } + } +} diff --git a/MediaBrowser.Providers/All/LocalImageProvider.cs b/MediaBrowser.Providers/All/LocalImageProvider.cs index 5883781f9..354b081cf 100644 --- a/MediaBrowser.Providers/All/LocalImageProvider.cs +++ b/MediaBrowser.Providers/All/LocalImageProvider.cs @@ -1,8 +1,8 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System; @@ -13,15 +13,8 @@ using System.Linq; namespace MediaBrowser.Providers.All { - public class LocalImageProvider : IImageFileProvider + public class LocalImageProvider : ILocalImageFileProvider { - private readonly IFileSystem _fileSystem; - - public LocalImageProvider(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - public string Name { get { return "Local Images"; } @@ -34,9 +27,7 @@ namespace MediaBrowser.Providers.All public bool Supports(IHasImages item) { - var locationType = item.LocationType; - - if (locationType == LocationType.FileSystem) + if (item.SupportsLocalMetadata) { // Episode has it's own provider if (item.IsOwnedItem || item is Episode || item is Audio) @@ -47,7 +38,7 @@ namespace MediaBrowser.Providers.All return true; } - if (locationType == LocationType.Virtual) + if (item.LocationType == LocationType.Virtual) { var season = item as Season; @@ -65,34 +56,63 @@ namespace MediaBrowser.Providers.All return false; } - private IEnumerable<string> GetFiles(IHasImages item, bool includeDirectories) + private IEnumerable<FileSystemInfo> GetFiles(IHasImages item, bool includeDirectories, IDirectoryService directoryService) { if (item.LocationType != LocationType.FileSystem) { - return new List<string>(); + return new List<FileSystemInfo>(); } var path = item.ContainingFolderPath; if (includeDirectories) { - return Directory.EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly); + return directoryService.GetFileSystemEntries(path) + .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase) || + (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory); } - return Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly); + + return directoryService.GetFiles(path) + .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)); } - public List<LocalImageInfo> GetImages(IHasImages item) + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) { - var files = GetFileDictionary(GetFiles(item, true)); + var files = GetFiles(item, true, directoryService).ToList(); var list = new List<LocalImageInfo>(); - PopulateImages(item, list, files); + PopulateImages(item, list, files, true, directoryService); return list; } - private void PopulateImages(IHasImages item, List<LocalImageInfo> images, Dictionary<string, string> files) + public List<LocalImageInfo> GetImages(IHasImages item, string path, IDirectoryService directoryService) + { + return GetImages(item, new[] { path }, directoryService); + } + + public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService) + { + var files = paths.SelectMany(directoryService.GetFiles) + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .Cast<FileSystemInfo>() + .ToList(); + + var list = new List<LocalImageInfo>(); + + PopulateImages(item, list, files, false, directoryService); + + return list; + } + + private void PopulateImages(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, bool supportParentSeriesFiles, IDirectoryService directoryService) { var imagePrefix = string.Empty; @@ -103,7 +123,7 @@ namespace MediaBrowser.Providers.All } PopulatePrimaryImages(item, images, files, imagePrefix); - PopulateBackdrops(item, images, files, imagePrefix); + PopulateBackdrops(item, images, files, imagePrefix, directoryService); PopulateScreenshots(images, files, imagePrefix); AddImage(files, images, imagePrefix + "logo", ImageType.Logo); @@ -122,15 +142,18 @@ namespace MediaBrowser.Providers.All AddImage(files, images, imagePrefix + "thumb", ImageType.Thumb); AddImage(files, images, imagePrefix + "landscape", ImageType.Thumb); - var season = item as Season; - - if (season != null) + if (supportParentSeriesFiles) { - PopulateSeasonImagesFromSeriesFolder(season, images); + var season = item as Season; + + if (season != null) + { + PopulateSeasonImagesFromSeriesFolder(season, images, directoryService); + } } } - private void PopulatePrimaryImages(IHasImages item, List<LocalImageInfo> images, Dictionary<string, string> files, string imagePrefix) + private void PopulatePrimaryImages(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix) { AddImage(files, images, imagePrefix + "folder", ImageType.Primary); AddImage(files, images, imagePrefix + "cover", ImageType.Primary); @@ -161,7 +184,7 @@ namespace MediaBrowser.Providers.All } } - private void PopulateBackdrops(IHasImages item, List<LocalImageInfo> images, Dictionary<string, string> files, string imagePrefix) + private void PopulateBackdrops(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix, IDirectoryService directoryService) { PopulateBackdrops(images, files, imagePrefix, "backdrop", "backdrop", ImageType.Backdrop); @@ -179,19 +202,21 @@ namespace MediaBrowser.Providers.All PopulateBackdrops(images, files, imagePrefix, "background", "background-", ImageType.Backdrop); PopulateBackdrops(images, files, imagePrefix, "art", "art-", ImageType.Backdrop); - string extraFanartFolder; - if (files.TryGetValue("extrafanart", out extraFanartFolder)) + var extraFanartFolder = files.OfType<DirectoryInfo>() + .FirstOrDefault(i => string.Equals(i.Name, "extrafanart", StringComparison.OrdinalIgnoreCase)); + + if (extraFanartFolder != null) { - PopulateBackdropsFromExtraFanart(extraFanartFolder, images); + PopulateBackdropsFromExtraFanart(extraFanartFolder.FullName, images, directoryService); } } - private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images) + private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images, IDirectoryService directoryService) { - var imageFiles = Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly) + var imageFiles = directoryService.GetFiles(path) .Where(i => { - var extension = Path.GetExtension(i); + var extension = i.Extension; if (string.IsNullOrEmpty(extension)) { @@ -203,17 +228,17 @@ namespace MediaBrowser.Providers.All images.AddRange(imageFiles.Select(i => new LocalImageInfo { - Path = i, + FileInfo = i, Type = ImageType.Backdrop })); } - private void PopulateScreenshots(List<LocalImageInfo> images, Dictionary<string, string> files, string imagePrefix) + private void PopulateScreenshots(List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix) { PopulateBackdrops(images, files, imagePrefix, "screenshot", "screenshot", ImageType.Screenshot); } - private void PopulateBackdrops(List<LocalImageInfo> images, Dictionary<string, string> files, string imagePrefix, string firstFileName, string subsequentFileNamePrefix, ImageType type) + private void PopulateBackdrops(List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix, string firstFileName, string subsequentFileNamePrefix, ImageType type) { AddImage(files, images, imagePrefix + firstFileName, type); @@ -236,7 +261,7 @@ namespace MediaBrowser.Providers.All } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images) + private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images, IDirectoryService directoryService) { var seasonNumber = season.IndexNumber; @@ -246,7 +271,7 @@ namespace MediaBrowser.Providers.All return; } - var files = GetFileDictionary(GetFiles(series, false)); + var seriesFiles = GetFiles(series, false, directoryService).ToList(); // Try using the season name var prefix = season.Name.ToLower().Replace(" ", string.Empty); @@ -265,39 +290,22 @@ namespace MediaBrowser.Providers.All foreach (var filename in filenamePrefixes) { - AddImage(files, images, filename + "-poster", ImageType.Primary); - AddImage(files, images, filename + "-fanart", ImageType.Backdrop); - AddImage(files, images, filename + "-banner", ImageType.Banner); - AddImage(files, images, filename + "-landscape", ImageType.Thumb); + AddImage(seriesFiles, images, filename + "-poster", ImageType.Primary); + AddImage(seriesFiles, images, filename + "-fanart", ImageType.Backdrop); + AddImage(seriesFiles, images, filename + "-banner", ImageType.Banner); + AddImage(seriesFiles, images, filename + "-landscape", ImageType.Thumb); } } - private Dictionary<string, string> GetFileDictionary(IEnumerable<string> paths) + private bool AddImage(IEnumerable<FileSystemInfo> files, List<LocalImageInfo> images, string name, ImageType type) { - var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - foreach (var path in paths) - { - var filename = Path.GetFileName(path); - - if (!string.IsNullOrEmpty(filename)) - { - dict[filename] = path; - } - } - - return dict; - } - - private bool AddImage(Dictionary<string, string> dict, List<LocalImageInfo> images, string name, ImageType type) - { - var image = GetImage(dict, name); + var image = GetImage(files, name) as FileInfo; if (image != null) { images.Add(new LocalImageInfo { - Path = image, + FileInfo = image, Type = type }); @@ -307,16 +315,14 @@ namespace MediaBrowser.Providers.All return false; } - private string GetImage(Dictionary<string, string> dict, string name) + private FileSystemInfo GetImage(IEnumerable<FileSystemInfo> files, string name) { - return BaseItem.SupportedImageExtensions - .Select(i => - { - var filename = name + i; - string path; + var candidates = files + .Where(i => string.Equals(name, Path.GetFileNameWithoutExtension(i.Name), StringComparison.OrdinalIgnoreCase)) + .ToList(); - return dict.TryGetValue(filename, out path) ? path : null; - }) + return BaseItem.SupportedImageExtensions + .Select(i => candidates.FirstOrDefault(c => string.Equals(c.Extension, i, StringComparison.OrdinalIgnoreCase))) .FirstOrDefault(i => i != null); } } diff --git a/MediaBrowser.Providers/BaseXmlProvider.cs b/MediaBrowser.Providers/BaseXmlProvider.cs index 521198e96..908688086 100644 --- a/MediaBrowser.Providers/BaseXmlProvider.cs +++ b/MediaBrowser.Providers/BaseXmlProvider.cs @@ -1,5 +1,7 @@ using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; using System; using System.IO; using System.Threading; @@ -12,11 +14,11 @@ namespace MediaBrowser.Providers { protected IFileSystem FileSystem; - public async Task<MetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken) + public async Task<LocalMetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken) { - var result = new MetadataResult<T>(); + var result = new LocalMetadataResult<T>(); - var file = GetXmlFile(info); + var file = GetXmlFile(info, new DirectoryService(new NullLogger())); if (file == null) { @@ -31,13 +33,17 @@ namespace MediaBrowser.Providers { result.Item = new T(); - Fetch(result.Item, path, cancellationToken); + Fetch(result, path, cancellationToken); result.HasMetadata = true; } catch (FileNotFoundException) { result.HasMetadata = false; } + catch (DirectoryNotFoundException) + { + result.HasMetadata = false; + } finally { XmlProviderUtils.XmlParsingResourcePool.Release(); @@ -46,25 +52,25 @@ namespace MediaBrowser.Providers return result; } - protected abstract void Fetch(T item, string path, CancellationToken cancellationToken); + protected abstract void Fetch(LocalMetadataResult<T> result, string path, CancellationToken cancellationToken); protected BaseXmlProvider(IFileSystem fileSystem) { FileSystem = fileSystem; } - protected abstract FileInfo GetXmlFile(ItemInfo info); + protected abstract FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService); - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path }); + var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path }, directoryService); if (file == null) { return false; } - return FileSystem.GetLastWriteTimeUtc(file) > date; + return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > date; } public string Name diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs index 6a06e3c8c..6e070fec9 100644 --- a/MediaBrowser.Providers/Books/BookMetadataService.cs +++ b/MediaBrowser.Providers/Books/BookMetadataService.cs @@ -7,12 +7,10 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Books { - public class BookMetadataService : MetadataService<Book, ItemId> + public class BookMetadataService : MetadataService<Book, BookInfo> { private readonly ILibraryManager _libraryManager; @@ -39,10 +37,5 @@ namespace MediaBrowser.Providers.Books target.SeriesName = source.SeriesName; } } - - protected override Task SaveItem(Book item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index e4701a116..9547eedd9 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -11,12 +11,10 @@ using MediaBrowser.Providers.Manager; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.BoxSets { - public class BoxSetMetadataService : MetadataService<BoxSet, ItemId> + public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo> { private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _iLocalizationManager; @@ -41,11 +39,6 @@ namespace MediaBrowser.Providers.BoxSets ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - protected override Task SaveItem(BoxSet item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - protected override ItemUpdateType BeforeSave(BoxSet item) { var updateType = base.BeforeSave(item); diff --git a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs index a214dff8c..e9896c28e 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs @@ -20,14 +20,14 @@ namespace MediaBrowser.Providers.BoxSets _logger = logger; } - protected override void Fetch(BoxSet item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<BoxSet> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<BoxSet>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "collection.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "collection.xml")); } } } diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs index b04687350..e990ddd65 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -17,7 +17,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.BoxSets { - public class MovieDbBoxSetProvider : IRemoteMetadataProvider<BoxSet> + public class MovieDbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo> { private readonly CultureInfo _enUs = new CultureInfo("en-US"); private const string GetCollectionInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; @@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.BoxSets Current = this; } - public async Task<MetadataResult<BoxSet>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken) { var tmdbId = id.GetProviderId(MetadataProviders.Tmdb); @@ -85,10 +85,12 @@ namespace MediaBrowser.Providers.BoxSets private BoxSet GetItem(RootObject obj) { - var item = new BoxSet(); + var item = new BoxSet + { + Name = obj.name, + Overview = obj.overview + }; - item.Name = obj.name; - item.Overview = obj.overview; item.SetProviderId(MetadataProviders.Tmdb, obj.id.ToString(_enUs)); return item; @@ -188,7 +190,7 @@ namespace MediaBrowser.Providers.BoxSets return DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken); } - private Task<string> GetTmdbId(ItemId id, CancellationToken cancellationToken) + private Task<string> GetTmdbId(ItemLookupInfo id, CancellationToken cancellationToken) { return new MovieDbSearch(_logger, _json).FindCollectionId(id, cancellationToken); } diff --git a/MediaBrowser.Providers/Folders/CollectionFolderImageProvider.cs b/MediaBrowser.Providers/Folders/CollectionFolderImageProvider.cs new file mode 100644 index 000000000..fa5946bd5 --- /dev/null +++ b/MediaBrowser.Providers/Folders/CollectionFolderImageProvider.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.All; +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Folders +{ + public class CollectionFolderLocalImageProvider : ILocalImageFileProvider, IHasOrder + { + public string Name + { + get { return "Collection Folder Images"; } + } + + public bool Supports(IHasImages item) + { + return item is CollectionFolder && item.SupportsLocalMetadata; + } + + public int Order + { + get + { + // Run after LocalImageProvider + return 1; + } + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var collectionFolder = (CollectionFolder)item; + + return new LocalImageProvider().GetImages(item, collectionFolder.PhysicalLocations, directoryService); + } + } +} diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs index cc688d91a..c9e44177e 100644 --- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs @@ -7,12 +7,10 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Folders { - public class FolderMetadataService : MetadataService<Folder, ItemId> + public class FolderMetadataService : MetadataService<Folder, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -35,11 +33,6 @@ namespace MediaBrowser.Providers.Folders ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - protected override Task SaveItem(Folder item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - public override int Order { get diff --git a/MediaBrowser.Providers/Folders/FolderXmlProvider.cs b/MediaBrowser.Providers/Folders/FolderXmlProvider.cs index 2fc6a8290..978fb0f0c 100644 --- a/MediaBrowser.Providers/Folders/FolderXmlProvider.cs +++ b/MediaBrowser.Providers/Folders/FolderXmlProvider.cs @@ -20,12 +20,12 @@ namespace MediaBrowser.Providers.Folders _logger = logger; } - protected override void Fetch(Folder item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Folder> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<Folder>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<Folder>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return new FileInfo(Path.Combine(info.Path, "folder.xml")); } diff --git a/MediaBrowser.Providers/Folders/ImagesByNameImageProvider.cs b/MediaBrowser.Providers/Folders/ImagesByNameImageProvider.cs new file mode 100644 index 000000000..7dc934573 --- /dev/null +++ b/MediaBrowser.Providers/Folders/ImagesByNameImageProvider.cs @@ -0,0 +1,57 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Providers.All; +using System.Collections.Generic; +using System.IO; + +namespace MediaBrowser.Providers.Folders +{ + public class ImagesByNameImageProvider : ILocalImageFileProvider, IHasOrder + { + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _config; + + public ImagesByNameImageProvider(IFileSystem fileSystem, IServerConfigurationManager config) + { + _fileSystem = fileSystem; + _config = config; + } + + public string Name + { + get { return "Images By Name"; } + } + + public bool Supports(IHasImages item) + { + return item is ICollectionFolder; + } + + public int Order + { + get + { + // Run after LocalImageProvider, and after CollectionFolderImageProvider + return 2; + } + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var name = _fileSystem.GetValidFilename(item.Name); + + var path = Path.Combine(_config.ApplicationPaths.GeneralPath, name); + + try + { + return new LocalImageProvider().GetImages(item, path, directoryService); + } + catch (DirectoryNotFoundException) + { + return new List<LocalImageInfo>(); + } + } + } +} diff --git a/MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs b/MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs deleted file mode 100644 index 043e32d11..000000000 --- a/MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs +++ /dev/null @@ -1,42 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Folders -{ - public class UserRootFolderNameProvider : BaseMetadataProvider - { - public UserRootFolderNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager) - : base(logManager, configurationManager) - { - } - - public override bool Supports(BaseItem item) - { - return item is UserRootFolder; - } - - public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) - { - var parentName = Path.GetFileNameWithoutExtension(item.Path); - - if (string.Equals(parentName, "default", StringComparison.OrdinalIgnoreCase)) - { - item.Name = "Media Library"; - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return TrueTaskResult; - } - - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.First; } - } - } -} diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs index 2e0e21a46..0eaed59c7 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.GameGenres { - public class GameGenreMetadataService : MetadataService<GameGenre, ItemId> + public class GameGenreMetadataService : MetadataService<GameGenre, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.GameGenres { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(GameGenre item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/Games/GameMetadataService.cs b/MediaBrowser.Providers/Games/GameMetadataService.cs index 8ca34eb05..1586414ea 100644 --- a/MediaBrowser.Providers/Games/GameMetadataService.cs +++ b/MediaBrowser.Providers/Games/GameMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Games { - public class GameMetadataService : MetadataService<Game, GameId> + public class GameMetadataService : MetadataService<Game, GameInfo> { private readonly ILibraryManager _libraryManager; @@ -39,19 +39,5 @@ namespace MediaBrowser.Providers.Games target.GameSystem = source.GameSystem; } } - - protected override Task SaveItem(Game item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - - protected override GameId GetId(Game item) - { - var id = base.GetId(item); - - id.GameSystem = item.GameSystem; - - return id; - } } } diff --git a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs index 6dd1b1bbc..ae1ee1993 100644 --- a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs +++ b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Games { - public class GameSystemMetadataService : MetadataService<GameSystem, GameSystemId> + public class GameSystemMetadataService : MetadataService<GameSystem, GameSystemInfo> { private readonly ILibraryManager _libraryManager; @@ -39,19 +39,5 @@ namespace MediaBrowser.Providers.Games target.GameSystemName = source.GameSystemName; } } - - protected override Task SaveItem(GameSystem item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - - protected override GameSystemId GetId(GameSystem item) - { - var id = base.GetId(item); - - id.Path = item.Path; - - return id; - } } } diff --git a/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs b/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs index 3e74f4c32..9efa93dfa 100644 --- a/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs +++ b/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs @@ -17,14 +17,14 @@ namespace MediaBrowser.Providers.Games _logger = logger; } - protected override void Fetch(GameSystem item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<GameSystem> result, string path, CancellationToken cancellationToken) { - new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken); + new GameSystemXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "gamesystem.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "gamesystem.xml")); } } } diff --git a/MediaBrowser.Providers/Games/GameXmlProvider.cs b/MediaBrowser.Providers/Games/GameXmlProvider.cs index 644fe3e42..c12feb85c 100644 --- a/MediaBrowser.Providers/Games/GameXmlProvider.cs +++ b/MediaBrowser.Providers/Games/GameXmlProvider.cs @@ -17,12 +17,12 @@ namespace MediaBrowser.Providers.Games _logger = logger; } - protected override void Fetch(Game item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Game> result, string path, CancellationToken cancellationToken) { - new GameXmlParser(_logger).Fetch(item, path, cancellationToken); + new GameXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { var fileInfo = FileSystem.GetFileSystemInfo(info.Path); diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs index da88457bd..b19241095 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Genres { - public class GenreMetadataService : MetadataService<Genre, ItemId> + public class GenreMetadataService : MetadataService<Genre, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.Genres { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(Genre item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs new file mode 100644 index 000000000..7ae27f4c9 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + public class AudioRecordingService : MetadataService<LiveTvAudioRecording, ItemLookupInfo> + { + private readonly ILibraryManager _libraryManager; + + public AudioRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + protected override void MergeData(LiveTvAudioRecording source, LiveTvAudioRecording target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + } +} diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs index 01adb3ac3..57bc70156 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.LiveTv { - public class ChannelMetadataService : MetadataService<LiveTvChannel, ItemId> + public class ChannelMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -29,10 +29,5 @@ namespace MediaBrowser.Providers.LiveTv { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(LiveTvChannel item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs index af3de824d..400ac825f 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs @@ -17,14 +17,14 @@ namespace MediaBrowser.Providers.LiveTv _logger = logger; } - protected override void Fetch(LiveTvChannel item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<LiveTvChannel> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "channel.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "channel.xml")); } } } diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs index 037fedd79..3172e0134 100644 --- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.LiveTv { - public class ProgramMetadataService : MetadataService<LiveTvProgram, ItemId> + public class ProgramMetadataService : MetadataService<LiveTvProgram, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -33,10 +33,5 @@ namespace MediaBrowser.Providers.LiveTv { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(LiveTvProgram item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs new file mode 100644 index 000000000..f526db775 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + public class VideoRecordingService : MetadataService<LiveTvVideoRecording, ItemLookupInfo> + { + private readonly ILibraryManager _libraryManager; + + public VideoRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Merges the specified source. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + /// <param name="lockedFields">The locked fields.</param> + /// <param name="replaceData">if set to <c>true</c> [replace data].</param> + protected override void MergeData(LiveTvVideoRecording source, LiveTvVideoRecording target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + } +} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 2a4ef7597..bac90ae37 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -30,10 +30,6 @@ namespace MediaBrowser.Providers.Manager private readonly IServerConfigurationManager _config; /// <summary> - /// The remote image cache - /// </summary> - private readonly FileSystemRepository _remoteImageCache; - /// <summary> /// The _directory watchers /// </summary> private readonly ILibraryMonitor _libraryMonitor; @@ -41,17 +37,18 @@ namespace MediaBrowser.Providers.Manager private readonly ILogger _logger; /// <summary> - /// Initializes a new instance of the <see cref="ImageSaver"/> class. + /// Initializes a new instance of the <see cref="ImageSaver" /> class. /// </summary> /// <param name="config">The config.</param> /// <param name="libraryMonitor">The directory watchers.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="logger">The logger.</param> public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { _config = config; _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; - _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); } /// <summary> @@ -96,25 +93,16 @@ namespace MediaBrowser.Providers.Manager { var series = season.Series; - if (series != null) + if (series != null && series.SupportsLocalMetadata) { - var seriesLocationType = series.LocationType; - if (seriesLocationType == LocationType.FileSystem || seriesLocationType == LocationType.Offline) - { - saveLocally = true; - } + saveLocally = true; } } } - if (type == ImageType.Backdrop && imageIndex == null) + if (!imageIndex.HasValue && item.AllowsMultipleImages(type)) { - imageIndex = item.BackdropImagePaths.Count; - } - else if (type == ImageType.Screenshot && imageIndex == null) - { - var hasScreenshots = (IHasScreenshots)item; - imageIndex = hasScreenshots.ScreenshotImagePaths.Count; + imageIndex = item.GetImages(type).Count(); } var index = imageIndex ?? 0; @@ -275,43 +263,7 @@ namespace MediaBrowser.Providers.Manager /// imageIndex</exception> private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path) { - switch (type) - { - case ImageType.Screenshot: - - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - - var hasScreenshots = (IHasScreenshots)item; - if (hasScreenshots.ScreenshotImagePaths.Count > imageIndex.Value) - { - hasScreenshots.ScreenshotImagePaths[imageIndex.Value] = path; - } - else if (!hasScreenshots.ScreenshotImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - hasScreenshots.ScreenshotImagePaths.Add(path); - } - break; - case ImageType.Backdrop: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - if (item.BackdropImagePaths.Count > imageIndex.Value) - { - item.BackdropImagePaths[imageIndex.Value] = path; - } - else if (!item.BackdropImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - item.BackdropImagePaths.Add(path); - } - break; - default: - item.SetImagePath(type, path); - break; - } + item.SetImagePath(type, imageIndex ?? 0, new FileInfo(path)); } /// <summary> @@ -347,19 +299,10 @@ namespace MediaBrowser.Providers.Manager filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder"; break; case ImageType.Backdrop: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - filename = GetBackdropSaveFilename(item.BackdropImagePaths, "backdrop", "backdrop", imageIndex.Value); + filename = GetBackdropSaveFilename(item.GetImages(type), "backdrop", "backdrop", imageIndex); break; case ImageType.Screenshot: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - var hasScreenshots = (IHasScreenshots)item; - filename = GetBackdropSaveFilename(hasScreenshots.ScreenshotImagePaths, "screenshot", "screenshot", imageIndex.Value); + filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex); break; default: filename = type.ToString().ToLower(); @@ -398,22 +341,26 @@ namespace MediaBrowser.Providers.Manager // None of the save local conditions passed, so store it in our internal folders if (string.IsNullOrEmpty(path)) { - path = _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id, filename + extension); + if (string.IsNullOrEmpty(filename)) + { + filename = "folder"; + } + path = Path.Combine(_config.ApplicationPaths.GetInternalMetadataPath(item.Id), filename + extension); } return path; } - private string GetBackdropSaveFilename(IEnumerable<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index) + private string GetBackdropSaveFilename(IEnumerable<ItemImageInfo> images, string zeroIndexFilename, string numberedIndexPrefix, int? index) { - if (index == 0) + if (index.HasValue && index.Value == 0) { return zeroIndexFilename; } - var filenames = images.Select(Path.GetFileNameWithoutExtension).ToList(); + var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); - var current = index; + var current = 1; while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) { current++; @@ -484,7 +431,7 @@ namespace MediaBrowser.Providers.Manager return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; } - var extraFanartFilename = GetBackdropSaveFilename(item.BackdropImagePaths, "fanart", "fanart", outputIndex); + var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex); return new[] { diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index f2fa4dc29..773ad354c 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -34,13 +34,13 @@ namespace MediaBrowser.Providers.Manager _fileSystem = fileSystem; } - public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers) + public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService) { - var hasChanges = item.ValidateImages(); + var hasChanges = item.ValidateImages(directoryService); - foreach (var provider in providers.OfType<IImageFileProvider>()) + foreach (var provider in providers.OfType<ILocalImageFileProvider>()) { - var images = provider.GetImages(item); + var images = provider.GetImages(item, directoryService); if (MergeImages(item, images)) { @@ -53,9 +53,9 @@ namespace MediaBrowser.Providers.Manager public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) { - var result = new RefreshResult { UpdateType = ItemUpdateType.Unspecified }; + var result = new RefreshResult { UpdateType = ItemUpdateType.None }; - var providers = GetImageProviders(item, imageProviders).ToList(); + var providers = imageProviders.ToList(); var providerIds = new List<Guid>(); @@ -63,18 +63,24 @@ namespace MediaBrowser.Providers.Manager var backdropLimit = item.HasImage(ImageType.Backdrop) ? 0 : savedOptions.GetLimit(ImageType.Backdrop); var screenshotLimit = item.HasImage(ImageType.Screenshot) ? 0 : savedOptions.GetLimit(ImageType.Screenshot); - foreach (var provider in providers.OfType<IRemoteImageProvider>()) + foreach (var provider in providers) { - await RefreshFromProvider(item, provider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, result, cancellationToken).ConfigureAwait(false); + var remoteProvider = provider as IRemoteImageProvider; - providerIds.Add(provider.GetType().FullName.GetMD5()); - } + if (remoteProvider != null) + { + await RefreshFromProvider(item, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, result, cancellationToken).ConfigureAwait(false); + providerIds.Add(provider.GetType().FullName.GetMD5()); + continue; + } - foreach (var provider in providers.OfType<IDynamicImageProvider>()) - { - await RefreshFromProvider(item, provider, savedOptions, result, cancellationToken).ConfigureAwait(false); + var dynamicImageProvider = provider as IDynamicImageProvider; - providerIds.Add(provider.GetType().FullName.GetMD5()); + if (dynamicImageProvider != null) + { + await RefreshFromProvider(item, dynamicImageProvider, savedOptions, result, cancellationToken).ConfigureAwait(false); + providerIds.Add(provider.GetType().FullName.GetMD5()); + } } result.Providers = providerIds; @@ -111,8 +117,7 @@ namespace MediaBrowser.Providers.Manager { var mimeType = "image/" + Path.GetExtension(response.Path).TrimStart('.').ToLower(); - var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, - FileShare.Read, true); + var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, true); await _providerManager.SaveImage((BaseItem)item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); } @@ -172,23 +177,16 @@ namespace MediaBrowser.Providers.Manager return false; } - if (images.Contains(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit) + if (images.Contains(ImageType.Backdrop) && item.GetImages(ImageType.Backdrop).Count() < backdropLimit) { return false; } - if (images.Contains(ImageType.Screenshot)) + if (images.Contains(ImageType.Screenshot) && item.GetImages(ImageType.Screenshot).Count() < backdropLimit) { - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots != null) - { - if (hasScreenshots.ScreenshotImagePaths.Count < screenshotLimit) - { - return false; - } - } - } - + return false; + } + return true; } @@ -214,24 +212,35 @@ namespace MediaBrowser.Providers.Manager } _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); - - var images = await provider.GetAllImages(item, cancellationToken).ConfigureAwait(false); + + var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery + { + ProviderName = provider.Name, + IncludeAllLanguages = false, + IncludeDisabledProviders = false, + + }, cancellationToken).ConfigureAwait(false); + var list = images.ToList(); + int minWidth; foreach (var type in _singularImages) { if (savedOptions.IsEnabled(type) && !item.HasImage(type)) { - await DownloadImage(item, provider, result, list, type, cancellationToken).ConfigureAwait(false); + minWidth = savedOptions.GetMinWidth(type); + await DownloadImage(item, provider, result, list, minWidth, type, cancellationToken).ConfigureAwait(false); } } - await DownloadBackdrops(item, backdropLimit, provider, result, list, cancellationToken).ConfigureAwait(false); + minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); + await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); var hasScreenshots = item as IHasScreenshots; if (hasScreenshots != null) { - await DownloadScreenshots(hasScreenshots, screenshotLimit, provider, result, list, cancellationToken).ConfigureAwait(false); + minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); + await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -246,25 +255,7 @@ namespace MediaBrowser.Providers.Manager } } - /// <summary> - /// Gets the image providers. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="imageProviders">The image providers.</param> - /// <returns>IEnumerable{IImageProvider}.</returns> - private IEnumerable<IImageProvider> GetImageProviders(IHasImages item, IEnumerable<IImageProvider> imageProviders) - { - var providers = imageProviders; - - if (!_config.Configuration.EnableInternetProviders) - { - providers = providers.Where(i => !(i is IRemoteImageProvider)); - } - - return providers; - } - - private bool MergeImages(IHasImages item, List<LocalImageInfo> images) + public bool MergeImages(IHasImages item, List<LocalImageInfo> images) { var changed = false; @@ -274,50 +265,42 @@ namespace MediaBrowser.Providers.Manager if (image != null) { - var oldPath = item.GetImagePath(type); + var currentImage = item.GetImageInfo(type, 0); - item.SetImagePath(type, image.Path); - - if (!string.Equals(oldPath, image.Path, StringComparison.OrdinalIgnoreCase)) + if (currentImage == null || !string.Equals(currentImage.Path, image.FileInfo.FullName, StringComparison.OrdinalIgnoreCase)) { + item.SetImagePath(type, image.FileInfo); changed = true; } } } - // The change reporting will only be accurate at the count level - // Improve this if/when needed var backdrops = images.Where(i => i.Type == ImageType.Backdrop).ToList(); if (backdrops.Count > 0) { - var oldCount = item.BackdropImagePaths.Count; - - item.BackdropImagePaths = item.BackdropImagePaths - .Concat(backdrops.Select(i => i.Path)) - .Distinct(StringComparer.OrdinalIgnoreCase) + var foundImages = images.Where(i => i.Type == ImageType.Backdrop) + .Select(i => i.FileInfo) .ToList(); - if (oldCount != item.BackdropImagePaths.Count) + if (foundImages.Count > 0) { - changed = true; + if (item.AddImages(ImageType.Backdrop, foundImages)) + { + changed = true; + } } } var hasScreenshots = item as IHasScreenshots; if (hasScreenshots != null) { - var screenshots = images.Where(i => i.Type == ImageType.Screenshot).ToList(); + var foundImages = images.Where(i => i.Type == ImageType.Screenshot) + .Select(i => i.FileInfo) + .ToList(); - if (screenshots.Count > 0) + if (foundImages.Count > 0) { - var oldCount = hasScreenshots.ScreenshotImagePaths.Count; - - hasScreenshots.ScreenshotImagePaths = hasScreenshots.ScreenshotImagePaths - .Concat(screenshots.Select(i => i.Path)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - if (oldCount != hasScreenshots.ScreenshotImagePaths.Count) + if (item.AddImages(ImageType.Screenshot, foundImages)) { changed = true; } @@ -327,10 +310,15 @@ namespace MediaBrowser.Providers.Manager return changed; } - private async Task DownloadImage(IHasImages item, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) + private async Task DownloadImage(IHasImages item, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, ImageType type, CancellationToken cancellationToken) { foreach (var image in images.Where(i => i.Type == type)) { + if (image.Width.HasValue && image.Width.Value < minWidth) + { + continue; + } + var url = image.Url; try @@ -344,7 +332,7 @@ namespace MediaBrowser.Providers.Manager } catch (HttpException ex) { - // Sometimes providers send back bad url's. Just move onto the next image + // Sometimes providers send back bad url's. Just move to the next image if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) { continue; @@ -354,48 +342,18 @@ namespace MediaBrowser.Providers.Manager } } - private async Task DownloadBackdrops(IHasImages item, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken) + private async Task DownloadBackdrops(IHasImages item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken) { - const ImageType imageType = ImageType.Backdrop; - foreach (var image in images.Where(i => i.Type == imageType)) { - if (item.BackdropImagePaths.Count >= limit) - { - break; - } - - var url = image.Url; - - try - { - var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false); - - await _providerManager.SaveImage((BaseItem)item, response.Content, response.ContentType, imageType, null, cancellationToken).ConfigureAwait(false); - result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate; - break; - } - catch (HttpException ex) + if (item.GetImages(imageType).Count() >= limit) { - // Sometimes providers send back bad url's. Just move onto the next image - if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) - { - continue; - } break; } - } - } - - private async Task DownloadScreenshots(IHasScreenshots item, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken) - { - const ImageType imageType = ImageType.Screenshot; - foreach (var image in images.Where(i => i.Type == imageType)) - { - if (item.ScreenshotImagePaths.Count >= limit) + if (image.Width.HasValue && image.Width.Value < minWidth) { - break; + continue; } var url = image.Url; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index e0272bc7b..beece997d 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -4,11 +4,11 @@ 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.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,8 +16,8 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Manager { public abstract class MetadataService<TItemType, TIdType> : IMetadataService - where TItemType : IHasMetadata, new() - where TIdType : ItemId, new() + where TItemType : IHasMetadata, IHasLookupInfo<TIdType>, new() + where TIdType : ItemLookupInfo, new() { protected readonly IServerConfigurationManager ServerConfigurationManager; protected readonly ILogger Logger; @@ -37,10 +37,19 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Saves the provider result. /// </summary> + /// <param name="item">The item.</param> /// <param name="result">The result.</param> /// <returns>Task.</returns> - protected Task SaveProviderResult(MetadataStatus result) + protected Task SaveProviderResult(TItemType item, MetadataStatus result) { + result.ItemId = item.Id; + result.ItemName = item.Name; + result.ItemType = item.GetType().Name; + + var series = item as IHasSeries; + + result.SeriesName = series == null ? null : series.SeriesName; + return ProviderRepo.SaveMetadataStatus(result, CancellationToken.None); } @@ -56,10 +65,15 @@ namespace MediaBrowser.Providers.Manager public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { + if (refreshOptions.DirectoryService == null) + { + refreshOptions.DirectoryService = new DirectoryService(Logger); + } + var itemOfType = (TItemType)item; - var config = GetMetadataOptions(itemOfType); + var config = ProviderManager.GetMetadataOptions(item); - var updateType = ItemUpdateType.Unspecified; + var updateType = ItemUpdateType.None; var refreshResult = GetLastResult(item.Id); refreshResult.LastErrorMessage = string.Empty; refreshResult.LastStatus = ProviderRefreshStatus.Success; @@ -73,7 +87,7 @@ namespace MediaBrowser.Providers.Manager try { // Always validate images and check for new locally stored ones. - if (itemImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>())) + if (itemImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions.DirectoryService)) { updateType = updateType | ItemUpdateType.ImageUpdate; } @@ -92,13 +106,12 @@ namespace MediaBrowser.Providers.Manager if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue) { - updateType = updateType | BeforeMetadataRefresh(itemOfType); + updateType = updateType | item.BeforeMetadataRefresh(); } if (providers.Count > 0) { - - var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false); + var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false); updateType = updateType | result.UpdateType; refreshResult.AddStatus(result.Status, result.ErrorMessage); @@ -125,44 +138,21 @@ namespace MediaBrowser.Providers.Manager updateType = updateType | BeforeSave(itemOfType); - var providersHadChanges = updateType > ItemUpdateType.Unspecified; + var providersHadChanges = updateType > ItemUpdateType.None; - if (refreshOptions.ForceSave || providersHadChanges) + // Save if changes were made, or it's never been saved before + if (refreshOptions.ForceSave || providersHadChanges || item.DateLastSaved == default(DateTime)) { - if (string.IsNullOrEmpty(item.Name)) - { - throw new InvalidOperationException(item.GetType().Name + " has no name: " + item.Path); - } - // Save to database await SaveItem(itemOfType, updateType, cancellationToken); } if (providersHadChanges || refreshResult.IsDirty) { - await SaveProviderResult(refreshResult).ConfigureAwait(false); + await SaveProviderResult(itemOfType, refreshResult).ConfigureAwait(false); } } - private readonly MetadataOptions _defaultOptions = new MetadataOptions(); - protected MetadataOptions GetMetadataOptions(TItemType item) - { - var type = item.GetType().Name; - return ServerConfigurationManager.Configuration.MetadataOptions - .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? - _defaultOptions; - } - - /// <summary> - /// Befores the metadata refresh. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>ItemUpdateType.</returns> - protected virtual ItemUpdateType BeforeMetadataRefresh(TItemType item) - { - return ItemUpdateType.Unspecified; - } - /// <summary> /// Befores the save. /// </summary> @@ -170,7 +160,15 @@ namespace MediaBrowser.Providers.Manager /// <returns>ItemUpdateType.</returns> protected virtual ItemUpdateType BeforeSave(TItemType item) { - return ItemUpdateType.Unspecified; + var updateType = ItemUpdateType.None; + + if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path)) + { + item.Name = Path.GetFileNameWithoutExtension(item.Path); + updateType = updateType | ItemUpdateType.MetadataEdit; + } + + return updateType; } /// <summary> @@ -194,15 +192,37 @@ namespace MediaBrowser.Providers.Manager var currentItem = item; var providersWithChanges = providers.OfType<IHasChangeMonitor>() - .Where(i => i.HasChanged(currentItem, currentItem.DateLastSaved)) + .Where(i => i.HasChanged(currentItem, options.DirectoryService, currentItem.DateLastSaved)) + .Cast<IMetadataProvider<TItemType>>() .ToList(); - // If local providers are the only ones with changes, then just run those - if (providersWithChanges.All(i => i is ILocalMetadataProvider)) + if (providersWithChanges.Count == 0) { - providers = providersWithChanges.Count == 0 ? - new List<IMetadataProvider<TItemType>>() : - providers.Where(i => i is ILocalMetadataProvider).ToList(); + providers = new List<IMetadataProvider<TItemType>>(); + } + else + { + providers = providers.Where(i => + { + // If any provider reports a change, always run local ones as well + if (i is ILocalMetadataProvider) + { + return true; + } + + var anyRemoteProvidersChanged = providersWithChanges.OfType<IRemoteMetadataProvider>() + .Any(); + + // If any remote providers changed, run them all so that priorities can be honored + if (i is IRemoteMetadataProvider) + { + return anyRemoteProvidersChanged; + } + + // Run custom providers if they report a change or any remote providers change + return anyRemoteProvidersChanged || providersWithChanges.Contains(i); + + }).ToList(); } } @@ -223,7 +243,7 @@ namespace MediaBrowser.Providers.Manager var currentItem = item; providers = providers.OfType<IHasChangeMonitor>() - .Where(i => i.HasChanged(currentItem, dateLastImageRefresh.Value)) + .Where(i => i.HasChanged(currentItem, options.DirectoryService, dateLastImageRefresh.Value)) .Cast<IImageProvider>() .ToList(); } @@ -231,27 +251,9 @@ namespace MediaBrowser.Providers.Manager return providers; } - protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken); - - protected virtual TIdType GetId(TItemType item) + protected Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken) { - var id = new TIdType - { - MetadataCountryCode = item.GetPreferredMetadataCountryCode(), - MetadataLanguage = item.GetPreferredMetadataLanguage(), - Name = item.Name, - ProviderIds = item.ProviderIds - }; - - var baseItem = item as BaseItem; - - if (baseItem != null) - { - id.IndexNumber = baseItem.IndexNumber; - id.ParentIndexNumber = baseItem.ParentIndexNumber; - } - - return id; + return item.UpdateToRepository(reason, cancellationToken); } public bool CanRefresh(IHasMetadata item) @@ -259,11 +261,11 @@ namespace MediaBrowser.Providers.Manager return item is TItemType; } - protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List<IMetadataProvider> providers, CancellationToken cancellationToken) + protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List<IMetadataProvider> providers, ItemImageProvider imageService, CancellationToken cancellationToken) { var refreshResult = new RefreshResult { - UpdateType = ItemUpdateType.Unspecified, + UpdateType = ItemUpdateType.None, Providers = providers.Select(i => i.GetType().FullName.GetMD5()).ToList() }; @@ -273,7 +275,7 @@ namespace MediaBrowser.Providers.Manager // If replacing all metadata, run internet providers first if (options.ReplaceAllMetadata) { - await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType>>(), refreshResult, cancellationToken).ConfigureAwait(false); + await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false); } var hasLocalMetadata = false; @@ -290,6 +292,11 @@ namespace MediaBrowser.Providers.Manager if (localItem.HasMetadata) { + if (imageService.MergeImages(item, localItem.Images)) + { + refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; + } + if (!string.IsNullOrEmpty(localItem.Item.Name)) { MergeData(localItem.Item, temp, new List<MetadataFields>(), !options.ReplaceAllMetadata, true); @@ -319,31 +326,32 @@ namespace MediaBrowser.Providers.Manager } } - if (!options.ReplaceAllMetadata && !hasLocalMetadata) + // Local metadata is king - if any is found don't run remote providers + if ((!options.ReplaceAllMetadata && !hasLocalMetadata) || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh) { - await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType>>(), refreshResult, cancellationToken).ConfigureAwait(false); + await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false); } - if (refreshResult.UpdateType > ItemUpdateType.Unspecified) + if (refreshResult.UpdateType > ItemUpdateType.None) { MergeData(temp, item, item.LockedFields, true, true); } foreach (var provider in providers.OfType<ICustomMetadataProvider<TItemType>>()) { - await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false); + await RunCustomProvider(provider, item, options.DirectoryService, refreshResult, cancellationToken).ConfigureAwait(false); } return refreshResult; } - private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, RefreshResult refreshResult, CancellationToken cancellationToken) + private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, IDirectoryService directoryService, RefreshResult refreshResult, CancellationToken cancellationToken) { Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); try { - refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false); + refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, directoryService, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -362,14 +370,23 @@ namespace MediaBrowser.Providers.Manager return new TItemType(); } - private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken) + private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken) { - var id = GetId(item); + TIdType id = null; foreach (var provider in providers) { Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); + if (id == null) + { + id = item.GetLookupInfo(); + } + else + { + MergeNewData(temp, id); + } + try { var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false); @@ -394,6 +411,15 @@ namespace MediaBrowser.Providers.Manager } } + private void MergeNewData(TItemType source, TIdType lookupInfo) + { + // Copy new provider id's that may have been obtained + foreach (var providerId in source.ProviderIds) + { + lookupInfo.ProviderIds[providerId.Key] = providerId.Value; + } + } + protected abstract void MergeData(TItemType source, TItemType target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings); public virtual int Order diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 030b3cbd9..f49bbb6ae 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -205,26 +205,31 @@ namespace MediaBrowser.Providers.Manager /// Gets the available remote images. /// </summary> /// <param name="item">The item.</param> + /// <param name="query">The query.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="providerName">Name of the provider.</param> - /// <param name="type">The type.</param> /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> - public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null) + public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, RemoteImageQuery query, CancellationToken cancellationToken) { - var providers = GetRemoteImageProviders(item); + var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders); - if (!string.IsNullOrEmpty(providerName)) + if (!string.IsNullOrEmpty(query.ProviderName)) { + var providerName = query.ProviderName; + providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)); } var preferredLanguage = item.GetPreferredMetadataLanguage(); - var tasks = providers.Select(i => GetImages(item, cancellationToken, i, preferredLanguage, type)); + var language = query.IncludeAllLanguages ? null : preferredLanguage; + + var tasks = providers.Select(i => GetImages(item, cancellationToken, i, language, query.ImageType)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); - return results.SelectMany(i => i); + var images = results.SelectMany(i => i); + + return images; } /// <summary> @@ -244,12 +249,15 @@ namespace MediaBrowser.Providers.Manager { var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false); - return FilterImages(result, preferredLanguage); + return string.IsNullOrEmpty(preferredLanguage) ? result : + FilterImages(result, preferredLanguage); } else { var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false); - return FilterImages(result, preferredLanguage); + + return string.IsNullOrEmpty(preferredLanguage) ? result : + FilterImages(result, preferredLanguage); } } catch (Exception ex) @@ -275,66 +283,111 @@ namespace MediaBrowser.Providers.Manager /// </summary> /// <param name="item">The item.</param> /// <returns>IEnumerable{IImageProvider}.</returns> - public IEnumerable<ImageProviderInfo> GetImageProviderInfo(IHasImages item) + public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(IHasImages item) { - return GetRemoteImageProviders(item).Select(i => new ImageProviderInfo + return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo { Name = i.Name, - Order = GetOrder(item, i) + SupportedImages = i.GetSupportedImages(item).ToList() }); } public IEnumerable<IImageProvider> GetImageProviders(IHasImages item) { - return ImageProviders.Where(i => + return GetImageProviders(item, GetMetadataOptions(item), false); + } + + private IEnumerable<IImageProvider> GetImageProviders(IHasImages item, MetadataOptions options, bool includeDisabled) + { + return ImageProviders.Where(i => CanRefresh(i, item, options, includeDisabled)) + .OrderBy(i => { - try - { - return i.Supports(item); - } - catch (Exception ex) + // See if there's a user-defined order + if (!(i is ILocalImageProvider)) { - _logger.ErrorException("{0} failed in Supports for type {1}", ex, i.GetType().Name, item.GetType().Name); - return false; + var index = Array.IndexOf(options.ImageFetcherOrder, i.Name); + + if (index != -1) + { + return index; + } } - }).OrderBy(i => GetOrder(item, i)); + // Not configured. Just return some high number to put it at the end. + return 100; + }) + .ThenBy(GetOrder); } public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(IHasMetadata item) where T : IHasMetadata { - return GetMetadataProvidersInternal<T>(item, false); + var options = GetMetadataOptions(item); + + return GetMetadataProvidersInternal<T>(item, options, false); } - private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, bool includeDisabled) + private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, MetadataOptions options, bool includeDisabled) where T : IHasMetadata { return _metadataProviders.OfType<IMetadataProvider<T>>() - .Where(i => CanRefresh(i, item, includeDisabled)) - .OrderBy(i => GetOrder(item, i)); + .Where(i => CanRefresh(i, item, options, includeDisabled)) + .OrderBy(i => + { + // See if there's a user-defined order + if (i is ILocalMetadataProvider) + { + var index = Array.IndexOf(options.LocalMetadataReaderOrder, i.Name); + + if (index != -1) + { + return index; + } + } + + // See if there's a user-defined order + if (i is IRemoteMetadataProvider) + { + var index = Array.IndexOf(options.MetadataFetcherOrder, i.Name); + + if (index != -1) + { + return index; + } + } + + // Not configured. Just return some high number to put it at the end. + return 100; + }) + .ThenBy(GetOrder); } - private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item) + private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item, bool includeDisabled) { - return GetImageProviders(item).OfType<IRemoteImageProvider>(); + var options = GetMetadataOptions(item); + + return GetImageProviders(item, options, includeDisabled).OfType<IRemoteImageProvider>(); } - /// <summary> - /// Determines whether this instance can refresh the specified provider. - /// </summary> - /// <param name="provider">The provider.</param> - /// <param name="item">The item.</param> - /// <param name="includeDisabled">if set to <c>true</c> [include disabled].</param> - /// <returns><c>true</c> if this instance can refresh the specified provider; otherwise, <c>false</c>.</returns> - private bool CanRefresh(IMetadataProvider provider, IHasMetadata item, bool includeDisabled) + private bool CanRefresh(IMetadataProvider provider, IHasMetadata item, MetadataOptions options, bool includeDisabled) { - if (!includeDisabled && !ConfigurationManager.Configuration.EnableInternetProviders && provider is IRemoteMetadataProvider) + if (!includeDisabled) { - return false; + if (provider is IRemoteMetadataProvider) + { + if (!ConfigurationManager.Configuration.EnableInternetProviders) + { + return false; + } + + if (Array.IndexOf(options.DisabledMetadataFetchers, provider.Name) != -1) + { + return false; + } + } } - if (item.LocationType != LocationType.FileSystem && provider is ILocalMetadataProvider) + if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider) { return false; } @@ -351,13 +404,46 @@ namespace MediaBrowser.Providers.Manager return true; } + private bool CanRefresh(IImageProvider provider, IHasImages item, MetadataOptions options, bool includeDisabled) + { + if (!includeDisabled) + { + if (provider is IRemoteImageProvider) + { + if (!ConfigurationManager.Configuration.EnableInternetProviders) + { + return false; + } + + if (Array.IndexOf(options.DisabledImageFetchers, provider.Name) != -1) + { + return false; + } + } + } + + if (!item.SupportsLocalMetadata && provider is ILocalImageProvider) + { + return false; + } + + try + { + return provider.Supports(item); + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name); + return false; + } + } + /// <summary> /// Gets the order. /// </summary> - /// <param name="item">The item.</param> /// <param name="provider">The provider.</param> /// <returns>System.Int32.</returns> - private int GetOrder(IHasImages item, IImageProvider provider) + private int GetOrder(IImageProvider provider) { var hasOrder = provider as IHasOrder; @@ -372,52 +458,49 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Gets the order. /// </summary> - /// <param name="item">The item.</param> /// <param name="provider">The provider.</param> /// <returns>System.Int32.</returns> - private int GetOrder(IHasMetadata item, IMetadataProvider provider) + private int GetOrder(IMetadataProvider provider) { var hasOrder = provider as IHasOrder; - if (hasOrder == null) + if (hasOrder != null) { - return 0; + return hasOrder.Order; } - return hasOrder.Order; + return 0; } public IEnumerable<MetadataPluginSummary> GetAllMetadataPlugins() { - var list = new List<MetadataPluginSummary>(); - - list.Add(GetPluginSummary<Game>()); - list.Add(GetPluginSummary<GameSystem>()); - list.Add(GetPluginSummary<Movie>()); - list.Add(GetPluginSummary<Trailer>()); - list.Add(GetPluginSummary<BoxSet>()); - list.Add(GetPluginSummary<Book>()); - list.Add(GetPluginSummary<Series>()); - list.Add(GetPluginSummary<Season>()); - list.Add(GetPluginSummary<Episode>()); - list.Add(GetPluginSummary<Person>()); - list.Add(GetPluginSummary<MusicAlbum>()); - list.Add(GetPluginSummary<MusicArtist>()); - list.Add(GetPluginSummary<Audio>()); - - list.Add(GetPluginSummary<Genre>()); - list.Add(GetPluginSummary<Studio>()); - list.Add(GetPluginSummary<GameGenre>()); - list.Add(GetPluginSummary<MusicGenre>()); - - list.Add(GetPluginSummary<AdultVideo>()); - list.Add(GetPluginSummary<MusicVideo>()); - list.Add(GetPluginSummary<Video>()); - - list.Add(GetPluginSummary<LiveTvChannel>()); - list.Add(GetPluginSummary<LiveTvProgram>()); - list.Add(GetPluginSummary<LiveTvVideoRecording>()); - list.Add(GetPluginSummary<LiveTvAudioRecording>()); + var list = new List<MetadataPluginSummary> + { + GetPluginSummary<Game>(), + GetPluginSummary<GameSystem>(), + GetPluginSummary<Movie>(), + GetPluginSummary<Trailer>(), + GetPluginSummary<BoxSet>(), + GetPluginSummary<Book>(), + GetPluginSummary<Series>(), + GetPluginSummary<Season>(), + GetPluginSummary<Episode>(), + GetPluginSummary<Person>(), + GetPluginSummary<MusicAlbum>(), + GetPluginSummary<MusicArtist>(), + GetPluginSummary<Audio>(), + GetPluginSummary<Genre>(), + GetPluginSummary<Studio>(), + GetPluginSummary<GameGenre>(), + GetPluginSummary<MusicGenre>(), + GetPluginSummary<AdultVideo>(), + GetPluginSummary<MusicVideo>(), + GetPluginSummary<Video>(), + GetPluginSummary<LiveTvChannel>(), + GetPluginSummary<LiveTvProgram>(), + GetPluginSummary<LiveTvVideoRecording>(), + GetPluginSummary<LiveTvAudioRecording>() + }; return list; } @@ -434,14 +517,16 @@ namespace MediaBrowser.Providers.Manager Parent = new Folder() }; + var options = GetMetadataOptions(dummy); + var summary = new MetadataPluginSummary { ItemType = typeof(T).Name }; - var imageProviders = GetImageProviders(dummy).ToList(); + var imageProviders = GetImageProviders(dummy, options, true).ToList(); - AddMetadataPlugins(summary.Plugins, dummy); + AddMetadataPlugins(summary.Plugins, dummy, options); AddImagePlugins(summary.Plugins, dummy, imageProviders); summary.SupportedImageTypes = imageProviders.OfType<IRemoteImageProvider>() @@ -452,10 +537,10 @@ namespace MediaBrowser.Providers.Manager return summary; } - private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item) + private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, MetadataOptions options) where T : IHasMetadata { - var providers = GetMetadataProvidersInternal<T>(item, true).ToList(); + var providers = GetMetadataProvidersInternal<T>(item, options, true).ToList(); // Locals list.AddRange(providers.Where(i => (i is ILocalMetadataProvider)).Select(i => new MetadataPlugin @@ -472,7 +557,7 @@ namespace MediaBrowser.Providers.Manager })); // Savers - list.AddRange(_savers.Where(i => i.IsEnabledFor(item, ItemUpdateType.MetadataEdit)).OrderBy(i => i.Name).Select(i => new MetadataPlugin + list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, ItemUpdateType.MetadataEdit, false)).OrderBy(i => i.Name).Select(i => new MetadataPlugin { Name = i.Name, Type = MetadataPluginType.MetadataSaver @@ -498,8 +583,21 @@ namespace MediaBrowser.Providers.Manager })); } - private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); + public MetadataOptions GetMetadataOptions(IHasImages item) + { + var type = item.GetType().Name; + + if (item is Trailer) + { + type = typeof(Movie).Name; + } + return ConfigurationManager.Configuration.MetadataOptions + .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? + new MetadataOptions(); + } + + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); /// <summary> /// Saves the metadata. /// </summary> @@ -508,21 +606,25 @@ namespace MediaBrowser.Providers.Manager /// <returns>Task.</returns> public async Task SaveMetadata(IHasMetadata item, ItemUpdateType updateType) { - foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType))) + foreach (var saver in _savers.Where(i => IsSaverEnabledForItem(i, item, updateType, true))) { _logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name); - + var fileSaver = saver as IMetadataFileSaver; if (fileSaver != null) { - var locationType = item.LocationType; - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) + string path = null; + + try { - throw new ArgumentException("Only file-system based items can save metadata."); + path = fileSaver.GetSavePath(item); + } + catch (Exception ex) + { + _logger.ErrorException("Error in {0} GetSavePath", ex, saver.Name); + continue; } - - var path = fileSaver.GetSavePath(item); var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); @@ -556,5 +658,25 @@ namespace MediaBrowser.Providers.Manager } } } + + private bool IsSaverEnabledForItem(IMetadataSaver saver, IHasMetadata item, ItemUpdateType updateType, bool enforceConfiguration) + { + var options = GetMetadataOptions(item); + + try + { + if (enforceConfiguration && options.DisabledMetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + return saver.IsEnabledFor(item, updateType); + } + catch (Exception ex) + { + _logger.ErrorException("Error in {0}.IsEnabledFor", ex, saver.Name); + return false; + } + } } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Providers/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index ecefb72c4..6e994c9f2 100644 --- a/MediaBrowser.Providers/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Entities; using System.Collections.Generic; -namespace MediaBrowser.Providers +namespace MediaBrowser.Providers.Manager { public static class ProviderUtils { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 2c710693e..5cf4b2591 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -66,20 +66,25 @@ <ItemGroup> <Compile Include="AdultVideos\AdultVideoMetadataService.cs" /> <Compile Include="AdultVideos\AdultVideoXmlProvider.cs" /> + <Compile Include="All\InternalMetadataFolderImageProvider.cs" /> <Compile Include="All\LocalImageProvider.cs" /> <Compile Include="Books\BookMetadataService.cs" /> <Compile Include="BoxSets\BoxSetMetadataService.cs" /> <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" /> <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" /> + <Compile Include="Folders\CollectionFolderImageProvider.cs" /> <Compile Include="Folders\FolderMetadataService.cs" /> + <Compile Include="Folders\ImagesByNameImageProvider.cs" /> <Compile Include="GameGenres\GameGenreMetadataService.cs" /> <Compile Include="Games\GameMetadataService.cs" /> <Compile Include="Games\GameSystemMetadataService.cs" /> <Compile Include="Games\GameSystemXmlParser.cs" /> <Compile Include="Genres\GenreMetadataService.cs" /> + <Compile Include="LiveTv\AudioRecordingService.cs" /> <Compile Include="LiveTv\ChannelMetadataService.cs" /> <Compile Include="LiveTv\ChannelXmlProvider.cs" /> <Compile Include="LiveTv\ProgramMetadataService.cs" /> + <Compile Include="LiveTv\VideoRecordingService.cs" /> <Compile Include="Manager\ImageSaver.cs" /> <Compile Include="Manager\ItemImageProvider.cs" /> <Compile Include="Manager\ProviderManager.cs" /> @@ -93,6 +98,7 @@ <Compile Include="MediaInfo\FFProbeHelpers.cs" /> <Compile Include="MediaInfo\FFProbeProvider.cs" /> <Compile Include="MediaInfo\FFProbeVideoInfo.cs" /> + <Compile Include="Movies\MovieDbTrailerProvider.cs" /> <Compile Include="Movies\TrailerMetadataService.cs" /> <Compile Include="Movies\GenericMovieDbInfo.cs" /> <Compile Include="Movies\MovieDbSearch.cs" /> @@ -110,23 +116,30 @@ <Compile Include="Movies\MovieDbImageProvider.cs" /> <Compile Include="Movies\FanartMovieImageProvider.cs" /> <Compile Include="MusicGenres\MusicGenreMetadataService.cs" /> + <Compile Include="Music\AlbumImageFromSongProvider.cs" /> <Compile Include="Music\AlbumMetadataService.cs" /> <Compile Include="Music\ArtistMetadataService.cs" /> + <Compile Include="Music\AudioDbAlbumImageProvider.cs" /> + <Compile Include="Music\AudioDbAlbumProvider.cs" /> + <Compile Include="Music\AudioDbArtistImageProvider.cs" /> + <Compile Include="Music\AudioDbArtistProvider.cs" /> <Compile Include="Music\AudioMetadataService.cs" /> + <Compile Include="Music\Extensions.cs" /> <Compile Include="Music\LastfmArtistProvider.cs" /> + <Compile Include="Music\MovieDbMusicVideoProvider.cs" /> <Compile Include="Music\MusicBrainzArtistProvider.cs" /> <Compile Include="Music\MusicVideoMetadataService.cs" /> <Compile Include="Music\MusicVideoXmlProvider.cs" /> <Compile Include="Omdb\OmdbProvider.cs" /> - <Compile Include="Omdb\OmdbSeriesProvider.cs" /> + <Compile Include="Omdb\OmdbItemProvider.cs" /> <Compile Include="People\MovieDbPersonImageProvider.cs" /> <Compile Include="Movies\MovieUpdatesPrescanTask.cs" /> <Compile Include="Movies\MovieXmlParser.cs" /> - <Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" /> + <Compile Include="Movies\FanArtMovieUpdatesPostScanTask.cs" /> <Compile Include="Movies\MovieDbProvider.cs" /> <Compile Include="Music\AlbumXmlProvider.cs" /> <Compile Include="Music\ArtistXmlProvider.cs" /> - <Compile Include="Music\FanArtUpdatesPrescanTask.cs" /> + <Compile Include="Music\FanArtUpdatesPostScanTask.cs" /> <Compile Include="Music\LastfmAlbumProvider.cs" /> <Compile Include="Music\LastfmHelper.cs" /> <Compile Include="Music\FanArtAlbumProvider.cs" /> @@ -139,8 +152,7 @@ <Compile Include="People\PersonXmlProvider.cs" /> <Compile Include="People\MovieDbPersonProvider.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="ProviderUtils.cs" /> - <Compile Include="RefreshIntrosTask.cs" /> + <Compile Include="Manager\ProviderUtils.cs" /> <Compile Include="Savers\AlbumXmlSaver.cs" /> <Compile Include="Savers\ArtistXmlSaver.cs" /> <Compile Include="Savers\BoxSetXmlSaver.cs" /> @@ -160,9 +172,11 @@ <Compile Include="TV\EpisodeMetadataService.cs" /> <Compile Include="TV\EpisodeXmlProvider.cs" /> <Compile Include="TV\EpisodeXmlParser.cs" /> - <Compile Include="TV\FanArtTvUpdatesPrescanTask.cs" /> + <Compile Include="TV\FanArtTvUpdatesPostScanTask.cs" /> <Compile Include="TV\FanartSeasonProvider.cs" /> <Compile Include="TV\FanartSeriesProvider.cs" /> + <Compile Include="TV\MovieDbSeriesImageProvider.cs" /> + <Compile Include="TV\MovieDbSeriesProvider.cs" /> <Compile Include="TV\SeriesMetadataService.cs" /> <Compile Include="TV\TvdbEpisodeImageProvider.cs" /> <Compile Include="People\TvdbPersonImageProvider.cs" /> @@ -176,7 +190,6 @@ <Compile Include="TV\SeriesXmlProvider.cs" /> <Compile Include="TV\SeriesXmlParser.cs" /> <Compile Include="TV\TvdbPrescanTask.cs" /> - <Compile Include="Folders\UserRootFolderNameProvider.cs" /> <Compile Include="Users\UserMetadataService.cs" /> <Compile Include="Videos\VideoMetadataService.cs" /> <Compile Include="Years\YearMetadataService.cs" /> @@ -198,7 +211,9 @@ <ItemGroup> <None Include="packages.config" /> </ItemGroup> - <ItemGroup /> + <ItemGroup> + <EmbeddedResource Include="MediaInfo\whitelist.txt" /> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 20ce952db..772f60163 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -1,5 +1,6 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.MediaInfo; @@ -7,7 +8,10 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,15 +22,19 @@ namespace MediaBrowser.Providers.MediaInfo /// </summary> public class AudioImageProvider : IDynamicImageProvider, IHasChangeMonitor { + private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); + private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; - public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config) + public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem) { _isoManager = isoManager; _mediaEncoder = mediaEncoder; _config = config; + _fileSystem = fileSystem; } /// <summary> @@ -65,21 +73,82 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(new DynamicImageResponse { HasImage = false }); } - return GetVideoImage((Audio)item, cancellationToken); + return GetImage((Audio)item, cancellationToken); } - public async Task<DynamicImageResponse> GetVideoImage(Audio item, CancellationToken cancellationToken) + public async Task<DynamicImageResponse> GetImage(Audio item, CancellationToken cancellationToken) { - var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false); + var path = GetAudioImagePath(item); + + if (!File.Exists(path)) + { + var semaphore = GetLock(path); + + // Acquire a lock + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + // Check again in case it was saved while waiting for the lock + if (!File.Exists(path)) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false)) + { + using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await stream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } + } + } + finally + { + semaphore.Release(); + } + } return new DynamicImageResponse { - Format = ImageFormat.Jpg, HasImage = true, - Stream = stream + Path = path }; } + private string GetAudioImagePath(Audio item) + { + var album = item.Parent as MusicAlbum; + + var filename = item.Album ?? string.Empty; + filename += item.Artists.FirstOrDefault() ?? string.Empty; + filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; + + filename = filename.GetMD5() + ".jpg"; + + var prefix = filename.Substring(0, 1); + + return Path.Combine(AudioImagesPath, prefix, filename); + } + + public string AudioImagesPath + { + get + { + return Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); + } + } + + /// <summary> + /// Gets the lock. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns>SemaphoreSlim.</returns> + private SemaphoreSlim GetLock(string filename) + { + return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + public string Name { get { return "Embedded Image"; } @@ -90,7 +159,7 @@ namespace MediaBrowser.Providers.MediaInfo return item.LocationType == LocationType.FileSystem && item is Audio; } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { return item.DateModified > date; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 4fc92ddeb..ebb2f13d1 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -1,13 +1,16 @@ -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,13 +21,17 @@ namespace MediaBrowser.Providers.MediaInfo { private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; + private readonly IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo) + + public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo, IApplicationPaths appPaths, IJsonSerializer json) { _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; + _appPaths = appPaths; + _json = json; } public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken) @@ -47,10 +54,30 @@ namespace MediaBrowser.Providers.MediaInfo { cancellationToken.ThrowIfCancellationRequested(); + var idString = item.Id.ToString("N"); + var cachePath = Path.Combine(_appPaths.CachePath, "ffprobe-audio", idString.Substring(0, 2), idString, "v" + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); + + try + { + return _json.DeserializeFromFile<InternalMediaInfoResult>(cachePath); + } + catch (FileNotFoundException) + { + + } + catch (DirectoryNotFoundException) + { + } + const InputType type = InputType.File; var inputPath = new[] { item.Path }; - return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); + var result = await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); + + Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); + _json.SerializeToFile(result, cachePath); + + return result; } /// <summary> @@ -178,9 +205,14 @@ namespace MediaBrowser.Providers.MediaInfo FetchStudios(audio, tags, "ensemble"); FetchStudios(audio, tags, "publisher"); } + + audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); + audio.SetProviderId(MetadataProviders.MusicBrainzArtist, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); + audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); + audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); } - private readonly char[] _nameDelimiters = new[] { '/', '|', ';', '\\' }; + private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; /// <summary> /// Splits the specified val. @@ -205,13 +237,63 @@ namespace MediaBrowser.Providers.MediaInfo val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase) .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase); + var artistsFound = new List<string>(); + + foreach (var whitelistArtist in GetSplitWhitelist()) + { + var originalVal = val; + val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); + + if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase)) + { + // TODO: Preserve casing from original value + artistsFound.Add(whitelistArtist); + } + } + // Only use the comma as a delimeter if there are no slashes or pipes. // We want to be careful not to split names that have commas in them var delimeter = _nameDelimiters; - return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) + var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => i.Trim()); + + artistsFound.AddRange(artists); + return artistsFound; + } + + + private List<string> _splitWhiteList = null; + + private IEnumerable<string> GetSplitWhitelist() + { + if (_splitWhiteList == null) + { + var file = GetType().Namespace + ".whitelist.txt"; + + using (var stream = GetType().Assembly.GetManifestResourceStream(file)) + { + using (var reader = new StreamReader(stream)) + { + var list = new List<string>(); + + while (!reader.EndOfStream) + { + var val = reader.ReadLine(); + + if (!string.IsNullOrWhiteSpace(val)) + { + list.Add(val); + } + } + + _splitWhiteList = list; + } + } + } + + return _splitWhiteList; } /// <summary> diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index d55a42d11..7ac48655a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -12,9 +13,11 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; using System; using System.Threading; using System.Threading.Tasks; +using System.Linq; namespace MediaBrowser.Providers.MediaInfo { @@ -27,7 +30,8 @@ namespace MediaBrowser.Providers.MediaInfo ICustomMetadataProvider<Trailer>, ICustomMetadataProvider<Video>, ICustomMetadataProvider<Audio>, - IHasChangeMonitor + IHasChangeMonitor, + IHasOrder { private readonly ILogger _logger; private readonly IIsoManager _isoManager; @@ -35,58 +39,60 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IItemRepository _itemRepo; private readonly IBlurayExaminer _blurayExaminer; private readonly ILocalizationManager _localization; + private readonly IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; public string Name { get { return "ffprobe"; } } - public Task<ItemUpdateType> FetchAsync(Episode item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Episode item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(MusicVideo item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(MusicVideo item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(Movie item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Movie item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(AdultVideo item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(AdultVideo item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(LiveTvVideoRecording item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(LiveTvVideoRecording item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Trailer item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(Video item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Video item, IDirectoryService directoryService, CancellationToken cancellationToken) { - return FetchVideoInfo(item, cancellationToken); + return FetchVideoInfo(item, directoryService, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(Audio item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Audio item, IDirectoryService directoryService, CancellationToken cancellationToken) { return FetchAudioInfo(item, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, IDirectoryService directoryService, CancellationToken cancellationToken) { return FetchAudioInfo(item, cancellationToken); } - public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization) + public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json) { _logger = logger; _isoManager = isoManager; @@ -94,10 +100,12 @@ namespace MediaBrowser.Providers.MediaInfo _itemRepo = itemRepo; _blurayExaminer = blurayExaminer; _localization = localization; + _appPaths = appPaths; + _json = json; } - private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.Unspecified); - public Task<ItemUpdateType> FetchVideoInfo<T>(T item, CancellationToken cancellationToken) + private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None); + public Task<ItemUpdateType> FetchVideoInfo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken) where T : Video { if (item.LocationType != LocationType.FileSystem) @@ -115,9 +123,9 @@ namespace MediaBrowser.Providers.MediaInfo return _cachedTask; } - var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization); + var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json); - return prober.ProbeVideo(item, cancellationToken); + return prober.ProbeVideo(item, directoryService, cancellationToken); } public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken) @@ -128,14 +136,40 @@ namespace MediaBrowser.Providers.MediaInfo return _cachedTask; } - var prober = new FFProbeAudioInfo(_mediaEncoder, _itemRepo); + var prober = new FFProbeAudioInfo(_mediaEncoder, _itemRepo, _appPaths, _json); return prober.Probe(item, cancellationToken); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return item.DateModified > date; + if (item.DateModified > date) + { + return true; + } + + if (item.SupportsLocalMetadata) + { + var video = item as Video; + + if (video != null) + { + var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json); + + return !video.SubtitleFiles.SequenceEqual(prober.GetSubtitleFiles(video, directoryService).Select(i => i.FullName).OrderBy(i => i), StringComparer.OrdinalIgnoreCase); + } + } + + return false; + } + + public int Order + { + get + { + // Run last + return 100; + } } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 9073441e4..074bcfdff 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -1,13 +1,16 @@ using DvdLib.Ifo; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Globalization; @@ -26,10 +29,12 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IItemRepository _itemRepo; private readonly IBlurayExaminer _blurayExaminer; private readonly ILocalizationManager _localization; + private readonly IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization) + public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json) { _logger = logger; _isoManager = isoManager; @@ -37,9 +42,11 @@ namespace MediaBrowser.Providers.MediaInfo _itemRepo = itemRepo; _blurayExaminer = blurayExaminer; _localization = localization; + _appPaths = appPaths; + _json = json; } - public async Task<ItemUpdateType> ProbeVideo<T>(T item, CancellationToken cancellationToken) + public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken) where T : Video { var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); @@ -66,7 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo cancellationToken.ThrowIfCancellationRequested(); - await Fetch(item, cancellationToken, result, isoMount).ConfigureAwait(false); + await Fetch(item, cancellationToken, result, isoMount, directoryService).ConfigureAwait(false); } finally @@ -84,6 +91,23 @@ namespace MediaBrowser.Providers.MediaInfo { cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); + + var idString = item.Id.ToString("N"); + var cachePath = Path.Combine(_appPaths.CachePath, "ffprobe-video", idString.Substring(0, 2), idString, "v" + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); + + try + { + return _json.DeserializeFromFile<InternalMediaInfoResult>(cachePath); + } + catch (FileNotFoundException) + { + + } + catch (DirectoryNotFoundException) + { + } + var type = InputType.File; var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath }; @@ -94,10 +118,15 @@ namespace MediaBrowser.Providers.MediaInfo inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); } - return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); + var result = await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); + + Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); + _json.SerializeToFile(result, cachePath); + + return result; } - protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount) + protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, IDirectoryService directoryService) { if (data.format != null) { @@ -120,7 +149,7 @@ namespace MediaBrowser.Providers.MediaInfo FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken); } - AddExternalSubtitles(video, mediaStreams); + AddExternalSubtitles(video, mediaStreams, directoryService); FetchWtvInfo(video, data); @@ -314,76 +343,104 @@ namespace MediaBrowser.Providers.MediaInfo } } + public IEnumerable<FileInfo> GetSubtitleFiles(Video video, IDirectoryService directoryService) + { + var containingPath = video.ContainingFolderPath; + + if (string.IsNullOrEmpty(containingPath)) + { + throw new ArgumentException(string.Format("Cannot search for items that don't have a path: {0} {1}", video.Name, video.Id)); + } + + var files = directoryService.GetFiles(containingPath); + + var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + return files.Where(i => + { + if (!i.Attributes.HasFlag(FileAttributes.Directory) && + SubtitleExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)) + { + var fullName = i.FullName; + + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); + + if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + }); + } + /// <summary> /// Adds the external subtitles. /// </summary> /// <param name="video">The video.</param> /// <param name="currentStreams">The current streams.</param> - private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams) + private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService) { - //var useParent = !video.ResolveArgs.IsDirectory; - - //if (useParent && video.Parent == null) - //{ - // return; - //} - - //var fileSystemChildren = useParent - // ? video.Parent.ResolveArgs.FileSystemChildren - // : video.ResolveArgs.FileSystemChildren; - - //var startIndex = currentStreams.Count; - //var streams = new List<MediaStream>(); - - //var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); - - //foreach (var file in fileSystemChildren - // .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase))) - //{ - // var fullName = file.FullName; - - // var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); - - // // If the subtitle file matches the video file name - // if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - // { - // streams.Add(new MediaStream - // { - // Index = startIndex++, - // Type = MediaStreamType.Subtitle, - // IsExternal = true, - // Path = fullName, - // Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') - // }); - // } - // else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - // { - // // Support xbmc naming conventions - 300.spanish.srt - // var language = fileNameWithoutExtension.Split('.').LastOrDefault(); - - // // Try to translate to three character code - // // Be flexible and check against both the full and three character versions - // var culture = _localization.GetCultures() - // .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); - - // if (culture != null) - // { - // language = culture.ThreeLetterISOLanguageName; - // } - - // streams.Add(new MediaStream - // { - // Index = startIndex++, - // Type = MediaStreamType.Subtitle, - // IsExternal = true, - // Path = fullName, - // Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), - // Language = language - // }); - // } - //} - - //currentStreams.AddRange(streams); + var files = GetSubtitleFiles(video, directoryService); + + var startIndex = currentStreams.Count; + var streams = new List<MediaStream>(); + + var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + foreach (var file in files) + { + var fullName = file.FullName; + + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); + + // If the subtitle file matches the video file name + if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + streams.Add(new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName, + Codec = Path.GetExtension(fullName).ToLower().TrimStart('.') + }); + } + else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) + { + // Support xbmc naming conventions - 300.spanish.srt + var language = fileNameWithoutExtension.Split('.').LastOrDefault(); + + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var culture = _localization.GetCultures() + .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); + + if (culture != null) + { + language = culture.ThreeLetterISOLanguageName; + } + + streams.Add(new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName, + Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'), + Language = language + }); + } + } + + video.SubtitleFiles = streams.Select(i => i.Path).OrderBy(i => i).ToList(); + + currentStreams.AddRange(streams); } /// <summary> diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 31d44f4ec..c230f65f9 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.MediaInfo { - public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor + public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor, IHasOrder { private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; @@ -126,9 +126,18 @@ namespace MediaBrowser.Providers.MediaInfo return item.LocationType == LocationType.FileSystem && item is Video; } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { return item.DateModified > date; } + + public int Order + { + get + { + // Make sure this comes after internet image providers + return 100; + } + } } } diff --git a/MediaBrowser.Providers/MediaInfo/whitelist.txt b/MediaBrowser.Providers/MediaInfo/whitelist.txt new file mode 100644 index 000000000..1fd366551 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/whitelist.txt @@ -0,0 +1 @@ +AC/DC
\ No newline at end of file diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs index 600ba1925..6f911d411 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { - class FanartMovieUpdatesPrescanTask : ILibraryPostScanTask + class FanartMovieUpdatesPostScanTask : ILibraryPostScanTask { private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmovies/{0}/{1}/"; @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Movies private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public FanartMovieUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) + public FanartMovieUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) { _jsonSerializer = jsonSerializer; _config = config; @@ -118,7 +118,7 @@ namespace MediaBrowser.Providers.Movies return new List<string>(); } - var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPrescanTask.FanArtUpdate>>(json); + var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPostScanTask.FanArtUpdate>>(json); var existingDictionary = existingIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index e0ed0ec25..26395e9cc 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; @@ -344,8 +345,13 @@ namespace MediaBrowser.Providers.Movies }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + if (!_config.Configuration.EnableFanArtUpdates) + { + return false; + } + var id = item.GetProviderId(MetadataProviders.Tmdb); if (!string.IsNullOrEmpty(id)) @@ -355,7 +361,7 @@ namespace MediaBrowser.Providers.Movies var fileInfo = new FileInfo(xmlPath); - return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; } return false; diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs index d06d12a49..44e2beb7f 100644 --- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Movies _jsonSerializer = jsonSerializer; } - public async Task<MetadataResult<T>> GetMetadata(ItemId itemId, CancellationToken cancellationToken) + public async Task<MetadataResult<T>> GetMetadata(ItemLookupInfo itemId, CancellationToken cancellationToken) { var result = new MetadataResult<T>(); @@ -142,10 +142,6 @@ namespace MediaBrowser.Providers.Movies movieItem.TmdbCollectionName = movieData.belongs_to_collection.name; } } - else - { - movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry - } float rating; string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture); diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs index be2f7ad61..8a38e294c 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { - class MovieDbImageProvider : IRemoteImageProvider, IHasOrder + class MovieDbImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; @@ -207,5 +207,10 @@ namespace MediaBrowser.Providers.Movies ResourcePool = MovieDbProvider.Current.MovieDbResourcePool }); } + + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + { + return MovieDbProvider.Current.HasChanged(item, date); + } } } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 47b92235b..36dfc64d2 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; @@ -18,7 +19,7 @@ namespace MediaBrowser.Providers.Movies /// <summary> /// Class MovieDbProvider /// </summary> - public class MovieDbProvider : IRemoteMetadataProvider<Movie>, IDisposable + public class MovieDbProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IDisposable { internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1); @@ -40,12 +41,12 @@ namespace MediaBrowser.Providers.Movies Current = this; } - public Task<MetadataResult<Movie>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken) { - return GetItemMetadata<Movie>(id, cancellationToken); + return GetItemMetadata<Movie>(info, cancellationToken); } - public Task<MetadataResult<T>> GetItemMetadata<T>(ItemId id, CancellationToken cancellationToken) + public Task<MetadataResult<T>> GetItemMetadata<T>(ItemLookupInfo id, CancellationToken cancellationToken) where T : Video, new () { var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer); @@ -314,6 +315,28 @@ namespace MediaBrowser.Providers.Movies } } + public bool HasChanged(IHasMetadata item, DateTime date) + { + if (!_configurationManager.Configuration.EnableTmdbUpdates) + { + return false; + } + + var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); + + if (!String.IsNullOrEmpty(tmdbId)) + { + // Process images + var dataFilePath = GetDataFilePath(tmdbId, item.GetPreferredMetadataLanguage()); + + var fileInfo = new FileInfo(dataFilePath); + + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + } + + return false; + } + public void Dispose() { Dispose(true); diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs index d4c6c6ff8..5a2b865f5 100644 --- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs +++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs @@ -29,17 +29,22 @@ namespace MediaBrowser.Providers.Movies _json = json; } - public Task<string> FindMovieId(ItemId idInfo, CancellationToken cancellationToken) + public Task<string> FindSeriesId(ItemLookupInfo idInfo, CancellationToken cancellationToken) + { + return FindId(idInfo, "tv", cancellationToken); + } + + public Task<string> FindMovieId(ItemLookupInfo idInfo, CancellationToken cancellationToken) { return FindId(idInfo, "movie", cancellationToken); } - public Task<string> FindCollectionId(ItemId idInfo, CancellationToken cancellationToken) + public Task<string> FindCollectionId(ItemLookupInfo idInfo, CancellationToken cancellationToken) { return FindId(idInfo, "collection", cancellationToken); } - private async Task<string> FindId(ItemId idInfo, string searchType, CancellationToken cancellationToken) + private async Task<string> FindId(ItemLookupInfo idInfo, string searchType, CancellationToken cancellationToken) { int? yearInName; var name = idInfo.Name; @@ -71,6 +76,10 @@ namespace MediaBrowser.Providers.Movies name = name.Replace(".", " "); name = name.Replace("_", " "); name = name.Replace("-", " "); + name = name.Replace("!", " "); + name = name.Replace("?", " "); + + name = name.Trim(); // Search again if the new name is different if (!string.Equals(name, originalName)) diff --git a/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs b/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs new file mode 100644 index 000000000..ed36cb7af --- /dev/null +++ b/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs @@ -0,0 +1,26 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Movies +{ + public class MovieDbTrailerProvider : IRemoteMetadataProvider<Trailer, TrailerInfo> + { + public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken) + { + return MovieDbProvider.Current.GetItemMetadata<Trailer>(info, cancellationToken); + } + + public string Name + { + get { return MovieDbProvider.Current.Name; } + } + + public bool HasChanged(IHasMetadata item, DateTime date) + { + return MovieDbProvider.Current.HasChanged(item, date); + } + } +} diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index cc513b395..9ea43786e 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { - public class MovieMetadataService : MetadataService<Movie, ItemId> + public class MovieMetadataService : MetadataService<Movie, MovieInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.Movies { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(Movie item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs index 3f200dc8c..483175f37 100644 --- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs @@ -1,6 +1,5 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; -using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs index 8eabc0a2d..bb7650255 100644 --- a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs @@ -17,12 +17,12 @@ namespace MediaBrowser.Providers.Movies _logger = logger; } - protected override void Fetch(Movie item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Movie> result, string path, CancellationToken cancellationToken) { - new MovieXmlParser(_logger).Fetch(item, path, cancellationToken); + new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs index ad9cf5231..9c937db84 100644 --- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs +++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { - public class TrailerMetadataService : MetadataService<Trailer, ItemId> + public class TrailerMetadataService : MetadataService<Trailer, TrailerInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.Movies { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(Trailer item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs b/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs index 52704b151..df90d32ca 100644 --- a/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs +++ b/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs @@ -17,12 +17,12 @@ namespace MediaBrowser.Providers.Movies _logger = logger; } - protected override void Fetch(Trailer item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Trailer> result, string path, CancellationToken cancellationToken) { - new MovieXmlParser(_logger).Fetch(item, path, cancellationToken); + new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs b/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs new file mode 100644 index 000000000..0e073c783 --- /dev/null +++ b/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class AlbumImageFromSongProvider : IDynamicImageProvider + { + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) + { + return new List<ImageType> { ImageType.Primary }; + } + + public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) + { + var album = (MusicAlbum)item; + + var image = album.RecursiveChildren.OfType<Audio>() + .Select(i => i.GetImagePath(type)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + + return Task.FromResult(new DynamicImageResponse + { + Path = image, + HasImage = !string.IsNullOrEmpty(image) + }); + } + + public string Name + { + get { return "Embedded Image"; } + } + + public bool Supports(IHasImages item) + { + return item is MusicAlbum; + } + } +} diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index a0e2cc81b..0a3359156 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumId> + public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo> { private readonly ILibraryManager _libraryManager; @@ -42,11 +42,6 @@ namespace MediaBrowser.Providers.Music } } - protected override Task SaveItem(MusicAlbum item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - protected override ItemUpdateType BeforeSave(MusicAlbum item) { var updateType = base.BeforeSave(item); @@ -102,26 +97,9 @@ namespace MediaBrowser.Providers.Music return updateType; } - protected override AlbumId GetId(MusicAlbum item) - { - var id = base.GetId(item); - - id.AlbumArtist = item.AlbumArtist; - - var artist = item.Parents.OfType<MusicArtist>().FirstOrDefault(); - - if (artist != null) - { - id.ArtistProviderIds = artist.ProviderIds; - id.AlbumArtist = id.AlbumArtist ?? artist.Name; - } - - return id; - } - private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IEnumerable<Audio> songs) { - var updateType = ItemUpdateType.Unspecified; + var updateType = ItemUpdateType.None; var albumArtist = songs .Select(i => i.AlbumArtist) @@ -141,7 +119,7 @@ namespace MediaBrowser.Providers.Music private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IEnumerable<Audio> songs) { - var updateType = ItemUpdateType.Unspecified; + var updateType = ItemUpdateType.None; var currentList = item.Artists.ToList(); @@ -159,7 +137,7 @@ namespace MediaBrowser.Providers.Music private ItemUpdateType SetDateFromSongs(MusicAlbum item, List<Audio> songs) { - var updateType = ItemUpdateType.Unspecified; + var updateType = ItemUpdateType.None; var date = songs.Select(i => i.PremiereDate) .FirstOrDefault(i => i.HasValue); diff --git a/MediaBrowser.Providers/Music/AlbumXmlProvider.cs b/MediaBrowser.Providers/Music/AlbumXmlProvider.cs index 06a1ed121..60bc18b2f 100644 --- a/MediaBrowser.Providers/Music/AlbumXmlProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumXmlProvider.cs @@ -17,14 +17,14 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - protected override void Fetch(MusicAlbum item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<MusicAlbum> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "album.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "album.xml")); } } } diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index 1642cdfaa..1b7f229e4 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -9,12 +9,10 @@ using MediaBrowser.Providers.Manager; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - public class ArtistMetadataService : MetadataService<MusicArtist, ItemId> + public class ArtistMetadataService : MetadataService<MusicArtist, ArtistInfo> { private readonly ILibraryManager _libraryManager; @@ -37,11 +35,6 @@ namespace MediaBrowser.Providers.Music ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - protected override Task SaveItem(MusicArtist item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - protected override ItemUpdateType BeforeSave(MusicArtist item) { var updateType = base.BeforeSave(item); diff --git a/MediaBrowser.Providers/Music/ArtistXmlProvider.cs b/MediaBrowser.Providers/Music/ArtistXmlProvider.cs index 921cbc8b9..89ba1c794 100644 --- a/MediaBrowser.Providers/Music/ArtistXmlProvider.cs +++ b/MediaBrowser.Providers/Music/ArtistXmlProvider.cs @@ -17,14 +17,14 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - protected override void Fetch(MusicArtist item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<MusicArtist> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<MusicArtist>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "artist.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "artist.xml")); } } } diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs new file mode 100644 index 000000000..a8f3b8a6d --- /dev/null +++ b/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs @@ -0,0 +1,122 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class AudioDbAlbumImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; + + public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IJsonSerializer json) + { + _config = config; + _httpClient = httpClient; + _json = json; + } + + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) + { + return new List<ImageType> + { + ImageType.Primary, + ImageType.Disc + }; + } + + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) + { + var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); + + return images.Where(i => i.Type == imageType); + } + + public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + { + var id = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + + if (!string.IsNullOrWhiteSpace(id)) + { + await AudioDbAlbumProvider.Current.EnsureInfo(id, cancellationToken).ConfigureAwait(false); + + var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id); + + var obj = _json.DeserializeFromFile<AudioDbAlbumProvider.RootObject>(path); + + if (obj != null && obj.album != null && obj.album.Count > 0) + { + return GetImages(obj.album[0]); + } + } + + return new List<RemoteImageInfo>(); + } + + private IEnumerable<RemoteImageInfo> GetImages(AudioDbAlbumProvider.Album item) + { + var list = new List<RemoteImageInfo>(); + + if (!string.IsNullOrWhiteSpace(item.strAlbumThumb)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strAlbumThumb, + Type = ImageType.Primary + }); + } + + if (!string.IsNullOrWhiteSpace(item.strAlbumCDart)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strAlbumCDart, + Type = ImageType.Disc + }); + } + + return list; + } + + public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url, + ResourcePool = AudioDbArtistProvider.Current.AudioDbResourcePool + }); + } + + public string Name + { + get { return "TheAudioDB"; } + } + + public int Order + { + get + { + // After embedded and fanart + return 2; + } + } + + public bool Supports(IHasImages item) + { + return item is MusicAlbum; + } + } +} diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs new file mode 100644 index 000000000..20f4af52c --- /dev/null +++ b/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs @@ -0,0 +1,210 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class AudioDbAlbumProvider : IRemoteMetadataProvider<MusicAlbum,AlbumInfo>, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; + + public static AudioDbAlbumProvider Current; + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json) + { + _config = config; + _fileSystem = fileSystem; + _httpClient = httpClient; + _json = json; + + Current = this; + } + + public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult<MusicAlbum>(); + + var id = info.GetReleaseGroupId(); + + if (!string.IsNullOrWhiteSpace(id)) + { + await EnsureInfo(id, cancellationToken).ConfigureAwait(false); + + var path = GetAlbumInfoPath(_config.ApplicationPaths, id); + + var obj = _json.DeserializeFromFile<RootObject>(path); + + if (obj != null && obj.album != null && obj.album.Count > 0) + { + result.Item = new MusicAlbum(); + result.HasMetadata = true; + ProcessResult(result.Item, obj.album[0]); + } + } + + return result; + } + + private void ProcessResult(MusicAlbum item, Album result) + { + item.AlbumArtist = result.strArtist; + + if (!string.IsNullOrEmpty(result.intYearReleased)) + { + item.ProductionYear = int.Parse(result.intYearReleased, _usCulture); + } + + if (!string.IsNullOrEmpty(result.strGenre)) + { + item.Genres = new List<string> { result.strGenre }; + } + + item.SetProviderId(MetadataProviders.AudioDbArtist, result.idArtist); + item.SetProviderId(MetadataProviders.AudioDbAlbum, result.idAlbum); + + item.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, result.strMusicBrainzArtistID); + item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, result.strMusicBrainzID); + } + + public string Name + { + get { return "TheAudioDB"; } + } + + private readonly Task _cachedTask = Task.FromResult(true); + internal Task EnsureInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) + { + var xmlPath = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); + + var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); + + if (fileInfo.Exists) + { + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + { + return _cachedTask; + } + } + + return DownloadInfo(musicBrainzReleaseGroupId, cancellationToken); + } + + internal async Task DownloadInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var url = AudioDbArtistProvider.BaseUrl + "/album-mb.php?i=" + musicBrainzReleaseGroupId; + + var path = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var response = await _httpClient.Get(new HttpRequestOptions + { + Url = url, + ResourcePool = AudioDbArtistProvider.Current.AudioDbResourcePool, + CancellationToken = cancellationToken + + }).ConfigureAwait(false)) + { + using (var xmlFileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await response.CopyToAsync(xmlFileStream).ConfigureAwait(false); + } + } + } + + private static string GetAlbumDataPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) + { + var dataPath = Path.Combine(GetAlbumDataPath(appPaths), musicBrainzReleaseGroupId); + + return dataPath; + } + + private static string GetAlbumDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.CachePath, "audiodb-album"); + + return dataPath; + } + + internal static string GetAlbumInfoPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) + { + var dataPath = GetAlbumDataPath(appPaths, musicBrainzReleaseGroupId); + + return Path.Combine(dataPath, "album.json"); + } + + public int Order + { + get + { + // After music brainz + return 1; + } + } + + public class Album + { + public string idAlbum { get; set; } + public string idArtist { get; set; } + public string strAlbum { get; set; } + public string strArtist { get; set; } + public string intYearReleased { get; set; } + public string strGenre { get; set; } + public string strSubGenre { get; set; } + public string strReleaseFormat { get; set; } + public string intSales { get; set; } + public string strAlbumThumb { get; set; } + public string strAlbumCDart { get; set; } + public string strDescriptionEN { get; set; } + public object strDescriptionDE { get; set; } + public object strDescriptionFR { get; set; } + public object strDescriptionCN { get; set; } + public object strDescriptionIT { get; set; } + public object strDescriptionJP { get; set; } + public object strDescriptionRU { get; set; } + public object strDescriptionES { get; set; } + public object strDescriptionPT { get; set; } + public object strDescriptionSE { get; set; } + public object strDescriptionNL { get; set; } + public object strDescriptionHU { get; set; } + public object strDescriptionNO { get; set; } + public object strDescriptionIL { get; set; } + public object strDescriptionPL { get; set; } + public object intLoved { get; set; } + public object intScore { get; set; } + public string strReview { get; set; } + public object strMood { get; set; } + public object strTheme { get; set; } + public object strSpeed { get; set; } + public object strLocation { get; set; } + public string strMusicBrainzID { get; set; } + public string strMusicBrainzArtistID { get; set; } + public object strItunesID { get; set; } + public object strAmazonID { get; set; } + public string strLocked { get; set; } + } + + public class RootObject + { + public List<Album> album { get; set; } + } + } +} diff --git a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs new file mode 100644 index 000000000..79238fd51 --- /dev/null +++ b/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs @@ -0,0 +1,164 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; + + public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClient httpClient) + { + _config = config; + _json = json; + _httpClient = httpClient; + } + + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) + { + return new List<ImageType> + { + ImageType.Primary, + ImageType.Logo, + ImageType.Banner, + ImageType.Backdrop + }; + } + + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) + { + var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); + + return images.Where(i => i.Type == imageType); + } + + public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + { + var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist); + + if (!string.IsNullOrWhiteSpace(id)) + { + await AudioDbArtistProvider.Current.EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); + + var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id); + + var obj = _json.DeserializeFromFile<AudioDbArtistProvider.RootObject>(path); + + if (obj != null && obj.artists != null && obj.artists.Count > 0) + { + return GetImages(obj.artists[0]); + } + } + + return new List<RemoteImageInfo>(); + } + + private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item) + { + var list = new List<RemoteImageInfo>(); + + if (!string.IsNullOrWhiteSpace(item.strArtistThumb)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistThumb, + Type = ImageType.Primary + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistLogo)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistLogo, + Type = ImageType.Logo + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistBanner)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistBanner, + Type = ImageType.Banner + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistFanart)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistFanart, + Type = ImageType.Backdrop + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistFanart2)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistFanart2, + Type = ImageType.Backdrop + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistFanart3)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistFanart3, + Type = ImageType.Backdrop + }); + } + + return list; + } + + public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url, + ResourcePool = AudioDbArtistProvider.Current.AudioDbResourcePool + }); + } + + public string Name + { + get { return "TheAudioDB"; } + } + + public bool Supports(IHasImages item) + { + return item is MusicArtist; + } + + public int Order + { + get + { + // After fanart + return 1; + } + } + } +} diff --git a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs new file mode 100644 index 000000000..00c5b94e0 --- /dev/null +++ b/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs @@ -0,0 +1,217 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class AudioDbArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; + + public static AudioDbArtistProvider Current; + + public SemaphoreSlim AudioDbResourcePool = new SemaphoreSlim(2, 2); + private const string ApiKey = "49jhsf8248yfahka89724011"; + public const string BaseUrl = "http://www.theaudiodb.com/api/v1/json/" + ApiKey; + + public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json) + { + _config = config; + _fileSystem = fileSystem; + _httpClient = httpClient; + _json = json; + Current = this; + } + + public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult<MusicArtist>(); + + var id = info.GetMusicBrainzArtistId(); + + if (!string.IsNullOrWhiteSpace(id)) + { + await EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); + + var path = GetArtistInfoPath(_config.ApplicationPaths, id); + + var obj = _json.DeserializeFromFile<RootObject>(path); + + if (obj != null && obj.artists != null && obj.artists.Count > 0) + { + result.Item = new MusicArtist(); + result.HasMetadata = true; + ProcessResult(result.Item, obj.artists[0]); + } + } + + return result; + } + + private void ProcessResult(MusicArtist item, Artist result) + { + item.HomePageUrl = result.strWebsite; + item.Overview = result.strBiographyEN; + + if (!string.IsNullOrEmpty(result.strGenre)) + { + item.Genres = new List<string> { result.strGenre }; + } + + item.SetProviderId(MetadataProviders.AudioDbArtist, result.idArtist); + item.SetProviderId(MetadataProviders.MusicBrainzArtist, result.strMusicBrainzID); + } + + public string Name + { + get { return "TheAudioDB"; } + } + + private readonly Task _cachedTask = Task.FromResult(true); + internal Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken) + { + var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); + + var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); + + if (fileInfo.Exists) + { + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + { + return _cachedTask; + } + } + + return DownloadArtistInfo(musicBrainzId, cancellationToken); + } + + internal async Task DownloadArtistInfo(string musicBrainzId, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var url = BaseUrl + "/artist-mb.php?i=" + musicBrainzId; + + var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var response = await _httpClient.Get(new HttpRequestOptions + { + Url = url, + ResourcePool = AudioDbResourcePool, + CancellationToken = cancellationToken + + }).ConfigureAwait(false)) + { + using (var xmlFileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await response.CopyToAsync(xmlFileStream).ConfigureAwait(false); + } + } + } + + /// <summary> + /// Gets the artist data path. + /// </summary> + /// <param name="appPaths">The application paths.</param> + /// <param name="musicBrainzArtistId">The music brainz artist identifier.</param> + /// <returns>System.String.</returns> + private static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId) + { + var dataPath = Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId); + + return dataPath; + } + + /// <summary> + /// Gets the artist data path. + /// </summary> + /// <param name="appPaths">The application paths.</param> + /// <returns>System.String.</returns> + private static string GetArtistDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.CachePath, "audiodb-artist"); + + return dataPath; + } + + internal static string GetArtistInfoPath(IApplicationPaths appPaths, string musicBrainzArtistId) + { + var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId); + + return Path.Combine(dataPath, "artist.json"); + } + + public class Artist + { + public string idArtist { get; set; } + public string strArtist { get; set; } + public string strArtistAlternate { get; set; } + public object idLabel { get; set; } + public string intFormedYear { get; set; } + public string intBornYear { get; set; } + public object intDiedYear { get; set; } + public object strDisbanded { get; set; } + public string strGenre { get; set; } + public string strSubGenre { get; set; } + public string strWebsite { get; set; } + public string strFacebook { get; set; } + public string strTwitter { get; set; } + public string strBiographyEN { get; set; } + public string strBiographyDE { get; set; } + public string strBiographyFR { get; set; } + public object strBiographyCN { get; set; } + public string strBiographyIT { get; set; } + public object strBiographyJP { get; set; } + public object strBiographyRU { get; set; } + public object strBiographyES { get; set; } + public object strBiographyPT { get; set; } + public object strBiographySE { get; set; } + public object strBiographyNL { get; set; } + public object strBiographyHU { get; set; } + public object strBiographyNO { get; set; } + public object strBiographyIL { get; set; } + public object strBiographyPL { get; set; } + public string strGender { get; set; } + public string intMembers { get; set; } + public string strCountry { get; set; } + public string strCountryCode { get; set; } + public string strArtistThumb { get; set; } + public string strArtistLogo { get; set; } + public string strArtistFanart { get; set; } + public string strArtistFanart2 { get; set; } + public string strArtistFanart3 { get; set; } + public string strArtistBanner { get; set; } + public string strMusicBrainzID { get; set; } + public object strLastFMChart { get; set; } + public string strLocked { get; set; } + } + + public class RootObject + { + public List<Artist> artists { get; set; } + } + + public int Order + { + get + { + // After musicbrainz + return 1; + } + } + } +} diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs index c056fdabf..93be8d2e6 100644 --- a/MediaBrowser.Providers/Music/AudioMetadataService.cs +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -7,12 +7,10 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - public class AudioMetadataService : MetadataService<Audio, ItemId> + public class AudioMetadataService : MetadataService<Audio, SongInfo> { private readonly ILibraryManager _libraryManager; @@ -44,10 +42,5 @@ namespace MediaBrowser.Providers.Music target.Album = source.Album; } } - - protected override Task SaveItem(Audio item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/Music/Extensions.cs b/MediaBrowser.Providers/Music/Extensions.cs new file mode 100644 index 000000000..a959eb08c --- /dev/null +++ b/MediaBrowser.Providers/Music/Extensions.cs @@ -0,0 +1,81 @@ +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using System.Linq; + +namespace MediaBrowser.Providers.Music +{ + public static class Extensions + { + public static string GetAlbumArtist(this AlbumInfo info) + { + var id = info.AlbumArtist; + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.AlbumArtist) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetReleaseGroupId(this AlbumInfo info) + { + var id = info.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetReleaseId(this AlbumInfo info) + { + var id = info.GetProviderId(MetadataProviders.MusicBrainzAlbum); + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzAlbum)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetMusicBrainzArtistId(this AlbumInfo info) + { + string id; + info.ProviderIds.TryGetValue(MetadataProviders.MusicBrainzAlbumArtist.ToString(), out id); + + if (string.IsNullOrEmpty(id)) + { + info.ArtistProviderIds.TryGetValue(MetadataProviders.MusicBrainzArtist.ToString(), out id); + } + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetMusicBrainzArtistId(this ArtistInfo info) + { + string id; + info.ProviderIds.TryGetValue(MetadataProviders.MusicBrainzArtist.ToString(), out id); + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + } +} diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index cd87d213b..8479b0c6e 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.Music var list = new List<RemoteImageInfo>(); - var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz); + var artistMusicBrainzId = album.MusicArtist.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!string.IsNullOrEmpty(artistMusicBrainzId)) { @@ -80,7 +80,7 @@ namespace MediaBrowser.Providers.Music var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); - var musicBrainzId = album.GetProviderId(MetadataProviders.Musicbrainz); + var musicBrainzId = album.GetProviderId(MetadataProviders.MusicBrainzAlbum); try { @@ -340,7 +340,11 @@ namespace MediaBrowser.Providers.Music public int Order { - get { return 0; } + get + { + // After embedded provider + return 1; + } } public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) @@ -353,20 +357,29 @@ namespace MediaBrowser.Providers.Music }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - var album = (MusicAlbum)item; + if (!_config.Configuration.EnableFanArtUpdates) + { + return false; + } - var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz); + var album = (MusicAlbum)item; + var artist = album.MusicArtist; - if (!String.IsNullOrEmpty(artistMusicBrainzId)) + if (artist != null) { - // Process images - var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId); + var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.MusicBrainzArtist); - var fileInfo = new FileInfo(artistXmlPath); + if (!String.IsNullOrEmpty(artistMusicBrainzId)) + { + // Process images + var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId); - return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + var fileInfo = new FileInfo(artistXmlPath); + + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + } } return false; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 446c80920..9b32ff4f2 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Providers.Music var list = new List<RemoteImageInfo>(); - var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz); + var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!String.IsNullOrEmpty(artistMusicBrainzId)) { @@ -374,9 +374,14 @@ namespace MediaBrowser.Providers.Music }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - var id = item.GetProviderId(MetadataProviders.Musicbrainz); + if (!_config.Configuration.EnableFanArtUpdates) + { + return false; + } + + var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!String.IsNullOrEmpty(id)) { @@ -385,7 +390,7 @@ namespace MediaBrowser.Providers.Music var fileInfo = new FileInfo(artistXmlPath); - return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; } return false; diff --git a/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs b/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs index d002c02f2..9d24490b2 100644 --- a/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - class FanartUpdatesPrescanTask : ILibraryPostScanTask + class FanartUpdatesPostScanTask : ILibraryPostScanTask { private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmusic/{0}/{1}/"; @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public FanartUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) + public FanartUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) { _jsonSerializer = jsonSerializer; _config = config; diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs index 659ae5bef..701e74da0 100644 --- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs @@ -61,7 +61,9 @@ namespace MediaBrowser.Providers.Music RemoteImageInfo info = null; - var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz); + var musicBrainzId = item is MusicAlbum ? + item.GetProviderId(MetadataProviders.MusicBrainzAlbum) : + item.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!string.IsNullOrEmpty(musicBrainzId)) { @@ -149,7 +151,11 @@ namespace MediaBrowser.Providers.Music public int Order { - get { return 1; } + get + { + // After all others + return 3; + } } public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs b/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs index f9d7d3018..e1fd05a50 100644 --- a/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmAlbumProvider.cs @@ -5,16 +5,18 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using MoreLinq; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - public class LastfmAlbumProvider : IRemoteMetadataProvider<MusicAlbum>, IHasOrder + public class LastfmAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder { private readonly IJsonSerializer _json; private readonly IHttpClient _httpClient; @@ -30,27 +32,29 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - public async Task<MetadataResult<MusicAlbum>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) { var result = new MetadataResult<MusicAlbum>(); - var lastFmData = await GetAlbumResult((AlbumId)id, cancellationToken).ConfigureAwait(false); + var lastFmData = await GetAlbumResult(id, cancellationToken).ConfigureAwait(false); if (lastFmData != null && lastFmData.album != null) { result.HasMetadata = true; + result.Item = new MusicAlbum(); ProcessAlbumData(result.Item, lastFmData.album); } return result; } - private async Task<LastfmGetAlbumResult> GetAlbumResult(AlbumId item, CancellationToken cancellationToken) + private async Task<LastfmGetAlbumResult> GetAlbumResult(AlbumInfo item, CancellationToken cancellationToken) { // Try album release Id - if (!string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Musicbrainz))) + var id = item.GetReleaseId(); + if (!string.IsNullOrEmpty(id)) { - var result = await GetAlbumResult(item.GetProviderId(MetadataProviders.Musicbrainz), cancellationToken).ConfigureAwait(false); + var result = await GetAlbumResult(id, cancellationToken).ConfigureAwait(false); if (result != null && result.album != null) { @@ -59,9 +63,10 @@ namespace MediaBrowser.Providers.Music } // Try album release group Id - if (!string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup))) + id = item.GetReleaseGroupId(); + if (!string.IsNullOrEmpty(id)) { - var result = await GetAlbumResult(item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup), cancellationToken).ConfigureAwait(false); + var result = await GetAlbumResult(id, cancellationToken).ConfigureAwait(false); if (result != null && result.album != null) { @@ -69,25 +74,26 @@ namespace MediaBrowser.Providers.Music } } - //// Get each song, distinct by the combination of AlbumArtist and Album - //var songs = item.RecursiveChildren.OfType<Audio>().DistinctBy(i => (i.AlbumArtist ?? string.Empty) + (i.Album ?? string.Empty), StringComparer.OrdinalIgnoreCase).ToList(); + var albumArtist = item.GetAlbumArtist(); + // Get each song, distinct by the combination of AlbumArtist and Album + var songs = item.SongInfos.DistinctBy(i => (i.AlbumArtist ?? string.Empty) + (i.Album ?? string.Empty), StringComparer.OrdinalIgnoreCase).ToList(); - //foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist))) - //{ - // var result = await GetAlbumResult(song.AlbumArtist, song.Album, cancellationToken).ConfigureAwait(false); + foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist))) + { + var result = await GetAlbumResult(song.AlbumArtist, song.Album, cancellationToken).ConfigureAwait(false); - // if (result != null && result.album != null) - // { - // return result; - // } - //} + if (result != null && result.album != null) + { + return result; + } + } - if (string.IsNullOrEmpty(item.AlbumArtist)) + if (string.IsNullOrEmpty(albumArtist)) { return null; } - return await GetAlbumResult(item.AlbumArtist, item.Name, cancellationToken); + return await GetAlbumResult(albumArtist, item.Name, cancellationToken); } private async Task<LastfmGetAlbumResult> GetAlbumResult(string artist, string album, CancellationToken cancellationToken) @@ -166,10 +172,13 @@ namespace MediaBrowser.Providers.Music string imageSize; var url = LastfmHelper.GetImageUrl(data, out imageSize); - var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz) ?? + var musicBrainzId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum) ?? item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); - LastfmHelper.SaveImageInfo(_config.ApplicationPaths, _logger, musicBrainzId, url, imageSize); + if (!string.IsNullOrEmpty(musicBrainzId) && !string.IsNullOrEmpty(url)) + { + LastfmHelper.SaveImageInfo(_config.ApplicationPaths, _logger, musicBrainzId, url, imageSize); + } } /// <summary> @@ -189,7 +198,11 @@ namespace MediaBrowser.Providers.Music public int Order { - get { return 1; } + get + { + // After fanart & audiodb + return 2; + } } } diff --git a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs index 2ae329c45..b52185ccc 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs @@ -17,7 +17,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - public class LastfmArtistProvider : IRemoteMetadataProvider<MusicArtist>, IHasOrder + public class LastfmArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IHasOrder { private readonly IJsonSerializer _json; private readonly IHttpClient _httpClient; @@ -38,11 +38,11 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - public async Task<MetadataResult<MusicArtist>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo id, CancellationToken cancellationToken) { var result = new MetadataResult<MusicArtist>(); - var musicBrainzId = id.GetProviderId(MetadataProviders.Musicbrainz); + var musicBrainzId = id.GetMusicBrainzArtistId(); if (!String.IsNullOrWhiteSpace(musicBrainzId)) { @@ -51,8 +51,6 @@ namespace MediaBrowser.Providers.Music result.Item = new MusicArtist(); result.HasMetadata = true; - result.Item.SetProviderId(MetadataProviders.Musicbrainz, musicBrainzId); - await FetchLastfmData(result.Item, musicBrainzId, cancellationToken).ConfigureAwait(false); } @@ -119,7 +117,10 @@ namespace MediaBrowser.Providers.Music string imageSize; var url = LastfmHelper.GetImageUrl(data, out imageSize); - LastfmHelper.SaveImageInfo(_config.ApplicationPaths, _logger, musicBrainzId, url, imageSize); + if (!string.IsNullOrEmpty(musicBrainzId) && !string.IsNullOrEmpty(url)) + { + LastfmHelper.SaveImageInfo(_config.ApplicationPaths, _logger, musicBrainzId, url, imageSize); + } } /// <summary> @@ -163,7 +164,11 @@ namespace MediaBrowser.Providers.Music public int Order { - get { return 1; } + get + { + // After fanart & audiodb + return 2; + } } } } diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs index 0c67e7ea7..7c83cec6b 100644 --- a/MediaBrowser.Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Providers/Music/LastfmHelper.cs @@ -39,6 +39,19 @@ namespace MediaBrowser.Providers.Music public static void SaveImageInfo(IApplicationPaths appPaths, ILogger logger, string musicBrainzId, string url, string size) { + if (appPaths == null) + { + throw new ArgumentNullException("appPaths"); + } + if (string.IsNullOrEmpty(musicBrainzId)) + { + throw new ArgumentNullException("musicBrainzId"); + } + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentNullException("url"); + } + var cachePath = Path.Combine(appPaths.CachePath, "lastfm", musicBrainzId, "image.txt"); try diff --git a/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs b/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs new file mode 100644 index 000000000..99fe5c38d --- /dev/null +++ b/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Providers.Movies; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + public class MovieDbMusicVideoProvider : IRemoteMetadataProvider<MusicVideo, MusicVideoInfo> + { + public Task<MetadataResult<MusicVideo>> GetMetadata(MusicVideoInfo info, CancellationToken cancellationToken) + { + return MovieDbProvider.Current.GetItemMetadata<MusicVideo>(info, cancellationToken); + } + + public string Name + { + get { return MovieDbProvider.Current.Name; } + } + + public bool HasChanged(IHasMetadata item, DateTime date) + { + return MovieDbProvider.Current.HasChanged(item, date); + } + } +} diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index f5cf49f74..1a3860dab 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -13,7 +13,7 @@ using System.Xml; namespace MediaBrowser.Providers.Music { - public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum>, IHasOrder + public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder { internal static MusicBrainzAlbumProvider Current; @@ -27,35 +27,32 @@ namespace MediaBrowser.Providers.Music Current = this; } - public async Task<MetadataResult<MusicAlbum>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public async Task<MetadataResult<MusicAlbum>> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) { - var albumId = (AlbumId)id; - var releaseId = albumId.GetProviderId(MetadataProviders.Musicbrainz); - var releaseGroupId = albumId.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + var releaseId = id.GetReleaseId(); + var releaseGroupId = id.GetReleaseGroupId(); - var result = new MetadataResult<MusicAlbum>(); + var result = new MetadataResult<MusicAlbum> + { + Item = new MusicAlbum() + }; if (string.IsNullOrEmpty(releaseId)) { - string artistMusicBrainzId; - albumId.ArtistProviderIds.TryGetValue(MetadataProviders.Musicbrainz.ToString(), out artistMusicBrainzId); + var artistMusicBrainzId = id.GetMusicBrainzArtistId(); - var releaseResult = await GetReleaseResult(artistMusicBrainzId, albumId.AlbumArtist, albumId.Name, cancellationToken).ConfigureAwait(false); - - result.Item = new MusicAlbum(); + var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(releaseResult.ReleaseId)) { releaseId = releaseResult.ReleaseId; result.HasMetadata = true; - result.Item.SetProviderId(MetadataProviders.Musicbrainz, releaseId); } if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId)) { releaseGroupId = releaseResult.ReleaseGroupId; result.HasMetadata = true; - result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); } } @@ -64,7 +61,19 @@ namespace MediaBrowser.Providers.Music { releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; - result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); + } + + if (result.HasMetadata) + { + if (!string.IsNullOrEmpty(releaseId)) + { + result.Item.SetProviderId(MetadataProviders.MusicBrainzAlbum, releaseId); + } + + if (!string.IsNullOrEmpty(releaseGroupId)) + { + result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId); + } } return result; diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 4cc408a00..3c45a7a48 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -12,13 +12,13 @@ using System.Xml; namespace MediaBrowser.Providers.Music { - public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist> + public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo> { - public async Task<MetadataResult<MusicArtist>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo id, CancellationToken cancellationToken) { var result = new MetadataResult<MusicArtist>(); - var musicBrainzId = id.GetProviderId(MetadataProviders.Musicbrainz) ?? await FindId(id, cancellationToken).ConfigureAwait(false); + var musicBrainzId = id.GetMusicBrainzArtistId() ?? await FindId(id, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(musicBrainzId)) { @@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.Music result.Item = new MusicArtist(); result.HasMetadata = true; - result.Item.SetProviderId(MetadataProviders.Musicbrainz, musicBrainzId); + result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId); } return result; @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Music /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.String}.</returns> - private async Task<string> FindId(ItemId item, CancellationToken cancellationToken) + private async Task<string> FindId(ItemLookupInfo item, CancellationToken cancellationToken) { // They seem to throw bad request failures on any term with a slash var nameToSearch = item.Name.Replace('/', ' '); diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs index 6dd16819b..e30f93af9 100644 --- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs +++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -12,7 +13,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { - class MusicVideoMetadataService : MetadataService<MusicVideo, ItemId> + class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo> { private readonly ILibraryManager _libraryManager; @@ -44,10 +45,5 @@ namespace MediaBrowser.Providers.Music target.Artist = source.Artist; } } - - protected override Task SaveItem(MusicVideo item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/Music/MusicVideoXmlParser.cs b/MediaBrowser.Providers/Music/MusicVideoXmlParser.cs index 5af203fa7..0c160ff66 100644 --- a/MediaBrowser.Providers/Music/MusicVideoXmlParser.cs +++ b/MediaBrowser.Providers/Music/MusicVideoXmlParser.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System.Xml; diff --git a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs index 5bdc2cdd0..4291f2369 100644 --- a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs +++ b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Movies; @@ -18,12 +19,12 @@ namespace MediaBrowser.Providers.Music _logger = logger; } - protected override void Fetch(MusicVideo item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<MusicVideo> result, string path, CancellationToken cancellationToken) { - new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken); + new MusicVideoXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index f64f30a27..db5f1b8ce 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.MusicGenres { - public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemId> + public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.MusicGenres { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(MusicGenre item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs index 4ce2ad5e1..8f870d25a 100644 --- a/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs @@ -10,13 +10,13 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Omdb { - public class OmdbSeriesProvider : ICustomMetadataProvider<Series>, + public class OmdbItemProvider : ICustomMetadataProvider<Series>, ICustomMetadataProvider<Movie>, ICustomMetadataProvider<Trailer> { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; - public OmdbSeriesProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) + public OmdbItemProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; @@ -24,21 +24,21 @@ namespace MediaBrowser.Providers.Omdb public string Name { - get { return "OMDb"; } + get { return "IMDb via The Open Movie Database"; } } - public Task<ItemUpdateType> FetchAsync(Series item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Series item, IDirectoryService directoryService, CancellationToken cancellationToken) { return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken); } - public Task<ItemUpdateType> FetchAsync(Movie item, CancellationToken cancellationToken) + public Task<ItemUpdateType> FetchAsync(Movie item, IDirectoryService directoryService, CancellationToken cancellationToken) { return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken); } - private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.Unspecified); - public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken) + private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None); + public Task<ItemUpdateType> FetchAsync(Trailer item, IDirectoryService directoryService, CancellationToken cancellationToken) { if (item.IsLocalTrailer) { diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index bc1be2dd6..8eabc5164 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.Omdb if (string.IsNullOrEmpty(imdbId)) { - return ItemUpdateType.Unspecified; + return ItemUpdateType.None; } var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId; diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs index 15a54474b..f439f4bc5 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs @@ -18,7 +18,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.People { - public class MovieDbPersonProvider : IRemoteMetadataProvider<Person> + public class MovieDbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo> { const string DataFileName = "info.json"; @@ -40,8 +40,8 @@ namespace MediaBrowser.Providers.People { get { return "TheMovieDb"; } } - - public async Task<MetadataResult<Person>> GetMetadata(ItemId id, CancellationToken cancellationToken) + + public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo id, CancellationToken cancellationToken) { var tmdbId = id.GetProviderId(MetadataProviders.Tmdb); @@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.People return; } - var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images", MovieDbProvider.ApiKey, id); + var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", MovieDbProvider.ApiKey, id); using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions { @@ -267,6 +267,14 @@ namespace MediaBrowser.Providers.People public List<Profile> profiles { get; set; } } + public class ExternalIds + { + public string imdb_id { get; set; } + public string freebase_mid { get; set; } + public string freebase_id { get; set; } + public int tvrage_id { get; set; } + } + public class PersonResult { public bool adult { get; set; } @@ -283,6 +291,7 @@ namespace MediaBrowser.Providers.People public string profile_path { get; set; } public Credits credits { get; set; } public Images images { get; set; } + public ExternalIds external_ids { get; set; } } #endregion diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs index b3d619918..faf606f3e 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/MediaBrowser.Providers/People/PersonMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.People { - public class PersonMetadataService : MetadataService<Person, ItemId> + public class PersonMetadataService : MetadataService<Person, PersonLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -39,10 +39,5 @@ namespace MediaBrowser.Providers.People target.PlaceOfBirth = source.PlaceOfBirth; } } - - protected override Task SaveItem(Person item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/People/PersonXmlProvider.cs b/MediaBrowser.Providers/People/PersonXmlProvider.cs index 0996615d3..4a145a1c0 100644 --- a/MediaBrowser.Providers/People/PersonXmlProvider.cs +++ b/MediaBrowser.Providers/People/PersonXmlProvider.cs @@ -17,14 +17,14 @@ namespace MediaBrowser.Providers.People _logger = logger; } - protected override void Fetch(Person item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Person> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<Person>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<Person>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "person.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "person.xml")); } } } diff --git a/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs b/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs index bc51a5286..58e5ad123 100644 --- a/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs @@ -1,7 +1,7 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; using System.Text; @@ -11,13 +11,6 @@ namespace MediaBrowser.Providers.Savers { class AlbumXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public AlbumXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -34,16 +27,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is MusicAlbum; + return false; } - return false; + return item is MusicAlbum && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs index 1ae1eaa64..3e98e6225 100644 --- a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs @@ -1,10 +1,7 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Providers.Music; -using System; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; using System.Text; @@ -14,13 +11,6 @@ namespace MediaBrowser.Providers.Savers { class ArtistXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public ArtistXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -37,29 +27,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) - { - if (item is MusicArtist) - { - return true; - } - } - - // If new metadata has been downloaded or metadata was manually edited, proceed - if (wasMetadataDownloaded || wasMetadataEdited) + if (!item.SupportsLocalMetadata) { - var artist = item as MusicArtist; - if (artist != null && artist.IsAccessedByName) - { - return true; - } + return false; } - return false; + return item is MusicArtist && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs b/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs index 06a17528b..dcf789b30 100644 --- a/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs @@ -1,24 +1,16 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; -using MediaBrowser.Controller.Providers; namespace MediaBrowser.Providers.Savers { public class BoxSetXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public BoxSetXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -35,16 +27,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is BoxSet; + return false; } - return false; + return item is BoxSet && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs index 0c857c5ec..2d4221bda 100644 --- a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs @@ -1,6 +1,7 @@ -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; using System.Text; @@ -21,16 +22,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded or metadata was manually edited, proceed - if ((wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is LiveTvChannel; + return false; } - return false; + return item is LiveTvChannel && updateType >= ItemUpdateType.MetadataDownload; } public string Name diff --git a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs index e60acaa38..661c75f41 100644 --- a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs @@ -1,8 +1,8 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -14,9 +14,15 @@ namespace MediaBrowser.Providers.Savers { public class EpisodeXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; private readonly IItemRepository _itemRepository; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public EpisodeXmlSaver(IItemRepository itemRepository) + { + _itemRepository = itemRepository; + } + /// <summary> /// Determines whether [is enabled for] [the specified item]. /// </summary> @@ -25,16 +31,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is Episode; + return false; } - return false; + return item is Episode && updateType >= ItemUpdateType.MetadataDownload; } public string Name @@ -45,14 +47,6 @@ namespace MediaBrowser.Providers.Savers } } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public EpisodeXmlSaver(IServerConfigurationManager config, IItemRepository itemRepository) - { - _config = config; - _itemRepository = itemRepository; - } - /// <summary> /// Saves the specified item. /// </summary> diff --git a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs index e0ae638e9..db08eafe3 100644 --- a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs @@ -1,10 +1,9 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; using System.Text; @@ -14,13 +13,6 @@ namespace MediaBrowser.Providers.Savers { public class FolderXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public FolderXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -37,33 +29,18 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var folder = item as Folder; - - if (folder == null) + if (!item.SupportsLocalMetadata) { return false; } - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (item is Folder) { if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) && !(item is Season) && !(item is GameSystem)) { - return true; - } - } - - // If new metadata has been downloaded or metadata was manually edited, proceed - if (wasMetadataDownloaded || wasMetadataEdited) - { - if (item is AggregateFolder || item is UserRootFolder || item is CollectionFolder) - { - return true; + return updateType >= ItemUpdateType.MetadataDownload; } } diff --git a/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs b/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs index 017f17f8d..98a2d03be 100644 --- a/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs @@ -1,7 +1,5 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; using System.Collections.Generic; using System.IO; using System.Security; @@ -12,13 +10,6 @@ namespace MediaBrowser.Providers.Savers { public class GameSystemXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public GameSystemXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -35,16 +26,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is GameSystem; + return false; } - return false; + return item is GameSystem && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/GameXmlSaver.cs b/MediaBrowser.Providers/Savers/GameXmlSaver.cs index 613819517..959041a8c 100644 --- a/MediaBrowser.Providers/Savers/GameXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/GameXmlSaver.cs @@ -1,7 +1,5 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Globalization; @@ -17,13 +15,6 @@ namespace MediaBrowser.Providers.Savers /// </summary> public class GameXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public GameXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -40,16 +31,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is Game; + return false; } - return false; + return item is Game && updateType >= ItemUpdateType.MetadataDownload; } private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index 15fdc6752..595793854 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -1,16 +1,15 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Security; using System.Text; using System.Threading; -using MediaBrowser.Controller.Providers; namespace MediaBrowser.Providers.Savers { @@ -19,12 +18,10 @@ namespace MediaBrowser.Providers.Savers /// </summary> public class MovieXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; private readonly IItemRepository _itemRepository; - public MovieXmlSaver(IServerConfigurationManager config, IItemRepository itemRepository) + public MovieXmlSaver(IItemRepository itemRepository) { - _config = config; _itemRepository = itemRepository; } @@ -44,15 +41,17 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; + if (!item.SupportsLocalMetadata) + { + return false; + } + + var video = item as Video; - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + // Check parent for null to avoid running this against things like video backdrops + if (video != null && !(item is Episode) && !video.IsOwnedItem) { - var video = item as Video; - // Check parent for null to avoid running this against things like video backdrops - return video != null && !(item is Episode) && !video.IsOwnedItem; + return updateType >= ItemUpdateType.MetadataDownload; } return false; diff --git a/MediaBrowser.Providers/Savers/PersonXmlSaver.cs b/MediaBrowser.Providers/Savers/PersonXmlSaver.cs index 167e514a8..9bbe5b5dc 100644 --- a/MediaBrowser.Providers/Savers/PersonXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/PersonXmlSaver.cs @@ -6,6 +6,7 @@ using System.IO; using System.Security; using System.Text; using System.Threading; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Providers.Savers { @@ -30,16 +31,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded or metadata was manually edited, proceed - if ((wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is Person; + return false; } - return false; + return item is Person && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs b/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs index 5773fc1de..2a036722a 100644 --- a/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs @@ -1,7 +1,7 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; using System.Text; @@ -11,13 +11,6 @@ namespace MediaBrowser.Providers.Savers { public class SeasonXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public SeasonXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -34,16 +27,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is Season; + return false; } - return false; + return item is Season && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs index b76167868..5f58001ec 100644 --- a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs @@ -1,7 +1,6 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; @@ -13,13 +12,6 @@ namespace MediaBrowser.Providers.Savers { public class SeriesXmlSaver : IMetadataFileSaver { - private readonly IServerConfigurationManager _config; - - public SeriesXmlSaver(IServerConfigurationManager config) - { - _config = config; - } - public string Name { get @@ -36,16 +28,12 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) { - var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; - var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; - - // If new metadata has been downloaded and save local is on - if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded)) + if (!item.SupportsLocalMetadata) { - return item is Series; + return false; } - return false; + return item is Series && updateType >= ItemUpdateType.MetadataDownload; } /// <summary> diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 230dab717..6a3d7d837 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -47,7 +47,9 @@ namespace MediaBrowser.Providers.Savers "Format3D", "Metascore", "MPAARating", - "MusicbrainzId", + "MusicBrainzArtistId", + "MusicBrainzAlbumArtistId", + "MusicBrainzAlbumId", "MusicBrainzReleaseGroupId", "Overview", "Persons", @@ -305,7 +307,7 @@ namespace MediaBrowser.Providers.Savers { builder.Append("<AwardSummary>" + SecurityElement.Escape(hasAwards.AwardSummary) + "</AwardSummary>"); } - + var hasBudget = item as IHasBudget; if (hasBudget != null) { @@ -394,53 +396,88 @@ namespace MediaBrowser.Providers.Savers } } - var tvcom = item.GetProviderId(MetadataProviders.Tvcom); + var externalId = item.GetProviderId(MetadataProviders.Tvcom); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<TVcomId>" + SecurityElement.Escape(externalId) + "</TVcomId>"); + } + + externalId = item.GetProviderId(MetadataProviders.RottenTomatoes); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<RottenTomatoesId>" + SecurityElement.Escape(externalId) + "</RottenTomatoesId>"); + } + + externalId = item.GetProviderId(MetadataProviders.Zap2It); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<Zap2ItId>" + SecurityElement.Escape(externalId) + "</Zap2ItId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<MusicBrainzAlbumId>" + SecurityElement.Escape(externalId) + "</MusicBrainzAlbumId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<MusicBrainzAlbumArtistId>" + SecurityElement.Escape(externalId) + "</MusicBrainzAlbumArtistId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist); - if (!string.IsNullOrEmpty(tvcom)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<TVcomId>" + SecurityElement.Escape(tvcom) + "</TVcomId>"); + builder.Append("<MusicBrainzArtistId>" + SecurityElement.Escape(externalId) + "</MusicBrainzArtistId>"); } - var rt = item.GetProviderId(MetadataProviders.RottenTomatoes); + externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); - if (!string.IsNullOrEmpty(rt)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<RottenTomatoesId>" + SecurityElement.Escape(rt) + "</RottenTomatoesId>"); + builder.Append("<MusicBrainzReleaseGroupId>" + SecurityElement.Escape(externalId) + "</MusicBrainzReleaseGroupId>"); } - var zap2It = item.GetProviderId(MetadataProviders.Zap2It); + externalId = item.GetProviderId(MetadataProviders.Gamesdb); - if (!string.IsNullOrEmpty(zap2It)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<Zap2ItId>" + SecurityElement.Escape(zap2It) + "</Zap2ItId>"); + builder.Append("<GamesDbId>" + SecurityElement.Escape(externalId) + "</GamesDbId>"); } - var mbz = item.GetProviderId(MetadataProviders.Musicbrainz); + externalId = item.GetProviderId(MetadataProviders.TmdbCollection); - if (!string.IsNullOrEmpty(mbz)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<MusicbrainzId>" + SecurityElement.Escape(mbz) + "</MusicbrainzId>"); + builder.Append("<TMDbCollectionId>" + SecurityElement.Escape(externalId) + "</TMDbCollectionId>"); } - mbz = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + externalId = item.GetProviderId(MetadataProviders.AudioDbArtist); - if (!string.IsNullOrEmpty(mbz)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<MusicBrainzReleaseGroupId>" + SecurityElement.Escape(mbz) + "</MusicBrainzReleaseGroupId>"); + builder.Append("<AudioDbArtistId>" + SecurityElement.Escape(externalId) + "</AudioDbArtistId>"); } - var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb); + externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum); - if (!string.IsNullOrEmpty(gamesdb)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<GamesDbId>" + SecurityElement.Escape(gamesdb) + "</GamesDbId>"); + builder.Append("<AudioDbAlbumId>" + SecurityElement.Escape(externalId) + "</AudioDbAlbumId>"); } - var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection); + externalId = item.GetProviderId(MetadataProviders.TvRage); - if (!string.IsNullOrEmpty(tmdbCollection)) + if (!string.IsNullOrEmpty(externalId)) { - builder.Append("<TMDbCollectionId>" + SecurityElement.Escape(tmdbCollection) + "</TMDbCollectionId>"); + builder.Append("<TvRageId>" + SecurityElement.Escape(externalId) + "</TvRageId>"); } var hasTagline = item as IHasTaglines; diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index 8d5184fc2..014ff3340 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Studios { - public class StudioMetadataService : MetadataService<Studio, ItemId> + public class StudioMetadataService : MetadataService<Studio, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -33,10 +33,5 @@ namespace MediaBrowser.Providers.Studios { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(Studio item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs b/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs index 96e8f3158..1ec0e0f48 100644 --- a/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeLocalImageProvider.cs @@ -9,7 +9,7 @@ using System.Linq; namespace MediaBrowser.Providers.TV { - public class EpisodeLocalImageProvider : IImageFileProvider + public class EpisodeLocalLocalImageProvider : ILocalImageFileProvider { public string Name { @@ -18,42 +18,64 @@ namespace MediaBrowser.Providers.TV public bool Supports(IHasImages item) { - return item is Episode && item.LocationType == LocationType.FileSystem; + return item is Episode && item.SupportsLocalMetadata; } - public List<LocalImageInfo> GetImages(IHasImages item) + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) { var parentPath = Path.GetDirectoryName(item.Path); + var parentPathFiles = directoryService.GetFileSystemEntries(parentPath); + var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path); - var thumbName = nameWithoutExtension + "-thumb"; - - return Directory.EnumerateFiles(parentPath, "*", SearchOption.AllDirectories) - .Where(i => - { - if (BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i) ?? string.Empty)) - { - var currentNameWithoutExtension = Path.GetFileNameWithoutExtension(i); - - if (string.Equals(nameWithoutExtension, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (string.Equals(thumbName, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - }) - .Select(i => new LocalImageInfo - { - Path = i, - Type = ImageType.Primary - }) - .ToList(); + + var files = GetFilesFromParentFolder(nameWithoutExtension, parentPathFiles); + + if (files.Count > 0) + { + return files; + } + + var metadataPath = Path.Combine(parentPath, "metadata"); + + if (parentPathFiles.Any(i => string.Equals(i.FullName, metadataPath, StringComparison.OrdinalIgnoreCase))) + { + return GetFilesFromParentFolder(nameWithoutExtension, directoryService.GetFiles(metadataPath)); + } + + return new List<LocalImageInfo>(); + } + + private List<LocalImageInfo> GetFilesFromParentFolder(string filenameWithoutExtension, IEnumerable<FileSystemInfo> parentPathFiles) + { + var thumbName = filenameWithoutExtension + "-thumb"; + + return parentPathFiles + .Where(i => + { + if (BaseItem.SupportedImageExtensions.Contains(i.Extension)) + { + var currentNameWithoutExtension = Path.GetFileNameWithoutExtension(i.Name); + + if (string.Equals(filenameWithoutExtension, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.Equals(thumbName, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + }) + .Select(i => new LocalImageInfo + { + FileInfo = (FileInfo)i, + Type = ImageType.Primary + }) + .ToList(); } } } diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index ddb89bd42..4b6ac04bc 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -1,26 +1,19 @@ -using System.IO; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class EpisodeMetadataService : MetadataService<Episode, EpisodeId> + public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo> { - private readonly ILibraryManager _libraryManager; - - public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) { - _libraryManager = libraryManager; } /// <summary> @@ -70,71 +63,5 @@ namespace MediaBrowser.Providers.TV target.IndexNumberEnd = source.IndexNumberEnd; } } - - protected override Task SaveItem(Episode item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - - protected override EpisodeId GetId(Episode item) - { - var id = base.GetId(item); - - var series = item.Series; - - if (series != null) - { - id.SeriesProviderIds = series.ProviderIds; - } - - id.IndexNumberEnd = item.IndexNumberEnd; - - return id; - } - - protected override ItemUpdateType BeforeMetadataRefresh(Episode item) - { - var updateType = base.BeforeMetadataRefresh(item); - - var locationType = item.LocationType; - if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) - { - var currentIndexNumber = item.IndexNumber; - var currentIndexNumberEnd = item.IndexNumberEnd; - var currentParentIndexNumber = item.ParentIndexNumber; - - var filename = Path.GetFileName(item.Path); - - item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(filename, item.Parent is Season); - item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(filename); - - if (!item.ParentIndexNumber.HasValue) - { - var season = item.Season; - - if (season != null) - { - item.ParentIndexNumber = season.IndexNumber; - } - } - - if ((currentIndexNumber ?? -1) != (item.IndexNumber ?? -1)) - { - updateType = updateType | ItemUpdateType.MetadataImport; - } - - if ((currentIndexNumberEnd ?? -1) != (item.IndexNumberEnd ?? -1)) - { - updateType = updateType | ItemUpdateType.MetadataImport; - } - - if ((currentParentIndexNumber ?? -1) != (item.ParentIndexNumber ?? -1)) - { - updateType = updateType | ItemUpdateType.MetadataImport; - } - } - - return updateType; - } } } diff --git a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs index b35c18e09..ee78c3777 100644 --- a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs +++ b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading; @@ -15,14 +16,18 @@ namespace MediaBrowser.Providers.TV /// </summary> public class EpisodeXmlParser : BaseItemXmlParser<Episode> { + private List<LocalImageInfo> _imagesFound; + public EpisodeXmlParser(ILogger logger) : base(logger) { } - public void FetchAsync(Episode item, string metadataFile, CancellationToken cancellationToken) + public void Fetch(Episode item, List<LocalImageInfo> images, string metadataFile, CancellationToken cancellationToken) { - Fetch(item, metadataFile, cancellationToken); + _imagesFound = images; + + Fetch(item, metadataFile, cancellationToken); } private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -70,12 +75,17 @@ namespace MediaBrowser.Providers.TV // even though it's actually using the metadata folder. filename = Path.GetFileName(filename); - var seasonFolder = Path.GetDirectoryName(item.Path); - filename = Path.Combine(seasonFolder, "metadata", filename); + var parentFolder = Path.GetDirectoryName(item.Path); + filename = Path.Combine(parentFolder, "metadata", filename); + var file = new FileInfo(filename); - if (File.Exists(filename)) + if (file.Exists) { - item.SetImagePath(ImageType.Primary, 0, filename); + _imagesFound.Add(new LocalImageInfo + { + Type = ImageType.Primary, + FileInfo = file + }); } } break; diff --git a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs index b1f8ef976..3def297e7 100644 --- a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs @@ -17,18 +17,19 @@ namespace MediaBrowser.Providers.TV _logger = logger; } - protected override void Fetch(Episode item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Episode> result, string path, CancellationToken cancellationToken) { - new EpisodeXmlParser(_logger).Fetch(item, path, cancellationToken); + new EpisodeXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { var metadataPath = Path.GetDirectoryName(info.Path); metadataPath = Path.Combine(metadataPath, "metadata"); + var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(info.Path), ".xml")); - return new FileInfo(metadataFile); + return directoryService.GetFile(metadataFile); } } } diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index b268c08a5..6f8da573d 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -275,8 +275,13 @@ namespace MediaBrowser.Providers.TV }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + if (!_config.Configuration.EnableFanArtUpdates) + { + return false; + } + var season = (Season)item; var series = season.Series; @@ -294,7 +299,7 @@ namespace MediaBrowser.Providers.TV var fileInfo = new FileInfo(imagesXmlPath); - return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; } return false; diff --git a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs b/MediaBrowser.Providers/TV/FanArtTvUpdatesPostScanTask.cs index db546f3a3..50099dcb8 100644 --- a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/TV/FanArtTvUpdatesPostScanTask.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - class FanArtTvUpdatesPrescanTask : ILibraryPostScanTask + class FanArtTvUpdatesPostScanTask : ILibraryPostScanTask { private const string UpdatesUrl = "http://api.fanart.tv/webservice/newtv/{0}/{1}/"; @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.TV private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public FanArtTvUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) + public FanArtTvUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) { _jsonSerializer = jsonSerializer; _config = config; @@ -127,7 +127,7 @@ namespace MediaBrowser.Providers.TV var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPrescanTask.FanArtUpdate>>(json); + var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPostScanTask.FanArtUpdate>>(json); return updates.Select(i => i.id).Where(existingDictionary.ContainsKey); } diff --git a/MediaBrowser.Providers/TV/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs index 66742cb99..f18189a2a 100644 --- a/MediaBrowser.Providers/TV/FanartSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs @@ -423,8 +423,13 @@ namespace MediaBrowser.Providers.TV } } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + if (!_config.Configuration.EnableFanArtUpdates) + { + return false; + } + var tvdbId = item.GetProviderId(MetadataProviders.Tvdb); if (!String.IsNullOrEmpty(tvdbId)) @@ -434,7 +439,7 @@ namespace MediaBrowser.Providers.TV var fileInfo = new FileInfo(imagesXmlPath); - return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; } return false; diff --git a/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs new file mode 100644 index 000000000..9795b7b11 --- /dev/null +++ b/MediaBrowser.Providers/TV/MovieDbSeriesImageProvider.cs @@ -0,0 +1,211 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Providers.Movies; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.TV +{ + public class MovieDbSeriesImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor + { + private readonly IJsonSerializer _jsonSerializer; + private readonly IHttpClient _httpClient; + + public MovieDbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient) + { + _jsonSerializer = jsonSerializer; + _httpClient = httpClient; + } + + public string Name + { + get { return ProviderName; } + } + + public static string ProviderName + { + get { return "TheMovieDb"; } + } + + public bool Supports(IHasImages item) + { + return item is Series; + } + + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) + { + return new List<ImageType> + { + ImageType.Primary, + ImageType.Backdrop + }; + } + + public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken) + { + var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false); + + return images.Where(i => i.Type == imageType); + } + + public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) + { + var list = new List<RemoteImageInfo>(); + + var results = await FetchImages((BaseItem)item, _jsonSerializer, cancellationToken).ConfigureAwait(false); + + if (results == null) + { + return list; + } + + var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); + + var tmdbImageUrl = tmdbSettings.images.base_url + "original"; + + list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo + { + Url = tmdbImageUrl + i.file_path, + CommunityRating = i.vote_average, + VoteCount = i.vote_count, + Width = i.width, + Height = i.height, + Language = i.iso_639_1, + ProviderName = Name, + Type = ImageType.Primary, + RatingType = RatingType.Score + })); + + list.AddRange(GetBackdrops(results).Select(i => new RemoteImageInfo + { + Url = tmdbImageUrl + i.file_path, + CommunityRating = i.vote_average, + VoteCount = i.vote_count, + Width = i.width, + Height = i.height, + ProviderName = Name, + Type = ImageType.Backdrop, + RatingType = RatingType.Score + })); + + var language = item.GetPreferredMetadataLanguage(); + + var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); + + return list.OrderByDescending(i => + { + if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (!isLanguageEn) + { + if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + } + if (string.IsNullOrEmpty(i.Language)) + { + return isLanguageEn ? 3 : 2; + } + return 0; + }) + .ThenByDescending(i => i.CommunityRating ?? 0) + .ThenByDescending(i => i.VoteCount ?? 0) + .ToList(); + } + + /// <summary> + /// Gets the posters. + /// </summary> + /// <param name="images">The images.</param> + private IEnumerable<MovieDbSeriesProvider.Poster> GetPosters(MovieDbSeriesProvider.Images images) + { + return images.posters ?? new List<MovieDbSeriesProvider.Poster>(); + } + + /// <summary> + /// Gets the backdrops. + /// </summary> + /// <param name="images">The images.</param> + private IEnumerable<MovieDbSeriesProvider.Backdrop> GetBackdrops(MovieDbSeriesProvider.Images images) + { + var eligibleBackdrops = images.backdrops == null ? new List<MovieDbSeriesProvider.Backdrop>() : + images.backdrops + .ToList(); + + return eligibleBackdrops.OrderByDescending(i => i.vote_average) + .ThenByDescending(i => i.vote_count); + } + + /// <summary> + /// Fetches the images. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{MovieImages}.</returns> + private async Task<MovieDbSeriesProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer, + CancellationToken cancellationToken) + { + var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); + var language = item.GetPreferredMetadataLanguage(); + + if (string.IsNullOrEmpty(tmdbId)) + { + return null; + } + + await MovieDbSeriesProvider.Current.EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); + + var path = MovieDbSeriesProvider.Current.GetDataFilePath(tmdbId, language); + + if (!string.IsNullOrEmpty(path)) + { + var fileInfo = new FileInfo(path); + + if (fileInfo.Exists) + { + return jsonSerializer.DeserializeFromFile<MovieDbSeriesProvider.RootObject>(path).images; + } + } + + return null; + } + + public int Order + { + get + { + // After tvdb and fanart + return 2; + } + } + + public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url, + ResourcePool = MovieDbProvider.Current.MovieDbResourcePool + }); + } + + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + { + return MovieDbSeriesProvider.Current.HasChanged(item, date); + } + } +} diff --git a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs new file mode 100644 index 000000000..c169fba79 --- /dev/null +++ b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs @@ -0,0 +1,442 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Providers.Movies; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; + +namespace MediaBrowser.Providers.TV +{ + public class MovieDbSeriesProvider : IRemoteMetadataProvider<Series,SeriesInfo>, IHasOrder + { + private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}?api_key={1}&append_to_response=casts,images,keywords,external_ids"; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + internal static MovieDbSeriesProvider Current { get; private set; } + + private readonly IJsonSerializer _jsonSerializer; + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _configurationManager; + private readonly ILogger _logger; + + public MovieDbSeriesProvider(IJsonSerializer jsonSerializer, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger) + { + _jsonSerializer = jsonSerializer; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _logger = logger; + Current = this; + } + + public string Name + { + get { return "TheMovieDb"; } + } + + public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult<Series>(); + + var tmdbId = info.GetProviderId(MetadataProviders.Tmdb); + var imdbId = info.GetProviderId(MetadataProviders.Imdb); + var tvdbId = info.GetProviderId(MetadataProviders.Tvdb); + + // Commenting our searching by imdb/tvdb because as of now it's not supported. + // But this is how movies work so most likely this can eventually be enabled. + + if (string.IsNullOrEmpty(tmdbId) /*&& string.IsNullOrEmpty(imdbId) && string.IsNullOrEmpty(tvdbId)*/) + { + tmdbId = await new MovieDbSearch(_logger, _jsonSerializer).FindSeriesId(info, cancellationToken).ConfigureAwait(false); + } + + if (!string.IsNullOrEmpty(tmdbId) /*|| !string.IsNullOrEmpty(imdbId) || !string.IsNullOrEmpty(tvdbId)*/) + { + cancellationToken.ThrowIfCancellationRequested(); + + result.Item = await FetchMovieData(tmdbId, imdbId, tvdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + + result.HasMetadata = result.Item != null; + } + + return result; + } + + private async Task<Series> FetchMovieData(string tmdbId, string imdbId, string tvdbId, string language, string preferredCountryCode, CancellationToken cancellationToken) + { + string dataFilePath = null; + RootObject seriesInfo = null; + + // Id could be ImdbId or TmdbId + if (string.IsNullOrEmpty(tmdbId)) + { + if (string.IsNullOrWhiteSpace(imdbId)) + { + seriesInfo = await FetchMainResult(imdbId, language, cancellationToken).ConfigureAwait(false); + } + if (seriesInfo == null) + { + if (string.IsNullOrWhiteSpace(imdbId)) + { + seriesInfo = await FetchMainResult(tvdbId, language, cancellationToken).ConfigureAwait(false); + } + } + + if (seriesInfo == null) + { + return null; + } + + tmdbId = seriesInfo.id.ToString(_usCulture); + + dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _jsonSerializer.SerializeToFile(seriesInfo, dataFilePath); + } + + await EnsureSeriesInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); + + dataFilePath = dataFilePath ?? GetDataFilePath(tmdbId, language); + seriesInfo = seriesInfo ?? _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath); + + var item = new Series(); + + ProcessMainInfo(item, preferredCountryCode, seriesInfo); + + return item; + } + + private void ProcessMainInfo(Series series, string countryCode, RootObject seriesInfo) + { + series.Name = seriesInfo.name; + series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.id.ToString(_usCulture)); + + series.VoteCount = seriesInfo.vote_count; + + string voteAvg = seriesInfo.vote_average.ToString(CultureInfo.InvariantCulture); + float rating; + + if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating)) + { + series.CommunityRating = rating; + } + + series.Overview = seriesInfo.overview; + + if (seriesInfo.networks != null) + { + series.Studios = seriesInfo.networks.Select(i => i.name).ToList(); + } + + if (seriesInfo.genres != null) + { + series.Genres = seriesInfo.genres.Select(i => i.name).ToList(); + } + + series.HomePageUrl = seriesInfo.homepage; + + series.RunTimeTicks = seriesInfo.episode_run_time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); + + if (string.Equals(seriesInfo.status, "Ended", StringComparison.OrdinalIgnoreCase)) + { + series.Status = SeriesStatus.Ended; + } + else + { + series.Status = SeriesStatus.Continuing; + } + + series.PremiereDate = seriesInfo.first_air_date; + series.EndDate = seriesInfo.last_air_date; + + var ids = seriesInfo.external_ids; + if (ids != null) + { + if (!string.IsNullOrWhiteSpace(ids.imdb_id)) + { + series.SetProviderId(MetadataProviders.Imdb, ids.imdb_id); + } + if (ids.tvrage_id > 0) + { + series.SetProviderId(MetadataProviders.TvRage, ids.tvrage_id.ToString(_usCulture)); + } + if (ids.tvdb_id > 0) + { + series.SetProviderId(MetadataProviders.Tvdb, ids.tvdb_id.ToString(_usCulture)); + } + } + } + + internal static string GetSeriesDataPath(IApplicationPaths appPaths, string tmdbId) + { + var dataPath = GetSeriesDataPath(appPaths); + + return Path.Combine(dataPath, tmdbId); + } + + internal static string GetSeriesDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.DataPath, "tmdb-tv"); + + return dataPath; + } + + internal async Task DownloadSeriesInfo(string id, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + var mainResult = await FetchMainResult(id, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + + if (mainResult == null) return; + + var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage); + + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + + _jsonSerializer.SerializeToFile(mainResult, dataFilePath); + } + + internal async Task<RootObject> FetchMainResult(string id, string language, CancellationToken cancellationToken) + { + var url = string.Format(GetTvInfo3, id, MovieDbProvider.ApiKey); + + // Get images in english and with no language + url += "&include_image_language=en,null"; + + if (!string.IsNullOrEmpty(language)) + { + // If preferred language isn't english, get those images too + if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) + { + url += string.Format(",{0}", language); + } + + url += string.Format("&language={0}", language); + } + + RootObject mainResult; + + cancellationToken.ThrowIfCancellationRequested(); + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = MovieDbProvider.AcceptHeader + + }).ConfigureAwait(false)) + { + mainResult = _jsonSerializer.DeserializeFromStream<RootObject>(json); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (mainResult != null && string.IsNullOrEmpty(mainResult.overview)) + { + if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) + { + _logger.Info("Couldn't find meta for language " + language + ". Trying English..."); + + url = string.Format(GetTvInfo3, id, MovieDbProvider.ApiKey) + "&include_image_language=en,null&language=en"; + + using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = MovieDbProvider.AcceptHeader + + }).ConfigureAwait(false)) + { + mainResult = _jsonSerializer.DeserializeFromStream<RootObject>(json); + } + + if (String.IsNullOrEmpty(mainResult.overview)) + { + _logger.Error("Unable to find information for (id:" + id + ")"); + return null; + } + } + } + return mainResult; + } + + private readonly Task _cachedTask = Task.FromResult(true); + internal Task EnsureSeriesInfo(string tmdbId, string language, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(tmdbId)) + { + throw new ArgumentNullException("tmdbId"); + } + if (string.IsNullOrEmpty(language)) + { + throw new ArgumentNullException("language"); + } + + var path = GetDataFilePath(tmdbId, language); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((_configurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) + { + return _cachedTask; + } + } + + return DownloadSeriesInfo(tmdbId, language, cancellationToken); + } + + internal string GetDataFilePath(string tmdbId, string preferredLanguage) + { + if (string.IsNullOrEmpty(tmdbId)) + { + throw new ArgumentNullException("tmdbId"); + } + if (string.IsNullOrEmpty(preferredLanguage)) + { + throw new ArgumentNullException("preferredLanguage"); + } + + var path = GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId); + + var filename = string.Format("series-{0}.json", + preferredLanguage ?? string.Empty); + + return Path.Combine(path, filename); + } + + public bool HasChanged(IHasMetadata item, DateTime date) + { + if (!_configurationManager.Configuration.EnableTmdbUpdates) + { + return false; + } + + var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); + + if (!String.IsNullOrEmpty(tmdbId)) + { + // Process images + var dataFilePath = GetDataFilePath(tmdbId, item.GetPreferredMetadataLanguage()); + + var fileInfo = new FileInfo(dataFilePath); + + return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > date; + } + + return false; + } + + public class CreatedBy + { + public int id { get; set; } + public string name { get; set; } + public string profile_path { get; set; } + } + + public class Genre + { + public int id { get; set; } + public string name { get; set; } + } + + public class Network + { + public int id { get; set; } + public string name { get; set; } + } + + public class Season + { + public string air_date { get; set; } + public string poster_path { get; set; } + public int season_number { get; set; } + } + + public class Backdrop + { + public double aspect_ratio { get; set; } + public string file_path { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public double vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + public class Poster + { + public double aspect_ratio { get; set; } + public string file_path { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public double vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + public class Images + { + public List<Backdrop> backdrops { get; set; } + public List<Poster> posters { get; set; } + } + + public class ExternalIds + { + public string imdb_id { get; set; } + public string freebase_id { get; set; } + public string freebase_mid { get; set; } + public int tvdb_id { get; set; } + public int tvrage_id { get; set; } + } + + public class RootObject + { + public string backdrop_path { get; set; } + public List<CreatedBy> created_by { get; set; } + public List<int> episode_run_time { get; set; } + public DateTime first_air_date { get; set; } + public List<Genre> genres { get; set; } + public string homepage { get; set; } + public int id { get; set; } + public bool in_production { get; set; } + public List<string> languages { get; set; } + public DateTime last_air_date { get; set; } + public string name { get; set; } + public List<Network> networks { get; set; } + public int number_of_episodes { get; set; } + public int number_of_seasons { get; set; } + public string original_name { get; set; } + public List<string> origin_country { get; set; } + public string overview { get; set; } + public string popularity { get; set; } + public string poster_path { get; set; } + public List<Season> seasons { get; set; } + public string status { get; set; } + public double vote_average { get; set; } + public int vote_count { get; set; } + public Images images { get; set; } + public ExternalIds external_ids { get; set; } + } + + public int Order + { + get + { + // After Tvdb + return 2; + } + } + } +} diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index da276221b..4c0149d2e 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -1,25 +1,19 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class SeasonMetadataService : MetadataService<Season, ItemId> + public class SeasonMetadataService : MetadataService<Season, SeasonInfo> { - private readonly ILibraryManager _libraryManager; - - public SeasonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager) + public SeasonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem) { - _libraryManager = libraryManager; } /// <summary> @@ -34,25 +28,5 @@ namespace MediaBrowser.Providers.TV { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(Season item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - - protected override ItemUpdateType BeforeMetadataRefresh(Season item) - { - var updateType = base.BeforeMetadataRefresh(item); - - var currentIndexNumber = item.IndexNumber; - - item.IndexNumber = item.IndexNumber ?? TVUtils.GetSeasonNumberFromPath(item.Path); - - if ((currentIndexNumber ?? -1) != (item.IndexNumber ?? -1)) - { - updateType = updateType | ItemUpdateType.MetadataImport; - } - return updateType; - } } } diff --git a/MediaBrowser.Providers/TV/SeasonXmlProvider.cs b/MediaBrowser.Providers/TV/SeasonXmlProvider.cs index f9fe45120..e53a2deff 100644 --- a/MediaBrowser.Providers/TV/SeasonXmlProvider.cs +++ b/MediaBrowser.Providers/TV/SeasonXmlProvider.cs @@ -20,14 +20,14 @@ namespace MediaBrowser.Providers.TV _logger = logger; } - protected override void Fetch(Season item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Season> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<Season>(_logger).Fetch(item, path, cancellationToken); + new BaseItemXmlParser<Season>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "season.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "season.xml")); } } } diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index ccefd370c..beb9ab595 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -8,12 +8,10 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class SeriesMetadataService : MetadataService<Series, ItemId> + public class SeriesMetadataService : MetadataService<Series, SeriesInfo> { private readonly ILibraryManager _libraryManager; @@ -36,11 +34,6 @@ namespace MediaBrowser.Providers.TV ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - protected override Task SaveItem(Series item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - protected override ItemUpdateType BeforeSave(Series item) { var updateType = base.BeforeSave(item); @@ -51,7 +44,9 @@ namespace MediaBrowser.Providers.TV var dateLastEpisodeAdded = item.DateLastEpisodeAdded; - item.DateLastEpisodeAdded = episodes.Select(i => i.DateCreated) + item.DateLastEpisodeAdded = episodes + .Where(i => i.LocationType != LocationType.Virtual) + .Select(i => i.DateCreated) .OrderByDescending(i => i) .FirstOrDefault(); diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs index dc06857ce..035528734 100644 --- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs +++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs @@ -17,7 +17,7 @@ using System.Xml; namespace MediaBrowser.Providers.TV { - class SeriesPostScanTask : ILibraryPostScanTask + class SeriesPostScanTask : ILibraryPostScanTask, IHasOrder { /// <summary> /// The _library manager @@ -89,6 +89,15 @@ namespace MediaBrowser.Providers.TV progress.Report(percent); } } + + public int Order + { + get + { + // Run after tvdb update task + return 1; + } + } } class MissingEpisodeProvider @@ -174,10 +183,9 @@ namespace MediaBrowser.Providers.TV { await series.RefreshMetadata(new MetadataRefreshOptions { - }, cancellationToken) - .ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); - await series.ValidateChildren(new Progress<double>(), cancellationToken, true) + await series.ValidateChildren(new Progress<double>(), cancellationToken, new MetadataRefreshOptions(), true) .ConfigureAwait(false); } } @@ -469,6 +477,7 @@ namespace MediaBrowser.Providers.TV }; await series.AddChild(season, cancellationToken).ConfigureAwait(false); + await season.RefreshMetadata(new MetadataRefreshOptions { }, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs index 4dfaa3925..53599ef55 100644 --- a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs +++ b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs @@ -20,14 +20,14 @@ namespace MediaBrowser.Providers.TV _logger = logger; } - protected override void Fetch(Series item, string path, CancellationToken cancellationToken) + protected override void Fetch(LocalMetadataResult<Series> result, string path, CancellationToken cancellationToken) { - new SeriesXmlParser(_logger).Fetch(item, path, cancellationToken); + new SeriesXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info) + protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return new FileInfo(Path.Combine(info.Path, "series.xml")); + return directoryService.GetFile(Path.Combine(info.Path, "series.xml")); } } } diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs index 6f988a2f6..0830b6713 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs @@ -61,8 +61,9 @@ namespace MediaBrowser.Providers.TV public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken) { var episode = (Episode)item; + var series = episode.Series; - var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null; + var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tvdb) : null; if (!string.IsNullOrEmpty(seriesId)) { @@ -191,8 +192,17 @@ namespace MediaBrowser.Providers.TV }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + if (item.LocationType != LocationType.Virtual) + { + // For non-virtual items, only enable if configured + if (!_config.Configuration.EnableTvDbUpdates) + { + return false; + } + } + if (!item.HasImage(ImageType.Primary)) { var episode = (Episode)item; @@ -210,6 +220,7 @@ namespace MediaBrowser.Providers.TV return files.Any(i => _fileSystem.GetLastWriteTimeUtc(i) > date); } } + return false; } } diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs index 178c7a265..0c9712cae 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs @@ -2,10 +2,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Net; using System; using System.Collections.Generic; using System.Globalization; @@ -23,7 +21,7 @@ namespace MediaBrowser.Providers.TV /// <summary> /// Class RemoteEpisodeProvider /// </summary> - class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode>, IHasChangeMonitor + class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasChangeMonitor { internal static TvdbEpisodeProvider Current; private readonly IFileSystem _fileSystem; @@ -41,9 +39,9 @@ namespace MediaBrowser.Providers.TV get { return "TheTVDB"; } } - public Task<MetadataResult<Episode>> GetMetadata(ItemId id, CancellationToken cancellationToken) + public Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo id, CancellationToken cancellationToken) { - var episodeId = (EpisodeId)id; + var episodeId = id; string seriesTvdbId; episodeId.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId); @@ -68,8 +66,14 @@ namespace MediaBrowser.Providers.TV return Task.FromResult(result); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + // Only enable for virtual items + if (item.LocationType != LocationType.Virtual) + { + return false; + } + var episode = (Episode)item; var series = episode.Series; @@ -167,7 +171,7 @@ namespace MediaBrowser.Providers.TV /// <param name="seriesDataPath">The series data path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> - private Episode FetchEpisodeData(EpisodeId id, string seriesDataPath, CancellationToken cancellationToken) + private Episode FetchEpisodeData(EpisodeInfo id, string seriesDataPath, CancellationToken cancellationToken) { if (id.IndexNumber == null) { @@ -417,28 +421,6 @@ namespace MediaBrowser.Providers.TV break; } - case "filename": - { - if (string.IsNullOrEmpty(item.PrimaryImagePath)) - { - var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) - { - try - { - var url = TVUtils.BannerUrl + val; - - //await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - } - catch (HttpException) - { - status = ProviderRefreshStatus.CompletedWithErrors; - } - } - } - break; - } - case "Overview": { if (!item.LockedFields.Contains(MetadataFields.Overview)) diff --git a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs index af4c27278..4164a05ba 100644 --- a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.TV /// <summary> /// Class TvdbPrescanTask /// </summary> - public class TvdbPrescanTask : ILibraryPrescanTask + public class TvdbPrescanTask : ILibraryPostScanTask { /// <summary> /// The server time URL @@ -98,8 +98,19 @@ namespace MediaBrowser.Providers.TV string newUpdateTime; - var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList(); + var existingDirectories = Directory.EnumerateDirectories(path) + .Select(Path.GetFileName) + .ToList(); + var seriesIdsInLibrary = _libraryManager.RootFolder.RecursiveChildren + .OfType<Series>() + .Select(i => i.GetProviderId(MetadataProviders.Tvdb)) + .Where(i => !string.IsNullOrEmpty(i)) + .ToList(); + + var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase) + .ToList(); + // If this is our first time, update all series if (string.IsNullOrEmpty(lastUpdateTime)) { @@ -116,6 +127,8 @@ namespace MediaBrowser.Providers.TV newUpdateTime = GetUpdateTime(stream); } + existingDirectories.AddRange(missingSeries); + await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false); } else @@ -130,7 +143,10 @@ namespace MediaBrowser.Providers.TV var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue; - await UpdateSeries(seriesToUpdate.Item1, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false); + var listToUpdate = seriesToUpdate.Item1.ToList(); + listToUpdate.AddRange(missingSeries); + + await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false); } File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); diff --git a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs index a63f3ee24..e4756ea71 100644 --- a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs @@ -340,8 +340,17 @@ namespace MediaBrowser.Providers.TV }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + if (item.LocationType != LocationType.Virtual) + { + // For non-virtual items, only enable if configured + if (!_config.Configuration.EnableTvDbUpdates) + { + return false; + } + } + var season = (Season)item; var series = season.Series; diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index e56830644..765d17aa7 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -337,8 +337,13 @@ namespace MediaBrowser.Providers.TV }); } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { + if (!_config.Configuration.EnableTvDbUpdates) + { + return false; + } + var tvdbId = item.GetProviderId(MetadataProviders.Tvdb); if (!String.IsNullOrEmpty(tvdbId)) diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs index 1c31b8ac6..73fd4e498 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs @@ -22,7 +22,7 @@ using System.Xml; namespace MediaBrowser.Providers.TV { - public class TvdbSeriesProvider : IRemoteMetadataProvider<Series>, IHasChangeMonitor + public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo> { internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(2, 2); internal static TvdbSeriesProvider Current { get; private set; } @@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.TV private const string SeriesQuery = "GetSeries.php?seriesname={0}"; private const string SeriesGetZip = "http://www.thetvdb.com/api/{0}/series/{1}/all/{2}.zip"; - public async Task<MetadataResult<Series>> GetMetadata(ItemId itemId, CancellationToken cancellationToken) + public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo itemId, CancellationToken cancellationToken) { var result = new MetadataResult<Series>(); @@ -158,33 +158,24 @@ namespace MediaBrowser.Providers.TV var automaticUpdatesEnabled = _config.Configuration.EnableTvDbUpdates; var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase)); - if (seriesFile == null || !seriesFile.Exists) + // No need to check age if automatic updates are enabled + if (seriesFile == null || !seriesFile.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalDays > 7)) { - // No need to check age if automatic updates are enabled - if (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalDays > 7) - { - download = true; - } + download = true; } var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase)); - if (actorsXml == null || !actorsXml.Exists) + // No need to check age if automatic updates are enabled + if (actorsXml == null || !actorsXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalDays > 7)) { - // No need to check age if automatic updates are enabled - if (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalDays > 7) - { - download = true; - } + download = true; } var bannersXml = files.FirstOrDefault(i => string.Equals("banners.xml", i.Name, StringComparison.OrdinalIgnoreCase)); - if (bannersXml == null || !bannersXml.Exists) + // No need to check age if automatic updates are enabled + if (bannersXml == null || !bannersXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalDays > 7)) { - // No need to check age if automatic updates are enabled - if (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalDays > 7) - { - download = true; - } + download = true; } // Only download if not already there @@ -1079,43 +1070,5 @@ namespace MediaBrowser.Providers.TV { get { return "TheTVDB"; } } - - public bool HasChanged(IHasMetadata item, DateTime date) - { - var seriesId = item.GetProviderId(MetadataProviders.Tvdb); - - if (!string.IsNullOrEmpty(seriesId)) - { - var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesId); - - try - { - var files = new DirectoryInfo(seriesDataPath).EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly) - .ToList(); - - var seriesXmlFilename = item.GetPreferredMetadataLanguage() + ".xml"; - - var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase)); - - if (seriesFile != null && seriesFile.Exists && _fileSystem.GetLastWriteTimeUtc(seriesFile) > date) - { - return true; - } - - var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase)); - - if (actorsXml != null && actorsXml.Exists && _fileSystem.GetLastWriteTimeUtc(actorsXml) > date) - { - return true; - } - } - catch (DirectoryNotFoundException) - { - // Don't blow up - } - } - - return false; - } } } diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs index 9f0b77dca..5b49033a9 100644 --- a/MediaBrowser.Providers/Users/UserMetadataService.cs +++ b/MediaBrowser.Providers/Users/UserMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Users { - public class UserMetadataService : MetadataService<User, ItemId> + public class UserMetadataService : MetadataService<User, ItemLookupInfo> { private readonly IUserManager _userManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.Users { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(User item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _userManager.UpdateUser(item); - } } } diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs index 9aa4ba138..de8bd44e6 100644 --- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs +++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Videos { - public class VideoMetadataService : MetadataService<Video, ItemId> + public class VideoMetadataService : MetadataService<Video, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -35,11 +35,6 @@ namespace MediaBrowser.Providers.Videos ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - protected override Task SaveItem(Video item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } - public override int Order { get diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs index 01e511177..621ea96ce 100644 --- a/MediaBrowser.Providers/Years/YearMetadataService.cs +++ b/MediaBrowser.Providers/Years/YearMetadataService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Years { - public class YearMetadataService : MetadataService<Year, ItemId> + public class YearMetadataService : MetadataService<Year, ItemLookupInfo> { private readonly ILibraryManager _libraryManager; @@ -34,10 +34,5 @@ namespace MediaBrowser.Providers.Years { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } - - protected override Task SaveItem(Year item, ItemUpdateType reason, CancellationToken cancellationToken) - { - return _libraryManager.UpdateItem(item, reason, cancellationToken); - } } } diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 5ae3af5e2..db839a66e 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -96,10 +96,36 @@ namespace MediaBrowser.Server.Implementations.Configuration ValidateItemByNamePath(newConfig); ValidateTranscodingTempPath(newConfig); + ValidatePathSubstitutions(newConfig); base.ReplaceConfiguration(newConfiguration); } + private void ValidatePathSubstitutions(ServerConfiguration newConfig) + { + foreach (var map in newConfig.PathSubstitutions) + { + if (string.IsNullOrWhiteSpace(map.From) || string.IsNullOrWhiteSpace(map.To)) + { + throw new ArgumentException("Invalid path substitution"); + } + + if (!map.From.EndsWith(":\\") && !map.From.EndsWith(":/")) + { + map.From = map.From.TrimEnd('/').TrimEnd('\\'); + } + if (!map.To.EndsWith(":\\") && !map.To.EndsWith(":/")) + { + map.To = map.To.TrimEnd('/').TrimEnd('\\'); + } + + if (string.IsNullOrWhiteSpace(map.From) || string.IsNullOrWhiteSpace(map.To)) + { + throw new ArgumentException("Invalid path substitution"); + } + } + } + /// <summary> /// Replaces the item by name path. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 06a03ba1c..12d3eadfd 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -619,27 +619,24 @@ namespace MediaBrowser.Server.Implementations.Drawing /// Gets the image cache tag. /// </summary> /// <param name="item">The item.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="imagePath">The image path.</param> + /// <param name="image">The image.</param> /// <returns>Guid.</returns> /// <exception cref="System.ArgumentNullException">item</exception> - public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath) + public Guid GetImageCacheTag(IHasImages item, ItemImageInfo image) { if (item == null) { throw new ArgumentNullException("item"); } - if (string.IsNullOrEmpty(imagePath)) + if (image == null) { - throw new ArgumentNullException("imagePath"); + throw new ArgumentNullException("image"); } - var dateModified = item.GetImageDateModified(imagePath); - - var supportedEnhancers = GetSupportedEnhancers(item, imageType); + var supportedEnhancers = GetSupportedEnhancers(item, image.Type); - return GetImageCacheTag(item, imageType, imagePath, dateModified, supportedEnhancers.ToList()); + return GetImageCacheTag(item, image.Type, image.Path, image.DateModified, supportedEnhancers.ToList()); } /// <summary> @@ -693,9 +690,10 @@ namespace MediaBrowser.Server.Implementations.Drawing { var enhancers = GetSupportedEnhancers(item, imageType).ToList(); - var imagePath = item.GetImagePath(imageType, imageIndex); + var imageInfo = item.GetImageInfo(imageType, imageIndex); + var imagePath = imageInfo.Path; - var dateModified = item.GetImageDateModified(imagePath); + var dateModified = imageInfo.DateModified; var result = await GetEnhancedImage(imagePath, dateModified, item, imageType, imageIndex, enhancers); diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 84033f9ad..2584fa4f0 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -34,8 +35,9 @@ namespace MediaBrowser.Server.Implementations.Dto private readonly IImageProcessor _imageProcessor; private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; - public DtoService(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config) + public DtoService(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem) { _logger = logger; _libraryManager = libraryManager; @@ -44,6 +46,7 @@ namespace MediaBrowser.Server.Implementations.Dto _itemRepo = itemRepo; _imageProcessor = imageProcessor; _config = config; + _fileSystem = fileSystem; } /// <summary> @@ -207,11 +210,11 @@ namespace MediaBrowser.Server.Implementations.Dto Configuration = user.Configuration }; - var image = user.PrimaryImagePath; + var image = user.GetImageInfo(ImageType.Primary, 0); - if (!string.IsNullOrEmpty(image)) + if (image != null) { - dto.PrimaryImageTag = GetImageCacheTag(user, ImageType.Primary, image); + dto.PrimaryImageTag = GetImageCacheTag(user, image); try { @@ -288,12 +291,7 @@ namespace MediaBrowser.Server.Implementations.Dto RunTimeTicks = item.RunTimeTicks }; - var imagePath = item.PrimaryImagePath; - - if (!string.IsNullOrEmpty(imagePath)) - { - info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary, imagePath); - } + info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary); return info; } @@ -380,11 +378,7 @@ namespace MediaBrowser.Server.Implementations.Dto /// <returns>List{System.String}.</returns> private List<Guid> GetBackdropImageTags(BaseItem item) { - return item.BackdropImagePaths - .Select(p => GetImageCacheTag(item, ImageType.Backdrop, p)) - .Where(i => i.HasValue) - .Select(i => i.Value) - .ToList(); + return GetCacheTags(item, ImageType.Backdrop).ToList(); } /// <summary> @@ -399,23 +393,40 @@ namespace MediaBrowser.Server.Implementations.Dto { return new List<Guid>(); } + return GetCacheTags(item, ImageType.Screenshot).ToList(); + } - return hasScreenshots.ScreenshotImagePaths - .Select(p => GetImageCacheTag(item, ImageType.Screenshot, p)) + private IEnumerable<Guid> GetCacheTags(BaseItem item, ImageType type) + { + return item.GetImages(type) + .Select(p => GetImageCacheTag(item, p)) .Where(i => i.HasValue) .Select(i => i.Value) .ToList(); } - private Guid? GetImageCacheTag(BaseItem item, ImageType type, string path) + private Guid? GetImageCacheTag(BaseItem item, ImageType type) + { + try + { + return _imageProcessor.GetImageCacheTag(item, type); + } + catch (IOException ex) + { + _logger.ErrorException("Error getting {0} image info", ex, type); + return null; + } + } + + private Guid? GetImageCacheTag(BaseItem item, ItemImageInfo image) { try { - return _imageProcessor.GetImageCacheTag(item, type, path); + return _imageProcessor.GetImageCacheTag(item, image); } catch (IOException ex) { - _logger.ErrorException("Error getting {0} image info for {1}", ex, type, path); + _logger.ErrorException("Error getting {0} image info for {1}", ex, image.Type, image.Path); return null; } } @@ -468,12 +479,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (dictionary.TryGetValue(person.Name, out entity)) { - var primaryImagePath = entity.PrimaryImagePath; - - if (!string.IsNullOrEmpty(primaryImagePath)) - { - baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary, primaryImagePath); - } + baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); } dto.People[i] = baseItemPerson; @@ -520,12 +526,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (dictionary.TryGetValue(studio, out entity)) { - var primaryImagePath = entity.PrimaryImagePath; - - if (!string.IsNullOrEmpty(primaryImagePath)) - { - studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary, primaryImagePath); - } + studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); } dto.Studios[i] = studioDto; @@ -544,7 +545,7 @@ namespace MediaBrowser.Server.Implementations.Dto while (parent != null) { - if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Count > 0) + if (parent.GetImages(ImageType.Backdrop).Any()) { return parent; } @@ -595,7 +596,12 @@ namespace MediaBrowser.Server.Implementations.Dto if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) { - dto.ImageTag = GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath); + dto.ImageTag = GetImageCacheTag(item, new ItemImageInfo + { + Path = chapterInfo.ImagePath, + Type = ImageType.Chapter, + DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath) + }); } return dto; @@ -698,7 +704,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (fields.Contains(ItemFields.Keywords)) { - var hasTags = item as IHasKeywords; + var hasTags = item as IHasKeywords; if (hasTags != null) { dto.Keywords = hasTags.Keywords; @@ -750,15 +756,15 @@ namespace MediaBrowser.Server.Implementations.Dto dto.ImageTags = new Dictionary<ImageType, Guid>(); - foreach (var image in item.Images) + // Prevent implicitly captured closure + var currentItem = item; + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))) { - var type = image.Key; - - var tag = GetImageCacheTag(item, type, image.Value); + var tag = GetImageCacheTag(item, image); if (tag.HasValue) { - dto.ImageTags[type] = tag.Value; + dto.ImageTags[image.Type] = tag.Value; } } @@ -804,7 +810,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.CollectionType = collectionFolder.CollectionType; } - + if (fields.Contains(ItemFields.RemoteTrailers)) { dto.RemoteTrailers = hasTrailers != null ? @@ -862,7 +868,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.ParentLogoItemId = GetDtoId(parentWithLogo); - dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImagePath(ImageType.Logo)); + dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo); } } @@ -875,7 +881,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.ParentArtItemId = GetDtoId(parentWithImage); - dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImagePath(ImageType.Art)); + dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art); } } @@ -888,7 +894,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.ParentThumbItemId = GetDtoId(parentWithImage); - dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImagePath(ImageType.Thumb)); + dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb); } } @@ -959,12 +965,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.AlbumId = GetDtoId(albumParent); - var imagePath = albumParent.PrimaryImagePath; - - if (!string.IsNullOrEmpty(imagePath)) - { - dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary, imagePath); - } + dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); } } @@ -1085,17 +1086,9 @@ namespace MediaBrowser.Server.Implementations.Dto dto.AirTime = series.AirTime; dto.SeriesStudio = series.Studios.FirstOrDefault(); - if (series.HasImage(ImageType.Thumb)) - { - dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImagePath(ImageType.Thumb)); - } - - var imagePath = series.PrimaryImagePath; + dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb); - if (!string.IsNullOrEmpty(imagePath)) - { - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary, imagePath); - } + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); } // Add SeasonInfo @@ -1110,12 +1103,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.AirTime = series.AirTime; dto.SeriesStudio = series.Studios.FirstOrDefault(); - var imagePath = series.PrimaryImagePath; - - if (!string.IsNullOrEmpty(imagePath)) - { - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary, imagePath); - } + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); } var game = item as Game; @@ -1225,6 +1213,7 @@ namespace MediaBrowser.Server.Implementations.Dto var unplayed = 0; long runtime = 0; + DateTime? dateLastMediaAdded = null; double totalPercentPlayed = 0; IEnumerable<BaseItem> children; @@ -1243,6 +1232,15 @@ namespace MediaBrowser.Server.Implementations.Dto // Loop through each recursive child foreach (var child in children) { + if (!dateLastMediaAdded.HasValue) + { + dateLastMediaAdded = child.DateCreated; + } + else + { + dateLastMediaAdded = new[] { dateLastMediaAdded.Value, child.DateCreated }.Max(); + } + var userdata = _userDataRepository.GetUserData(user.Id, child.GetUserDataKey()); recursiveItemCount++; @@ -1293,6 +1291,11 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.CumulativeRunTimeTicks = runtime; } + + if (fields.Contains(ItemFields.DateLastMediaAdded)) + { + dto.DateLastMediaAdded = dateLastMediaAdded; + } } /// <summary> @@ -1303,15 +1306,17 @@ namespace MediaBrowser.Server.Implementations.Dto /// <returns>Task.</returns> public void AttachPrimaryImageAspectRatio(IItemDto dto, IHasImages item) { - var path = item.PrimaryImagePath; + var imageInfo = item.GetImageInfo(ImageType.Primary, 0); - if (string.IsNullOrEmpty(path)) + if (imageInfo == null) { return; } + var path = imageInfo.Path; + // See if we can avoid a file system lookup by looking for the file in ResolveArgs - var dateModified = item.GetImageDateModified(path); + var dateModified = imageInfo.DateModified; ImageSize size; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 7dacfacc2..a91033839 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -273,8 +273,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (item.LocationType == LocationType.FileSystem) { - return collections.Where(i => i.LocationType == LocationType.FileSystem && - i.PhysicalLocations.Contains(item.Path)).Cast<T>(); + return collections.Where(i => i.PhysicalLocations.Contains(item.Path)).Cast<T>(); } } @@ -283,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (item.Id == user.RootFolder.Id) { - return new T[] { item }; + return new[] { item }; } return new T[] { }; diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index f1f9048a9..8634919fc 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -161,7 +161,6 @@ namespace MediaBrowser.Server.Implementations.IO .RootFolder .Children .OfType<Folder>() - .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) .SelectMany(f => f.PhysicalLocations) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(i => i) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 813d279ab..735565e25 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -38,24 +38,13 @@ namespace MediaBrowser.Server.Implementations.Library /// Gets or sets the postscan tasks. /// </summary> /// <value>The postscan tasks.</value> - private IEnumerable<ILibraryPostScanTask> PostscanTasks { get; set; } - /// <summary> - /// Gets or sets the prescan tasks. - /// </summary> - /// <value>The prescan tasks.</value> - private IEnumerable<ILibraryPrescanTask> PrescanTasks { get; set; } - - /// <summary> - /// Gets or sets the people prescan tasks. - /// </summary> - /// <value>The people prescan tasks.</value> - private IEnumerable<IPeoplePrescanTask> PeoplePrescanTasks { get; set; } + private ILibraryPostScanTask[] PostscanTasks { get; set; } /// <summary> /// Gets the intro providers. /// </summary> /// <value>The intro providers.</value> - private IEnumerable<IIntroProvider> IntroProviders { get; set; } + private IIntroProvider[] IntroProviders { get; set; } /// <summary> /// Gets the list of entity resolution ignore rules @@ -205,26 +194,27 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="resolvers">The resolvers.</param> /// <param name="introProviders">The intro providers.</param> /// <param name="itemComparers">The item comparers.</param> - /// <param name="prescanTasks">The prescan tasks.</param> /// <param name="postscanTasks">The postscan tasks.</param> - /// <param name="peoplePrescanTasks">The people prescan tasks.</param> public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, - IEnumerable<ILibraryPrescanTask> prescanTasks, - IEnumerable<ILibraryPostScanTask> postscanTasks, - IEnumerable<IPeoplePrescanTask> peoplePrescanTasks) + IEnumerable<ILibraryPostScanTask> postscanTasks) { EntityResolutionIgnoreRules = rules.ToArray(); PluginFolderCreators = pluginFolders.ToArray(); EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); - IntroProviders = introProviders; + IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - PrescanTasks = prescanTasks; - PostscanTasks = postscanTasks; - PeoplePrescanTasks = peoplePrescanTasks; + + PostscanTasks = postscanTasks.OrderBy(i => + { + var hasOrder = i as IHasOrder; + + return hasOrder == null ? 0 : hasOrder.Order; + + }).ToArray(); } /// <summary> @@ -507,7 +497,9 @@ namespace MediaBrowser.Server.Implementations.Library // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + var directoryService = new DirectoryService(_logger); + + var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action @@ -845,7 +837,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { - return new PeopleValidator(this, PeoplePrescanTasks, _logger).ValidatePeople(cancellationToken, progress); + return new PeopleValidator(this, _logger).ValidatePeople(cancellationToken, progress); } /// <summary> @@ -955,7 +947,7 @@ namespace MediaBrowser.Server.Implementations.Library progress.Report(.5); // Start by just validating the children of the root, but go no further - await RootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false); + await RootFolder.ValidateChildren(new Progress<double>(), cancellationToken, new MetadataRefreshOptions(), recursive: false); progress.Report(1); @@ -970,17 +962,12 @@ namespace MediaBrowser.Server.Implementations.Library innerProgress.RegisterAction(pct => progress.Report(2 + pct * .13)); - // Run prescan tasks - await RunPrescanTasks(innerProgress, cancellationToken).ConfigureAwait(false); - - progress.Report(15); - innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(15 + pct * .6)); + innerProgress.RegisterAction(pct => progress.Report(2 + pct * .73)); // Now validate the entire media library - await RootFolder.ValidateChildren(innerProgress, cancellationToken, recursive: true).ConfigureAwait(false); + await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(), recursive: true).ConfigureAwait(false); progress.Report(75); @@ -999,55 +986,6 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> - /// Runs the prescan tasks. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task RunPrescanTasks(IProgress<double> progress, CancellationToken cancellationToken) - { - var tasks = PrescanTasks.ToList(); - - var numComplete = 0; - var numTasks = tasks.Count; - - foreach (var task in tasks) - { - var innerProgress = new ActionableProgress<double>(); - - // Prevent access to modified closure - var currentNumComplete = numComplete; - - innerProgress.RegisterAction(pct => - { - double innerPercent = (currentNumComplete * 100) + pct; - innerPercent /= numTasks; - progress.Report(innerPercent); - }); - - try - { - await task.Run(innerProgress, cancellationToken); - } - catch (OperationCanceledException) - { - _logger.Info("Pre-scan task cancelled: {0}", task.GetType().Name); - } - catch (Exception ex) - { - _logger.ErrorException("Error running pre-scan task", ex); - } - - numComplete++; - double percent = numComplete; - percent /= numTasks; - progress.Report(percent * 100); - } - - progress.Report(100); - } - - /// <summary> /// Runs the post scan tasks. /// </summary> /// <param name="progress">The progress.</param> @@ -1109,8 +1047,7 @@ namespace MediaBrowser.Server.Implementations.Library cancellationToken.ThrowIfCancellationRequested(); - await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false).ConfigureAwait(false); - var b = true; + await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, new MetadataRefreshOptions(), recursive: false).ConfigureAwait(false); } /// <summary> @@ -1464,22 +1401,7 @@ namespace MediaBrowser.Server.Implementations.Library .Distinct() .SelectMany(i => i.Children) .OfType<CollectionFolder>() - .Where(i => - { - var locationType = i.LocationType; - - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) - { - return false; - } - - if (string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return i.PhysicalLocations.Contains(item.Path); - }) + .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path)) .Select(i => i.CollectionType) .Where(i => !string.IsNullOrEmpty(i)) .Distinct() diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 998895cbf..f355f4bf6 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 40ef5304c..1d9eea866 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { cancellationToken.ThrowIfCancellationRequested(); - // Only do this for artists accessed by name. Folder-based artists use ArtistInfoFromSongsProvider + // Only do this for artists accessed by name. Folder-based artists get it from the normal refresh if (artist.IsAccessedByName && !artist.LockedFields.Contains(MetadataFields.Genres)) { // Avoid implicitly captured closure @@ -91,9 +91,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators .ToList(); } - // Populate counts of items - //SetItemCounts(artist, null, allItems.OfType<IHasArtist>()); - foreach (var lib in userLibraries) { SetItemCounts(artist, lib.Item1, lib.Item2); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs index 9e140c626..097e94216 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs @@ -16,10 +16,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; /// <summary> - /// Initializes a new instance of the <see cref="GameGenresPostScanTask"/> class. + /// Initializes a new instance of the <see cref="GameGenresPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - /// <param name="userManager">The user manager.</param> public GameGenresPostScanTask(ILibraryManager libraryManager) { _libraryManager = libraryManager; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index c7af7a238..9e64c7810 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -47,9 +47,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - // Populate counts of items - //SetItemCounts(null, allLibraryItems, masterDictionary); - progress.Report(2); var numComplete = 0; @@ -98,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) + private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) { var itemByName = _libraryManager.GetGameGenre(name); @@ -109,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators itemByName.SetItemByNameCounts(libraryId, itemCounts); } - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + return itemByName.RefreshMetadata(cancellationToken); } private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index cb1253df0..e0a9e2ce8 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -48,9 +48,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - // Populate counts of items - //SetItemCounts(null, allLibraryItems, masterDictionary); - progress.Report(2); var numComplete = 0; @@ -99,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) + private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) { var itemByName = _libraryManager.GetGenre(name); @@ -110,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators itemByName.SetItemByNameCounts(libraryId, itemCounts); } - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + return itemByName.RefreshMetadata(cancellationToken); } private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index e5535c6e0..da378228a 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -19,7 +19,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - /// <param name="userManager">The user manager.</param> public MusicGenresPostScanTask(ILibraryManager libraryManager) { _libraryManager = libraryManager; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 57a6a612b..b55ab1cbe 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -48,9 +48,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - // Populate counts of items - //SetItemCounts(null, allLibraryItems, masterDictionary); - progress.Report(2); var numComplete = 0; @@ -99,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) + private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) { var itemByName = _libraryManager.GetMusicGenre(name); @@ -110,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators itemByName.SetItemByNameCounts(libraryId, itemCounts); } - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + return itemByName.RefreshMetadata(cancellationToken); } private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index 706ff67a7..86c5dbc4c 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -53,9 +53,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - // Populate counts of items - //SetItemCounts(null, allLibraryItems, masterDictionary); - progress.Report(2); var numComplete = 0; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs index 26e9a23e9..268bccd7a 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using MoreLinq; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -24,19 +23,15 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// </summary> private readonly ILogger _logger; - private readonly IEnumerable<IPeoplePrescanTask> _prescanTasks; - /// <summary> /// Initializes a new instance of the <see cref="PeopleValidator" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - /// <param name="prescanTasks">The prescan tasks.</param> /// <param name="logger">The logger.</param> - public PeopleValidator(ILibraryManager libraryManager, IEnumerable<IPeoplePrescanTask> prescanTasks, ILogger logger) + public PeopleValidator(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; _logger = logger; - _prescanTasks = prescanTasks; } /// <summary> @@ -51,11 +46,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators innerProgress.RegisterAction(pct => progress.Report(pct * .15)); - // Run prescan tasks - await RunPrescanTasks(innerProgress, cancellationToken).ConfigureAwait(false); - - progress.Report(15); - var people = _libraryManager.RootFolder.GetRecursiveChildren() .SelectMany(c => c.People) .DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase) @@ -94,55 +84,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators GC.Collect(2, GCCollectionMode.Forced, true); GC.Collect(2, GCCollectionMode.Forced, true); } - - /// <summary> - /// Runs the prescan tasks. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task RunPrescanTasks(IProgress<double> progress, CancellationToken cancellationToken) - { - var tasks = _prescanTasks.ToList(); - - var numComplete = 0; - var numTasks = tasks.Count; - - foreach (var task in tasks) - { - var innerProgress = new ActionableProgress<double>(); - - // Prevent access to modified closure - var currentNumComplete = numComplete; - - innerProgress.RegisterAction(pct => - { - double innerPercent = (currentNumComplete * 100) + pct; - innerPercent /= numTasks; - progress.Report(innerPercent); - }); - - try - { - await task.Run(innerProgress, cancellationToken); - } - catch (OperationCanceledException) - { - _logger.Info("Pre-scan task cancelled: {0}", task.GetType().Name); - break; - } - catch (Exception ex) - { - _logger.ErrorException("Error running pre-scan task", ex); - } - - numComplete++; - double percent = numComplete; - percent /= numTasks; - progress.Report(percent * 100); - } - - progress.Report(100); - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index fb9562da2..a3a8b8678 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -19,7 +19,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - /// <param name="userManager">The user manager.</param> public StudiosPostScanTask(ILibraryManager libraryManager) { _libraryManager = libraryManager; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index 0f4ff562e..54fadfb77 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -47,9 +47,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - // Populate counts of items - //SetItemCounts(null, allLibraryItems, masterDictionary); - progress.Report(2); var numComplete = 0; @@ -98,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) + private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) { var itemByName = _libraryManager.GetStudio(name); @@ -109,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators itemByName.SetItemByNameCounts(libraryId, itemCounts); } - await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); + return itemByName.RefreshMetadata(cancellationToken); } private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs index c65568db9..78783db90 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs @@ -26,6 +26,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators .Distinct() .ToList(); + progress.Report(10); + var count = allYears.Count; var numComplete = 0; diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index 9fdca568e..9a1373460 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv get { return 0; } } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 4805adb1f..22d683e1b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -401,16 +401,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv private Guid? GetImageTag(IHasImages info) { - var path = info.PrimaryImagePath; - - if (string.IsNullOrEmpty(path)) - { - return null; - } - try { - return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path); + return _imageProcessor.GetImageCacheTag(info, ImageType.Primary); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 94b6cdd9e..f25cf541b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -701,7 +701,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } } - } internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken) @@ -726,6 +725,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv foreach (var channelInfo in allChannelsList) { + cancellationToken.ThrowIfCancellationRequested(); + try { var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false); @@ -758,8 +759,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv var guideDays = GetGuideDays(list.Count); + cancellationToken.ThrowIfCancellationRequested(); + foreach (var item in list) { + cancellationToken.ThrowIfCancellationRequested(); + // Avoid implicitly captured closure var currentChannel = item; @@ -788,10 +793,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv double percent = numComplete; percent /= allChannelsList.Count; - progress.Report(90 * percent + 10); + progress.Report(80 * percent + 10); } _programs = programs.ToDictionary(i => i.Id); + + // Load these now which will prefetch metadata + await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false); + + progress.Report(100); } private double GetGuideDays(int channelCount) diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index 1dd6eb272..cb7635b45 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv get { return 0; } } - public bool HasChanged(IHasMetadata item, DateTime date) + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 6; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs index 1f5610f67..be8955d16 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs @@ -1,156 +1,111 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - public class RecordingImageProvider : BaseMetadataProvider + public class RecordingImageProvider : IDynamicImageProvider, IHasChangeMonitor { private readonly ILiveTvManager _liveTvManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; + private readonly ILogger _logger; - public RecordingImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) - : base(logManager, configurationManager) + public RecordingImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger) { _liveTvManager = liveTvManager; - _providerManager = providerManager; - _fileSystem = fileSystem; _httpClient = httpClient; + _logger = logger; } - public override bool Supports(BaseItem item) - { - return item is ILiveTvRecording; - } - - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + public IEnumerable<ImageType> GetSupportedImages(IHasImages item) { - return !item.HasImage(ImageType.Primary); + return new[] { ImageType.Primary }; } - public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - if (item.HasImage(ImageType.Primary)) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } + var liveTvItem = (ILiveTvRecording)item; - var changed = true; + var imageResponse = new DynamicImageResponse(); - try + if (!string.IsNullOrEmpty(liveTvItem.RecordingInfo.ImagePath)) { - changed = await DownloadImage((ILiveTvRecording)item, cancellationToken).ConfigureAwait(false); - } - catch (HttpException ex) - { - // Don't fail the provider on a 404 - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } + imageResponse.Path = liveTvItem.RecordingInfo.ImagePath; + imageResponse.HasImage = true; } - - if (changed) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - } - - return changed; - } - - private async Task<bool> DownloadImage(ILiveTvRecording item, CancellationToken cancellationToken) - { - var recordingInfo = item.RecordingInfo; - - Stream imageStream = null; - string contentType = null; - - if (!string.IsNullOrEmpty(recordingInfo.ImagePath)) - { - contentType = "image/" + Path.GetExtension(recordingInfo.ImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(recordingInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); - } - else if (!string.IsNullOrEmpty(recordingInfo.ImageUrl)) + else if (!string.IsNullOrEmpty(liveTvItem.RecordingInfo.ImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = recordingInfo.ImageUrl + Url = liveTvItem.RecordingInfo.ImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { - Logger.Error("Provider did not return an image content type."); - return false; + imageResponse.HasImage = true; + imageResponse.Stream = response.Content; + imageResponse.SetFormatFromMimeType(response.ContentType); + } + else + { + _logger.Error("Provider did not return an image content type."); } - - imageStream = response.Content; - contentType = response.ContentType; } - else if (recordingInfo.HasImage ?? true) + else if (liveTvItem.RecordingInfo.HasImage ?? true) { - var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase)); if (service != null) { try { - var response = await service.GetRecordingImageAsync(recordingInfo.Id, cancellationToken).ConfigureAwait(false); + var response = await service.GetRecordingImageAsync(liveTvItem.RecordingInfo.Id, cancellationToken).ConfigureAwait(false); if (response != null) { - imageStream = response.Stream; - contentType = "image/" + response.Format.ToString().ToLower(); + imageResponse.HasImage = true; + imageResponse.Stream = response.Stream; + imageResponse.Format = response.Format; } } catch (NotImplementedException) { - return false; } } } - if (imageStream != null) - { - // Dummy up the original url - var url = item.ServiceName + recordingInfo.Id; + return imageResponse; + } - await _providerManager.SaveImage((BaseItem)item, imageStream, contentType, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - return true; - } + public string Name + { + get { return "Live TV Service Provider"; } + } - return false; + public bool Supports(IHasImages item) + { + return item is ILiveTvRecording; } - public override MetadataProviderPriority Priority + public int Order { - get { return MetadataProviderPriority.Second; } + get { return 0; } } - public override ItemUpdateType ItemUpdateType + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - get - { - return ItemUpdateType.ImageUpdate; - } + return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 3; } } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index fe4283368..8489624fc 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -192,6 +192,7 @@ <Compile Include="Roku\RokuControllerFactory.cs" /> <Compile Include="ScheduledTasks\PeopleValidationTask.cs" /> <Compile Include="ScheduledTasks\ChapterImagesTask.cs" /> + <Compile Include="ScheduledTasks\RefreshIntrosTask.cs" /> <Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" /> <Compile Include="ServerApplicationPaths.cs" /> <Compile Include="ServerManager\ServerManager.cs" /> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index c48120b24..c1843504c 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -795,40 +795,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder } } - /// <summary> - /// Extracts the image. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="type">The type.</param> - /// <param name="isAudio">if set to <c>true</c> [is audio].</param> - /// <param name="threedFormat">The threed format.</param> - /// <param name="offset">The offset.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception> - public async Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken) - { - var resourcePool = isAudio ? _audioImageResourcePool : _videoImageResourcePool; - - var inputArgument = GetInputArgument(inputFiles, type); - - if (!isAudio) - { - try - { - await ExtractImageInternal(inputArgument, type, threedFormat, offset, outputPath, true, resourcePool, cancellationToken).ConfigureAwait(false); - return; - } - catch - { - _logger.Error("I-frame image extraction failed, will attempt standard way. Input: {0}", inputArgument); - } - } - - await ExtractImageInternal(inputArgument, type, threedFormat, offset, outputPath, false, resourcePool, cancellationToken).ConfigureAwait(false); - } - public async Task<Stream> ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { @@ -851,134 +817,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder return await ExtractImageInternal(inputArgument, type, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false); } - /// <summary> - /// Extracts the image. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="type">The type.</param> - /// <param name="threedFormat">The threed format.</param> - /// <param name="offset">The offset.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="useIFrame">if set to <c>true</c> [use I frame].</param> - /// <param name="resourcePool">The resource pool.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">inputPath - /// or - /// outputPath</exception> - /// <exception cref="System.ApplicationException"></exception> - private async Task ExtractImageInternal(string inputPath, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. - // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar - var vf = "crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; - - if (threedFormat.HasValue) - { - switch (threedFormat.Value) - { - case Video3DFormat.HalfSideBySide: - vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; - // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. - break; - case Video3DFormat.FullSideBySide: - vf = "crop=iw/2:ih:0:0,setdar=dar=a,,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; - //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. - break; - case Video3DFormat.HalfTopAndBottom: - vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; - //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 - break; - case Video3DFormat.FullTopAndBottom: - vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; - // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600 - break; - } - } - - // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. - var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"thumbnail,{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) : - string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf); - - var probeSize = GetProbeSizeArgument(type); - - if (!string.IsNullOrEmpty(probeSize)) - { - args = probeSize + " " + args; - } - - if (offset.HasValue) - { - args = string.Format("-ss {0} ", Convert.ToInt32(offset.Value.TotalSeconds)).ToString(UsCulture) + args; - } - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - } - }; - - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - var ranToCompletion = StartAndWaitForProcess(process); - - resourcePool.Release(); - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - if (File.Exists(outputPath)) - { - try - { - _logger.Info("Deleting extracted image due to failure: ", outputPath); - File.Delete(outputPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting extracted image {0}", ex, outputPath); - } - } - } - else if (!File.Exists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath); - - _logger.Error(msg); - - throw new ApplicationException(msg); - } - } - private async Task<Stream> ExtractImageInternal(string inputPath, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs index 8a82c062d..b99a84c5c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs @@ -1,10 +1,9 @@ -using System.IO; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Data; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,8 +16,6 @@ namespace MediaBrowser.Server.Implementations.Persistence private readonly ILogger _logger; - private IDbCommand _deleteInfosCommand; - private IDbCommand _saveInfoCommand; private IDbCommand _saveStatusCommand; private readonly IApplicationPaths _appPaths; @@ -48,16 +45,13 @@ namespace MediaBrowser.Server.Implementations.Persistence /// <returns>Task.</returns> public async Task Initialize() { - var dbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db"); + var dbFile = Path.Combine(_appPaths.DataPath, "refreshinfo.db"); _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { - "create table if not exists providerinfos (ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))", - "create index if not exists idx_providerinfos on providerinfos(ItemId, ProviderId)", - - "create table if not exists MetadataStatus (ItemId GUID PRIMARY KEY, DateLastMetadataRefresh datetime, DateLastImagesRefresh datetime, LastStatus TEXT, LastErrorMessage TEXT, MetadataProvidersRefreshed TEXT, ImageProvidersRefreshed TEXT)", + "create table if not exists MetadataStatus (ItemId GUID PRIMARY KEY, ItemName TEXT, ItemType TEXT, SeriesName TEXT, DateLastMetadataRefresh datetime, DateLastImagesRefresh datetime, LastStatus TEXT, LastErrorMessage TEXT, MetadataProvidersRefreshed TEXT, ImageProvidersRefreshed TEXT)", "create index if not exists idx_MetadataStatus on MetadataStatus(ItemId)", //pragmas @@ -73,21 +67,12 @@ namespace MediaBrowser.Server.Implementations.Persistence _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger); } - private static readonly string[] SaveHistoryColumns = - { - "ItemId", - "ProviderId", - "ProviderVersion", - "FileStamp", - "LastRefreshStatus", - "LastRefreshed" - }; - - private readonly string[] _historySelectColumns = SaveHistoryColumns.Skip(1).ToArray(); - private static readonly string[] StatusColumns = { "ItemId", + "ItemName", + "ItemType", + "SeriesName", "DateLastMetadataRefresh", "DateLastImagesRefresh", "LastStatus", @@ -106,21 +91,6 @@ namespace MediaBrowser.Server.Implementations.Persistence /// </summary> private void PrepareStatements() { - _deleteInfosCommand = _connection.CreateCommand(); - _deleteInfosCommand.CommandText = "delete from providerinfos where ItemId=@ItemId"; - _deleteInfosCommand.Parameters.Add(_deleteInfosCommand, "@ItemId"); - - _saveInfoCommand = _connection.CreateCommand(); - - _saveInfoCommand.CommandText = string.Format("replace into providerinfos ({0}) values ({1})", - string.Join(",", SaveHistoryColumns), - string.Join(",", SaveHistoryColumns.Select(i => "@" + i).ToArray())); - - foreach (var col in SaveHistoryColumns) - { - _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@" + col); - } - _saveStatusCommand = _connection.CreateCommand(); _saveStatusCommand.CommandText = string.Format("replace into MetadataStatus ({0}) values ({1})", @@ -133,132 +103,6 @@ namespace MediaBrowser.Server.Implementations.Persistence } } - public IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId) - { - if (itemId == Guid.Empty) - { - throw new ArgumentNullException("itemId"); - } - - using (var cmd = _connection.CreateCommand()) - { - var cmdText = "select " + string.Join(",", _historySelectColumns) + " from providerinfos where"; - - cmdText += " ItemId=@ItemId"; - cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId; - - cmd.CommandText = cmdText; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) - { - while (reader.Read()) - { - yield return GetBaseProviderInfo(reader); - } - } - } - } - - /// <summary> - /// Gets the base provider information. - /// </summary> - /// <param name="reader">The reader.</param> - /// <returns>BaseProviderInfo.</returns> - private BaseProviderInfo GetBaseProviderInfo(IDataReader reader) - { - var item = new BaseProviderInfo - { - ProviderId = reader.GetGuid(0) - }; - - if (!reader.IsDBNull(1)) - { - item.ProviderVersion = reader.GetString(1); - } - - item.FileStamp = reader.GetGuid(2); - item.LastRefreshStatus = (ProviderRefreshStatus)Enum.Parse(typeof(ProviderRefreshStatus), reader.GetString(3), true); - item.LastRefreshed = reader.GetDateTime(4).ToUniversalTime(); - - return item; - } - - public async Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> infos, CancellationToken cancellationToken) - { - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - if (infos == null) - { - throw new ArgumentNullException("infos"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - IDbTransaction transaction = null; - - try - { - transaction = _connection.BeginTransaction(); - - _deleteInfosCommand.GetParameter(0).Value = id; - - _deleteInfosCommand.Transaction = transaction; - - _deleteInfosCommand.ExecuteNonQuery(); - - foreach (var stream in infos) - { - cancellationToken.ThrowIfCancellationRequested(); - - _saveInfoCommand.GetParameter(0).Value = id; - _saveInfoCommand.GetParameter(1).Value = stream.ProviderId; - _saveInfoCommand.GetParameter(2).Value = stream.ProviderVersion; - _saveInfoCommand.GetParameter(3).Value = stream.FileStamp; - _saveInfoCommand.GetParameter(4).Value = stream.LastRefreshStatus.ToString(); - _saveInfoCommand.GetParameter(5).Value = stream.LastRefreshed; - - _saveInfoCommand.Transaction = transaction; - _saveInfoCommand.ExecuteNonQuery(); - } - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - _logger.ErrorException("Failed to save provider info:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - - _writeLock.Release(); - } - } - public MetadataStatus GetMetadataStatus(Guid itemId) { if (itemId == Guid.Empty) @@ -296,32 +140,47 @@ namespace MediaBrowser.Server.Implementations.Persistence if (!reader.IsDBNull(1)) { - result.DateLastMetadataRefresh = reader.GetDateTime(1).ToUniversalTime(); + result.ItemName = reader.GetString(1); } if (!reader.IsDBNull(2)) { - result.DateLastImagesRefresh = reader.GetDateTime(2).ToUniversalTime(); + result.ItemName = reader.GetString(2); } if (!reader.IsDBNull(3)) { - result.LastStatus = (ProviderRefreshStatus)Enum.Parse(typeof(ProviderRefreshStatus), reader.GetString(3), true); + result.SeriesName = reader.GetString(3); } if (!reader.IsDBNull(4)) { - result.LastErrorMessage = reader.GetString(4); + result.DateLastMetadataRefresh = reader.GetDateTime(4).ToUniversalTime(); } if (!reader.IsDBNull(5)) { - result.MetadataProvidersRefreshed = reader.GetString(5).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + result.DateLastImagesRefresh = reader.GetDateTime(5).ToUniversalTime(); } if (!reader.IsDBNull(6)) { - result.ImageProvidersRefreshed = reader.GetString(6).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + result.LastStatus = (ProviderRefreshStatus)Enum.Parse(typeof(ProviderRefreshStatus), reader.GetString(6), true); + } + + if (!reader.IsDBNull(7)) + { + result.LastErrorMessage = reader.GetString(7); + } + + if (!reader.IsDBNull(8)) + { + result.MetadataProvidersRefreshed = reader.GetString(8).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); + } + + if (!reader.IsDBNull(9)) + { + result.ImageProvidersRefreshed = reader.GetString(9).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList(); } return result; @@ -345,12 +204,15 @@ namespace MediaBrowser.Server.Implementations.Persistence transaction = _connection.BeginTransaction(); _saveStatusCommand.GetParameter(0).Value = status.ItemId; - _saveStatusCommand.GetParameter(1).Value = status.DateLastMetadataRefresh; - _saveStatusCommand.GetParameter(2).Value = status.DateLastImagesRefresh; - _saveStatusCommand.GetParameter(3).Value = status.LastStatus.ToString(); - _saveStatusCommand.GetParameter(4).Value = status.LastErrorMessage; - _saveStatusCommand.GetParameter(5).Value = string.Join("|", status.MetadataProvidersRefreshed.ToArray()); - _saveStatusCommand.GetParameter(6).Value = string.Join("|", status.ImageProvidersRefreshed.ToArray()); + _saveStatusCommand.GetParameter(1).Value = status.ItemName; + _saveStatusCommand.GetParameter(2).Value = status.ItemType; + _saveStatusCommand.GetParameter(3).Value = status.SeriesName; + _saveStatusCommand.GetParameter(4).Value = status.DateLastMetadataRefresh; + _saveStatusCommand.GetParameter(5).Value = status.DateLastImagesRefresh; + _saveStatusCommand.GetParameter(6).Value = status.LastStatus.ToString(); + _saveStatusCommand.GetParameter(7).Value = status.LastErrorMessage; + _saveStatusCommand.GetParameter(8).Value = string.Join("|", status.MetadataProvidersRefreshed.ToArray()); + _saveStatusCommand.GetParameter(9).Value = string.Join("|", status.ImageProvidersRefreshed.ToArray()); _saveStatusCommand.Transaction = transaction; diff --git a/MediaBrowser.Providers/RefreshIntrosTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs index bfe7e7609..a65b46f64 100644 --- a/MediaBrowser.Providers/RefreshIntrosTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; @@ -7,7 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace MediaBrowser.Providers +namespace MediaBrowser.Server.Implementations.ScheduledTasks { /// <summary> /// Class RefreshIntrosTask @@ -24,12 +23,13 @@ namespace MediaBrowser.Providers private readonly ILogger _logger; private readonly IFileSystem _fileSystem; - + /// <summary> - /// Initializes a new instance of the <see cref="RefreshIntrosTask"/> class. + /// Initializes a new instance of the <see cref="RefreshIntrosTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> + /// <param name="fileSystem">The file system.</param> public RefreshIntrosTask(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) { _libraryManager = libraryManager; diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index abb60a1d5..c36c49df0 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Implementations; +using System; +using MediaBrowser.Common.Implementations; using MediaBrowser.Controller; using System.IO; @@ -214,18 +215,6 @@ namespace MediaBrowser.Server.Implementations } /// <summary> - /// Gets the images data path. - /// </summary> - /// <value>The images data path.</value> - public string DownloadedImagesDataPath - { - get - { - return Path.Combine(DataPath, "remote-images"); - } - } - - /// <summary> /// Gets the artists path. /// </summary> /// <value>The artists path.</value> @@ -249,5 +238,20 @@ namespace MediaBrowser.Server.Implementations return Path.Combine(ItemsByNamePath, "GameGenre"); } } + + public string InternalMetadataPath + { + get + { + return Path.Combine(DataPath, "metadata"); + } + } + + public string GetInternalMetadataPath(Guid id) + { + var idString = id.ToString("N"); + + return Path.Combine(InternalMetadataPath, idString.Substring(0, 2), idString); + } } } diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index 671312274..4e8469326 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -210,7 +210,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager } catch (Exception ex) { - _logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType); + _logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType ?? string.Empty); } })); diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index fd78d1aaa..09a442966 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -615,13 +615,9 @@ namespace MediaBrowser.Server.Implementations.Session } var items = command.ItemIds.Select(i => _libraryManager.GetItemById(new Guid(i))) + .Where(i => i.LocationType != LocationType.Virtual) .ToList(); - if (items.Any(i => i.LocationType == LocationType.Virtual)) - { - throw new ArgumentException("Virtual items are not playable."); - } - if (command.PlayCommand != PlayCommand.PlayNow) { if (items.Any(i => !session.QueueableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase))) diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index d7858222e..695697f8f 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -309,7 +309,7 @@ namespace MediaBrowser.ServerApplication ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer); RegisterSingleInstance(ImageProcessor); - DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager); + DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(DtoService); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); @@ -497,9 +497,7 @@ namespace MediaBrowser.ServerApplication GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>(), - GetExports<ILibraryPrescanTask>(), - GetExports<ILibraryPostScanTask>(), - GetExports<IPeoplePrescanTask>()); + GetExports<ILibraryPostScanTask>()); ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(), GetExports<IMetadataSaver>()); diff --git a/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id b/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id deleted file mode 100644 index 9f83b949b..000000000 --- a/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -8f1dfd62d31e48c31bef4b9ccc0e514f46650a79
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs index 51d661518..66a99f2e5 100644 --- a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs +++ b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs @@ -258,8 +258,8 @@ namespace MediaBrowser.ServerApplication previews.Add(new PreviewItem(item.GetImagePath(ImageType.Thumb), "Thumb")); } previews.AddRange( - item.BackdropImagePaths.Select( - image => new PreviewItem(image, "Backdrop"))); + item.GetImages(ImageType.Backdrop).Select( + image => new PreviewItem(image.Path, "Backdrop"))); }); lstPreviews.ItemsSource = previews; lstPreviews.Items.Refresh(); diff --git a/MediaBrowser.ServerApplication/NextPvr/LiveTvService.cs b/MediaBrowser.ServerApplication/NextPvr/LiveTvService.cs deleted file mode 100644 index ab72b2c8e..000000000 --- a/MediaBrowser.ServerApplication/NextPvr/LiveTvService.cs +++ /dev/null @@ -1,66 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.LiveTv; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Plugins.NextPvr -{ - /// <summary> - /// Class LiveTvService - /// </summary> - public class LiveTvService : ILiveTvService - { - private readonly ILogger _logger; - - private IApplicationPaths _appPaths; - private IJsonSerializer _json; - private IHttpClient _httpClient; - - public LiveTvService(ILogger logger) - { - _logger = logger; - } - - /// <summary> - /// Gets the channels async. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{ChannelInfo}}.</returns> - public Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken) - { - //using (var stream = await _httpClient.Get(new HttpRequestOptions() - // { - // Url = "", - // CancellationToken = cancellationToken - // })) - //{ - - //} - _logger.Info("GetChannelsAsync"); - - var channels = new List<ChannelInfo> - { - new ChannelInfo - { - Name = "NBC", - ServiceName = Name - } - }; - - return Task.FromResult<IEnumerable<ChannelInfo>>(channels); - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return "Next Pvr"; } - } - } -} diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index d38e020fa..a0540f78d 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.318</version> + <version>3.0.325</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.318" /> + <dependency id="MediaBrowser.Common" version="3.0.325" /> <dependency id="NLog" version="2.1.0" /> <dependency id="SimpleInjector" version="2.4.1" /> <dependency id="sharpcompress" version="0.10.2" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 5ad30a09f..4813ea39b 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.318</version> + <version>3.0.325</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index a24d6c3ac..3a08bb75a 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.318</version> + <version>3.0.325</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.318" /> + <dependency id="MediaBrowser.Common" version="3.0.325" /> </dependencies> </metadata> <files> |
