From d577e1c7b01c45bca49cb47a1af3697f904f9e4d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 16 Oct 2015 13:06:31 -0400 Subject: support image stubbing --- .../Channels/ChannelItemImageProvider.cs | 22 ++++------------------ .../Dto/DtoService.cs | 2 +- .../Library/LibraryManager.cs | 12 ++++++++++++ .../LiveTv/LiveTvManager.cs | 4 ++-- .../LiveTv/ProgramImageProvider.cs | 22 ++++------------------ .../Photos/BaseDynamicImageProvider.cs | 17 ++++++++++++++++- 6 files changed, 39 insertions(+), 40 deletions(-) (limited to 'MediaBrowser.Server.Implementations') 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 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/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/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 GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable 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)); } -- cgit v1.2.3 From a0b1ddf0a7fbe285ce44a971a6f073895b8c2521 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 16 Oct 2015 14:11:11 -0400 Subject: add migrations for new release --- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 5 +- .../Configuration/ServerConfiguration.cs | 4 + .../LiveTv/EmbyTV/EmbyTV.cs | 89 ++++++++++++++++------ .../LiveTv/RefreshChannelsScheduledTask.cs | 2 +- .../LiveTv/TunerHosts/BaseTunerHost.cs | 44 +++++++++-- .../Persistence/CleanDatabaseScheduledTask.cs | 5 +- .../ApplicationHost.cs | 13 ++-- .../MediaBrowser.Server.Startup.Common.csproj | 1 + .../Migrations/Release5767.cs | 47 ++++++++++++ 9 files changed, 169 insertions(+), 41 deletions(-) create mode 100644 MediaBrowser.Server.Startup.Common/Migrations/Release5767.cs (limited to 'MediaBrowser.Server.Implementations') 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 /// The stream identifier. /// The cancellation token. /// Task<MediaSourceInfo>. - Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); + Task> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); /// /// Gets the channel stream media sources. /// 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; } + /// /// Initializes a new instance of the class. /// public ServerConfiguration() { + Migrations = new string[] {}; + ImageSavingConvention = ImageSavingConvention.Compatible; PublicPort = 8096; PublicHttpsPort = 8920; 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 @@ -487,6 +487,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { _logger.Info("Streaming Channel " + channelId); + foreach (var hostInstance in _liveTvManager.TunerHosts) + { + 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> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) + { + _logger.Info("Streaming Channel " + channelId); + foreach (var hostInstance in _liveTvManager.TunerHosts) { try @@ -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/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 GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); - public async Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) + public async Task> 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(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) + /// + /// The _semaphoreLocks + /// + private readonly ConcurrentDictionary _semaphoreLocks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// + /// Gets the lock. + /// + /// The filename. + /// System.Object. + 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.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 840ccd8af..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 progress) + public override Task Init(IProgress 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(); + var migrations = new List + { + new Release5767(ServerConfigurationManager, TaskManager) + }; foreach (var task in migrations) { 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 @@ + 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(); + _taskManager.QueueScheduledTask(); + _taskManager.QueueScheduledTask(); + }); + + var list = _config.Configuration.Migrations.ToList(); + list.Add(name); + _config.Configuration.Migrations = list.ToArray(); + _config.SaveConfiguration(); + } + } +} -- cgit v1.2.3