diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index e29bbf6743..0a39b51c3a 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 85cc879f4f..4d9afa260e 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 2713059212..fc48e9a3ac 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -81,6 +81,7 @@
+
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 49d5f1c8d8..a40e0f8c3f 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; }
///
/// Initializes a new instance of the class.
///
- 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;
- }
- }
-
///
/// Gets the input argument.
///
@@ -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 2da5c33ce8..fdfa6e6d76 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
///
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 e639dbdfe3..4f4f5f1cb7 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 80451c0cc8..05909402c9 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 8de52ea028..d27296bfdb 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
///
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 77178c8cc1..330a8777c7 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
{
[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 37155b8f94..08ec13f4ff 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
///
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 9dbe3389e6..0af4587b67 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();
- 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;
- }
-
///
/// Gets the static remote stream result.
///
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 7e86b867f6..9b161085a9 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
///
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 dc22fc7548..907b17b244 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 40e765f1ae..588d3b75cc 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 0000000000..432f4667da
--- /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 0000000000..2e076bd882
--- /dev/null
+++ b/MediaBrowser.Controller/Diagnostics/IProcessManager.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics;
+
+namespace MediaBrowser.Controller.Diagnostics
+{
+ ///
+ /// Interface IProcessManager
+ ///
+ public interface IProcessManager
+ {
+ ///
+ /// Gets a value indicating whether [supports suspension].
+ ///
+ /// true if [supports suspension]; otherwise, false.
+ bool SupportsSuspension { get; }
+
+ ///
+ /// Suspends the process.
+ ///
+ /// The process.
+ void SuspendProcess(Process process);
+
+ ///
+ /// Resumes the process.
+ ///
+ /// The process.
+ void ResumeProcess(Process process);
+ }
+}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 8ac7d56d29..6fafc2b464 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
///
/// Gets the size of the image.
///
- /// The path.
- /// The image date modified.
+ /// The information.
/// ImageSize.
- ImageSize GetImageSize(string path, DateTime imageDateModified);
+ ImageSize GetImageSize(ItemImageInfo info);
///
/// Adds the parts.
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 9024479999..d868227d95 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 d4507bc337..a0c3a6cf98 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 GetMediaStreams()
+ public IEnumerable GetMediaStreams()
{
- return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
+ var mediaSource = GetMediaSources(false)
+ .FirstOrDefault();
+
+ if (mediaSource == null)
{
- ItemId = Id
- });
+ return new List();
+ }
+
+ 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 4378bc85d9..5d79f613db 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
{
+ ///
+ /// Gets the media streams.
+ ///
+ /// The item identifier.
+ /// IEnumerable<MediaStream>.
+ IEnumerable GetMediaStreams(Guid itemId);
+ ///
+ /// Gets the media streams.
+ ///
+ /// The media source identifier.
+ /// IEnumerable<MediaStream>.
+ IEnumerable GetMediaStreams(string mediaSourceId);
+ ///
+ /// Gets the media streams.
+ ///
+ /// The query.
+ /// IEnumerable<MediaStream>.
IEnumerable GetMediaStreams(MediaStreamQuery query);
}
}
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 8119e5afbb..a167cdbed0 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Library
event EventHandler> UserCreated;
event EventHandler> UserConfigurationUpdated;
event EventHandler> UserPasswordChanged;
+ event EventHandler> UserLockedOut;
///
/// Gets a User by Id
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index e9531e0571..36809c5d38 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -104,6 +104,7 @@
+
@@ -341,8 +342,8 @@
-
+
diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs
index 5bb2c9a5c3..6a104554af 100644
--- a/MediaBrowser.Controller/Net/StaticResultOptions.cs
+++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs
@@ -18,11 +18,6 @@ namespace MediaBrowser.Controller.Net
public IDictionary ResponseHeaders { get; set; }
- public bool Throttle { get; set; }
- public long ThrottleLimit { get; set; }
- public long MinThrottlePosition { get; set; }
- public Func 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 dd7fda2c5a..0000000000
--- 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
- {
- ///
- /// Gets the name.
- ///
- /// The name.
- string Name { get; }
-
- ///
- /// Gets the synchronize targets.
- ///
- /// The user identifier.
- /// IEnumerable<SyncTarget>.
- IEnumerable GetSyncTargets(string userId);
-
- ///
- /// Transfers the item file.
- ///
- /// The server identifier.
- /// The item identifier.
- /// The input file.
- /// The path parts.
- /// The target.
- /// The cancellation token.
- /// Task.
- 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 d11dd9d732..775a3648d2 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;
@@ -7,35 +9,64 @@ namespace MediaBrowser.Controller.Sync
{
public interface IServerSyncProvider : ISyncProvider
{
- ///
- /// Gets the server item ids.
- ///
- /// The server identifier.
- /// The target.
- /// The cancellation token.
- /// Task<List<System.String>>.
- Task> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken);
-
- ///
- /// Removes the item.
- ///
- /// The server identifier.
- /// The item identifier.
- /// The target.
- /// The cancellation token.
- /// Task.
- Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken);
-
///
/// Transfers the file.
///
- /// The server identifier.
- /// The item identifier.
/// The input file.
- /// The path parts.
+ /// The path.
+ /// The target.
+ /// The progress.
+ /// The cancellation token.
+ /// Task.
+ Task SendFile(string inputFile, string path, SyncTarget target, IProgress progress, CancellationToken cancellationToken);
+
+ ///
+ /// Deletes the file.
+ ///
+ /// The path.
/// The target.
/// The cancellation token.
/// Task.
- Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken);
+ Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken);
+
+ ///
+ /// Gets the file.
+ ///
+ /// The path.
+ /// The target.
+ /// The progress.
+ /// The cancellation token.
+ /// Task<Stream>.
+ Task GetFile(string path, SyncTarget target, IProgress progress, CancellationToken cancellationToken);
+
+ ///
+ /// Gets the full path.
+ ///
+ /// The path.
+ /// The target.
+ /// System.String.
+ string GetFullPath(IEnumerable path, SyncTarget target);
+
+ ///
+ /// Gets the parent directory path.
+ ///
+ /// The path.
+ /// The target.
+ /// System.String.
+ string GetParentDirectoryPath(string path, SyncTarget target);
+
+ ///
+ /// Gets the file system entries.
+ ///
+ /// The path.
+ /// The target.
+ /// Task<List<DeviceFileInfo>>.
+ Task> GetFileSystemEntries(string path, SyncTarget target);
+
+ ///
+ /// Gets the data provider.
+ ///
+ /// ISyncDataProvider.
+ ISyncDataProvider GetDataProvider();
}
}
diff --git a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs
new file mode 100644
index 0000000000..cf698dd3c0
--- /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
+ {
+ ///
+ /// Gets the server item ids.
+ ///
+ /// The target.
+ /// The server identifier.
+ /// Task<List<System.String>>.
+ Task> GetServerItemIds(SyncTarget target, string serverId);
+
+ ///
+ /// Adds the or update.
+ ///
+ /// The target.
+ /// The item.
+ /// Task.
+ Task AddOrUpdate(SyncTarget target, LocalItem item);
+
+ ///
+ /// Deletes the specified identifier.
+ ///
+ /// The target.
+ /// The identifier.
+ /// Task.
+ Task Delete(SyncTarget target, string id);
+
+ ///
+ /// Gets the specified identifier.
+ ///
+ /// The target.
+ /// The identifier.
+ /// Task<LocalItem>.
+ Task Get(SyncTarget target, string id);
+ }
+}
diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs
index 6f24eac1ae..ef34bfe692 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
/// The user identifier.
/// IEnumerable<SyncTarget>.
IEnumerable GetSyncTargets(string userId);
-
+
///
- /// Gets the device profile.
+ /// Gets all synchronize targets.
///
- /// The target.
- /// DeviceProfile.
- DeviceProfile GetDeviceProfile(SyncTarget target);
+ /// IEnumerable<SyncTarget>.
+ IEnumerable GetAllSyncTargets();
}
public interface IHasUniqueTargetIds
diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
index af7c8dbed0..b2eedad7c6 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 ffae8612ed..ead080c605 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 b9cad27e0d..7d74c51bad 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 37057f2d74..62677f8182 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -362,6 +362,12 @@
Dlna\MediaFormatProfileResolver.cs
+
+ Dlna\PlaybackErrorCode.cs
+
+
+ Dlna\PlaybackException.cs
+
Dlna\ProfileCondition.cs
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index f38a8f597b..4ed8cceae3 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -327,6 +327,12 @@
Dlna\MediaFormatProfileResolver.cs
+
+ Dlna\PlaybackErrorCode.cs
+
+
+ Dlna\PlaybackException.cs
+
Dlna\ProfileCondition.cs
diff --git a/MediaBrowser.Model/ApiClient/ConnectionOptions.cs b/MediaBrowser.Model/ApiClient/ConnectionOptions.cs
index 445eaa04ef..e12676311b 100644
--- a/MediaBrowser.Model/ApiClient/ConnectionOptions.cs
+++ b/MediaBrowser.Model/ApiClient/ConnectionOptions.cs
@@ -13,11 +13,17 @@ namespace MediaBrowser.Model.ApiClient
///
/// true if [report capabilities]; otherwise, false.
public bool ReportCapabilities { get; set; }
+ ///
+ /// Gets or sets a value indicating whether [update date last accessed].
+ ///
+ /// true if [update date last accessed]; otherwise, false.
+ 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 aa49ee50d5..a78161140f 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 0000000000..4ed4129854
--- /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 0000000000..761fa1c904
--- /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 6fa29a533b..559a543f2d 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -31,7 +31,13 @@ namespace MediaBrowser.Model.Dlna
List streams = new List();
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 streams = new List();
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 57a3899d4d..f0854c3f85 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 068443238b..cdc97b7ea7 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();
RequiredHttpHeaders = new Dictionary();
PlayableStreamFileNames = new List();
+ SupportsTranscoding = true;
}
public int? DefaultAudioStreamIndex { get; set; }
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 9fd632cbd0..27b5a53db0 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -125,6 +125,8 @@
+
+
diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs
index 269e27a4fc..f5e3624f00 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 410cdc51f3..7efc2cf6f6 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 ab6cb89a62..523d393759 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 userDataList, CancellationToken cancellationToken)
{
var hasUserData = item as IHasUserData;
diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
index af9970b699..172ae6814c 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 3eaef59fbe..96160dcc46 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 df06525c1f..a5959f0b75 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 2fe5d8f742..566f4c5f40 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 81d4a786aa..6287d0bb86 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 85eadd73cf..180faa6bbe 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
@@ -350,9 +350,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
}
///
- /// Increment this when indicator drawings change
+ /// Increment this when there's a change requiring caches to be invalidated
///
- private const string IndicatorVersion = "2";
+ private const string Version = "3";
///
/// 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);
+ }
+
///
/// Gets the size of the image.
///
@@ -421,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// The image date modified.
/// ImageSize.
/// path
- 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;
- }
-
///
/// Executes the image enhancers.
///
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index b158097380..75037159c2 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 0b06613219..28883e9a21 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 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 37bca4ddbc..f6a35973b7 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 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 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 681d3ac5e5..ecf58e4a67 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 cdd4c6d7ca..8c72f9e7e6 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 ThrottleCallback { get; set; }
public Action OnComplete { get; set; }
///
@@ -165,14 +161,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// The response stream.
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 1db14e8878..fe662542ef 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 ThrottleCallback { get; set; }
public Action OnComplete { get; set; }
///
@@ -82,14 +78,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// The response stream.
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 a45757d135..6ce989b029 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 GetMediaStreams(string mediaSourceId)
+ {
+ var list = GetMediaStreams(new MediaStreamQuery
+ {
+ ItemId = new Guid(mediaSourceId)
+ });
+
+ return GetMediaStreamsForItem(list);
+ }
+
+ public IEnumerable GetMediaStreams(Guid itemId)
+ {
+ var list = GetMediaStreams(new MediaStreamQuery
+ {
+ ItemId = itemId
+ });
+
+ return GetMediaStreamsForItem(list);
+ }
+
+ private IEnumerable GetMediaStreamsForItem(IEnumerable 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 7371ca5a9c..3551b71b7f 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 bf87924613..00c6744368 100644
--- a/MediaBrowser.Server.Implementations/Library/UserManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs
@@ -97,6 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library
///
public event EventHandler> UserUpdated;
public event EventHandler> UserConfigurationUpdated;
+ public event EventHandler> UserLockedOut;
///
/// 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), _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 f1bb5c13ae..6473d10d66 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 dc80778da9..2f593efcdc 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 509a274ee0..54df9a86d5 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -47,7 +47,7 @@
False
- ..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll
+ ..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll
False
@@ -278,6 +278,7 @@
+
@@ -303,8 +304,12 @@
-
+
+
+
+
+
diff --git a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs
index d8acbe06c8..a33fe21477 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{"DeviceName"}
+ },
+
+ new NotificationTypeInfo
+ {
+ Type = NotificationType.UserLockedOut.ToString(),
+ DefaultTitle = "{UserName} has been locked out.",
+ Variables = new List{"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 e1f98c6592..40b85dad10 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 GetThumbCollage(List 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 GetSquareCollage(List 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 c2af9cdafc..e7cd2f4d26 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 d02ef9d270..8eef8536ab 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 0000000000..68cd44ec94
--- /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
+ {
+ ///
+ /// Gets or sets the user.
+ ///
+ /// The user.
+ public User User { get; set; }
+
+ ///
+ /// Gets or sets the user manager.
+ ///
+ /// The user manager.
+ public IUserManager UserManager { get; set; }
+
+ ///
+ /// Gets or sets the user data repository.
+ ///
+ /// The user data repository.
+ public IUserDataManager UserDataRepository { get; set; }
+
+ ///
+ /// Compares the specified x.
+ ///
+ /// The x.
+ /// The y.
+ /// System.Int32.
+ public int Compare(BaseItem x, BaseItem y)
+ {
+ return GetDate(x).CompareTo(GetDate(y));
+ }
+
+ ///
+ /// Gets the date.
+ ///
+ /// The x.
+ /// DateTime.
+ 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;
+ }
+
+ ///
+ /// Gets the name.
+ ///
+ /// The name.
+ public string Name
+ {
+ get { return ItemSortBy.DateLastContentAdded; }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
index 7605a7a50d..c881591beb 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 d35ff8fc46..2106fc12e9 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 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 0000000000..babdf3f800
--- /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 37caa561e0..0000000000
--- 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().ToArray();
- }
-
- public IEnumerable 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> 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 0000000000..b9008d87e5
--- /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> 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 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 0000000000..3183816c8a
--- /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 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 GetFile(string path, SyncTarget target, IProgress progress, CancellationToken cancellationToken)
+ {
+ return Task.FromResult((Stream)File.OpenRead(path));
+ }
+
+ public string GetFullPath(IEnumerable 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> GetFileSystemEntries(string path, SyncTarget target)
+ {
+ List files;
+
+ try
+ {
+ files = new DirectoryInfo(path).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
+ }
+ catch (DirectoryNotFoundException)
+ {
+ files = new List();
+ }
+
+ 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 GetSyncTargets(string userId)
+ {
+ return GetSyncAccounts()
+ .Where(i => i.UserIds.Contains(userId, StringComparer.OrdinalIgnoreCase))
+ .Select(GetSyncTarget);
+ }
+
+ public IEnumerable GetAllSyncTargets()
+ {
+ return GetSyncAccounts().Select(GetSyncTarget);
+ }
+
+ private SyncTarget GetSyncTarget(SyncAccount account)
+ {
+ return new SyncTarget
+ {
+ Id = account.Id,
+ Name = account.Name
+ };
+ }
+
+ private IEnumerable GetSyncAccounts()
+ {
+ return new List();
+ // Dummy this up
+ return _userManager
+ .Users
+ .Select(i => new SyncAccount
+ {
+ Id = i.Id.ToString("N"),
+ UserIds = new List { 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 UserIds { get; set; }
+
+ public SyncAccount()
+ {
+ UserIds = new List();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs
new file mode 100644
index 0000000000..b7e9daf496
--- /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
+ {
+ ///
+ /// Gets the device profile.
+ ///
+ /// The target.
+ /// DeviceProfile.
+ DeviceProfile GetDeviceProfile(SyncTarget target);
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs
index 349e6aa1d4..0407510a84 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 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();
@@ -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 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();
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 string[] GetPathParts(string serverId, BaseItemDto item)
+ private Task SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken)
{
- return null;
+ return provider.SendFile(inputPath, item.LocalPath, target, new Progress(), 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 GetDirectoryPath(IServerSyncProvider provider, BaseItemDto item, string serverId)
+ {
+ var parts = new List
+ {
+ 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 async Task> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target)
+ {
+ var path = item.LocalPath;
+ path = provider.GetParentDirectoryPath(path, target);
+
+ var list = await provider.GetFileSystemEntries(path, target).ConfigureAwait(false);
+
+ var itemFiles = new List();
+
+ 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 0000000000..cbfa82f1d6
--- /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 providers, IProgress progress, CancellationToken cancellationToken)
+ {
+ var targets = providers
+ .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple(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();
+ 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 a2fd92bf50..8fc3651f76 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 GetSyncTargets(ISyncProvider provider)
+ {
+ return provider.GetAllSyncTargets().Select(i => new SyncTarget
+ {
+ Name = i.Name,
+ Id = GetSyncTargetId(provider, i)
+ });
+ }
+
private IEnumerable 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 41df1b4710..8c530e0155 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -1,6 +1,6 @@
-
+
diff --git a/MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs b/MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs
new file mode 100644
index 0000000000..a66365212b
--- /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 cd010e1c13..8f552ee362 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -74,6 +74,7 @@
Properties\SharedVersion.cs
+
diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
index 1ec0109ad8..139661aa28 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 fac704b687..63d30a6069 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 0000000000..d01756d0e2
--- /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 2dbd844baa..1c4b5b1d58 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.
///
void PreventSystemStandby();
+
+ ///
+ /// Gets the process manager.
+ ///
+ /// IProcessManager.
+ 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 38e07fde49..625b29d369 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 @@
+
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index 6f569718ee..e01353f80e 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -62,7 +62,7 @@
False
- ..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll
+ ..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll
..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll
@@ -113,6 +113,7 @@
+
diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
index 476fb58b9d..74abcb36c2 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 0000000000..f3497aef55
--- /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 ab51c30cc8..3dd0c908d6 100644
--- a/MediaBrowser.ServerApplication/packages.config
+++ b/MediaBrowser.ServerApplication/packages.config
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index 1e188ceae0..8328cf8ab4 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 812deabe11..fa6413b8ba 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -87,6 +87,9 @@
+
+ PreserveNewest
+
PreserveNewest
@@ -114,6 +117,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -129,6 +135,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 5455709e99..7aa33b053c 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 143a3da41b..f73971374a 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 6d9ead15a4..88c6e9f8ae 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Common.Internal
- 3.0.576
+ 3.0.579
MediaBrowser.Common.Internal
Luke
ebr,Luke,scottisafool
@@ -12,7 +12,7 @@
Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.
Copyright © Media Browser 2013
-
+
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index 305db5a1f0..facbe53661 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Common
- 3.0.576
+ 3.0.579
MediaBrowser.Common
Media Browser Team
ebr,Luke,scottisafool
diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec
index 9305fe51ce..dab1034466 100644
--- a/Nuget/MediaBrowser.Model.Signed.nuspec
+++ b/Nuget/MediaBrowser.Model.Signed.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Model.Signed
- 3.0.576
+ 3.0.579
MediaBrowser.Model - Signed Edition
Media Browser Team
ebr,Luke,scottisafool
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index cc7979590a..e94d18fb5a 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Server.Core
- 3.0.576
+ 3.0.579
Media Browser.Server.Core
Media Browser Team
ebr,Luke,scottisafool
@@ -12,7 +12,7 @@
Contains core components required to build plugins for Media Browser Server.
Copyright © Media Browser 2013
-
+