diff options
96 files changed, 1616 insertions, 467 deletions
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index e29bbf674..0a39b51c3 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -318,7 +318,7 @@ namespace MediaBrowser.Api.Images try { - var size = _imageProcessor.GetImageSize(info.Path, info.DateModified); + var size = _imageProcessor.GetImageSize(info); width = Convert.ToInt32(size.Width); height = Convert.ToInt32(size.Height); diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 85cc879f4..4d9afa260 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Activity; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -9,11 +8,9 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using ServiceStack; using System; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 271305921..fc48e9a3a 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -81,6 +81,7 @@ <Compile Include="Library\ChapterService.cs" /> <Compile Include="Playback\Hls\MpegDashService.cs" /> <Compile Include="Playback\MediaInfoService.cs" /> + <Compile Include="Playback\TranscodingThrottler.cs" /> <Compile Include="PlaylistService.cs" /> <Compile Include="Reports\ReportFieldType.cs" /> <Compile Include="Reports\ReportResult.cs" /> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 49d5f1c8d..a40e0f8c3 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Model.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; @@ -70,12 +71,14 @@ namespace MediaBrowser.Api.Playback protected IDeviceManager DeviceManager { get; private set; } protected IChannelManager ChannelManager { get; private set; } protected ISubtitleEncoder SubtitleEncoder { get; private set; } + protected IProcessManager ProcessManager { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// </summary> - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) + protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) { + ProcessManager = processManager; DeviceManager = deviceManager; SubtitleEncoder = subtitleEncoder; ChannelManager = channelManager; @@ -877,14 +880,6 @@ namespace MediaBrowser.Api.Playback return "copy"; } - private bool SupportsThrottleWithStream - { - get - { - return false; - } - } - /// <summary> /// Gets the input argument. /// </summary> @@ -908,23 +903,15 @@ namespace MediaBrowser.Api.Playback private string GetInputPathArgument(string transcodingJobId, StreamState state) { - if (state.InputProtocol == MediaProtocol.File && - state.RunTimeTicks.HasValue && - state.VideoType == VideoType.VideoFile && - !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) - { - if (SupportsThrottleWithStream) - { - var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId; - - url += "&transcodingJobId=" + transcodingJobId; - - return string.Format("\"{0}\"", url); - } - } - } + //if (state.InputProtocol == MediaProtocol.File && + // state.RunTimeTicks.HasValue && + // state.VideoType == VideoType.VideoFile && + // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + //{ + // if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) + // { + // } + //} var protocol = state.InputProtocol; @@ -1109,9 +1096,26 @@ namespace MediaBrowser.Api.Playback } } + StartThrottler(state, transcodingJob); + return transcodingJob; } + private void StartThrottler(StreamState state, TranscodingJob transcodingJob) + { + if (state.InputProtocol == MediaProtocol.File && + state.RunTimeTicks.HasValue && + state.VideoType == VideoType.VideoFile && + !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) + { + state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ProcessManager); + state.TranscodingThrottler.Start(); + } + } + } + private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) { try diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 2da5c33ce..fdfa6e6d7 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -23,7 +24,7 @@ namespace MediaBrowser.Api.Playback.Hls /// </summary> public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager) { } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index e639dbdfe..4f4f5f1cb 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Model.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; @@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) + public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager) { NetworkManager = networkManager; } @@ -115,6 +116,7 @@ namespace MediaBrowser.Api.Playback.Hls if (File.Exists(segmentPath)) { + job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false); } @@ -123,6 +125,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (File.Exists(segmentPath)) { + job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false); } else diff --git a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs index 80451c0cc..05909402c 100644 --- a/MediaBrowser.Api/Playback/Hls/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Hls/MpegDashService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -51,7 +52,7 @@ namespace MediaBrowser.Api.Playback.Hls public class MpegDashService : BaseHlsService { - public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) + public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager) { NetworkManager = networkManager; } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 8de52ea02..d27296bfd 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -57,7 +58,7 @@ namespace MediaBrowser.Api.Playback.Hls /// </summary> public class VideoHlsService : BaseHlsService { - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) + public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager) { } diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 77178c8cc..330a8777c 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Playback { [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")] + [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")] public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult> { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 37155b8f9..08ec13f4f 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; @@ -32,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// </summary> public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient) + public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager, imageProcessor, httpClient) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 9dbe3389e..0af4587b6 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; @@ -29,7 +30,7 @@ namespace MediaBrowser.Api.Playback.Progressive protected readonly IImageProcessor ImageProcessor; protected readonly IHttpClient HttpClient; - protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager) + protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager) { ImageProcessor = imageProcessor; HttpClient = httpClient; @@ -153,49 +154,12 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { - var job = string.IsNullOrEmpty(request.TranscodingJobId) ? - null : - ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId); - - var limits = new List<long>(); - if (state.InputBitrate.HasValue) - { - // Bytes per second - limits.Add((state.InputBitrate.Value / 8)); - } - if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue) - { - var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds; - - if (totalSeconds > 1) - { - var timeBasedLimit = state.InputFileSize.Value / totalSeconds; - limits.Add(Convert.ToInt64(timeBasedLimit)); - } - } - - // Take the greater of the above to methods, just to be safe - var throttleLimit = limits.Count > 0 ? limits.First() : 0; - - // Pad to play it safe - var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit); - - // Don't even start evaluating this until at least two minutes have content have been consumed - var targetGap = throttleLimit * 120; - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { ResponseHeaders = responseHeaders, ContentType = contentType, IsHeadRequest = isHeadRequest, - Path = state.MediaPath, - Throttle = request.Throttle, - - ThrottleLimit = bytesPerSecond, - - MinThrottlePosition = targetGap, - - ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job) + Path = state.MediaPath }); } } @@ -234,67 +198,6 @@ namespace MediaBrowser.Api.Playback.Progressive } } - private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(3).Ticks; - - private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job) - { - var bytesDownloaded = job.BytesDownloaded ?? 0; - var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; - var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - - var path = job.Path; - - if (bytesDownloaded > 0 && transcodingPositionTicks > 0) - { - // Progressive Streaming - byte-based consideration - - try - { - var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; - - // Estimate the bytes the transcoder should be ahead - double gapFactor = _gapLengthInTicks; - gapFactor /= transcodingPositionTicks; - var targetGap = bytesTranscoded * gapFactor; - - var gap = bytesTranscoded - bytesDownloaded; - - if (gap < targetGap) - { - //Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return 0; - } - - //Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - } - catch - { - //Logger.Error("Error getting output size"); - } - } - else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) - { - // HLS - time-based consideration - - var targetGap = _gapLengthInTicks; - var gap = transcodingPositionTicks - downloadPositionTicks; - - if (gap < targetGap) - { - //Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); - return 0; - } - - //Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); - } - else - { - //Logger.Debug("No throttle data for " + path); - } - - return originalBytesPerSecond; - } - /// <summary> /// Gets the static remote stream result. /// </summary> diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 7e86b867f..9b161085a 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; @@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// </summary> public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient) + public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager, imageProcessor, httpClient) { } diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index dc22fc754..907b17b24 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -72,7 +72,6 @@ namespace MediaBrowser.Api.Playback public string Params { get; set; } public string ClientTime { get; set; } - public bool Throttle { get; set; } public string TranscodingJobId { get; set; } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 40e765f1a..588d3b75c 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,17 +1,16 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading; -using MediaBrowser.Model.Net; namespace MediaBrowser.Api.Playback { @@ -23,6 +22,7 @@ namespace MediaBrowser.Api.Playback public string RequestedUrl { get; set; } public StreamRequest Request { get; set; } + public TranscodingThrottler TranscodingThrottler { get; set; } public VideoStreamRequest VideoRequest { @@ -125,6 +125,7 @@ namespace MediaBrowser.Api.Playback public void Dispose() { + DisposeTranscodingThrottler(); DisposeLiveStream(); DisposeLogStream(); DisposeIsoMount(); @@ -147,6 +148,23 @@ namespace MediaBrowser.Api.Playback } } + private void DisposeTranscodingThrottler() + { + if (TranscodingThrottler != null) + { + try + { + TranscodingThrottler.Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing TranscodingThrottler", ex); + } + + TranscodingThrottler = null; + } + } + private void DisposeIsoMount() { if (IsoMount != null) diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs new file mode 100644 index 000000000..432f4667d --- /dev/null +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -0,0 +1,164 @@ +using MediaBrowser.Controller.Diagnostics; +using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; + +namespace MediaBrowser.Api.Playback +{ + public class TranscodingThrottler : IDisposable + { + private readonly TranscodingJob _job; + private readonly ILogger _logger; + private readonly IProcessManager _processManager; + private Timer _timer; + private bool _isPaused; + + public void Start() + { + if (_processManager.SupportsSuspension) + { + _timer = new Timer(TimerCallback, null, 5000, 5000); + } + } + + private void TimerCallback(object state) + { + if (_job.HasExited) + { + DisposeTimer(); + return; + } + + if (IsThrottleAllowed(_job)) + { + PauseTranscoding(); + } + else + { + UnpauseTranscoding(); + } + } + + private void PauseTranscoding() + { + if (!_isPaused) + { + _logger.Debug("Sending pause command to ffmpeg"); + } + + try + { + //_job.Process.StandardInput.WriteLine("p"); + _processManager.SuspendProcess(_job.Process); + _isPaused = true; + } + catch (Exception ex) + { + _logger.ErrorException("Error pausing transcoding", ex); + } + } + + private void UnpauseTranscoding() + { + if (_isPaused) + { + _logger.Debug("Sending unpause command to ffmpeg"); + } + + try + { + //_job.Process.StandardInput.WriteLine("u"); + _processManager.ResumeProcess(_job.Process); + _isPaused = false; + } + catch (Exception ex) + { + _logger.ErrorException("Error unpausing transcoding", ex); + } + } + + private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks; + + public TranscodingThrottler(TranscodingJob job, ILogger logger, IProcessManager processManager) + { + _job = job; + _logger = logger; + _processManager = processManager; + } + + private bool IsThrottleAllowed(TranscodingJob job) + { + var bytesDownloaded = job.BytesDownloaded ?? 0; + var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; + var downloadPositionTicks = job.DownloadPositionTicks ?? 0; + + var path = job.Path; + + if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) + { + // HLS - time-based consideration + + var targetGap = _gapLengthInTicks; + var gap = transcodingPositionTicks - downloadPositionTicks; + + if (gap < targetGap) + { + //_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); + return false; + } + + //_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); + return true; + } + + if (bytesDownloaded > 0 && transcodingPositionTicks > 0) + { + // Progressive Streaming - byte-based consideration + + try + { + var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; + + // Estimate the bytes the transcoder should be ahead + double gapFactor = _gapLengthInTicks; + gapFactor /= transcodingPositionTicks; + var targetGap = bytesTranscoded * gapFactor; + + var gap = bytesTranscoded - bytesDownloaded; + + if (gap < targetGap) + { + //_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); + return false; + } + + //_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); + return true; + } + catch + { + //_logger.Error("Error getting output size"); + return false; + } + } + + //_logger.Debug("No throttle data for " + path); + return false; + } + + public void Dispose() + { + DisposeTimer(); + } + + private void DisposeTimer() + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + } +} diff --git a/MediaBrowser.Controller/Diagnostics/IProcessManager.cs b/MediaBrowser.Controller/Diagnostics/IProcessManager.cs new file mode 100644 index 000000000..2e076bd88 --- /dev/null +++ b/MediaBrowser.Controller/Diagnostics/IProcessManager.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; + +namespace MediaBrowser.Controller.Diagnostics +{ + /// <summary> + /// Interface IProcessManager + /// </summary> + public interface IProcessManager + { + /// <summary> + /// Gets a value indicating whether [supports suspension]. + /// </summary> + /// <value><c>true</c> if [supports suspension]; otherwise, <c>false</c>.</value> + bool SupportsSuspension { get; } + + /// <summary> + /// Suspends the process. + /// </summary> + /// <param name="process">The process.</param> + void SuspendProcess(Process process); + + /// <summary> + /// Resumes the process. + /// </summary> + /// <param name="process">The process.</param> + void ResumeProcess(Process process); + } +} diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 8ac7d56d2..6fafc2b46 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; -using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -30,10 +29,9 @@ namespace MediaBrowser.Controller.Drawing /// <summary> /// Gets the size of the image. /// </summary> - /// <param name="path">The path.</param> - /// <param name="imageDateModified">The image date modified.</param> + /// <param name="info">The information.</param> /// <returns>ImageSize.</returns> - ImageSize GetImageSize(string path, DateTime imageDateModified); + ImageSize GetImageSize(ItemImageInfo info); /// <summary> /// Adds the parts. diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 902447999..d868227d9 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.Audio { Id = i.Id.ToString("N"), Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File, - MediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(), + MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(), Name = i.Name, Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path, RunTimeTicks = i.RunTimeTicks, diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index d4507bc33..a0c3a6cf9 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -420,12 +420,17 @@ namespace MediaBrowser.Controller.Entities return base.GetDeletePaths(); } - public virtual IEnumerable<MediaStream> GetMediaStreams() + public IEnumerable<MediaStream> GetMediaStreams() { - return MediaSourceManager.GetMediaStreams(new MediaStreamQuery + var mediaSource = GetMediaSources(false) + .FirstOrDefault(); + + if (mediaSource == null) { - ItemId = Id - }); + return new List<MediaStream>(); + } + + return mediaSource.MediaStreams; } public virtual MediaStream GetDefaultVideoStream() @@ -474,7 +479,7 @@ namespace MediaBrowser.Controller.Entities private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type) { - var mediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }) + var mediaStreams = MediaSourceManager.GetMediaStreams(i.Id) .ToList(); var locationType = i.LocationType; diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 4378bc85d..5d79f613d 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -1,11 +1,29 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Library { public interface IMediaSourceManager { + /// <summary> + /// Gets the media streams. + /// </summary> + /// <param name="itemId">The item identifier.</param> + /// <returns>IEnumerable<MediaStream>.</returns> + IEnumerable<MediaStream> GetMediaStreams(Guid itemId); + /// <summary> + /// Gets the media streams. + /// </summary> + /// <param name="mediaSourceId">The media source identifier.</param> + /// <returns>IEnumerable<MediaStream>.</returns> + IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId); + /// <summary> + /// Gets the media streams. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>IEnumerable<MediaStream>.</returns> IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query); } } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 8119e5afb..a167cdbed 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Library event EventHandler<GenericEventArgs<User>> UserCreated; event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; event EventHandler<GenericEventArgs<User>> UserPasswordChanged; + event EventHandler<GenericEventArgs<User>> UserLockedOut; /// <summary> /// Gets a User by Id diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index e9531e057..36809c5d3 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -104,6 +104,7 @@ <Compile Include="Devices\CameraImageUploadInfo.cs" /> <Compile Include="Devices\IDeviceManager.cs" /> <Compile Include="Devices\IDeviceRepository.cs" /> + <Compile Include="Diagnostics\IProcessManager.cs" /> <Compile Include="Dlna\ControlRequest.cs" /> <Compile Include="Dlna\ControlResponse.cs" /> <Compile Include="Dlna\EventSubscriptionResponse.cs" /> @@ -341,8 +342,8 @@ <Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" /> <Compile Include="Subtitles\SubtitleResponse.cs" /> <Compile Include="Subtitles\SubtitleSearchRequest.cs" /> - <Compile Include="Sync\ICloudSyncProvider.cs" /> <Compile Include="Sync\IServerSyncProvider.cs" /> + <Compile Include="Sync\ISyncDataProvider.cs" /> <Compile Include="Sync\ISyncManager.cs" /> <Compile Include="Sync\ISyncProvider.cs" /> <Compile Include="Sync\ISyncRepository.cs" /> diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs index 5bb2c9a5c..6a104554a 100644 --- a/MediaBrowser.Controller/Net/StaticResultOptions.cs +++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs @@ -18,11 +18,6 @@ namespace MediaBrowser.Controller.Net public IDictionary<string, string> ResponseHeaders { get; set; } - public bool Throttle { get; set; } - public long ThrottleLimit { get; set; } - public long MinThrottlePosition { get; set; } - public Func<long, long, long> ThrottleCallback { get; set; } - public Action OnComplete { get; set; } public StaticResultOptions() diff --git a/MediaBrowser.Controller/Sync/ICloudSyncProvider.cs b/MediaBrowser.Controller/Sync/ICloudSyncProvider.cs deleted file mode 100644 index dd7fda2c5..000000000 --- a/MediaBrowser.Controller/Sync/ICloudSyncProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using MediaBrowser.Model.Sync; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Sync -{ - public interface ICloudSyncProvider - { - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - string Name { get; } - - /// <summary> - /// Gets the synchronize targets. - /// </summary> - /// <param name="userId">The user identifier.</param> - /// <returns>IEnumerable<SyncTarget>.</returns> - IEnumerable<SyncTarget> GetSyncTargets(string userId); - - /// <summary> - /// Transfers the item file. - /// </summary> - /// <param name="serverId">The server identifier.</param> - /// <param name="itemId">The item identifier.</param> - /// <param name="inputFile">The input file.</param> - /// <param name="pathParts">The path parts.</param> - /// <param name="target">The target.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs index d11dd9d73..775a3648d 100644 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs @@ -1,5 +1,7 @@ using MediaBrowser.Model.Sync; +using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -8,34 +10,63 @@ namespace MediaBrowser.Controller.Sync public interface IServerSyncProvider : ISyncProvider { /// <summary> - /// Gets the server item ids. + /// Transfers the file. /// </summary> - /// <param name="serverId">The server identifier.</param> + /// <param name="inputFile">The input file.</param> + /// <param name="path">The path.</param> /// <param name="target">The target.</param> + /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task<List<System.String>>.</returns> - Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken); + /// <returns>Task.</returns> + Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken); /// <summary> - /// Removes the item. + /// Deletes the file. /// </summary> - /// <param name="serverId">The server identifier.</param> - /// <param name="itemId">The item identifier.</param> + /// <param name="path">The path.</param> /// <param name="target">The target.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken); + Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken); /// <summary> - /// Transfers the file. + /// Gets the file. /// </summary> - /// <param name="serverId">The server identifier.</param> - /// <param name="itemId">The item identifier.</param> - /// <param name="inputFile">The input file.</param> - /// <param name="pathParts">The path parts.</param> + /// <param name="path">The path.</param> /// <param name="target">The target.</param> + /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken); + /// <returns>Task<Stream>.</returns> + Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken); + + /// <summary> + /// Gets the full path. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="target">The target.</param> + /// <returns>System.String.</returns> + string GetFullPath(IEnumerable<string> path, SyncTarget target); + + /// <summary> + /// Gets the parent directory path. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="target">The target.</param> + /// <returns>System.String.</returns> + string GetParentDirectoryPath(string path, SyncTarget target); + + /// <summary> + /// Gets the file system entries. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="target">The target.</param> + /// <returns>Task<List<DeviceFileInfo>>.</returns> + Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target); + + /// <summary> + /// Gets the data provider. + /// </summary> + /// <returns>ISyncDataProvider.</returns> + ISyncDataProvider GetDataProvider(); } } diff --git a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs new file mode 100644 index 000000000..cf698dd3c --- /dev/null +++ b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Model.Sync; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Sync +{ + public interface ISyncDataProvider + { + /// <summary> + /// Gets the server item ids. + /// </summary> + /// <param name="target">The target.</param> + /// <param name="serverId">The server identifier.</param> + /// <returns>Task<List<System.String>>.</returns> + Task<List<string>> GetServerItemIds(SyncTarget target, string serverId); + + /// <summary> + /// Adds the or update. + /// </summary> + /// <param name="target">The target.</param> + /// <param name="item">The item.</param> + /// <returns>Task.</returns> + Task AddOrUpdate(SyncTarget target, LocalItem item); + + /// <summary> + /// Deletes the specified identifier. + /// </summary> + /// <param name="target">The target.</param> + /// <param name="id">The identifier.</param> + /// <returns>Task.</returns> + Task Delete(SyncTarget target, string id); + + /// <summary> + /// Gets the specified identifier. + /// </summary> + /// <param name="target">The target.</param> + /// <param name="id">The identifier.</param> + /// <returns>Task<LocalItem>.</returns> + Task<LocalItem> Get(SyncTarget target, string id); + } +} diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs index 6f24eac1a..ef34bfe69 100644 --- a/MediaBrowser.Controller/Sync/ISyncProvider.cs +++ b/MediaBrowser.Controller/Sync/ISyncProvider.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Sync; +using MediaBrowser.Model.Sync; using System.Collections.Generic; namespace MediaBrowser.Controller.Sync @@ -18,13 +17,12 @@ namespace MediaBrowser.Controller.Sync /// <param name="userId">The user identifier.</param> /// <returns>IEnumerable<SyncTarget>.</returns> IEnumerable<SyncTarget> GetSyncTargets(string userId); - + /// <summary> - /// Gets the device profile. + /// Gets all synchronize targets. /// </summary> - /// <param name="target">The target.</param> - /// <returns>DeviceProfile.</returns> - DeviceProfile GetDeviceProfile(SyncTarget target); + /// <returns>IEnumerable<SyncTarget>.</returns> + IEnumerable<SyncTarget> GetAllSyncTargets(); } public interface IHasUniqueTargetIds diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index af7c8dbed..b2eedad7c 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -930,7 +930,7 @@ namespace MediaBrowser.Dlna.Didl try { - var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified); + var size = _imageProcessor.GetImageSize(imageInfo); width = Convert.ToInt32(size.Width); height = Convert.ToInt32(size.Height); diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index ffae8612e..ead080c60 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -452,24 +452,6 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetInputPathArgument(EncodingJob job) { - //if (job.InputProtocol == MediaProtocol.File && - // job.RunTimeTicks.HasValue && - // job.VideoType == VideoType.VideoFile && - // !string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - //{ - // if (job.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && job.IsInputVideo) - // { - // if (SupportsThrottleWithStream) - // { - // var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + job.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + job.Request.MediaSourceId; - - // url += "&transcodingJobId=" + transcodingJobId; - - // return string.Format("\"{0}\"", url); - // } - // } - //} - var protocol = job.InputProtocol; var inputPath = new[] { job.MediaPath }; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index b9cad27e0..7d74c51ba 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -773,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { try { - using (var file = new FileStream(path, FileMode.Open)) + using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { var detector = new CharsetDetector(); detector.Feed(file); @@ -797,12 +797,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles return null; } - private static Encoding GetFileEncoding(string srcFile) + private Encoding GetFileEncoding(string srcFile) { // *** Detect byte order mark if any - otherwise assume default var buffer = new byte[5]; - using (var file = new FileStream(srcFile, FileMode.Open)) + using (var file = _fileSystem.GetFileStream(srcFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { file.Read(buffer, 0, 5); } diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 37057f2d7..62677f818 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -362,6 +362,12 @@ <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs"> <Link>Dlna\MediaFormatProfileResolver.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs"> + <Link>Dlna\PlaybackErrorCode.cs</Link> + </Compile> + <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackException.cs"> + <Link>Dlna\PlaybackException.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs"> <Link>Dlna\ProfileCondition.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index f38a8f597..4ed8cceae 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -327,6 +327,12 @@ <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs"> <Link>Dlna\MediaFormatProfileResolver.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs"> + <Link>Dlna\PlaybackErrorCode.cs</Link> + </Compile> + <Compile Include="..\MediaBrowser.Model\Dlna\PlaybackException.cs"> + <Link>Dlna\PlaybackException.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs"> <Link>Dlna\ProfileCondition.cs</Link> </Compile> diff --git a/MediaBrowser.Model/ApiClient/ConnectionOptions.cs b/MediaBrowser.Model/ApiClient/ConnectionOptions.cs index 445eaa04e..e12676311 100644 --- a/MediaBrowser.Model/ApiClient/ConnectionOptions.cs +++ b/MediaBrowser.Model/ApiClient/ConnectionOptions.cs @@ -13,11 +13,17 @@ namespace MediaBrowser.Model.ApiClient /// </summary> /// <value><c>true</c> if [report capabilities]; otherwise, <c>false</c>.</value> public bool ReportCapabilities { get; set; } + /// <summary> + /// Gets or sets a value indicating whether [update date last accessed]. + /// </summary> + /// <value><c>true</c> if [update date last accessed]; otherwise, <c>false</c>.</value> + public bool UpdateDateLastAccessed { get; set; } public ConnectionOptions() { EnableWebSocket = true; ReportCapabilities = true; + UpdateDateLastAccessed = true; } } } diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index aa49ee50d..a78161140 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -33,20 +33,12 @@ namespace MediaBrowser.Model.Configuration public bool DisplayMissingEpisodes { get; set; } public bool DisplayUnairedEpisodes { get; set; } - public bool EnableLiveTvManagement { get; set; } - public bool EnableLiveTvAccess { get; set; } - - public bool EnableMediaPlayback { get; set; } - public bool EnableContentDeletion { get; set; } - public bool GroupMoviesIntoBoxSets { get; set; } public string[] DisplayChannelsWithinViews { get; set; } public string[] ExcludeFoldersFromGrouping { get; set; } - public UnratedItem[] BlockUnratedItems { get; set; } - public SubtitlePlaybackMode SubtitleMode { get; set; } public bool DisplayCollectionsView { get; set; } public bool DisplayFoldersView { get; set; } @@ -69,14 +61,10 @@ namespace MediaBrowser.Model.Configuration public UserConfiguration() { PlayDefaultAudioTrack = true; - EnableLiveTvManagement = true; - EnableMediaPlayback = true; - EnableLiveTvAccess = true; LatestItemsExcludes = new string[] { }; OrderedViews = new string[] { }; DisplayChannelsWithinViews = new string[] { }; - BlockUnratedItems = new UnratedItem[] { }; ExcludeFoldersFromGrouping = new string[] { }; DisplayCollectionsView = true; diff --git a/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs b/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs new file mode 100644 index 000000000..4ed412985 --- /dev/null +++ b/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs @@ -0,0 +1,10 @@ + +namespace MediaBrowser.Model.Dlna +{ + public enum PlaybackErrorCode + { + NotAllowed = 0, + NoCompatibleStream = 1, + RateLimitExceeded = 2 + } +} diff --git a/MediaBrowser.Model/Dlna/PlaybackException.cs b/MediaBrowser.Model/Dlna/PlaybackException.cs new file mode 100644 index 000000000..761fa1c90 --- /dev/null +++ b/MediaBrowser.Model/Dlna/PlaybackException.cs @@ -0,0 +1,9 @@ +using System; + +namespace MediaBrowser.Model.Dlna +{ + public class PlaybackException : Exception + { + public PlaybackErrorCode ErrorCode { get; set;} + } +} diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 6fa29a533..559a543f2 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -31,7 +31,13 @@ namespace MediaBrowser.Model.Dlna List<StreamInfo> streams = new List<StreamInfo>(); foreach (MediaSourceInfo i in mediaSources) - streams.Add(BuildAudioItem(i, options)); + { + StreamInfo streamInfo = BuildAudioItem(i, options); + if (streamInfo != null) + { + streams.Add(streamInfo); + } + } foreach (StreamInfo stream in streams) { @@ -63,7 +69,13 @@ namespace MediaBrowser.Model.Dlna List<StreamInfo> streams = new List<StreamInfo>(); foreach (MediaSourceInfo i in mediaSources) - streams.Add(BuildVideoItem(i, options)); + { + StreamInfo streamInfo = BuildVideoItem(i, options); + if (streamInfo != null) + { + streams.Add(streamInfo); + } + } foreach (StreamInfo stream in streams) { @@ -97,7 +109,10 @@ namespace MediaBrowser.Model.Dlna { return stream; } - return null; + + PlaybackException error = new PlaybackException(); + error.ErrorCode = PlaybackErrorCode.NoCompatibleStream; + throw error; } private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) @@ -186,6 +201,11 @@ namespace MediaBrowser.Model.Dlna if (transcodingProfile != null) { + if (!item.SupportsTranscoding) + { + return null; + } + playlistItem.PlayMethod = PlayMethod.Transcode; playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength; @@ -267,7 +287,7 @@ namespace MediaBrowser.Model.Dlna if (subtitleStream != null) { - SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile); + SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; @@ -290,9 +310,14 @@ namespace MediaBrowser.Model.Dlna if (transcodingProfile != null) { + if (!item.SupportsTranscoding) + { + return null; + } + if (subtitleStream != null) { - SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile); + SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; @@ -527,7 +552,7 @@ namespace MediaBrowser.Model.Dlna { if (subtitleStream != null) { - SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile); + SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context); if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) { @@ -538,14 +563,20 @@ namespace MediaBrowser.Model.Dlna return IsAudioEligibleForDirectPlay(item, maxBitrate); } - public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, DeviceProfile deviceProfile) + public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, DeviceProfile deviceProfile, EncodingContext context) { // Look for an external profile that matches the stream type (text/graphical) foreach (SubtitleProfile profile in deviceProfile.SubtitleProfiles) { - if (subtitleStream.SupportsExternalStream) + if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) { - if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) + if (subtitleStream.SupportsExternalStream) + { + return profile; + } + + // For sync we can handle the longer extraction times + if (context == EncodingContext.Static && subtitleStream.IsTextSubtitleStream) { return profile; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 57a3899d4..f0854c3f8 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -262,7 +262,7 @@ namespace MediaBrowser.Model.Dlna private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream) { - SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile); + SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile, Context); if (subtitleProfile.Method != SubtitleDeliveryMethod.External) { diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 068443238..cdc97b7ea 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -22,6 +22,7 @@ namespace MediaBrowser.Model.Dto public long? RunTimeTicks { get; set; } public bool ReadAtNativeFramerate { get; set; } + public bool SupportsTranscoding { get; set; } public VideoType? VideoType { get; set; } @@ -45,6 +46,7 @@ namespace MediaBrowser.Model.Dto MediaStreams = new List<MediaStream>(); RequiredHttpHeaders = new Dictionary<string, string>(); PlayableStreamFileNames = new List<string>(); + SupportsTranscoding = true; } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 9fd632cbd..27b5a53db 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -125,6 +125,8 @@ <Compile Include="Devices\DeviceInfo.cs" /> <Compile Include="Devices\DevicesOptions.cs" /> <Compile Include="Dlna\EncodingContext.cs" /> + <Compile Include="Dlna\PlaybackErrorCode.cs" /> + <Compile Include="Dlna\PlaybackException.cs" /> <Compile Include="Dlna\Profiles\DefaultProfile.cs" /> <Compile Include="Dlna\ResolutionConfiguration.cs" /> <Compile Include="Dlna\ResolutionNormalizer.cs" /> diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs index 269e27a4f..f5e3624f0 100644 --- a/MediaBrowser.Model/Notifications/NotificationType.cs +++ b/MediaBrowser.Model/Notifications/NotificationType.cs @@ -19,6 +19,7 @@ namespace MediaBrowser.Model.Notifications NewLibraryContentMultiple, ServerRestartRequired, TaskFailed, - CameraImageUploaded + CameraImageUploaded, + UserLockedOut } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 410cdc51f..7efc2cf6f 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -59,6 +59,8 @@ namespace MediaBrowser.Model.Users public string[] EnabledFolders { get; set; } public bool EnableAllFolders { get; set; } + public int InvalidLoginAttemptCount { get; set; } + public UserPolicy() { EnableLiveTvManagement = true; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index ab6cb89a6..523d39375 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -397,7 +397,10 @@ namespace MediaBrowser.Providers.Manager refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; // Only one local provider allowed per item - hasLocalMetadata = true; + if (IsFullLocalMetadata(localItem.Item)) + { + hasLocalMetadata = true; + } successfulProviderCount++; break; } @@ -473,6 +476,11 @@ namespace MediaBrowser.Providers.Manager return refreshResult; } + protected virtual bool IsFullLocalMetadata(TItemType item) + { + return true; + } + private async Task ImportUserData(TItemType item, List<UserItemData> userDataList, CancellationToken cancellationToken) { var hasUserData = item as IHasUserData; diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index af9970b69..172ae6814 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -33,5 +33,22 @@ namespace MediaBrowser.Providers.Movies target.TmdbCollectionName = source.TmdbCollectionName; } } + + protected override bool IsFullLocalMetadata(Movie item) + { + if (string.IsNullOrWhiteSpace(item.Name)) + { + return false; + } + if (string.IsNullOrWhiteSpace(item.Overview)) + { + return false; + } + if (!item.ProductionYear.HasValue) + { + return false; + } + return base.IsFullLocalMetadata(item); + } } } diff --git a/MediaBrowser.Providers/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs index 3eaef59fb..96160dcc4 100644 --- a/MediaBrowser.Providers/Photos/PhotoProvider.cs +++ b/MediaBrowser.Providers/Photos/PhotoProvider.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Providers.Photos try { - var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified); + var size = _imageProcessor.GetImageSize(imageInfo); item.Width = Convert.ToInt32(size.Width); item.Height = Convert.ToInt32(size.Height); diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index df06525c1..a5959f0b7 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -74,5 +74,22 @@ namespace MediaBrowser.Providers.TV await provider.Run(item, CancellationToken.None).ConfigureAwait(false); } } + + protected override bool IsFullLocalMetadata(Series item) + { + if (string.IsNullOrWhiteSpace(item.Name)) + { + return false; + } + if (string.IsNullOrWhiteSpace(item.Overview)) + { + return false; + } + if (!item.ProductionYear.HasValue) + { + return false; + } + return base.IsFullLocalMetadata(item); + } } } diff --git a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs index 2fe5d8f74..566f4c5f4 100644 --- a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs +++ b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; +using System; using System.IO; using System.Linq; @@ -14,6 +15,11 @@ namespace MediaBrowser.Server.Implementations.Devices public override bool IsVisible(User user) { + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + { + return false; + } + return GetChildren(user, true).Any() && base.IsVisible(user); } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs b/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs index 81d4a786a..6287d0bb8 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs @@ -62,8 +62,9 @@ namespace MediaBrowser.Server.Implementations.Drawing logger.Info("Failed to read image header for {0}. Doing it the slow way.", path); } - using (var wand = new MagickWand(path)) + using (var wand = new MagickWand()) { + wand.PingImage(path); var img = wand.CurrentImage; return new ImageSize diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 85eadd73c..180faa6bb 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -350,9 +350,9 @@ namespace MediaBrowser.Server.Implementations.Drawing } /// <summary> - /// Increment this when indicator drawings change + /// Increment this when there's a change requiring caches to be invalidated /// </summary> - private const string IndicatorVersion = "2"; + private const string Version = "3"; /// <summary> /// Gets the cache file path based on a set of parameters @@ -371,29 +371,19 @@ namespace MediaBrowser.Server.Implementations.Drawing filename += "f=" + format; - var hasIndicator = false; - if (addPlayedIndicator) { filename += "pl=true"; - hasIndicator = true; } if (percentPlayed > 0) { filename += "p=" + percentPlayed; - hasIndicator = true; } if (unwatchedCount.HasValue) { filename += "p=" + unwatchedCount.Value; - hasIndicator = true; - } - - if (hasIndicator) - { - filename += "iv=" + IndicatorVersion; } if (!string.IsNullOrEmpty(backgroundColor)) @@ -401,6 +391,8 @@ namespace MediaBrowser.Server.Implementations.Drawing filename += "b=" + backgroundColor; } + filename += "v=" + Version; + return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower()); } @@ -414,6 +406,11 @@ namespace MediaBrowser.Server.Implementations.Drawing return GetImageSize(path, File.GetLastWriteTimeUtc(path)); } + public ImageSize GetImageSize(ItemImageInfo info) + { + return GetImageSize(info.Path, info.DateModified); + } + /// <summary> /// Gets the size of the image. /// </summary> @@ -421,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="imageDateModified">The image date modified.</param> /// <returns>ImageSize.</returns> /// <exception cref="System.ArgumentNullException">path</exception> - public ImageSize GetImageSize(string path, DateTime imageDateModified) + private ImageSize GetImageSize(string path, DateTime imageDateModified) { if (string.IsNullOrEmpty(path)) { @@ -666,30 +663,6 @@ namespace MediaBrowser.Server.Implementations.Drawing return enhancedImagePath; } - private ImageFormat GetFormat(string path) - { - var extension = Path.GetExtension(path); - - if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Png; - } - if (string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Gif; - } - if (string.Equals(extension, ".webp", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Webp; - } - if (string.Equals(extension, ".bmp", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Bmp; - } - - return ImageFormat.Jpg; - } - /// <summary> /// Executes the image enhancers. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index b15809738..75037159c 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1598,14 +1598,11 @@ namespace MediaBrowser.Server.Implementations.Dto var path = imageInfo.Path; - // See if we can avoid a file system lookup by looking for the file in ResolveArgs - var dateModified = imageInfo.DateModified; - ImageSize size; try { - size = _imageProcessor.GetImageSize(path, dateModified); + size = _imageProcessor.GetImageSize(imageInfo); } catch (FileNotFoundException) { diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs index 0b0661321..28883e9a2 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs @@ -86,6 +86,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; _userManager.UserDeleted += _userManager_UserDeleted; _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; + _userManager.UserLockedOut += _userManager_UserLockedOut; //_config.ConfigurationUpdated += _config_ConfigurationUpdated; //_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; @@ -95,6 +96,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; } + void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name), + Type = "UserLockedOut", + UserId = e.Argument.Id.ToString("N") + }); + } + void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) { CreateLogEntry(new ActivityLogEntry @@ -482,6 +493,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; _userManager.UserDeleted -= _userManager_UserDeleted; _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; + _userManager.UserLockedOut -= _userManager_UserLockedOut; _config.ConfigurationUpdated -= _config_ConfigurationUpdated; _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs index 37bca4ddb..f6a35973b 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs @@ -78,6 +78,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; _deviceManager.CameraImageUploaded +=_deviceManager_CameraImageUploaded; + + _userManager.UserLockedOut += _userManager_UserLockedOut; + } + + async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e) + { + var type = NotificationType.UserLockedOut.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type + }; + + notification.Variables["UserName"] = e.Argument.Name; + + await SendNotification(notification).ConfigureAwait(false); } async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) @@ -235,7 +251,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications return; } - var notification = new NotificationRequest { NotificationType = type @@ -471,6 +486,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; + _userManager.UserLockedOut -= _userManager_UserLockedOut; } private void DisposeLibraryUpdateTimer() diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 681d3ac5e..ecf58e4a6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -461,10 +461,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer { return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest) { - Throttle = options.Throttle, - ThrottleLimit = options.ThrottleLimit, - MinThrottlePosition = options.MinThrottlePosition, - ThrottleCallback = options.ThrottleCallback, OnComplete = options.OnComplete }; } @@ -480,10 +476,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer return new StreamWriter(stream, contentType, _logger) { - Throttle = options.Throttle, - ThrottleLimit = options.ThrottleLimit, - MinThrottlePosition = options.MinThrottlePosition, - ThrottleCallback = options.ThrottleCallback, OnComplete = options.OnComplete }; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index cdd4c6d7c..8c72f9e7e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -24,10 +24,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer private long RangeLength { get; set; } private long TotalContentLength { get; set; } - public bool Throttle { get; set; } - public long ThrottleLimit { get; set; } - public long MinThrottlePosition; - public Func<long, long, long> ThrottleCallback { get; set; } public Action OnComplete { get; set; } /// <summary> @@ -165,14 +161,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - if (Throttle) - { - responseStream = new ThrottledStream(responseStream, ThrottleLimit) - { - MinThrottlePosition = MinThrottlePosition, - ThrottleCallback = ThrottleCallback - }; - } WriteToInternal(responseStream); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index 1db14e887..fe662542e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -35,10 +35,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer get { return _options; } } - public bool Throttle { get; set; } - public long ThrottleLimit { get; set; } - public long MinThrottlePosition; - public Func<long, long, long> ThrottleCallback { get; set; } public Action OnComplete { get; set; } /// <summary> @@ -82,14 +78,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - if (Throttle) - { - responseStream = new ThrottledStream(responseStream, ThrottleLimit) - { - MinThrottlePosition = MinThrottlePosition, - ThrottleCallback = ThrottleCallback - }; - } WriteToInternal(responseStream); } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index a45757d13..6ce989b02 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; using System.Linq; @@ -47,5 +48,59 @@ namespace MediaBrowser.Server.Implementations.Library { return true; } + + public IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId) + { + var list = GetMediaStreams(new MediaStreamQuery + { + ItemId = new Guid(mediaSourceId) + }); + + return GetMediaStreamsForItem(list); + } + + public IEnumerable<MediaStream> GetMediaStreams(Guid itemId) + { + var list = GetMediaStreams(new MediaStreamQuery + { + ItemId = itemId + }); + + return GetMediaStreamsForItem(list); + } + + private IEnumerable<MediaStream> GetMediaStreamsForItem(IEnumerable<MediaStream> streams) + { + var list = streams.ToList(); + + var subtitleStreams = list + .Where(i => i.Type == MediaStreamType.Subtitle) + .ToList(); + + if (subtitleStreams.Count > 0) + { + var videoStream = list.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + // This is abitrary but at some point it becomes too slow to extract subtitles on the fly + // We need to learn more about when this is the case vs. when it isn't + const int maxAllowedBitrateForExternalSubtitleStream = 10000000; + + var videoBitrate = videoStream == null ? maxAllowedBitrateForExternalSubtitleStream : videoStream.BitRate ?? maxAllowedBitrateForExternalSubtitleStream; + + foreach (var subStream in subtitleStreams) + { + var supportsExternalStream = StreamSupportsExternalStream(subStream); + + if (supportsExternalStream && videoBitrate >= maxAllowedBitrateForExternalSubtitleStream) + { + supportsExternalStream = false; + } + + subStream.SupportsExternalStream = supportsExternalStream; + } + } + + return list; + } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 7371ca5a9..3551b71b7 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,19 +1,18 @@ -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; -using System; -using System.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Naming.Common; using MediaBrowser.Naming.IO; using MediaBrowser.Naming.TV; using MediaBrowser.Server.Implementations.Logging; -using EpisodeInfo = MediaBrowser.Controller.Providers.EpisodeInfo; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index bf8792461..00c674436 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -97,6 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> public event EventHandler<GenericEventArgs<User>> UserUpdated; public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; + public event EventHandler<GenericEventArgs<User>> UserLockedOut; /// <summary> /// Called when [user updated]. @@ -259,6 +260,11 @@ namespace MediaBrowser.Server.Implementations.Library { user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; await UpdateUser(user).ConfigureAwait(false); + await UpdateInvalidLoginAttemptCount(user, 0).ConfigureAwait(false); + } + else + { + await UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1).ConfigureAwait(false); } _logger.Info("Authentication request for {0} {1}.", user.Name, (success ? "has succeeded" : "has been denied")); @@ -266,6 +272,38 @@ namespace MediaBrowser.Server.Implementations.Library return success; } + private async Task UpdateInvalidLoginAttemptCount(User user, int newValue) + { + if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0) + { + user.Policy.InvalidLoginAttemptCount = newValue; + + var maxCount = user.Policy.IsAdministrator ? + 3 : + 5; + + var fireLockout = false; + + if (newValue >= maxCount) + { + _logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); + user.Policy.IsDisabled = true; + + fireLockout = true; + } + + await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false); + + if (fireLockout) + { + if (UserLockedOut != null) + { + EventHelper.FireEventIfNotNull(UserLockedOut, this, new GenericEventArgs<User>(user), _logger); + } + } + } + } + private string GetPasswordHash(User user) { return string.IsNullOrEmpty(user.Password) @@ -332,11 +370,6 @@ namespace MediaBrowser.Server.Implementations.Library { if (!user.Configuration.HasMigratedToPolicy) { - user.Policy.BlockUnratedItems = user.Configuration.BlockUnratedItems; - user.Policy.EnableContentDeletion = user.Configuration.EnableContentDeletion; - user.Policy.EnableLiveTvAccess = user.Configuration.EnableLiveTvAccess; - user.Policy.EnableLiveTvManagement = user.Configuration.EnableLiveTvManagement; - user.Policy.EnableMediaPlayback = user.Configuration.EnableMediaPlayback; user.Policy.IsAdministrator = user.Configuration.IsAdministrator; await UpdateUserPolicy(user, user.Policy, false); @@ -915,10 +948,6 @@ namespace MediaBrowser.Server.Implementations.Library } user.Configuration.IsAdministrator = user.Policy.IsAdministrator; - user.Configuration.EnableLiveTvManagement = user.Policy.EnableLiveTvManagement; - user.Configuration.EnableLiveTvAccess = user.Policy.EnableLiveTvAccess; - user.Configuration.EnableMediaPlayback = user.Policy.EnableMediaPlayback; - user.Configuration.EnableContentDeletion = user.Policy.EnableContentDeletion; await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index f1bb5c13a..6473d10d6 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return null; } - private const string InternalVersionNumber = "3"; + private const string InternalVersionNumber = "4"; public Guid GetInternalChannelId(string serviceName, string externalId) { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index dc80778da..2f593efcd 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -48,8 +48,10 @@ "LabelDashboardSourcePathHelp": "If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location.", "ButtonConvertMedia": "Convert media", "ButtonOrganize": "Organize", + "LabelPinCode": "Pin code:", "ButtonOk": "Ok", "ButtonCancel": "Cancel", + "ButtonExit": "Exit", "ButtonNew": "New", "HeaderTV": "TV", "HeaderAudio": "Audio", @@ -57,6 +59,12 @@ "HeaderPaths": "Paths", "CategorySync": "Sync", "HeaderEasyPinCode": "Easy Pin Code", + "HeaderGrownupsOnly": "Grown-ups Only!", + "DividerOr": "-- or --", + "HeaderToAccessPleaseEnterEasyPinCode": "To access, please enter your easy pin code", + "KidsModeAdultInstruction": "Click the lock icon in the bottom right to configure or leave kids mode. Your pin code will be required.", + "ButtonConfigurePinCode": "Configure pin code", + "HeaderAdultsReadHere": "Adults Read Here!", "RegisterWithPayPal": "Register with PayPal", "HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership", "HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial", @@ -670,6 +678,7 @@ "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)", "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionUserLockedOut": "User locked out", "SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.", "NotificationOptionServerRestartRequired": "Server restart required", "LabelNotificationEnabled": "Enable this notification", @@ -1061,6 +1070,7 @@ "OptionBox": "Box", "OptionBoxRear": "Box rear", "OptionDisc": "Disc", + "OptionIcon": "Icon", "OptionLogo": "Logo", "OptionMenu": "Menu", "OptionScreenshot": "Screenshot", @@ -1105,6 +1115,7 @@ "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "LabelRunningTimeValue": "Running time: {0}", "LabelIpAddressValue": "Ip address: {0}", + "UserLockedOutWithName": "User {0} has been locked out", "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", "UserCreatedWithName": "User {0} has been created", "UserPasswordChangedWithName": "Password has been changed for user {0}", @@ -1114,7 +1125,7 @@ "MessageApplicationUpdated": "Media Browser Server has been updated", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "UserDownloadingItemWithValues": "{0} is downloading {1}", + "UserDownloadingItemWithValues": "{0} is downloading {1}", "UserStartedPlayingItemWithValues": "{0} has started playing {1}", "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", "AppDeviceValues": "App: {0}, Device: {1}", @@ -1369,5 +1380,7 @@ "TabJobs": "Jobs", "TabSyncJobs": "Sync Jobs", "LabelTagFilterMode": "Mode:", - "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well." + "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well.", + "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", + "MessageReenableUser": "See below to reenable" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 509a274ee..54df9a86d 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -47,7 +47,7 @@ <ItemGroup> <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath> + <HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath> </Reference> <Reference Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> @@ -278,6 +278,7 @@ <Compile Include="Sorting\CommunityRatingComparer.cs" /> <Compile Include="Sorting\CriticRatingComparer.cs" /> <Compile Include="Sorting\DateCreatedComparer.cs" /> + <Compile Include="Sorting\DateLastMediaAddedComparer.cs" /> <Compile Include="Sorting\DatePlayedComparer.cs" /> <Compile Include="Sorting\GameSystemComparer.cs" /> <Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" /> @@ -303,8 +304,12 @@ <Compile Include="Sorting\StudioComparer.cs" /> <Compile Include="Sorting\VideoBitRateComparer.cs" /> <Compile Include="Sync\AppSyncProvider.cs" /> - <Compile Include="Sync\CloudSyncProvider.cs" /> + <Compile Include="Sync\FolderSync\FolderSyncDataProvider.cs" /> + <Compile Include="Sync\FolderSync\FolderSyncProvider.cs" /> + <Compile Include="Sync\CloudSyncProfile.cs" /> + <Compile Include="Sync\IHasSyncProfile.cs" /> <Compile Include="Sync\MediaSync.cs" /> + <Compile Include="Sync\MultiProviderSync.cs" /> <Compile Include="Sync\SyncRegistrationInfo.cs" /> <Compile Include="Sync\SyncConfig.cs" /> <Compile Include="Sync\SyncJobProcessor.cs" /> diff --git a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs index d8acbe06c..a33fe2147 100644 --- a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs +++ b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs @@ -143,6 +143,13 @@ namespace MediaBrowser.Server.Implementations.Notifications Type = NotificationType.CameraImageUploaded.ToString(), DefaultTitle = "A new camera image has been uploaded from {DeviceName}.", Variables = new List<string>{"DeviceName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.UserLockedOut.ToString(), + DefaultTitle = "{UserName} has been locked out.", + Variables = new List<string>{"UserName"} } }; @@ -185,6 +192,10 @@ namespace MediaBrowser.Server.Implementations.Notifications { note.Category = _localization.GetLocalizedString("CategorySync"); } + else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1) + { + note.Category = _localization.GetLocalizedString("CategoryUser"); + } else { note.Category = _localization.GetLocalizedString("CategorySystem"); diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index e1f98c659..40b85dad1 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -108,7 +108,12 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task<Stream> GetThumbCollage(List<BaseItem> items) { - return DynamicImageHelpers.GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(), + var files = items + .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + return DynamicImageHelpers.GetThumbCollage(files, FileSystem, 1600, 900, @@ -117,7 +122,12 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task<Stream> GetSquareCollage(List<BaseItem> items) { - return DynamicImageHelpers.GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(), + var files = items + .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + return DynamicImageHelpers.GetSquareCollage(files, FileSystem, 800, ApplicationPaths); } diff --git a/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs b/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs index c2af9cdaf..e7cd2f4d2 100644 --- a/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs +++ b/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs @@ -4,6 +4,7 @@ using MediaBrowser.Common.IO; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Photos @@ -15,6 +16,11 @@ namespace MediaBrowser.Server.Implementations.Photos int width, int height, IApplicationPaths appPaths) { + if (files.Any(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Empty file found in files list"); + } + if (files.Count < 3) { return await GetSingleImage(files, fileSystem).ConfigureAwait(false); @@ -27,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Photos int cellHeight = height; var index = 0; - using (var wand = new MagickWand(width, height, "transparent")) + using (var wand = new MagickWand(width, height, new PixelWand(ColorName.None, 1))) { for (var row = 0; row < rows; row++) { @@ -57,6 +63,11 @@ namespace MediaBrowser.Server.Implementations.Photos IFileSystem fileSystem, int size, IApplicationPaths appPaths) { + if (files.Any(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Empty file found in files list"); + } + if (files.Count < 4) { return await GetSingleImage(files, fileSystem).ConfigureAwait(false); @@ -68,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Photos int singleSize = size / 2; var index = 0; - using (var wand = new MagickWand(size, size, "transparent")) + using (var wand = new MagickWand(size, size, new PixelWand(ColorName.None, 1))) { for (var row = 0; row < rows; row++) { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index d02ef9d27..8eef8536a 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -399,7 +399,7 @@ namespace MediaBrowser.Server.Implementations.Session Client = clientType, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = Guid.NewGuid().ToString("N") + Id = key.GetMD5().ToString("N") }; sessionInfo.DeviceName = deviceName; @@ -798,6 +798,19 @@ namespace MediaBrowser.Server.Implementations.Session return session; } + private SessionInfo GetSessionToRemoteControl(string sessionId) + { + // Accept either device id or session id + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); + + if (session == null) + { + throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); + } + + return session; + } + public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { var generalCommand = new GeneralCommand @@ -818,7 +831,7 @@ namespace MediaBrowser.Server.Implementations.Session public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken) { - var session = GetSession(sessionId); + var session = GetSessionToRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); @@ -828,7 +841,7 @@ namespace MediaBrowser.Server.Implementations.Session public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { - var session = GetSession(sessionId); + var session = GetSessionToRemoteControl(sessionId); var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null; @@ -955,7 +968,7 @@ namespace MediaBrowser.Server.Implementations.Session public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken) { - var session = GetSession(sessionId); + var session = GetSessionToRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); @@ -1566,11 +1579,7 @@ namespace MediaBrowser.Server.Implementations.Session if (!string.IsNullOrWhiteSpace(mediaSourceId)) { - info.MediaStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery - { - ItemId = new Guid(mediaSourceId) - - }).ToList(); + info.MediaStreams = _mediaSourceManager.GetMediaStreams(mediaSourceId).ToList(); } return info; diff --git a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs new file mode 100644 index 000000000..68cd44ec9 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -0,0 +1,70 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class DateLastMediaAddedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Gets or sets the user data repository. + /// </summary> + /// <value>The user data repository.</value> + public IUserDataManager UserDataRepository { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private DateTime GetDate(BaseItem x) + { + var folder = x as Folder; + + if (folder != null) + { + return folder.GetRecursiveChildren(User, i => !i.IsFolder) + .Select(i => i.DateCreated) + .OrderByDescending(i => i) + .FirstOrDefault(); + } + + return x.DateCreated; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.DateLastContentAdded; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs index 7605a7a50..c881591be 100644 --- a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,6 +1,5 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; using System; diff --git a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs index d35ff8fc4..2106fc12e 100644 --- a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs @@ -8,7 +8,7 @@ using System.Linq; namespace MediaBrowser.Server.Implementations.Sync { - public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds + public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncProfile { private readonly IDeviceManager _deviceManager; @@ -42,5 +42,18 @@ namespace MediaBrowser.Server.Implementations.Sync { get { return "App Sync"; } } + + public IEnumerable<SyncTarget> GetAllSyncTargets() + { + return _deviceManager.GetDevices(new DeviceQuery + { + SupportsSync = true + + }).Items.Select(i => new SyncTarget + { + Id = i.Id, + Name = i.Name + }); + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs new file mode 100644 index 000000000..babdf3f80 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -0,0 +1,118 @@ +using MediaBrowser.Model.Dlna; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class CloudSyncProfile : DeviceProfile + { + public CloudSyncProfile(bool supportsAc3, bool supportsDca) + { + Name = "Cloud Sync"; + + MaxStreamingBitrate = 20000000; + MaxStaticBitrate = 20000000; + + var mkvAudio = "aac,mp3"; + var mp4Audio = "aac"; + + if (supportsAc3) + { + mkvAudio += ",ac3"; + mp4Audio += ",ac3"; + } + + if (supportsDca) + { + mkvAudio += ",dca"; + } + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Container = "mkv", + VideoCodec = "h264,mpeg4", + AudioCodec = mkvAudio, + Type = DlnaProfileType.Video + }, + new DirectPlayProfile + { + Container = "mp4,mov,m4v", + VideoCodec = "h264,mpeg4", + AudioCodec = mp4Audio, + Type = DlnaProfileType.Video + } + }; + + ContainerProfiles = new ContainerProfile[] { }; + + CodecProfiles = new[] + { + new CodecProfile + { + Type = CodecType.Video, + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoBitDepth, + Value = "8", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Height, + Value = "1080", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.RefFrames, + Value = "12", + IsRequired = false + } + } + } + }; + + SubtitleProfiles = new[] + { + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.External + } + }; + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + AudioCodec = "mp3", + Type = DlnaProfileType.Audio, + Context = EncodingContext.Static + }, + + new TranscodingProfile + { + Container = "mp4", + Type = DlnaProfileType.Video, + AudioCodec = "aac", + VideoCodec = "h264", + Context = EncodingContext.Static + }, + + new TranscodingProfile + { + Container = "jpeg", + Type = DlnaProfileType.Photo, + Context = EncodingContext.Static + } + }; + + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs deleted file mode 100644 index 37caa561e..000000000 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs +++ /dev/null @@ -1,59 +0,0 @@ -using MediaBrowser.Common; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Sync -{ - public class CloudSyncProvider : IServerSyncProvider - { - private readonly ICloudSyncProvider[] _providers = {}; - - public CloudSyncProvider(IApplicationHost appHost) - { - _providers = appHost.GetExports<ICloudSyncProvider>().ToArray(); - } - - public IEnumerable<SyncTarget> GetSyncTargets(string userId) - { - return _providers.SelectMany(i => i.GetSyncTargets(userId)); - } - - public DeviceProfile GetDeviceProfile(SyncTarget target) - { - return new DeviceProfile(); - } - - public string Name - { - get { return "Cloud Sync"; } - } - - private ICloudSyncProvider GetProvider(SyncTarget target) - { - return null; - } - - public Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken) - { - var provider = GetProvider(target); - - return provider.TransferItemFile(serverId, itemId, inputFile, pathParts, target, cancellationToken); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs new file mode 100644 index 000000000..b9008d87e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs @@ -0,0 +1,31 @@ +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync.FolderSync +{ + public class FolderSyncDataProvider : ISyncDataProvider + { + public Task<List<string>> GetServerItemIds(SyncTarget target, string serverId) + { + throw new NotImplementedException(); + } + + public Task AddOrUpdate(SyncTarget target, LocalItem item) + { + throw new NotImplementedException(); + } + + public Task Delete(SyncTarget target, string id) + { + throw new NotImplementedException(); + } + + public Task<LocalItem> Get(SyncTarget target, string id) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs new file mode 100644 index 000000000..3183816c8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs @@ -0,0 +1,143 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync.FolderSync +{ + public class FolderSyncProvider : IServerSyncProvider + { + private readonly IApplicationPaths _appPaths; + private readonly IUserManager _userManager; + + public FolderSyncProvider(IApplicationPaths appPaths, IUserManager userManager) + { + _appPaths = appPaths; + _userManager = userManager; + } + + public Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) + { + return Task.Run(() => File.Copy(inputFile, path, true), cancellationToken); + } + + public Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken) + { + return Task.Run(() => File.Delete(path), cancellationToken); + } + + public Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) + { + return Task.FromResult((Stream)File.OpenRead(path)); + } + + public string GetFullPath(IEnumerable<string> paths, SyncTarget target) + { + var account = GetSyncAccounts() + .FirstOrDefault(i => string.Equals(i.Id, target.Id, StringComparison.OrdinalIgnoreCase)); + + if (account == null) + { + throw new ArgumentException("Invalid SyncTarget supplied."); + } + + var list = paths.ToList(); + list.Insert(0, account.Path); + + return Path.Combine(list.ToArray()); + } + + public string GetParentDirectoryPath(string path, SyncTarget target) + { + return Path.GetDirectoryName(path); + } + + public Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target) + { + List<FileInfo> files; + + try + { + files = new DirectoryInfo(path).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); + } + catch (DirectoryNotFoundException) + { + files = new List<FileInfo>(); + } + + return Task.FromResult(files.Select(i => new DeviceFileInfo + { + Name = i.Name, + Path = i.FullName + + }).ToList()); + } + + public ISyncDataProvider GetDataProvider() + { + // If single instances are needed, manage them here + return new FolderSyncDataProvider(); + } + + public string Name + { + get { return "Folder Sync"; } + } + + public IEnumerable<SyncTarget> GetSyncTargets(string userId) + { + return GetSyncAccounts() + .Where(i => i.UserIds.Contains(userId, StringComparer.OrdinalIgnoreCase)) + .Select(GetSyncTarget); + } + + public IEnumerable<SyncTarget> GetAllSyncTargets() + { + return GetSyncAccounts().Select(GetSyncTarget); + } + + private SyncTarget GetSyncTarget(SyncAccount account) + { + return new SyncTarget + { + Id = account.Id, + Name = account.Name + }; + } + + private IEnumerable<SyncAccount> GetSyncAccounts() + { + return new List<SyncAccount>(); + // Dummy this up + return _userManager + .Users + .Select(i => new SyncAccount + { + Id = i.Id.ToString("N"), + UserIds = new List<string> { i.Id.ToString("N") }, + Path = Path.Combine(_appPaths.DataPath, "foldersync", i.Id.ToString("N")), + Name = i.Name + "'s Folder Sync" + }); + } + + // An internal class to manage all configured Folder Sync accounts for differnet users + class SyncAccount + { + public string Id { get; set; } + public string Name { get; set; } + public string Path { get; set; } + public List<string> UserIds { get; set; } + + public SyncAccount() + { + UserIds = new List<string>(); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs new file mode 100644 index 000000000..b7e9daf49 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Sync; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public interface IHasSyncProfile + { + /// <summary> + /// Gets the device profile. + /// </summary> + /// <param name="target">The target.</param> + /// <returns>DeviceProfile.</returns> + DeviceProfile GetDeviceProfile(SyncTarget target); + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 349e6aa1d..0407510a8 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -1,10 +1,18 @@ -using MediaBrowser.Common.Progress; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Sync; using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -15,22 +23,25 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly ISyncManager _syncManager; private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; - public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost) + public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem) { _logger = logger; _syncManager = syncManager; _appHost = appHost; + _fileSystem = fileSystem; } - public async Task Sync(IServerSyncProvider provider, + public async Task Sync(IServerSyncProvider provider, + ISyncDataProvider dataProvider, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) { var serverId = _appHost.SystemId; - await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false); + await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); progress.Report(3); var innerProgress = new ActionableProgress<double>(); @@ -40,20 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync totalProgress += 1; progress.Report(totalProgress); }); - await GetNewMedia(provider, target, serverId, innerProgress, cancellationToken); + await GetNewMedia(provider, dataProvider, target, serverId, innerProgress, cancellationToken); // Do the data sync twice so the server knows what was removed from the device - await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false); + await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); progress.Report(100); } private async Task SyncData(IServerSyncProvider provider, + ISyncDataProvider dataProvider, string serverId, SyncTarget target, CancellationToken cancellationToken) { - var localIds = await provider.GetServerItemIds(serverId, target, cancellationToken).ConfigureAwait(false); + var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false); var result = await _syncManager.SyncData(new SyncDataRequest { @@ -68,23 +80,24 @@ namespace MediaBrowser.Server.Implementations.Sync { try { - await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false); + await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { - _logger.ErrorException("Error deleting item from sync target. Id: {0}", ex, itemIdToRemove); + _logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove); } } } private async Task GetNewMedia(IServerSyncProvider provider, + ISyncDataProvider dataProvider, SyncTarget target, string serverId, IProgress<double> progress, CancellationToken cancellationToken) { - var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false); - + var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false); + var numComplete = 0; double startingPercent = 0; double percentPerItem = 1; @@ -106,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Sync progress.Report(totalProgress); }); - await GetItem(provider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); + await GetItem(provider, dataProvider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); numComplete++; startingPercent = numComplete; @@ -117,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Sync } private async Task GetItem(IServerSyncProvider provider, + ISyncDataProvider dataProvider, SyncTarget target, string serverId, SyncedItem jobItem, @@ -129,6 +143,8 @@ namespace MediaBrowser.Server.Implementations.Sync var fileTransferProgress = new ActionableProgress<double>(); fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); + var localItem = CreateLocalItem(provider, target, libraryItem, serverId, jobItem.OriginalFileName); + await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); var transferSuccess = false; @@ -136,10 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync try { - string[] pathParts = GetPathParts(serverId, libraryItem); + await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false); - await provider.TransferItemFile(serverId, libraryItem.Id, internalSyncJobItem.OutputPath, pathParts, target, cancellationToken) - .ConfigureAwait(false); + // Create db record + await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); progress.Report(92); @@ -165,18 +181,189 @@ namespace MediaBrowser.Server.Implementations.Sync } } - private Task RemoveItem(IServerSyncProvider provider, + private async Task RemoveItem(IServerSyncProvider provider, + ISyncDataProvider dataProvider, string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken) { - return provider.DeleteItem(serverId, itemId, target, cancellationToken); + var localId = GetLocalId(serverId, itemId); + var localItem = await dataProvider.Get(target, localId); + + if (localItem == null) + { + return; + } + + var files = await GetFiles(provider, localItem, target); + + foreach (var file in files) + { + await provider.DeleteFile(file.Path, target, cancellationToken).ConfigureAwait(false); + } + + await dataProvider.Delete(target, localId).ConfigureAwait(false); + } + + private Task SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken) + { + return provider.SendFile(inputPath, item.LocalPath, target, new Progress<double>(), cancellationToken); + } + + private string GetLocalId(string serverId, string itemId) + { + var bytes = Encoding.UTF8.GetBytes(serverId + itemId); + bytes = CreateMD5(bytes); + return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty); + } + + private byte[] CreateMD5(byte[] value) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(value); + } + } + + public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName) + { + var path = GetDirectoryPath(provider, libraryItem, serverId); + path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); + + var localPath = provider.GetFullPath(path, target); + + foreach (var mediaSource in libraryItem.MediaSources) + { + mediaSource.Path = localPath; + mediaSource.Protocol = MediaProtocol.File; + } + + return new LocalItem + { + Item = libraryItem, + ItemId = libraryItem.Id, + ServerId = serverId, + LocalPath = localPath, + Id = GetLocalId(serverId, libraryItem.Id) + }; + } + + private List<string> GetDirectoryPath(IServerSyncProvider provider, BaseItemDto item, string serverId) + { + var parts = new List<string> + { + serverId + }; + + if (item.IsType("episode")) + { + parts.Add("TV"); + parts.Add(item.SeriesName); + + if (!string.IsNullOrWhiteSpace(item.SeasonName)) + { + parts.Add(item.SeasonName); + } + } + else if (item.IsVideo) + { + parts.Add("Videos"); + parts.Add(item.Name); + } + else if (item.IsAudio) + { + parts.Add("Music"); + + if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) + { + parts.Add(item.AlbumArtist); + } + + if (!string.IsNullOrWhiteSpace(item.Album)) + { + parts.Add(item.Album); + } + } + else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) + { + parts.Add("Photos"); + + if (!string.IsNullOrWhiteSpace(item.Album)) + { + parts.Add(item.Album); + } + } + + return parts.Select(i => GetValidFilename(provider, i)).ToList(); + } + + private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName) + { + var filename = originalFileName; + + if (string.IsNullOrEmpty(filename)) + { + filename = item.Name; + } + + return GetValidFilename(provider, filename); + } + + private string GetValidFilename(IServerSyncProvider provider, string filename) + { + // We can always add this method to the sync provider if it's really needed + return _fileSystem.GetValidFilename(filename); } - private string[] GetPathParts(string serverId, BaseItemDto item) + private async Task<List<ItemFileInfo>> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target) { - return null; + var path = item.LocalPath; + path = provider.GetParentDirectoryPath(path, target); + + var list = await provider.GetFileSystemEntries(path, target).ConfigureAwait(false); + + var itemFiles = new List<ItemFileInfo>(); + + var name = Path.GetFileNameWithoutExtension(item.LocalPath); + + foreach (var file in list.Where(f => f.Name.Contains(name))) + { + var itemFile = new ItemFileInfo + { + Path = file.Path, + Name = file.Name + }; + + if (IsSubtitleFile(file.Name)) + { + itemFile.Type = ItemFileType.Subtitles; + } + else if (!IsImageFile(file.Name)) + { + itemFile.Type = ItemFileType.Media; + } + + itemFiles.Add(itemFile); + } + + return itemFiles; + } + + private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".webp" }; + private bool IsImageFile(string path) + { + var ext = Path.GetExtension(path) ?? string.Empty; + + return SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + } + + private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" }; + private bool IsSubtitleFile(string path) + { + var ext = Path.GetExtension(path) ?? string.Empty; + + return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs b/MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs new file mode 100644 index 000000000..cbfa82f1d --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs @@ -0,0 +1,69 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class MultiProviderSync + { + private readonly ISyncManager _syncManager; + private readonly IServerApplicationHost _appHost; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public MultiProviderSync(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem) + { + _syncManager = syncManager; + _appHost = appHost; + _logger = logger; + _fileSystem = fileSystem; + } + + public async Task Sync(IEnumerable<IServerSyncProvider> providers, IProgress<double> progress, CancellationToken cancellationToken) + { + var targets = providers + .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple<IServerSyncProvider, SyncTarget>(i, t))) + .ToList(); + + var numComplete = 0; + double startingPercent = 0; + double percentPerItem = 1; + if (targets.Count > 0) + { + percentPerItem /= targets.Count; + } + + foreach (var target in targets) + { + cancellationToken.ThrowIfCancellationRequested(); + + var currentPercent = startingPercent; + var innerProgress = new ActionableProgress<double>(); + innerProgress.RegisterAction(pct => + { + var totalProgress = pct * percentPerItem; + totalProgress += currentPercent; + progress.Report(totalProgress); + }); + + await new MediaSync(_logger, _syncManager, _appHost, _fileSystem) + .Sync(target.Item1, target.Item1.GetDataProvider(), target.Item2, innerProgress, cancellationToken) + .ConfigureAwait(false); + + numComplete++; + startingPercent = numComplete; + startingPercent /= targets.Count; + startingPercent *= 100; + progress.Report(startingPercent); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index a2fd92bf5..8fc3651f7 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -407,6 +407,15 @@ namespace MediaBrowser.Server.Implementations.Sync .OrderBy(i => i.Name); } + private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider) + { + return provider.GetAllSyncTargets().Select(i => new SyncTarget + { + Name = i.Name, + Id = GetSyncTargetId(provider, i) + }); + } + private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId) { return provider.GetSyncTargets(userId).Select(i => new SyncTarget @@ -429,13 +438,6 @@ namespace MediaBrowser.Server.Implementations.Sync return (providerId + "-" + target.Id).GetMD5().ToString("N"); } - private ISyncProvider GetSyncProvider(SyncTarget target) - { - var providerId = target.Id.Split(new[] { '-' }, 2).First(); - - return _providers.First(i => string.Equals(providerId, GetSyncProviderId(i))); - } - private string GetSyncProviderId(ISyncProvider provider) { return (provider.GetType().Name).GetMD5().ToString("N"); @@ -543,11 +545,11 @@ namespace MediaBrowser.Server.Implementations.Sync { foreach (var provider in _providers) { - foreach (var target in GetSyncTargets(provider, null)) + foreach (var target in GetSyncTargets(provider)) { if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) { - return provider.GetDeviceProfile(target); + return GetDeviceProfile(provider, target); } } } @@ -555,6 +557,18 @@ namespace MediaBrowser.Server.Implementations.Sync return null; } + public DeviceProfile GetDeviceProfile(ISyncProvider provider, SyncTarget target) + { + var hasProfile = provider as IHasSyncProfile; + + if (hasProfile != null) + { + return hasProfile.GetDeviceProfile(target); + } + + return new CloudSyncProfile(true, false); + } + public async Task ReportSyncJobItemTransferred(string id) { var jobItem = _repo.GetJobItem(id); diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 41df1b471..8c530e015 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" />
+ <package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.32" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
<package id="morelinq" version="1.1.0" targetFramework="net45" />
diff --git a/MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs b/MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs new file mode 100644 index 000000000..a66365212 --- /dev/null +++ b/MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs @@ -0,0 +1,25 @@ +using MediaBrowser.Controller.Diagnostics; +using System.Diagnostics; + +namespace MediaBrowser.Server.Mono.Diagnostics +{ + public class LinuxProcessManager : IProcessManager + { + public bool SupportsSuspension + { + get { return true; } + } + + public void SuspendProcess(Process process) + { + // http://jumptuck.com/2011/11/23/quick-tip-pause-process-linux/ + process.StandardInput.WriteLine("^Z"); + } + + public void ResumeProcess(Process process) + { + // http://jumptuck.com/2011/11/23/quick-tip-pause-process-linux/ + process.StandardInput.WriteLine("fg"); + } + } +} diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index cd010e1c1..8f552ee36 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -74,6 +74,7 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="Diagnostics\LinuxProcessManager.cs" /> <Compile Include="Native\BaseMonoApp.cs" /> <Compile Include="Networking\CertificateGenerator.cs" /> <Compile Include="Program.cs" /> diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 1ec0109ad..139661aa2 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -1,6 +1,8 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.IsoMounter; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Mono.Diagnostics; using MediaBrowser.Server.Mono.Networking; using MediaBrowser.Server.Startup.Common; using Mono.Unix.Native; @@ -189,5 +191,16 @@ namespace MediaBrowser.Server.Mono.Native public string sysname = string.Empty; public string machine = string.Empty; } + + + public IProcessManager GetProcessManager() + { + if (Environment.OperatingSystem == Startup.Common.OperatingSystem.Linux) + { + return new LinuxProcessManager(); + } + + return new ProcessManager(); + } } } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index fac704b68..63d30a606 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -380,6 +380,8 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(ServerConfigurationManager); + RegisterSingleInstance(NativeApp.GetProcessManager()); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer); RegisterSingleInstance(LocalizationManager); diff --git a/MediaBrowser.Server.Startup.Common/Diagnostics/ProcessManager.cs b/MediaBrowser.Server.Startup.Common/Diagnostics/ProcessManager.cs new file mode 100644 index 000000000..d01756d0e --- /dev/null +++ b/MediaBrowser.Server.Startup.Common/Diagnostics/ProcessManager.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Diagnostics; +using System.Diagnostics; + +namespace MediaBrowser.Server.Mono.Diagnostics +{ + public class ProcessManager : IProcessManager + { + public void SuspendProcess(Process process) + { + process.PriorityClass = ProcessPriorityClass.Idle; + } + + public void ResumeProcess(Process process) + { + process.PriorityClass = ProcessPriorityClass.Normal; + } + + public bool SupportsSuspension + { + get { return true; } + } + } +} diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs index 2dbd844ba..1c4b5b1d5 100644 --- a/MediaBrowser.Server.Startup.Common/INativeApp.cs +++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Reflection; @@ -84,5 +85,11 @@ namespace MediaBrowser.Server.Startup.Common /// Prevents the system stand by. /// </summary> void PreventSystemStandby(); + + /// <summary> + /// Gets the process manager. + /// </summary> + /// <returns>IProcessManager.</returns> + IProcessManager GetProcessManager(); } } diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 38e07fde4..625b29d36 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -56,6 +56,7 @@ <Compile Include="ApplicationHost.cs" /> <Compile Include="ApplicationPathHelper.cs" /> <Compile Include="Browser\BrowserLauncher.cs" /> + <Compile Include="Diagnostics\ProcessManager.cs" /> <Compile Include="EntryPoints\KeepServerAwake.cs" /> <Compile Include="EntryPoints\StartupWizard.cs" /> <Compile Include="FFMpeg\FFMpegDownloader.cs" /> diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 6f569718e..e01353f80 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -62,7 +62,7 @@ <ItemGroup> <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath> + <HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath> </Reference> <Reference Include="MediaBrowser.IsoMounter"> <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath> @@ -113,6 +113,7 @@ <Compile Include="Native\Standby.cs" /> <Compile Include="Native\ServerAuthorization.cs" /> <Compile Include="Native\WindowsApp.cs" /> + <Compile Include="Native\WindowsProcessManager.cs" /> <Compile Include="Networking\CertificateGenerator.cs" /> <Compile Include="Networking\NativeMethods.cs" /> <Compile Include="Networking\NetworkManager.cs" /> diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 476fb58b9..74abcb36c 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -1,7 +1,9 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Diagnostics; using MediaBrowser.IsoMounter; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Mono.Diagnostics; using MediaBrowser.Server.Startup.Common; using MediaBrowser.ServerApplication.Networking; using System.Collections.Generic; @@ -109,5 +111,10 @@ namespace MediaBrowser.ServerApplication.Native { Standby.PreventSystemStandby(); } + + public IProcessManager GetProcessManager() + { + return new WindowsProcessManager(); + } } } diff --git a/MediaBrowser.ServerApplication/Native/WindowsProcessManager.cs b/MediaBrowser.ServerApplication/Native/WindowsProcessManager.cs new file mode 100644 index 000000000..f3497aef5 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/WindowsProcessManager.cs @@ -0,0 +1,78 @@ +using MediaBrowser.Controller.Diagnostics; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace MediaBrowser.ServerApplication.Native +{ + public class WindowsProcessManager : IProcessManager + { + public void SuspendProcess(Process process) + { + process.Suspend(); + } + + public void ResumeProcess(Process process) + { + process.Resume(); + } + + public bool SupportsSuspension + { + get { return true; } + } + } + + public static class ProcessExtension + { + [DllImport("kernel32.dll")] + static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + [DllImport("kernel32.dll")] + static extern uint SuspendThread(IntPtr hThread); + [DllImport("kernel32.dll")] + static extern int ResumeThread(IntPtr hThread); + + public static void Suspend(this Process process) + { + foreach (ProcessThread thread in process.Threads) + { + var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id); + if (pOpenThread == IntPtr.Zero) + { + break; + } + SuspendThread(pOpenThread); + } + } + public static void Resume(this Process process) + { + foreach (ProcessThread thread in process.Threads) + { + var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id); + if (pOpenThread == IntPtr.Zero) + { + break; + } + ResumeThread(pOpenThread); + } + } + public static void Print(this Process process) + { + Console.WriteLine("{0,8} {1}", process.Id, process.ProcessName); + } + } + + [Flags] + public enum ThreadAccess : int + { + TERMINATE = (0x0001), + SUSPEND_RESUME = (0x0002), + GET_CONTEXT = (0x0008), + SET_CONTEXT = (0x0010), + SET_INFORMATION = (0x0020), + QUERY_INFORMATION = (0x0040), + SET_THREAD_TOKEN = (0x0080), + IMPERSONATE = (0x0100), + DIRECT_IMPERSONATION = (0x0200) + } +} diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index ab51c30cc..3dd0c908d 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" /> + <package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" /> <package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" /> <package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 1e188ceae..8328cf8ab 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -421,6 +421,7 @@ namespace MediaBrowser.WebDashboard.Api "itembynamedetailpage.js", "itemdetailpage.js", "itemlistpage.js", + "kids.js", "librarypathmapping.js", "reports.js", "librarysettings.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 812deabe1..fa6413b8b 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -87,6 +87,9 @@ </ProjectReference>
</ItemGroup>
<ItemGroup>
+ <Content Include="dashboard-ui\css\images\kids\bg.jpg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\css\images\server.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -114,6 +117,9 @@ <Content Include="dashboard-ui\forgotpasswordpin.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\kids.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\mysync.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -129,6 +135,9 @@ <Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\kids.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\selectserver.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 5455709e9..7aa33b053 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -214,7 +214,7 @@ namespace MediaBrowser.XbmcMetadata.Savers } } - using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var filestream = FileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) { stream.CopyTo(filestream); } diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 143a3da41..f73971374 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -520,4 +520,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 6d9ead15a..88c6e9f8a 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.576</version> + <version>3.0.579</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.576" /> + <dependency id="MediaBrowser.Common" version="3.0.579" /> <dependency id="NLog" version="3.2.0.0" /> <dependency id="SimpleInjector" version="2.7.0" /> </dependencies> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 305db5a1f..facbe5366 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.576</version> + <version>3.0.579</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 9305fe51c..dab103446 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Model.Signed</id> - <version>3.0.576</version> + <version>3.0.579</version> <title>MediaBrowser.Model - Signed Edition</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 cc7979590..e94d18fb5 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.576</version> + <version>3.0.579</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.576" /> + <dependency id="MediaBrowser.Common" version="3.0.579" /> </dependencies> </metadata> <files> |
