diff options
29 files changed, 366 insertions, 149 deletions
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 120a4cedb..af086ce6d 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -18,6 +18,7 @@ using System.Threading; using System.Threading.Tasks; using CommonIO; using Emby.Drawing.Common; +using MediaBrowser.Controller.Library; namespace Emby.Drawing { @@ -53,18 +54,20 @@ namespace Emby.Drawing private readonly IServerApplicationPaths _appPaths; private readonly IImageEncoder _imageEncoder; private readonly SemaphoreSlim _imageProcessingSemaphore; + private readonly Func<ILibraryManager> _libraryManager; public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IImageEncoder imageEncoder, - int maxConcurrentImageProcesses) + int maxConcurrentImageProcesses, Func<ILibraryManager> libraryManager) { _logger = logger; _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _imageEncoder = imageEncoder; + _libraryManager = libraryManager; _appPaths = appPaths; ImageEnhancers = new List<IImageEnhancer>(); @@ -158,7 +161,14 @@ namespace Emby.Drawing throw new ArgumentNullException("options"); } - var originalImagePath = options.Image.Path; + var originalImage = options.Image; + + if (!originalImage.IsLocalFile) + { + originalImage = await _libraryManager().ConvertImageToLocal(options.Item, originalImage, options.ImageIndex).ConfigureAwait(false); + } + + var originalImagePath = originalImage.Path; if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace) { @@ -166,7 +176,7 @@ namespace Emby.Drawing return originalImagePath; } - var dateModified = options.Image.DateModified; + var dateModified = originalImage.DateModified; if (options.CropWhiteSpace) { @@ -181,7 +191,7 @@ namespace Emby.Drawing var tuple = await GetEnhancedImage(new ItemImageInfo { DateModified = dateModified, - Type = options.Image.Type, + Type = originalImage.Type, Path = originalImagePath }, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); @@ -360,16 +370,6 @@ namespace Emby.Drawing return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower()); } - /// <summary> - /// Gets the size of the image. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>ImageSize.</returns> - public ImageSize GetImageSize(string path) - { - return GetImageSize(path, _fileSystem.GetLastWriteTimeUtc(path), false); - } - public ImageSize GetImageSize(ItemImageInfo info) { return GetImageSize(info.Path, info.DateModified, false); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index d264b896d..65b837eac 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -319,10 +319,13 @@ namespace MediaBrowser.Api.Images try { - var size = _imageProcessor.GetImageSize(info); + if (info.IsLocalFile) + { + var size = _imageProcessor.GetImageSize(info); - width = Convert.ToInt32(size.Width); - height = Convert.ToInt32(size.Height); + width = Convert.ToInt32(size.Width); + height = Convert.ToInt32(size.Height); + } } catch { diff --git a/MediaBrowser.Api/Social/SharingService.cs b/MediaBrowser.Api/Social/SharingService.cs index 608008455..7d61c24ec 100644 --- a/MediaBrowser.Api/Social/SharingService.cs +++ b/MediaBrowser.Api/Social/SharingService.cs @@ -124,7 +124,7 @@ namespace MediaBrowser.Api.Social Task.WaitAll(task); } - public object Get(GetShareImage request) + public async Task<object> Get(GetShareImage request) { var share = _sharingManager.GetShareInfo(request.Id); @@ -143,7 +143,21 @@ namespace MediaBrowser.Api.Social if (image != null) { - return ToStaticFileResult(image.Path); + if (image.IsLocalFile) + { + return ToStaticFileResult(image.Path); + } + + try + { + // Don't fail the request over this + var updatedImage = await _libraryManager.ConvertImageToLocal(item, image, 0).ConfigureAwait(false); + return ToStaticFileResult(updatedImage.Path); + } + catch + { + + } } // Grab a dlna icon if nothing else is available diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index aeb817392..838dfc9fb 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -28,13 +28,6 @@ namespace MediaBrowser.Controller.Drawing /// <summary> /// Gets the size of the image. /// </summary> - /// <param name="path">The path.</param> - /// <returns>ImageSize.</returns> - ImageSize GetImageSize(string path); - - /// <summary> - /// Gets the size of the image. - /// </summary> /// <param name="info">The information.</param> /// <returns>ImageSize.</returns> ImageSize GetImageSize(ItemImageInfo info); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 22ca607ba..0595473f2 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1432,6 +1432,23 @@ namespace MediaBrowser.Controller.Entities return GetImageInfo(type, imageIndex) != null; } + public void SetImage(ItemImageInfo image, int index) + { + if (image.Type == ImageType.Chapter) + { + throw new ArgumentException("Cannot set chapter images using SetImagePath"); + } + + var existingImage = GetImageInfo(image.Type, index); + + if (existingImage != null) + { + ImageInfos.Remove(existingImage); + } + + ImageInfos.Add(image); + } + public void SetImagePath(ImageType type, int index, FileSystemMetadata file) { if (type == ImageType.Chapter) @@ -1473,18 +1490,21 @@ namespace MediaBrowser.Controller.Entities // Remove it from the item RemoveImage(info); - // Delete the source file - var currentFile = new FileInfo(info.Path); - - // Deletion will fail if the file is hidden so remove the attribute first - if (currentFile.Exists) + if (info.IsLocalFile) { - if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + // Delete the source file + var currentFile = new FileInfo(info.Path); + + // Deletion will fail if the file is hidden so remove the attribute first + if (currentFile.Exists) { - currentFile.Attributes &= ~FileAttributes.Hidden; - } + if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + currentFile.Attributes &= ~FileAttributes.Hidden; + } - FileSystem.DeleteFile(currentFile.FullName); + FileSystem.DeleteFile(currentFile.FullName); + } } return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); @@ -1505,11 +1525,16 @@ namespace MediaBrowser.Controller.Entities /// </summary> public bool ValidateImages(IDirectoryService directoryService) { - 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 allFiles = ImageInfos + .Where(i => i.IsLocalFile) + .Select(i => System.IO.Path.GetDirectoryName(i.Path)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .SelectMany(directoryService.GetFiles) + .Select(i => i.FullName) + .ToList(); var deletedImages = ImageInfos - .Where(image => !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase)) + .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase)) .ToList(); if (deletedImages.Count > 0) @@ -1619,7 +1644,10 @@ namespace MediaBrowser.Controller.Entities } else { - existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage); + if (existing.IsLocalFile) + { + existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage); + } } } @@ -1628,7 +1656,7 @@ namespace MediaBrowser.Controller.Entities var newImagePaths = images.Select(i => i.FullName).ToList(); var deleted = existingImages - .Where(i => !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !FileSystem.FileExists(i.Path)) + .Where(i => i.IsLocalFile && !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !FileSystem.FileExists(i.Path)) .ToList(); ImageInfos = ImageInfos.Except(deleted).ToList(); @@ -1679,6 +1707,12 @@ namespace MediaBrowser.Controller.Entities return Task.FromResult(true); } + if (!info1.IsLocalFile || !info2.IsLocalFile) + { + // TODO: Not supported yet + return Task.FromResult(true); + } + var path1 = info1.Path; var path2 = info2.Path; diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index ef478c516..da729176d 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -2,9 +2,11 @@ using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Entities { @@ -191,6 +193,21 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <param name="image">The image.</param> void RemoveImage(ItemImageInfo image); + + /// <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> + /// Sets the image. + /// </summary> + /// <param name="image">The image.</param> + /// <param name="index">The index.</param> + void SetImage(ItemImageInfo image, int index); } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index 4eb25718e..473ee120e 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -37,14 +37,6 @@ namespace MediaBrowser.Controller.Entities DateTime DateLastRefreshed { get; set; } /// <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 true or false indicating if changes were made /// </summary> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index 9f3074c5e..bb113e596 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -29,6 +29,13 @@ namespace MediaBrowser.Controller.Entities { get { + if (Path != null) + { + if (Path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } return true; } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 980a3bbb5..c7ab88524 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -534,5 +534,14 @@ namespace MediaBrowser.Controller.Library /// <param name="to">To.</param> /// <returns>System.String.</returns> string SubstitutePath(string path, string from, string to); + + /// <summary> + /// Converts the image to local. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="image">The image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>Task.</returns> + Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index bedbcffe3..2e3a71f70 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Dto; +using System; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.LiveTv; using System.Collections.Generic; using System.Threading; @@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="streamId">The stream identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task<MediaSourceInfo>.</returns> - Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); + Task<Tuple<MediaSourceInfo,SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); /// <summary> /// Gets the channel stream media sources. /// </summary> diff --git a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs index 71a937cd9..fdd1891ed 100644 --- a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs +++ b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs @@ -1,12 +1,14 @@ using System; using System.IO; using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.Providers { public class DynamicImageResponse { public string Path { get; set; } + public MediaProtocol Protocol { get; set; } public Stream Stream { get; set; } public ImageFormat Format { get; set; } public bool HasImage { get; set; } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 06a35ac0d..dfcafa32d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -226,11 +226,15 @@ namespace MediaBrowser.Model.Configuration public bool EnableDateLastRefresh { get; set; } + public string[] Migrations { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="ServerConfiguration" /> class. /// </summary> public ServerConfiguration() { + Migrations = new string[] {}; + ImageSavingConvention = ImageSavingConvention.Compatible; PublicPort = 8096; PublicHttpsPort = 8920; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 4106d3dca..9d183c880 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Manager source = memoryStream; - var currentPath = GetCurrentImagePath(item, type, index); + var currentImage = GetCurrentImage(item, type, index); using (source) { @@ -160,8 +160,10 @@ namespace MediaBrowser.Providers.Manager SetImagePath(item, type, imageIndex, paths[0]); // Delete the current path - if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) + if (currentImage != null && currentImage.IsLocalFile && !paths.Contains(currentImage.Path, StringComparer.OrdinalIgnoreCase)) { + var currentPath = currentImage.Path; + _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try @@ -301,9 +303,9 @@ namespace MediaBrowser.Providers.Manager /// or /// imageIndex /// </exception> - private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex) + private ItemImageInfo GetCurrentImage(IHasImages item, ImageType type, int imageIndex) { - return item.GetImagePath(type, imageIndex); + return item.GetImageInfo(type, imageIndex); } /// <summary> diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index b39deba89..cfa3e67f5 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -17,6 +17,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Providers.Manager { @@ -138,11 +139,24 @@ namespace MediaBrowser.Providers.Manager { if (!string.IsNullOrEmpty(response.Path)) { - var mimeType = MimeTypes.GetMimeType(response.Path); - - var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, true); - - await _providerManager.SaveImage(item, stream, mimeType, imageType, null, response.InternalCacheKey, cancellationToken).ConfigureAwait(false); + if (response.Protocol == MediaProtocol.Http) + { + _logger.Debug("Setting image url into item {0}", item.Id); + item.SetImage(new ItemImageInfo + { + Path = response.Path, + Type = imageType + + }, 0); + } + else + { + var mimeType = MimeTypes.GetMimeType(response.Path); + + var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, true); + + await _providerManager.SaveImage(item, stream, mimeType, imageType, null, response.InternalCacheKey, cancellationToken).ConfigureAwait(false); + } } else { @@ -391,7 +405,7 @@ namespace MediaBrowser.Providers.Manager else { var existing = item.GetImageInfo(type, 0); - if (existing != null && !_fileSystem.FileExists(existing.Path)) + if (existing != null && !_fileSystem.FileExists(existing.Path)) { item.RemoveImage(existing); changed = true; diff --git a/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs b/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs index 2ba700b09..7abe9cc07 100644 --- a/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs @@ -22,13 +22,15 @@ namespace MediaBrowser.Providers.Music var image = album.GetRecursiveChildren() .OfType<Audio>() - .Select(i => i.GetImagePath(type)) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + .Select(i => i.GetImageInfo(type, 0)) + .FirstOrDefault(i => i != null && i.IsLocalFile); + + var imagePath = image == null ? null : image.Path; return Task.FromResult(new DynamicImageResponse { - Path = image, - HasImage = !string.IsNullOrEmpty(image) + Path = imagePath, + HasImage = !string.IsNullOrEmpty(imagePath) }); } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelItemImageProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelItemImageProvider.cs index 55e6e0103..7cffa58db 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelItemImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelItemImageProvider.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Server.Implementations.Channels { @@ -35,24 +36,9 @@ namespace MediaBrowser.Server.Implementations.Channels if (!string.IsNullOrEmpty(channelItem.ExternalImagePath)) { - var options = new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = channelItem.ExternalImagePath - }; - - var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - - if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - imageResponse.HasImage = true; - imageResponse.Stream = response.Content; - imageResponse.SetFormatFromMimeType(response.ContentType); - } - else - { - _logger.Error("Provider did not return an image content type."); - } + imageResponse.Path = channelItem.ExternalImagePath; + imageResponse.Protocol = MediaProtocol.Http; + imageResponse.HasImage = true; } return imageResponse; diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 5c0b5e5b2..97867d6a2 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1743,7 +1743,7 @@ namespace MediaBrowser.Server.Implementations.Dto { var imageInfo = item.GetImageInfo(ImageType.Primary, 0); - if (imageInfo == null) + if (imageInfo == null || !imageInfo.IsLocalFile) { return; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 0156a46a9..4ccfca1bf 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2354,5 +2354,17 @@ namespace MediaBrowser.Server.Implementations.Library return ItemRepository.UpdatePeople(item.Id, people); } + + private readonly SemaphoreSlim _dynamicImageResourcePool = new SemaphoreSlim(1,1); + public async Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex) + { + _logger.Debug("ConvertImageToLocal item {0}", item.Id); + + await _providerManagerFactory().SaveImage(item, image.Path, _dynamicImageResourcePool, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); + + await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); + + return item.GetImageInfo(image.Type, imageIndex); + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3ec64a017..0d4960795 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -491,6 +491,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { try { + var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); + + result.Item2.Release(); + + return result.Item1; + } + catch (Exception e) + { + _logger.ErrorException("Error getting channel stream", e); + } + } + + throw new ApplicationException("Tuner not found."); + } + + private async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) + { + _logger.Info("Streaming Channel " + channelId); + + foreach (var hostInstance in _liveTvManager.TunerHosts) + { + try + { return await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); } catch (Exception e) @@ -653,40 +676,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None); - - // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg - await Task.Delay(3000, cancellationToken).ConfigureAwait(false); + var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None); + var mediaStreamInfo = result.Item1; + var isResourceOpen = true; - var duration = recordingEndDate - DateTime.UtcNow; - - HttpRequestOptions httpRequestOptions = new HttpRequestOptions() + // Unfortunately due to the semaphore we have to have a nested try/finally + try { - Url = mediaStreamInfo.Path - }; + // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg + await Task.Delay(3000, cancellationToken).ConfigureAwait(false); + + var duration = recordingEndDate - DateTime.UtcNow; - recording.Path = recordPath; - recording.Status = RecordingStatus.InProgress; - recording.DateLastUpdated = DateTime.UtcNow; - _recordingProvider.Update(recording); + HttpRequestOptions httpRequestOptions = new HttpRequestOptions() + { + Url = mediaStreamInfo.Path + }; + + recording.Path = recordPath; + recording.Status = RecordingStatus.InProgress; + recording.DateLastUpdated = DateTime.UtcNow; + _recordingProvider.Update(recording); + + _logger.Info("Beginning recording."); + + httpRequestOptions.BufferContent = false; + var durationToken = new CancellationTokenSource(duration); + var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + httpRequestOptions.CancellationToken = linkedToken; + _logger.Info("Writing file to path: " + recordPath); + using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) + { + using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + result.Item2.Release(); + isResourceOpen = false; - _logger.Info("Beginning recording."); + await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); + } + } - httpRequestOptions.BufferContent = false; - var durationToken = new CancellationTokenSource(duration); - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - httpRequestOptions.CancellationToken = linkedToken; - _logger.Info("Writing file to path: " + recordPath); - using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) + recording.Status = RecordingStatus.Completed; + _logger.Info("Recording completed"); + } + finally { - using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + if (isResourceOpen) { - await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); + result.Item2.Release(); } } - - recording.Status = RecordingStatus.Completed; - _logger.Info("Recording completed"); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 65125da66..538a963fd 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1446,7 +1446,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { dto.ChannelName = channel.Name; - if (!string.IsNullOrEmpty(channel.PrimaryImagePath)) + if (channel.HasImage(ImageType.Primary)) { dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel); } @@ -1512,7 +1512,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { dto.ChannelName = channel.Name; - if (!string.IsNullOrEmpty(channel.PrimaryImagePath)) + if (channel.HasImage(ImageType.Primary)) { dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index ba9ce0db5..e0d90d349 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -40,24 +41,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { if (liveTvItem.ExternalImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { - var options = new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = liveTvItem.ExternalImagePath - }; - - var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - - if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - imageResponse.HasImage = true; - imageResponse.Stream = response.Content; - imageResponse.SetFormatFromMimeType(response.ContentType); - } - else - { - _logger.Error("Provider did not return an image content type."); - } + imageResponse.Path = liveTvItem.ExternalImagePath; + imageResponse.Protocol = MediaProtocol.Http; + imageResponse.HasImage = true; } else { diff --git a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index d8d91c2f9..3fb1d9661 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey + public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey { private readonly ILiveTvManager _liveTvManager; private readonly IConfigurationManager _config; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 616d01a32..d811152c2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -141,7 +141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); - public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) + public async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) { if (IsValidChannelId(channelId)) { @@ -173,9 +173,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts try { var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); - - await AddMediaInfo(stream, false, cancellationToken).ConfigureAwait(false); - return stream; + var resourcePool = GetLock(host.Url); + + await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false); + return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool); } catch (Exception ex) { @@ -187,7 +188,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts throw new LiveTvConflictException(); } - private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) + /// <summary> + /// The _semaphoreLocks + /// </summary> + private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase); + /// <summary> + /// Gets the lock. + /// </summary> + /// <param name="url">The filename.</param> + /// <returns>System.Object.</returns> + private SemaphoreSlim GetLock(string url) + { + return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1)); + } + + private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + { + await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false); + + // Leave the resource locked. it will be released upstream + } + catch (Exception) + { + // Release the resource if there's some kind of failure. + resourcePool.Release(); + + throw; + } + } + + private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) { var originalRuntime = mediaSource.RunTimeTicks; diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 6259c61af..60b8c00bd 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Progress; +using MediaBrowser.Common.Progress; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -17,7 +16,7 @@ using MediaBrowser.Controller.Entities.Audio; namespace MediaBrowser.Server.Implementations.Persistence { - class CleanDatabaseScheduledTask : IScheduledTask + public class CleanDatabaseScheduledTask : IScheduledTask { private readonly ILibraryManager _libraryManager; private readonly IItemRepository _itemRepo; diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 37ee2b319..b07b5b8c4 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -125,7 +125,22 @@ namespace MediaBrowser.Server.Implementations.Photos protected virtual IEnumerable<string> GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable<BaseItem> items) { return items - .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) + .Select(i => + { + var image = i.GetImageInfo(ImageType.Primary, 0); + + if (image != null && image.IsLocalFile) + { + return image.Path; + } + image = i.GetImageInfo(ImageType.Thumb, 0); + + if (image != null && image.IsLocalFile) + { + return image.Path; + } + return null; + }) .Where(i => !string.IsNullOrWhiteSpace(i)); } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 0f01e8aea..bcb75d9a0 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -333,18 +333,18 @@ namespace MediaBrowser.Server.Startup.Common }); LogManager.RemoveConsoleOutput(); + + PerformPostInitMigrations(); } - public override async Task Init(IProgress<double> progress) + public override Task Init(IProgress<double> progress) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; PerformPreInitMigrations(); - await base.Init(progress).ConfigureAwait(false); - - PerformPostInitMigrations(); + return base.Init(progress); } private void PerformPreInitMigrations() @@ -362,7 +362,10 @@ namespace MediaBrowser.Server.Startup.Common private void PerformPostInitMigrations() { - var migrations = new List<IVersionMigration>(); + var migrations = new List<IVersionMigration> + { + new Release5767(ServerConfigurationManager, TaskManager) + }; foreach (var task in migrations) { @@ -563,7 +566,7 @@ namespace MediaBrowser.Server.Startup.Common int.TryParse(_startupOptions.GetOption("-imagethreads"), NumberStyles.Any, CultureInfo.InvariantCulture, out maxConcurrentImageProcesses); } - return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, GetImageEncoder(), maxConcurrentImageProcesses); + return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, GetImageEncoder(), maxConcurrentImageProcesses, () => LibraryManager); } private IImageEncoder GetImageEncoder() diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 9def89073..13b782e40 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -72,6 +72,7 @@ <Compile Include="INativeApp.cs" /> <Compile Include="MbLinkShortcutHandler.cs" /> <Compile Include="Migrations\IVersionMigration.cs" /> + <Compile Include="Migrations\Release5767.cs" /> <Compile Include="Migrations\RenameXmlOptions.cs" /> <Compile Include="NativeEnvironment.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> diff --git a/MediaBrowser.Server.Startup.Common/Migrations/Release5767.cs b/MediaBrowser.Server.Startup.Common/Migrations/Release5767.cs new file mode 100644 index 000000000..9a4580c12 --- /dev/null +++ b/MediaBrowser.Server.Startup.Common/Migrations/Release5767.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Server.Implementations.LiveTv; +using MediaBrowser.Server.Implementations.Persistence; +using MediaBrowser.Server.Implementations.ScheduledTasks; + +namespace MediaBrowser.Server.Startup.Common.Migrations +{ + public class Release5767 : IVersionMigration + { + private readonly IServerConfigurationManager _config; + private readonly ITaskManager _taskManager; + + public Release5767(IServerConfigurationManager config, ITaskManager taskManager) + { + _config = config; + _taskManager = taskManager; + } + + public void Run() + { + var name = "5767"; + + if (_config.Configuration.Migrations.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + Task.Run(async () => + { + await Task.Delay(3000).ConfigureAwait(false); + + _taskManager.QueueScheduledTask<RefreshChannelsScheduledTask>(); + _taskManager.QueueScheduledTask<CleanDatabaseScheduledTask>(); + _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>(); + }); + + var list = _config.Configuration.Migrations.ToList(); + list.Add(name); + _config.Configuration.Migrations = list.ToArray(); + _config.SaveConfiguration(); + } + } +} diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 0e5a74a9a..fa58b67da 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -884,11 +884,11 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteStartElement("art"); - var poster = item.PrimaryImagePath; + var image = item.GetImageInfo(ImageType.Primary, 0); - if (!string.IsNullOrEmpty(poster)) + if (image != null && image.IsLocalFile) { - writer.WriteElementString("poster", GetPathToSave(item.PrimaryImagePath, libraryManager, config)); + writer.WriteElementString("poster", GetPathToSave(image.Path, libraryManager, config)); } foreach (var backdrop in item.GetImages(ImageType.Backdrop)) @@ -985,10 +985,11 @@ namespace MediaBrowser.XbmcMetadata.Savers try { var personEntity = libraryManager.GetPerson(person.Name); + var image = personEntity.GetImageInfo(ImageType.Primary, 0); - if (!string.IsNullOrEmpty(personEntity.PrimaryImagePath)) + if (image != null && image.IsLocalFile) { - writer.WriteElementString("thumb", GetPathToSave(personEntity.PrimaryImagePath, libraryManager, config)); + writer.WriteElementString("thumb", GetPathToSave(image.Path, libraryManager, config)); } } catch (Exception) |
