diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ee6870271..18cef5819 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -57,3 +57,4 @@ - [Detector1](https://github.com/Detector1) - [BlackIce013](https://github.com/blackice013) - [mporcas] (https://github.com/mporcas) + - [tikuf] (https://github.com/tikuf/) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 52707c3c6..8754e57a1 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs, OnTranscodeKillTimerStopped); + Parallel.ForEach(_activeTranscodingJobs, KillTranscodingJob); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -84,7 +84,8 @@ namespace MediaBrowser.Api /// The process. /// if set to true [is video]. /// The start time ticks. - public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, bool isVideo, long? startTimeTicks) + /// The source path. + public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, bool isVideo, long? startTimeTicks, string sourcePath) { lock (_activeTranscodingJobs) { @@ -95,7 +96,8 @@ namespace MediaBrowser.Api Process = process, ActiveRequestCount = 1, IsVideo = isVideo, - StartTimeTicks = startTimeTicks + StartTimeTicks = startTimeTicks, + SourcePath = sourcePath }); } } @@ -178,7 +180,7 @@ namespace MediaBrowser.Api if (job.ActiveRequestCount == 0) { - var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 60000; + var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 180000; if (job.KillTimer == null) { @@ -196,10 +198,47 @@ namespace MediaBrowser.Api /// Called when [transcode kill timer stopped]. /// /// The state. - private async void OnTranscodeKillTimerStopped(object state) + private void OnTranscodeKillTimerStopped(object state) { var job = (TranscodingJob)state; + KillTranscodingJob(job); + } + + /// + /// Kills the single transcoding job. + /// + /// The source path. + internal void KillSingleTranscodingJob(string sourcePath) + { + if (string.IsNullOrEmpty(sourcePath)) + { + throw new ArgumentNullException("sourcePath"); + } + + var jobs = new List(); + + lock (_activeTranscodingJobs) + { + // This is really only needed for HLS. + // Progressive streams can stop on their own reliably + jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(sourcePath, i.SourcePath) && i.Type == TranscodingJobType.Hls)); + } + + // This method of killing is a bit of a shortcut, but it saves clients from having to send a request just for that + // But we can only kill if there's one active job. If there are more we won't know which one to stop + if (jobs.Count == 1) + { + KillTranscodingJob(jobs.First()); + } + } + + /// + /// Kills the transcoding job. + /// + /// The job. + private async void KillTranscodingJob(TranscodingJob job) + { lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); @@ -373,6 +412,7 @@ namespace MediaBrowser.Api public bool IsVideo { get; set; } public long? StartTimeTicks { get; set; } + public string SourcePath { get; set; } } /// diff --git a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs new file mode 100644 index 000000000..d225bdd99 --- /dev/null +++ b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs @@ -0,0 +1,181 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using ServiceStack.Common.Web; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Api +{ + public class AuthorizationRequestFilterAttribute : Attribute, IHasRequestFilter + { + //This property will be resolved by the IoC container + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + + public ISessionManager SessionManager { get; set; } + + /// + /// Gets or sets the logger. + /// + /// The logger. + public ILogger Logger { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IHttpRequest request, IHttpResponse response, object requestDto) + { + //This code is executed before the service + + var auth = GetAuthorization(request); + + if (auth != null) + { + User user = null; + + if (auth.ContainsKey("UserId")) + { + var userId = auth["UserId"]; + + if (!string.IsNullOrEmpty(userId)) + { + user = UserManager.GetUserById(new Guid(userId)); + } + } + + string deviceId; + string device; + string client; + string version; + + auth.TryGetValue("DeviceId", out deviceId); + auth.TryGetValue("Device", out device); + auth.TryGetValue("Client", out client); + auth.TryGetValue("Version", out version); + + if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + { + SessionManager.LogConnectionActivity(client, version, deviceId, device, user); + } + } + } + + /// + /// Gets the auth. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + public static Dictionary GetAuthorization(IHttpRequest httpReq) + { + var auth = httpReq.Headers[HttpHeaders.Authorization]; + + return GetAuthorization(auth); + } + + /// + /// Gets the authorization. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + public static AuthorizationInfo GetAuthorization(IRequestContext httpReq) + { + var header = httpReq.GetHeader("Authorization"); + + var auth = GetAuthorization(header); + + string userId; + string deviceId; + string device; + string client; + string version; + + auth.TryGetValue("UserId", out userId); + auth.TryGetValue("DeviceId", out deviceId); + auth.TryGetValue("Device", out device); + auth.TryGetValue("Client", out client); + auth.TryGetValue("Version", out version); + + return new AuthorizationInfo + { + Client = client, + Device = device, + DeviceId = deviceId, + UserId = userId, + Version = version + }; + } + + /// + /// Gets the authorization. + /// + /// The authorization header. + /// Dictionary{System.StringSystem.String}. + private static Dictionary GetAuthorization(string authorizationHeader) + { + if (authorizationHeader == null) return null; + + var parts = authorizationHeader.Split(' '); + + // There should be at least to parts + if (parts.Length < 2) return null; + + // It has to be a digest request + if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Remove uptil the first space + authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' ')); + parts = authorizationHeader.Split(','); + + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var item in parts) + { + var param = item.Trim().Split(new[] { '=' }, 2); + result.Add(param[0], param[1].Trim(new[] { '"' })); + } + + return result; + } + + /// + /// A new shallow copy of this filter is used on every request. + /// + /// IHasRequestFilter. + public IHasRequestFilter Copy() + { + return this; + } + + /// + /// Order in which Request Filters are executed. + /// <0 Executed before global request filters + /// >0 Executed after global request filters + /// + /// The priority. + public int Priority + { + get { return 0; } + } + } + + public class AuthorizationInfo + { + public string UserId; + public string DeviceId; + public string Device; + public string Client; + public string Version; + } +} diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index b3f5027e0..069bc0fe1 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -2,9 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; -using ServiceStack.Common.Web; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; @@ -15,7 +13,7 @@ namespace MediaBrowser.Api /// /// Class BaseApiService /// - [RequestFilter] + [AuthorizationRequestFilter] public class BaseApiService : IHasResultFactory, IRestfulService { /// @@ -308,147 +306,4 @@ namespace MediaBrowser.Api return item; } } - - /// - /// Class RequestFilterAttribute - /// - public class RequestFilterAttribute : Attribute, IHasRequestFilter - { - //This property will be resolved by the IoC container - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } - - public ISessionManager SessionManager { get; set; } - - /// - /// Gets or sets the logger. - /// - /// The logger. - public ILogger Logger { get; set; } - - /// - /// The request filter is executed before the service. - /// - /// The http request wrapper - /// The http response wrapper - /// The request DTO - public void RequestFilter(IHttpRequest request, IHttpResponse response, object requestDto) - { - //This code is executed before the service - - var auth = GetAuthorization(request); - - if (auth != null) - { - User user = null; - - if (auth.ContainsKey("UserId")) - { - var userId = auth["UserId"]; - - if (!string.IsNullOrEmpty(userId)) - { - user = UserManager.GetUserById(new Guid(userId)); - } - } - - string deviceId; - string device; - string client; - string version; - - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - - if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) - { - SessionManager.LogConnectionActivity(client, version, deviceId, device, user); - } - } - } - - /// - /// Gets the auth. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - public static Dictionary GetAuthorization(IHttpRequest httpReq) - { - var auth = httpReq.Headers[HttpHeaders.Authorization]; - - return GetAuthorization(auth); - } - - /// - /// Gets the authorization. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - public static Dictionary GetAuthorization(IRequestContext httpReq) - { - var auth = httpReq.GetHeader("Authorization"); - - return GetAuthorization(auth); - } - - /// - /// Gets the authorization. - /// - /// The authorization header. - /// Dictionary{System.StringSystem.String}. - private static Dictionary GetAuthorization(string authorizationHeader) - { - if (authorizationHeader == null) return null; - - var parts = authorizationHeader.Split(' '); - - // There should be at least to parts - if (parts.Length < 2) return null; - - // It has to be a digest request - if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Remove uptil the first space - authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' ')); - parts = authorizationHeader.Split(','); - - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var item in parts) - { - var param = item.Trim().Split(new[] { '=' }, 2); - result.Add(param[0], param[1].Trim(new[] { '"' })); - } - - return result; - } - - /// - /// A new shallow copy of this filter is used on every request. - /// - /// IHasRequestFilter. - public IHasRequestFilter Copy() - { - return this; - } - - /// - /// Order in which Request Filters are executed. - /// <0 Executed before global request filters - /// >0 Executed after global request filters - /// - /// The priority. - public int Priority - { - get { return 0; } - } - } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index c7cca812f..4f54b5249 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -36,29 +38,25 @@ Always - - False - ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll - - - False - ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll - - + + ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll + + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll + + @@ -70,6 +68,7 @@ + @@ -88,6 +87,8 @@ + + @@ -128,22 +129,24 @@ - {9142eefa-7570-41e1-bfcc-468bb571af2f} + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} MediaBrowser.Controller - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model - + + + diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index e31a112d5..c782c243d 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -613,7 +613,7 @@ namespace MediaBrowser.Api.Playback EnableRaisingEvents = true }; - ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks); + ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks, state.Item.Path); Logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments); diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs index d7ee73a9e..6e36ba0ad 100644 --- a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs @@ -6,7 +6,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using ServiceStack.ServiceHost; using System; -using System.IO; namespace MediaBrowser.Api.Playback.Hls { @@ -20,27 +19,6 @@ namespace MediaBrowser.Api.Playback.Hls } - /// - /// Class GetHlsAudioSegment - /// - [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")] - [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")] - [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] - public class GetHlsAudioSegment - { - /// - /// Gets or sets the id. - /// - /// The id. - public string Id { get; set; } - - /// - /// Gets or sets the segment id. - /// - /// The segment id. - public string SegmentId { get; set; } - } - /// /// Class AudioHlsService /// @@ -59,20 +37,6 @@ namespace MediaBrowser.Api.Playback.Hls { } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetHlsAudioSegment request) - { - var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo); - - file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file); - - return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite); - } - /// /// Gets the specified request. /// diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index e680546b0..05441bba7 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -10,7 +10,6 @@ using MediaBrowser.Model.IO; using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; @@ -213,29 +212,6 @@ namespace MediaBrowser.Api.Playback.Hls return count; } - protected void ExtendHlsTimer(string itemId, string playlistId) - { - var normalizedPlaylistId = playlistId.Replace("-low", string.Empty); - - foreach (var playlist in Directory.EnumerateFiles(ApplicationPaths.EncodedMediaCachePath, "*.m3u8") - .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) - .ToList()) - { - ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); - - // Avoid implicitly captured closure - var playlist1 = playlist; - - Task.Run(async () => - { - // This is an arbitrary time period corresponding to when the request completes. - await Task.Delay(30000).ConfigureAwait(false); - - ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist1, TranscodingJobType.Hls); - }); - } - } - /// /// Gets the command line arguments. /// diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentResponseFilter.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentResponseFilter.cs new file mode 100644 index 000000000..44996c99f --- /dev/null +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentResponseFilter.cs @@ -0,0 +1,53 @@ +using MediaBrowser.Controller; +using MediaBrowser.Model.Logging; +using ServiceStack.ServiceHost; +using ServiceStack.Text.Controller; +using System; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Api.Playback.Hls +{ + public class HlsSegmentResponseFilter : Attribute, IHasResponseFilter + { + public ILogger Logger { get; set; } + public IServerApplicationPaths ApplicationPaths { get; set; } + + public void ResponseFilter(IHttpRequest req, IHttpResponse res, object response) + { + var pathInfo = PathInfo.Parse(req.PathInfo); + var itemId = pathInfo.GetArgumentValue(1); + var playlistId = pathInfo.GetArgumentValue(3); + + OnEndRequest(itemId, playlistId); + } + + public IHasResponseFilter Copy() + { + return this; + } + + public int Priority + { + get { return -1; } + } + + /// + /// Called when [end request]. + /// + /// The item id. + /// The playlist id. + protected void OnEndRequest(string itemId, string playlistId) + { + Logger.Info("OnEndRequest " + playlistId); + var normalizedPlaylistId = playlistId.Replace("-low", string.Empty); + + foreach (var playlist in Directory.EnumerateFiles(ApplicationPaths.EncodedMediaCachePath, "*.m3u8") + .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) + .ToList()) + { + ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); + } + } + } +} diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs new file mode 100644 index 000000000..f1fa86f78 --- /dev/null +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -0,0 +1,147 @@ +using MediaBrowser.Controller; +using ServiceStack.ServiceHost; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Playback.Hls +{ + /// + /// Class GetHlsAudioSegment + /// + [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")] + [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")] + [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] + public class GetHlsAudioSegment + { + /// + /// Gets or sets the id. + /// + /// The id. + public string Id { get; set; } + + /// + /// Gets or sets the segment id. + /// + /// The segment id. + public string SegmentId { get; set; } + } + + /// + /// Class GetHlsVideoSegment + /// + [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")] + [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] + public class GetHlsVideoSegment + { + /// + /// Gets or sets the id. + /// + /// The id. + public string Id { get; set; } + + public string PlaylistId { get; set; } + + /// + /// Gets or sets the segment id. + /// + /// The segment id. + public string SegmentId { get; set; } + } + + /// + /// Class GetHlsVideoSegment + /// + [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")] + [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] + public class GetHlsPlaylist + { + /// + /// Gets or sets the id. + /// + /// The id. + public string Id { get; set; } + + public string PlaylistId { get; set; } + } + + public class HlsSegmentService : BaseApiService + { + private readonly IServerApplicationPaths _appPaths; + + public HlsSegmentService(IServerApplicationPaths appPaths) + { + _appPaths = appPaths; + } + + public object Get(GetHlsPlaylist request) + { + OnBeginRequest(request.PlaylistId); + + var file = request.PlaylistId + Path.GetExtension(RequestContext.PathInfo); + + file = Path.Combine(_appPaths.EncodedMediaCachePath, file); + + return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetHlsVideoSegment request) + { + var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo); + + file = Path.Combine(_appPaths.EncodedMediaCachePath, file); + + OnBeginRequest(request.PlaylistId); + + return ResultFactory.GetStaticFileResult(RequestContext, file); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetHlsAudioSegment request) + { + var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo); + + file = Path.Combine(_appPaths.EncodedMediaCachePath, file); + + return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite); + } + + /// + /// Called when [begin request]. + /// + /// The playlist id. + protected void OnBeginRequest(string playlistId) + { + var normalizedPlaylistId = playlistId.Replace("-low", string.Empty); + + foreach (var playlist in Directory.EnumerateFiles(_appPaths.EncodedMediaCachePath, "*.m3u8") + .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) + .ToList()) + { + ExtendPlaylistTimer(playlist); + } + } + + private void ExtendPlaylistTimer(string playlist) + { + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); + + Task.Run(async () => + { + await Task.Delay(20000).ConfigureAwait(false); + + ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); + }); + } + } +} diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 901b27688..4694b68a1 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; using ServiceStack.ServiceHost; using System; -using System.IO; namespace MediaBrowser.Api.Playback.Hls { @@ -31,44 +30,6 @@ namespace MediaBrowser.Api.Playback.Hls } } - /// - /// Class GetHlsVideoSegment - /// - [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")] - [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] - public class GetHlsVideoSegment - { - /// - /// Gets or sets the id. - /// - /// The id. - public string Id { get; set; } - - public string PlaylistId { get; set; } - - /// - /// Gets or sets the segment id. - /// - /// The segment id. - public string SegmentId { get; set; } - } - - /// - /// Class GetHlsVideoSegment - /// - [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")] - [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] - public class GetHlsPlaylist - { - /// - /// Gets or sets the id. - /// - /// The id. - public string Id { get; set; } - - public string PlaylistId { get; set; } - } - /// /// Class VideoHlsService /// @@ -82,38 +43,12 @@ namespace MediaBrowser.Api.Playback.Hls /// The library manager. /// The iso manager. /// The media encoder. + /// The dto service. public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService) : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService) { } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetHlsVideoSegment request) - { - ExtendHlsTimer(request.Id, request.PlaylistId); - - var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo); - - file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file); - - return ResultFactory.GetStaticFileResult(RequestContext, file); - } - - public object Get(GetHlsPlaylist request) - { - ExtendHlsTimer(request.Id, request.PlaylistId); - - var file = request.PlaylistId + Path.GetExtension(RequestContext.PathInfo); - - file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file); - - return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite); - } - /// /// Gets the specified request. /// diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index cad3c4384..5888d9fba 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -1,7 +1,5 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Session; using ServiceStack.ServiceHost; using System; @@ -189,6 +187,7 @@ namespace MediaBrowser.Api /// Initializes a new instance of the class. /// /// The session manager. + /// The dto service. public SessionsService(ISessionManager sessionManager, IDtoService dtoService) { _sessionManager = sessionManager; @@ -214,277 +213,82 @@ namespace MediaBrowser.Api public void Post(SendPlaystateCommand request) { - var task = SendPlaystateCommand(request); + var command = new PlaystateRequest + { + Command = request.Command, + SeekPositionTicks = request.SeekPositionTicks + }; + + var task = _sessionManager.SendPlaystateCommand(request.Id, command, CancellationToken.None); Task.WaitAll(task); } - private async Task SendPlaystateCommand(SendPlaystateCommand request) - { - var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); - - if (session == null) - { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); - } - - if (!session.SupportsRemoteControl) - { - throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); - } - - var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); - - if (socket != null) - { - try - { - await socket.SendAsync(new WebSocketMessage - { - MessageType = "Playstate", - - Data = new PlaystateRequest - { - Command = request.Command, - SeekPositionTicks = request.SeekPositionTicks - } - - }, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error sending web socket message", ex); - } - } - else - { - throw new InvalidOperationException("The requested session does not have an open web socket."); - } - } - /// /// Posts the specified request. /// /// The request. public void Post(BrowseTo request) { - var task = BrowseTo(request); + var command = new BrowseRequest + { + Context = request.Context, + ItemId = request.ItemId, + ItemName = request.ItemName, + ItemType = request.ItemType + }; + + var task = _sessionManager.SendBrowseCommand(request.Id, command, CancellationToken.None); Task.WaitAll(task); } - /// - /// Browses to. - /// - /// The request. - /// Task. - /// - /// - /// The requested session does not have an open web socket. - private async Task BrowseTo(BrowseTo request) - { - var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); - - if (session == null) - { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); - } - - if (!session.SupportsRemoteControl) - { - throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); - } - - var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); - - if (socket != null) - { - try - { - await socket.SendAsync(new WebSocketMessage - { - MessageType = "Browse", - Data = request - - }, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error sending web socket message", ex); - } - } - else - { - throw new InvalidOperationException("The requested session does not have an open web socket."); - } - } - /// /// Posts the specified request. /// /// The request. public void Post(SendSystemCommand request) { - var task = SendSystemCommand(request); + var task = _sessionManager.SendSystemCommand(request.Id, request.Command, CancellationToken.None); Task.WaitAll(task); } - private async Task SendSystemCommand(SendSystemCommand request) - { - var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); - - if (session == null) - { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); - } - - if (!session.SupportsRemoteControl) - { - throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); - } - - var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); - - if (socket != null) - { - try - { - await socket.SendAsync(new WebSocketMessage - { - MessageType = "SystemCommand", - Data = request.Command.ToString() - - }, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error sending web socket message", ex); - } - } - else - { - throw new InvalidOperationException("The requested session does not have an open web socket."); - } - } - /// /// Posts the specified request. /// /// The request. public void Post(SendMessageCommand request) { - var task = SendMessageCommand(request); + var command = new MessageCommand + { + Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, + TimeoutMs = request.TimeoutMs, + Text = request.Text + }; + + var task = _sessionManager.SendMessageCommand(request.Id, command, CancellationToken.None); Task.WaitAll(task); } - private async Task SendMessageCommand(SendMessageCommand request) - { - var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); - - if (session == null) - { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); - } - - if (!session.SupportsRemoteControl) - { - throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); - } - - var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); - - if (socket != null) - { - try - { - await socket.SendAsync(new WebSocketMessage - { - MessageType = "MessageCommand", - - Data = new MessageCommand - { - Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, - TimeoutMs = request.TimeoutMs, - Text = request.Text - } - - }, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error sending web socket message", ex); - } - } - else - { - throw new InvalidOperationException("The requested session does not have an open web socket."); - } - } - /// /// Posts the specified request. /// /// The request. public void Post(Play request) { - var task = Play(request); + var command = new PlayRequest + { + ItemIds = request.ItemIds.Split(',').ToArray(), + + PlayCommand = request.PlayCommand, + StartPositionTicks = request.StartPositionTicks + }; + + var task = _sessionManager.SendPlayCommand(request.Id, command, CancellationToken.None); Task.WaitAll(task); } - - /// - /// Plays the specified request. - /// - /// The request. - /// Task. - /// - /// - /// The requested session does not have an open web socket. - private async Task Play(Play request) - { - var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); - - if (session == null) - { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); - } - - if (!session.SupportsRemoteControl) - { - throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); - } - - var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); - - if (socket != null) - { - try - { - await socket.SendAsync(new WebSocketMessage - { - MessageType = "Play", - - Data = new PlayRequest - { - ItemIds = request.ItemIds.Split(',').ToArray(), - - PlayCommand = request.PlayCommand, - StartPositionTicks = request.StartPositionTicks - } - - }, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error sending web socket message", ex); - } - } - else - { - throw new InvalidOperationException("The requested session does not have an open web socket."); - } - } } } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index ab3e2af19..abd42910f 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -186,7 +186,7 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public DateTime? DatePlayed { get; set; } - + /// /// Gets or sets the id. /// @@ -224,6 +224,13 @@ namespace MediaBrowser.Api.UserLibrary [Api(Description = "Reports that a user has begun playing an item")] public class OnPlaybackStart : IReturnVoid { + public OnPlaybackStart() + { + // Have to default these until all clients have a chance to incorporate them + CanSeek = true; + QueueableMediaTypes = "Audio,Video,Book,Game"; + } + /// /// Gets or sets the user id. /// @@ -237,6 +244,20 @@ namespace MediaBrowser.Api.UserLibrary /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } + + /// + /// Gets or sets a value indicating whether this is likes. + /// + /// true if likes; otherwise, false. + [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public bool CanSeek { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string QueueableMediaTypes { get; set; } } /// @@ -378,6 +399,8 @@ namespace MediaBrowser.Api.UserLibrary /// The library manager. /// The user data repository. /// The item repo. + /// The session manager. + /// The dto service. /// jsonSerializer public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager, IDtoService dtoService) { @@ -640,19 +663,11 @@ namespace MediaBrowser.Api.UserLibrary private SessionInfo GetSession() { - var auth = RequestFilterAttribute.GetAuthorization(RequestContext); + var auth = AuthorizationRequestFilterAttribute.GetAuthorization(RequestContext); - string deviceId; - string client; - string version; - - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - - return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, deviceId) && - string.Equals(i.Client, client) && - string.Equals(i.ApplicationVersion, version)); + return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && + string.Equals(i.Client, auth.Client) && + string.Equals(i.ApplicationVersion, auth.Version)); } /// @@ -665,7 +680,17 @@ namespace MediaBrowser.Api.UserLibrary var item = _dtoService.GetItemByDtoId(request.Id, user.Id); - _sessionManager.OnPlaybackStart(item, GetSession().Id); + var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty); + + var info = new PlaybackInfo + { + CanSeek = request.CanSeek, + Item = item, + SessionId = GetSession().Id, + QueueableMediaTypes = queueableMediaTypes.Split(',').ToList() + }; + + _sessionManager.OnPlaybackStart(info); } /// @@ -693,7 +718,12 @@ namespace MediaBrowser.Api.UserLibrary var item = _dtoService.GetItemByDtoId(request.Id, user.Id); - var task = _sessionManager.OnPlaybackStopped(item, request.PositionTicks, GetSession().Id); + // Kill the encoding + ApiEntryPoint.Instance.KillSingleTranscodingJob(item.Path); + + var session = GetSession(); + + var task = _sessionManager.OnPlaybackStopped(item, request.PositionTicks, session.Id); Task.WaitAll(task); } diff --git a/MediaBrowser.Common.Implementations/Archiving/ZipClient.cs b/MediaBrowser.Common.Implementations/Archiving/ZipClient.cs new file mode 100644 index 000000000..39690eb07 --- /dev/null +++ b/MediaBrowser.Common.Implementations/Archiving/ZipClient.cs @@ -0,0 +1,87 @@ +using MediaBrowser.Model.IO; +using SharpCompress.Archive.SevenZip; +using SharpCompress.Common; +using SharpCompress.Reader; +using System.IO; + +namespace MediaBrowser.Common.Implementations.Archiving +{ + /// + /// Class DotNetZipClient + /// + public class ZipClient : IZipClient + { + /// + /// Extracts all. + /// + /// The source file. + /// The target path. + /// if set to true [overwrite existing files]. + public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles) + { + using (var fileStream = File.OpenRead(sourceFile)) + { + ExtractAll(fileStream, targetPath, overwriteExistingFiles); + } + } + + /// + /// Extracts all. + /// + /// The source. + /// The target path. + /// if set to true [overwrite existing files]. + public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles) + { + using (var reader = ReaderFactory.Open(source)) + { + var options = ExtractOptions.ExtractFullPath; + + if (overwriteExistingFiles) + { + options = options | ExtractOptions.Overwrite; + } + + reader.WriteAllToDirectory(targetPath, options); + } + } + + /// + /// Extracts all from7z. + /// + /// The source file. + /// The target path. + /// if set to true [overwrite existing files]. + public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles) + { + using (var fileStream = File.OpenRead(sourceFile)) + { + ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles); + } + } + + /// + /// Extracts all from7z. + /// + /// The source. + /// The target path. + /// if set to true [overwrite existing files]. + public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles) + { + using (var archive = SevenZipArchive.Open(source)) + { + using (var reader = archive.ExtractAllEntries()) + { + var options = ExtractOptions.ExtractFullPath; + + if (overwriteExistingFiles) + { + options = options | ExtractOptions.Overwrite; + } + + reader.WriteAllToDirectory(targetPath, options); + } + } + } + } +} diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index c0ac6a4b3..0d96df9a2 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; +using MediaBrowser.Common.Implementations.Archiving; using MediaBrowser.Common.Implementations.NetworkManagement; using MediaBrowser.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.Implementations.Security; @@ -10,6 +11,7 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; @@ -149,6 +151,12 @@ namespace MediaBrowser.Common.Implementations /// The installation manager. protected IInstallationManager InstallationManager { get; set; } + /// + /// Gets or sets the zip client. + /// + /// The zip client. + protected IZipClient ZipClient { get; set; } + /// /// Initializes a new instance of the class. /// @@ -202,12 +210,27 @@ namespace MediaBrowser.Common.Implementations { Resolve().AddTasks(GetExports(false)); - Task.Run(() => ConfigureAutoRunAtStartup()); + Task.Run(() => ConfigureAutorun()); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; }); } + /// + /// Configures the autorun. + /// + private void ConfigureAutorun() + { + try + { + ConfigureAutoRunAtStartup(ConfigurationManager.CommonConfiguration.RunAtStartup); + } + catch (Exception ex) + { + Logger.ErrorException("Error configuring autorun", ex); + } + } + /// /// Gets the composable part assemblies. /// @@ -281,6 +304,9 @@ namespace MediaBrowser.Common.Implementations InstallationManager = new InstallationManager(Logger, this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, NetworkManager, ConfigurationManager); RegisterSingleInstance(InstallationManager); + + ZipClient = new ZipClient(); + RegisterSingleInstance(ZipClient); }); } @@ -453,11 +479,6 @@ namespace MediaBrowser.Common.Implementations } } - /// - /// Defines the full path to our shortcut in the start menu - /// - protected abstract string ProductShortcutPath { get; } - /// /// Handles the ConfigurationUpdated event of the ConfigurationManager control. /// @@ -466,32 +487,10 @@ namespace MediaBrowser.Common.Implementations /// protected virtual void OnConfigurationUpdated(object sender, EventArgs e) { - ConfigureAutoRunAtStartup(); + ConfigureAutorun(); } - /// - /// Configures the auto run at startup. - /// - private void ConfigureAutoRunAtStartup() - { - if (ConfigurationManager.CommonConfiguration.RunAtStartup) - { - //Copy our shortut into the startup folder for this user - File.Copy(ProductShortcutPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(ProductShortcutPath) ?? "MBstartup.lnk"), true); - } - else - { - //Remove our shortcut from the startup folder for this user - try - { - File.Delete(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(ProductShortcutPath) ?? "MBstartup.lnk")); - } - catch (FileNotFoundException) - { - //This is okay - trying to remove it anyway - } - } - } + protected abstract void ConfigureAutoRunAtStartup(bool autorun); /// /// Removes the plugin. diff --git a/MediaBrowser.Common.Implementations/Logging/NlogManager.cs b/MediaBrowser.Common.Implementations/Logging/NlogManager.cs index 109e85d80..e20f9bc13 100644 --- a/MediaBrowser.Common.Implementations/Logging/NlogManager.cs +++ b/MediaBrowser.Common.Implementations/Logging/NlogManager.cs @@ -5,7 +5,6 @@ using NLog.Targets; using System; using System.IO; using System.Linq; -using System.Threading.Tasks; namespace MediaBrowser.Common.Implementations.Logging { @@ -193,17 +192,14 @@ namespace MediaBrowser.Common.Implementations.Logging if (LoggerLoaded != null) { - Task.Run(() => + try { - try - { - LoggerLoaded(this, EventArgs.Empty); - } - catch (Exception ex) - { - GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex); - } - }); + LoggerLoaded(this, EventArgs.Empty); + } + catch (Exception ex) + { + GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex); + } } } } diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index a96f2c354..11da950f7 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -35,17 +37,8 @@ Always - - False - ..\packages\NLog.2.0.1.2\lib\net45\NLog.dll - - - False - ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll - - - False - ..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll + + ..\packages\sharpcompress.0.10.1.3\lib\net40\SharpCompress.dll @@ -54,11 +47,21 @@ + + ..\packages\NLog.2.0.1.2\lib\net45\NLog.dll + + + ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll + + + ..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll + Properties\SharedVersion.cs + @@ -88,11 +91,11 @@ - {9142eefa-7570-41e1-bfcc-468bb571af2f} + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs index 8278c8a28..6605432fa 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Model.Logging; @@ -8,6 +7,7 @@ using MediaBrowser.Model.Tasks; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace MediaBrowser.Common.Implementations.ScheduledTasks { @@ -77,6 +77,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks QueueScheduledTask(); } + /// + /// Cancels if running + /// + /// + public void CancelIfRunning() + where T : IScheduledTask + { + var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); + ((ScheduledTaskWorker)task).CancelIfRunning(); + } + /// /// Queues the scheduled task. /// diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 15f955723..bfd626adb 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -54,33 +54,32 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Task. public Task Execute(CancellationToken cancellationToken, IProgress progress) { - return Task.Run(() => + // Delete log files more than n days old + var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays)); + + var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories) + .Where(f => f.LastWriteTimeUtc < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) { - // Delete log files more than n days old - var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays)); + double percent = index; + percent /= filesToDelete.Count; - var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories) - .Where(f => f.LastWriteTimeUtc < minDateModified) - .ToList(); + progress.Report(100 * percent); - var index = 0; + cancellationToken.ThrowIfCancellationRequested(); - foreach (var file in filesToDelete) - { - double percent = index; - percent /= filesToDelete.Count; + File.Delete(file.FullName); - progress.Report(100 * percent); + index++; + } - cancellationToken.ThrowIfCancellationRequested(); + progress.Report(100); - File.Delete(file.FullName); - - index++; - } - - progress.Report(100); - }); + return Task.FromResult(true); } /// diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs index e860834ec..00928255c 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs @@ -58,7 +58,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks progress.Report(0); - return Task.Run(() => LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info)); + LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging + ? LogSeverity.Debug + : LogSeverity.Info); + + return Task.FromResult(true); } /// diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 4be861cce..d03cb14e0 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 1611c55da..8acd1a83c 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -32,26 +34,19 @@ prompt 4 - - - - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll - - - False - ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll - + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll + @@ -113,7 +108,7 @@ - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs b/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs index ec0e7c1c9..394872783 100644 --- a/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs +++ b/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs @@ -21,6 +21,13 @@ namespace MediaBrowser.Common.ScheduledTasks void CancelIfRunningAndQueue() where T : IScheduledTask; + /// + /// Cancels if running. + /// + /// + void CancelIfRunning() + where T : IScheduledTask; + /// /// Queues the scheduled task. /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 0d91a2e86..0f090f587 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -16,7 +16,6 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; -using MoreLinq; namespace MediaBrowser.Controller.Entities { @@ -171,6 +170,25 @@ namespace MediaBrowser.Controller.Entities return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken); } + /// + /// Clears the children. + /// + /// The cancellation token. + /// Task. + public Task ClearChildren(CancellationToken cancellationToken) + { + var items = ActualChildren.ToList(); + + ClearChildrenInternal(); + + foreach (var item in items) + { + LibraryManager.ReportItemRemoved(item); + } + + return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken); + } + #region Indexing /// @@ -671,7 +689,7 @@ namespace MediaBrowser.Controller.Entities var options = new ParallelOptions { - MaxDegreeOfParallelism = 20 + MaxDegreeOfParallelism = 10 }; Parallel.ForEach(nonCachedChildren, options, child => @@ -733,6 +751,11 @@ namespace MediaBrowser.Controller.Entities if (actualRemovals.Count > 0) { RemoveChildrenInternal(actualRemovals); + + foreach (var item in actualRemovals) + { + LibraryManager.ReportItemRemoved(item); + } } await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); @@ -781,7 +804,7 @@ namespace MediaBrowser.Controller.Entities foreach (var tuple in list) { - if (tasks.Count > 8) + if (tasks.Count > 5) { await Task.WhenAll(tasks).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index f49bd8cf0..0b27a350b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -42,6 +44,8 @@ x86 prompt MinimumRecommendedRules.ruleset + 4 + false bin\x86\Release\ @@ -51,12 +55,9 @@ x86 prompt MinimumRecommendedRules.ruleset + 4 - - False - ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll - @@ -66,6 +67,9 @@ + + ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll + @@ -165,6 +169,8 @@ + + @@ -172,11 +178,11 @@ - {9142eefa-7570-41e1-bfcc-468bb571af2f} + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index a8dc8788f..2364debed 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -385,7 +385,7 @@ namespace MediaBrowser.Controller.Providers var sb = new StringBuilder(); var extensions = FileStampExtensionsDictionary; - var numExtensions = extensions.Count; + var numExtensions = FilestampExtensions.Length; // Record the name of each file // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 1976c653a..0932ee52d 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,7 +1,9 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Session; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Session @@ -11,6 +13,12 @@ namespace MediaBrowser.Controller.Session /// public interface ISessionManager { + /// + /// Adds the parts. + /// + /// The remote controllers. + void AddParts(IEnumerable remoteControllers); + /// /// Occurs when [playback start]. /// @@ -47,11 +55,9 @@ namespace MediaBrowser.Controller.Session /// /// Used to report that playback has started for an item /// - /// The item. - /// The session id. + /// The info. /// Task. - /// - Task OnPlaybackStart(BaseItem item, Guid sessionId); + Task OnPlaybackStart(PlaybackInfo info); /// /// Used to report playback progress for an item @@ -59,6 +65,7 @@ namespace MediaBrowser.Controller.Session /// The item. /// The position ticks. /// if set to true [is paused]. + /// if set to true [is muted]. /// The session id. /// Task. /// @@ -73,5 +80,50 @@ namespace MediaBrowser.Controller.Session /// Task. /// Task OnPlaybackStopped(BaseItem item, long? positionTicks, Guid sessionId); + + /// + /// Sends the system command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken); + + /// + /// Sends the message command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken); + + /// + /// Sends the play command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken); + + /// + /// Sends the browse command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken); + + /// + /// Sends the playstate command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Session/ISessionRemoteController.cs b/MediaBrowser.Controller/Session/ISessionRemoteController.cs new file mode 100644 index 000000000..9ba5c983d --- /dev/null +++ b/MediaBrowser.Controller/Session/ISessionRemoteController.cs @@ -0,0 +1,61 @@ +using MediaBrowser.Model.Session; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Session +{ + public interface ISessionRemoteController + { + /// + /// Supportses the specified session. + /// + /// The session. + /// true if XXXX, false otherwise + bool Supports(SessionInfo session); + + /// + /// Sends the system command. + /// + /// The session. + /// The command. + /// The cancellation token. + /// Task. + Task SendSystemCommand(SessionInfo session, SystemCommand command, CancellationToken cancellationToken); + + /// + /// Sends the message command. + /// + /// The session. + /// The command. + /// The cancellation token. + /// Task. + Task SendMessageCommand(SessionInfo session, MessageCommand command, CancellationToken cancellationToken); + + /// + /// Sends the play command. + /// + /// The session. + /// The command. + /// The cancellation token. + /// Task. + Task SendPlayCommand(SessionInfo session, PlayRequest command, CancellationToken cancellationToken); + + /// + /// Sends the browse command. + /// + /// The session. + /// The command. + /// The cancellation token. + /// Task. + Task SendBrowseCommand(SessionInfo session, BrowseRequest command, CancellationToken cancellationToken); + + /// + /// Sends the playstate command. + /// + /// The session. + /// The command. + /// The cancellation token. + /// Task. + Task SendPlaystateCommand(SessionInfo session, PlaystateRequest command, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Session/PlaybackInfo.cs b/MediaBrowser.Controller/Session/PlaybackInfo.cs new file mode 100644 index 000000000..ab3111e76 --- /dev/null +++ b/MediaBrowser.Controller/Session/PlaybackInfo.cs @@ -0,0 +1,38 @@ +using MediaBrowser.Controller.Entities; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Session +{ + public class PlaybackInfo + { + public PlaybackInfo() + { + QueueableMediaTypes = new List(); + } + + /// + /// Gets or sets a value indicating whether this instance can seek. + /// + /// true if this instance can seek; otherwise, false. + public bool CanSeek { get; set; } + + /// + /// Gets or sets the queueable media types. + /// + /// The queueable media types. + public List QueueableMediaTypes { get; set; } + + /// + /// Gets or sets the item. + /// + /// The item. + public BaseItem Item { get; set; } + + /// + /// Gets or sets the session id. + /// + /// The session id. + public Guid SessionId { get; set; } + } +} diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 6c0f1a085..dc934b70a 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -15,8 +15,21 @@ namespace MediaBrowser.Controller.Session public SessionInfo() { WebSockets = new List(); + QueueableMediaTypes = new List(); } + /// + /// Gets or sets a value indicating whether this instance can seek. + /// + /// true if this instance can seek; otherwise, false. + public bool CanSeek { get; set; } + + /// + /// Gets or sets the queueable media types. + /// + /// The queueable media types. + public List QueueableMediaTypes { get; set; } + /// /// Gets or sets the id. /// @@ -70,7 +83,7 @@ namespace MediaBrowser.Controller.Session /// /// The name of the now viewing item. public string NowViewingItemName { get; set; } - + /// /// Gets or sets the now playing item. /// @@ -94,7 +107,7 @@ namespace MediaBrowser.Controller.Session /// /// true if this instance is muted; otherwise, false. public bool IsMuted { get; set; } - + /// /// Gets or sets the device id. /// @@ -126,7 +139,7 @@ namespace MediaBrowser.Controller.Session return WebSockets.Any(i => i.State == WebSocketState.Open); } - return (DateTime.UtcNow - LastActivityDate).TotalMinutes <= 5; + return (DateTime.UtcNow - LastActivityDate).TotalMinutes <= 10; } } diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 03ea79b3b..4a459cac8 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -497,9 +497,11 @@ namespace MediaBrowser.Model.ApiClient /// /// The item id. /// The user id. + /// if set to true [is seekable]. + /// The list of media types that the client is capable of queuing onto the playlist. See MediaType class. /// Task{UserItemDataDto}. /// itemId - Task ReportPlaybackStartAsync(string itemId, string userId); + Task ReportPlaybackStartAsync(string itemId, string userId, bool isSeekable, List queueableMediaTypes); /// /// Reports playback progress to the server diff --git a/MediaBrowser.Model/IO/IZipClient.cs b/MediaBrowser.Model/IO/IZipClient.cs index c9e7e0db6..694c393aa 100644 --- a/MediaBrowser.Model/IO/IZipClient.cs +++ b/MediaBrowser.Model/IO/IZipClient.cs @@ -22,5 +22,21 @@ namespace MediaBrowser.Model.IO /// The target path. /// if set to true [overwrite existing files]. void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles); + + /// + /// Extracts all from7z. + /// + /// The source file. + /// The target path. + /// if set to true [overwrite existing files]. + void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles); + + /// + /// Extracts all from7z. + /// + /// The source. + /// The target path. + /// if set to true [overwrite existing files]. + void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles); } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 8fb471c2d..fa4fc2986 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -14,6 +14,8 @@ ..\ true ..\packages\Fody.1.17.0.0 + 10.0.0 + 2.0 true @@ -162,14 +164,13 @@ - - False - ..\packages\PropertyChanged.Fody.1.41.0.0\Lib\NET35\PropertyChanged.dll - False - + + ..\packages\PropertyChanged.Fody.1.41.0.0\Lib\NET35\PropertyChanged.dll + False + diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs index f9b0e0abd..02b7f0226 100644 --- a/MediaBrowser.Model/Session/SessionInfoDto.cs +++ b/MediaBrowser.Model/Session/SessionInfoDto.cs @@ -1,11 +1,24 @@ -using System.ComponentModel; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using System; +using System.Collections.Generic; +using System.ComponentModel; namespace MediaBrowser.Model.Session { public class SessionInfoDto : INotifyPropertyChanged { + /// + /// Gets or sets a value indicating whether this instance can seek. + /// + /// true if this instance can seek; otherwise, false. + public bool CanSeek { get; set; } + + /// + /// Gets or sets the queueable media types. + /// + /// The queueable media types. + public List QueueableMediaTypes { get; set; } + /// /// Gets or sets the id. /// diff --git a/MediaBrowser.Mono.sln b/MediaBrowser.Mono.sln new file mode 100644 index 000000000..0dc78ca2a --- /dev/null +++ b/MediaBrowser.Mono.sln @@ -0,0 +1,68 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common.Implementations", "MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj", "{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Implementations", "MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj", "{2E781478-814D-4A48-9D80-BFF206441A65}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Mono", "MediaBrowser.Server.Mono\MediaBrowser.Server.Mono.csproj", "{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|x86 + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.Build.0 = Debug|x86 + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|x86 + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|x86 + {2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.Build.0 = Debug|Any CPU + {2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU + {2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU + {A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Debug|x86.ActiveCfg = Debug|x86 + {A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Debug|x86.Build.0 = Debug|x86 + {A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Release|x86.ActiveCfg = Release|x86 + {A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Release|x86.Build.0 = Release|x86 + {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.Build.0 = Debug|Any CPU + {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.ActiveCfg = Release|Any CPU + {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = MediaBrowser.Server.Mono\MediaBrowser.Server.Mono.csproj + EndGlobalSection +EndGlobal diff --git a/MediaBrowser.Mono.userprefs b/MediaBrowser.Mono.userprefs new file mode 100644 index 000000000..80da5915d --- /dev/null +++ b/MediaBrowser.Mono.userprefs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 139c622fc..ef94d77d1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -32,10 +34,6 @@ 4 - - False - ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll - @@ -44,6 +42,9 @@ + + ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll + @@ -116,15 +117,15 @@ - {9142eefa-7570-41e1-bfcc-468bb571af2f} + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} MediaBrowser.Controller - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs index 690c9b3ff..c28d06cbb 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs @@ -178,6 +178,7 @@ namespace MediaBrowser.Providers.MediaInfo } } + SetLastRefreshed(item, DateTime.UtcNow); return true; } diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index fefcd8371..adc013699 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs b/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs index 18868d3ea..e18351248 100644 --- a/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs +++ b/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs @@ -23,7 +23,9 @@ namespace MediaBrowser.Providers.Music public Task Run(IProgress progress, CancellationToken cancellationToken) { - return Task.Run(() => RunInternal(progress, cancellationToken)); + RunInternal(progress, cancellationToken); + + return Task.FromResult(true); } private void RunInternal(IProgress progress, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs b/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs index e6195c03e..bd63b5fbd 100644 --- a/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/AlbumXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -58,7 +59,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { }); + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); // Set last refreshed so that the provider doesn't trigger after the file save PersonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); diff --git a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs index 795e824fc..a27fb7363 100644 --- a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -70,7 +71,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { }); + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); // Set last refreshed so that the provider doesn't trigger after the file save ArtistProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); diff --git a/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs b/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs index f5fc37fe7..f09b34570 100644 --- a/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; @@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { }); + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); BoxSetProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); } diff --git a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs index d90cb94c2..854c508b9 100644 --- a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -87,7 +88,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new[] + XmlSaverHelpers.Save(builder, xmlFilePath, new List { "FirstAired", "SeasonNumber", diff --git a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs index 23339ec75..6e95cc8c5 100644 --- a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -77,7 +78,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { }); + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); FolderProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); } diff --git a/MediaBrowser.Providers/Savers/GameXmlSaver.cs b/MediaBrowser.Providers/Savers/GameXmlSaver.cs index 41bd364c8..1e654f72f 100644 --- a/MediaBrowser.Providers/Savers/GameXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/GameXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Providers.Movies; @@ -66,7 +67,7 @@ namespace MediaBrowser.Providers.Savers if (!string.IsNullOrEmpty(game.GameSystem)) { - builder.Append(""); + builder.Append("" + SecurityElement.Escape(game.GameSystem) + ""); } XmlSaverHelpers.AddCommonNodes(item, builder); @@ -75,7 +76,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new[] + XmlSaverHelpers.Save(builder, xmlFilePath, new List { "Players", "GameSystem" diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index 2402bcd7f..761bcefd1 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; @@ -103,7 +104,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new[] + XmlSaverHelpers.Save(builder, xmlFilePath, new List { "IMDBrating", "Description", diff --git a/MediaBrowser.Providers/Savers/PersonXmlSaver.cs b/MediaBrowser.Providers/Savers/PersonXmlSaver.cs index 1b1377ac8..92f6db29b 100644 --- a/MediaBrowser.Providers/Savers/PersonXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/PersonXmlSaver.cs @@ -1,4 +1,5 @@ -using System.Security; +using System.Collections.Generic; +using System.Security; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Providers.Movies; @@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new[] + XmlSaverHelpers.Save(builder, xmlFilePath, new List { "PlaceOfBirth" }); diff --git a/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs b/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs index 97e8b671f..e484b3d39 100644 --- a/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeasonXmlSaver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { }); + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); SeasonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); } diff --git a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs index 4e2f538c7..a4ff9c7d8 100644 --- a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -105,7 +106,7 @@ namespace MediaBrowser.Providers.Savers var xmlFilePath = GetSavePath(item); - XmlSaverHelpers.Save(builder, xmlFilePath, new[] + XmlSaverHelpers.Save(builder, xmlFilePath, new List { "id", "SeriesName", @@ -113,7 +114,10 @@ namespace MediaBrowser.Providers.Savers "Network", "Airs_Time", "Airs_DayOfWeek", - "FirstAired" + "FirstAired", + + // Don't preserve old series node + "Series" }); // Set last refreshed so that the provider doesn't trigger after the file save diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index cea7cf926..338447c10 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -29,13 +29,11 @@ namespace MediaBrowser.Providers.Savers /// The XML. /// The path. /// The XML tags used. - public static void Save(StringBuilder xml, string path, IEnumerable xmlTagsUsed) + public static void Save(StringBuilder xml, string path, List xmlTagsUsed) { if (File.Exists(path)) { - var tags = xmlTagsUsed.ToList(); - - tags.AddRange(new[] + xmlTagsUsed.AddRange(new[] { "MediaInfo", "ContentRating", @@ -88,7 +86,7 @@ namespace MediaBrowser.Providers.Savers }); var position = xml.ToString().LastIndexOf("The path. /// The XML tags used. /// System.String. - private static string GetCustomTags(string path, ICollection xmlTagsUsed) + private static string GetCustomTags(string path, IEnumerable xmlTagsUsed) { - var doc = new XmlDocument(); - doc.Load(path); + var settings = new XmlReaderSettings + { + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true, + ValidationType = ValidationType.None + }; - var nodes = doc.DocumentElement.ChildNodes.Cast() - .Where(i => !xmlTagsUsed.Contains(i.Name)) - .Select(i => i.OuterXml) - .ToArray(); + var tagsDictionary = xmlTagsUsed.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - return string.Join(Environment.NewLine, nodes); + var builder = new StringBuilder(); + + using (var streamReader = new StreamReader(path, Encoding.UTF8)) + { + // Use XmlReader for best performance + using (var reader = XmlReader.Create(streamReader, settings)) + { + reader.MoveToContent(); + + // Loop through each element + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + if (!tagsDictionary.ContainsKey(reader.Name)) + { + builder.AppendLine(reader.ReadOuterXml()); + } + else + { + reader.Skip(); + } + } + } + } + } + + return builder.ToString(); } /// diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs index a781551de..2b73ba1f7 100644 --- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs +++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs @@ -21,7 +21,9 @@ namespace MediaBrowser.Providers.TV public Task Run(IProgress progress, CancellationToken cancellationToken) { - return Task.Run(() => RunInternal(progress, cancellationToken)); + RunInternal(progress, cancellationToken); + + return Task.FromResult(true); } private void RunInternal(IProgress progress, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 1f7361d2f..305bede56 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Drawing } catch (IOException) { - // Cache file doesn't exist or is currently being written ro + // Cache file doesn't exist or is currently being written to } var semaphore = GetLock(cacheFilePath); @@ -129,21 +129,24 @@ namespace MediaBrowser.Server.Implementations.Drawing await semaphore.WaitAsync().ConfigureAwait(false); // Check again in case of lock contention - if (File.Exists(cacheFilePath)) + try { - try - { - using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - return; - } - } - finally + using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); semaphore.Release(); + return; } } + catch (IOException) + { + // Cache file doesn't exist or is currently being written to + } + catch + { + semaphore.Release(); + throw; + } try { @@ -188,12 +191,10 @@ namespace MediaBrowser.Server.Implementations.Drawing var bytes = outputMemoryStream.ToArray(); - var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length); + await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); // kick off a task to cache the result - var cacheTask = CacheResizedImage(cacheFilePath, bytes); - - await Task.WhenAll(outputTask, cacheTask).ConfigureAwait(false); + CacheResizedImage(cacheFilePath, bytes, semaphore); } } } @@ -202,12 +203,51 @@ namespace MediaBrowser.Server.Implementations.Drawing } } } - finally + catch { semaphore.Release(); + + throw; } } + /// + /// Caches the resized image. + /// + /// The cache file path. + /// The bytes. + /// The semaphore. + private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore) + { + Task.Run(async () => + { + try + { + var parentPath = Path.GetDirectoryName(cacheFilePath); + + if (!Directory.Exists(parentPath)) + { + Directory.CreateDirectory(parentPath); + } + + // Save to the cache location + using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + // Save to the filestream + await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath); + } + finally + { + semaphore.Release(); + } + }); + } + /// /// Sets the color of the background. /// @@ -363,28 +403,6 @@ namespace MediaBrowser.Server.Implementations.Drawing return croppedImagePath; } - /// - /// Caches the resized image. - /// - /// The cache file path. - /// The bytes. - private async Task CacheResizedImage(string cacheFilePath, byte[] bytes) - { - var parentPath = Path.GetDirectoryName(cacheFilePath); - - if (!Directory.Exists(parentPath)) - { - Directory.CreateDirectory(parentPath); - } - - // Save to the cache location - using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - // Save to the filestream - await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - } - } - /// /// Gets the cache file path based on a set of parameters /// diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index e5260004a..a5f54b938 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -237,7 +237,9 @@ namespace MediaBrowser.Server.Implementations.Dto NowViewingItemId = session.NowViewingItemId, NowViewingItemName = session.NowViewingItemName, NowViewingItemType = session.NowViewingItemType, - ApplicationVersion = session.ApplicationVersion + ApplicationVersion = session.ApplicationVersion, + CanSeek = session.CanSeek, + QueueableMediaTypes = session.QueueableMediaTypes }; if (session.NowPlayingItem != null) diff --git a/MediaBrowser.ServerApplication/EntryPoints/UdpServerEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs similarity index 95% rename from MediaBrowser.ServerApplication/EntryPoints/UdpServerEntryPoint.cs rename to MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 595d5c89f..9c1a953b1 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/UdpServerEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.Udp; using System.Net.Sockets; -namespace MediaBrowser.ServerApplication.EntryPoints +namespace MediaBrowser.Server.Implementations.EntryPoints { /// /// Class UdpServerEntryPoint @@ -35,6 +35,8 @@ namespace MediaBrowser.ServerApplication.EntryPoints /// private readonly IHttpServer _httpServer; + public const int PortNumber = 7359; + /// /// Initializes a new instance of the class. /// @@ -59,7 +61,7 @@ namespace MediaBrowser.ServerApplication.EntryPoints try { - udpServer.Start(ApplicationHost.UdpServerPort); + udpServer.Start(PortNumber); UdpServer = udpServer; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs index f6547dec1..4f795fdd5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs @@ -314,6 +314,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The CTX. private async void ProcessHttpRequestAsync(HttpListenerContext context) { + var date = DateTime.Now; + LogHttpRequest(context); if (context.Request.IsWebSocketRequest) @@ -360,7 +362,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer var url = context.Request.Url.ToString(); var endPoint = context.Request.RemoteEndPoint; - LogResponse(context, url, endPoint); + var duration = DateTime.Now - date; + + LogResponse(context, url, endPoint, duration); } catch (Exception ex) @@ -461,14 +465,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The CTX. /// The URL. /// The end point. - private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint) + /// The duration. + private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration) { if (!EnableHttpRequestLogging) { return; } - var statusode = ctx.Response.StatusCode; + var statusCode = ctx.Response.StatusCode; var log = new StringBuilder(); @@ -476,7 +481,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k]))); - var msg = "Http Response Sent (" + statusode + ") to " + endPoint; + var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds); + + var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime; _logger.LogMultiline(msg, LogSeverity.Debug, log); } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index a5b792726..1bc3f1094 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -829,10 +829,6 @@ namespace MediaBrowser.Server.Implementations.Library /// Task. public async Task ValidatePeople(CancellationToken cancellationToken, IProgress progress) { - const int maxTasks = 3; - - var tasks = new List(); - var people = RootFolder.RecursiveChildren .SelectMany(c => c.People) .DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase) @@ -842,47 +838,27 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var person in people) { - if (tasks.Count > maxTasks) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); + cancellationToken.ThrowIfCancellationRequested(); - // Safe cancellation point, when there are no pending tasks - cancellationToken.ThrowIfCancellationRequested(); + try + { + var item = GetPerson(person.Name); + + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + catch (IOException ex) + { + _logger.ErrorException("Error validating IBN entry {0}", ex, person.Name); } - // Avoid accessing the foreach variable within the closure - var currentPerson = person; + // Update progress + numComplete++; + double percent = numComplete; + percent /= people.Count; - tasks.Add(Task.Run(async () => - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var item = GetPerson(currentPerson.Name); - - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (IOException ex) - { - _logger.ErrorException("Error validating IBN entry {0}", ex, currentPerson.Name); - } - - // Update progress - lock (progress) - { - numComplete++; - double percent = numComplete; - percent /= people.Count; - - progress.Report(100 * percent); - } - })); + progress.Report(100 * percent); } - await Task.WhenAll(tasks).ConfigureAwait(false); - progress.Report(100); _logger.Info("People validation complete"); @@ -956,7 +932,9 @@ namespace MediaBrowser.Server.Implementations.Library public Task ValidateMediaLibrary(IProgress progress, CancellationToken cancellationToken) { // Just run the scheduled task so that the user can see it - return Task.Run(() => _taskManager.CancelIfRunningAndQueue()); + _taskManager.CancelIfRunningAndQueue(); + + return Task.FromResult(true); } /// diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index eb89210ff..b9e033d23 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren.OfType().ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType().ToList())) .ToList(); @@ -79,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index 9a34dd1b0..e4d989c33 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren - .Where(i => !(i is IHasMusicGenres) && !(i is Game)) - .ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => !(m is IHasMusicGenres) && !(m is Game)).ToList())) .ToList(); - var allLibraryItems = allItems; - var masterDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); // Populate counts of items @@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 1b211d5f4..1edc24762 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren - .Where(i => i is IHasMusicGenres) - .ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => m is IHasMusicGenres).ToList())) .ToList(); - var allLibraryItems = allItems; - var masterDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); // Populate counts of items @@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index efefaeba3..dc96632f6 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -41,7 +41,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Task. public Task Run(IProgress progress, CancellationToken cancellationToken) { - return Task.Run(() => RunInternal(progress, cancellationToken)); + RunInternal(progress, cancellationToken); + + return Task.FromResult(true); } private void RunInternal(IProgress progress, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index a4d880329..05689f8e5 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -41,14 +41,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple>(i.Id, i.RootFolder.GetRecursiveChildren(i).ToList())) .ToList(); - var allLibraryItems = allItems; - var masterDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); // Populate counts of items @@ -81,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9d2fc8c6b..f409b7205 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -35,62 +37,14 @@ ..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll - - False - ..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll ..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll - - False - ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll - - - False - ..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll - - - False - ..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll - - - False - ..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll - - - False - ..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll - - - False - ..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll - - - False - ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll - - - False - ..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.dll - - - False - ..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.Linq.dll - ..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll @@ -106,6 +60,42 @@ + + ..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll + + + ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll + + + ..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll + + + ..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll + + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll + + + ..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll + + + ..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll + + + ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll + + + ..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.dll + + + ..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.Linq.dll + @@ -123,6 +113,7 @@ + @@ -183,6 +174,7 @@ Code + @@ -222,19 +214,19 @@ - {c4d2573a-3fd3-441f-81af-174ac4cd4e1d} + {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D} MediaBrowser.Common.Implementations - {9142eefa-7570-41e1-bfcc-468bb571af2f} + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} MediaBrowser.Controller - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index b24c9a5ca..2f353b8c0 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// /// The FF probe resource pool /// - private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2); + private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(1, 1); public string FFMpegPath { get; private set; } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index f4f5f08e4..9c5cf6f1c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -333,17 +333,16 @@ namespace MediaBrowser.Server.Implementations.Persistence /// Task. public Task SaveCriticReviews(Guid itemId, IEnumerable criticReviews) { - return Task.Run(() => + if (!Directory.Exists(_criticReviewsPath)) { - if (!Directory.Exists(_criticReviewsPath)) - { - Directory.CreateDirectory(_criticReviewsPath); - } + Directory.CreateDirectory(_criticReviewsPath); + } - var path = Path.Combine(_criticReviewsPath, itemId + ".json"); + var path = Path.Combine(_criticReviewsPath, itemId + ".json"); - _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); - }); + _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); + + return Task.FromResult(true); } /// diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index 608738f7f..d8872f318 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -102,6 +102,18 @@ namespace MediaBrowser.Server.Implementations.Providers using (source) { + // If the file is currently hidden we'll have to remove that or the save will fail + var file = new FileInfo(path); + + // This will fail if the file is hidden + if (file.Exists) + { + if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + file.Attributes &= ~FileAttributes.Hidden; + } + } + using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) { await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index d9b88368b..4829dc405 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -159,6 +159,10 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { previouslyFailedImages = new List(); } + catch (DirectoryNotFoundException) + { + previouslyFailedImages = new List(); + } foreach (var video in videos) { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index ce757d142..79dfbc8a5 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Events; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -75,6 +77,12 @@ namespace MediaBrowser.Server.Implementations.Session _userRepository = userRepository; } + private List _remoteControllers; + public void AddParts(IEnumerable remoteControllers) + { + _remoteControllers = remoteControllers.ToList(); + } + /// /// Gets all connections. /// @@ -122,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Session var activityDate = DateTime.UtcNow; var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user); - + session.LastActivityDate = activityDate; if (user == null) @@ -207,25 +215,33 @@ namespace MediaBrowser.Server.Implementations.Session /// /// Used to report that playback has started for an item /// - /// The item. - /// The session id. + /// The info. /// Task. - /// - public async Task OnPlaybackStart(BaseItem item, Guid sessionId) + /// info + public async Task OnPlaybackStart(PlaybackInfo info) { - if (item == null) + if (info == null) { - throw new ArgumentNullException(); + throw new ArgumentNullException("info"); + } + if (info.SessionId == Guid.Empty) + { + throw new ArgumentNullException("info"); } - var session = Sessions.First(i => i.Id.Equals(sessionId)); + var session = Sessions.First(i => i.Id.Equals(info.SessionId)); + + var item = info.Item; UpdateNowPlayingItem(session, item, false, false); + session.CanSeek = info.CanSeek; + session.QueueableMediaTypes = info.QueueableMediaTypes; + var key = item.GetUserDataKey(); var user = session.User; - + var data = _userDataRepository.GetUserData(user.Id, key); data.PlayCount++; @@ -313,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Session { throw new ArgumentOutOfRangeException("positionTicks"); } - + var session = Sessions.First(i => i.Id.Equals(sessionId)); RemoveNowPlayingItem(session, item); @@ -321,7 +337,7 @@ namespace MediaBrowser.Server.Implementations.Session var key = item.GetUserDataKey(); var user = session.User; - + var data = _userDataRepository.GetUserData(user.Id, key); if (positionTicks.HasValue) @@ -400,5 +416,118 @@ namespace MediaBrowser.Server.Implementations.Session data.PlaybackPositionTicks = positionTicks; } + + /// + /// Gets the session for remote control. + /// + /// The session id. + /// SessionInfo. + /// + private SessionInfo GetSessionForRemoteControl(Guid sessionId) + { + var session = Sessions.First(i => i.Id.Equals(sessionId)); + + if (session == null) + { + throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); + } + + if (!session.SupportsRemoteControl) + { + throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); + } + + return session; + } + + /// + /// Gets the controllers. + /// + /// The session. + /// IEnumerable{ISessionRemoteController}. + private IEnumerable GetControllers(SessionInfo session) + { + return _remoteControllers.Where(i => i.Supports(session)); + } + + /// + /// Sends the system command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + public Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendSystemCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// + /// Sends the message command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + public Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendMessageCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// + /// Sends the play command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + public Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendPlayCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// + /// Sends the browse command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + public Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendBrowseCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// + /// Sends the playstate command. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + public Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendPlaystateCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } } } diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index 2a4361e61..95eb5948f 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -101,16 +101,7 @@ namespace MediaBrowser.Server.Implementations.Session } else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase)) { - _logger.Debug("Received PlaybackStart message"); - - var session = _sessionManager.Sessions.FirstOrDefault(i => i.WebSockets.Contains(message.Connection)); - - if (session != null && session.User != null) - { - var item = _dtoService.GetItemByDtoId(message.Data); - - _sessionManager.OnPlaybackStart(item, session.Id); - } + ReportPlaybackStart(message); } else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase)) { @@ -170,5 +161,46 @@ namespace MediaBrowser.Server.Implementations.Session return _trueTaskResult; } + + /// + /// Reports the playback start. + /// + /// The message. + private void ReportPlaybackStart(WebSocketMessageInfo message) + { + _logger.Debug("Received PlaybackStart message"); + + var session = _sessionManager.Sessions + .FirstOrDefault(i => i.WebSockets.Contains(message.Connection)); + + if (session != null && session.User != null) + { + var vals = message.Data.Split('|'); + + var item = _dtoService.GetItemByDtoId(vals[0]); + + var queueableMediaTypes = string.Empty; + var canSeek = true; + + if (vals.Length > 1) + { + canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase); + } + if (vals.Length > 2) + { + queueableMediaTypes = vals[2]; + } + + var info = new PlaybackInfo + { + CanSeek = canSeek, + Item = item, + SessionId = session.Id, + QueueableMediaTypes = queueableMediaTypes.Split(',').ToList() + }; + + _sessionManager.OnPlaybackStart(info); + } + } } } diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs new file mode 100644 index 000000000..6915cfc64 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -0,0 +1,92 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Session +{ + public class WebSocketController : ISessionRemoteController + { + public bool Supports(SessionInfo session) + { + return session.WebSockets.Any(i => i.State == WebSocketState.Open); + } + + private IWebSocketConnection GetSocket(SessionInfo session) + { + var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); + + + if (socket == null) + { + throw new InvalidOperationException("The requested session does not have an open web socket."); + } + + return socket; + } + + public Task SendSystemCommand(SessionInfo session, SystemCommand command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "SystemCommand", + Data = command.ToString() + + }, cancellationToken); + } + + public Task SendMessageCommand(SessionInfo session, MessageCommand command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "MessageCommand", + Data = command + + }, cancellationToken); + } + + public Task SendPlayCommand(SessionInfo session, PlayRequest command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "Play", + Data = command + + }, cancellationToken); + } + + public Task SendBrowseCommand(SessionInfo session, BrowseRequest command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "Browse", + Data = command + + }, cancellationToken); + } + + public Task SendPlaystateCommand(SessionInfo session, PlaystateRequest command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "Playstate", + Data = command + + }, cancellationToken); + } + } +} diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs index 958201625..de998254c 100644 --- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs +++ b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs @@ -92,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.WebSocket /// Task. public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken) { - return Task.Run(() => UserContext.Send(bytes)); + UserContext.Send(bytes); + + return Task.FromResult(true); } /// diff --git a/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloader.cs new file mode 100644 index 000000000..cc268ef07 --- /dev/null +++ b/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloader.cs @@ -0,0 +1,36 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.ServerApplication.FFMpeg +{ + public class FFMpegDownloader + { + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + private readonly IZipClient _zipClient; + + public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient) + { + _logger = logger; + _appPaths = appPaths; + _httpClient = httpClient; + _zipClient = zipClient; + } + + public Task GetFFMpegInfo() + { + return Task.FromResult (new FFMpegInfo()); + } + } +} diff --git a/MediaBrowser.Server.Mono/MainWindow.cs b/MediaBrowser.Server.Mono/MainWindow.cs new file mode 100644 index 000000000..229f44dab --- /dev/null +++ b/MediaBrowser.Server.Mono/MainWindow.cs @@ -0,0 +1,16 @@ +using System; +using Gtk; + +public partial class MainWindow: Gtk.Window +{ + public MainWindow (): base (Gtk.WindowType.Toplevel) + { + Build (); + } + + protected void OnDeleteEvent (object sender, DeleteEventArgs a) + { + Application.Quit (); + a.RetVal = true; + } +} diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj new file mode 100644 index 000000000..1c369daac --- /dev/null +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -0,0 +1,119 @@ + + + + Debug + x86 + 10.0.0 + 2.0 + {A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0} + WinExe + MediaBrowser.Server.Mono + MediaBrowser.Server.Mono + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + x86 + false + + + full + true + bin\Release + prompt + 4 + x86 + false + + + + + + + + + + + + + + + + + gui.stetic + + + + + + + + + + EntryPoints\StartupWizard.cs + + + Native\BrowserLauncher.cs + + + + + + FFMpeg\FFMpegInfo.cs + + + ApplicationHost.cs + + + + + + + + + + {5624B7B5-B5A7-41D8-9F10-CC5611109619} + MediaBrowser.WebDashboard + + + {2E781478-814D-4A48-9D80-BFF206441A65} + MediaBrowser.Server.Implementations + + + {442B5058-DCAF-4263-BB6A-F21E31120A1B} + MediaBrowser.Providers + + + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} + MediaBrowser.Model + + + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} + MediaBrowser.Controller + + + {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D} + MediaBrowser.Common.Implementations + + + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} + MediaBrowser.Common + + + {4FD51AC5-2C16-4308-A993-C3A84F3B4582} + MediaBrowser.Api + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.Server.Mono/Native/Assemblies.cs b/MediaBrowser.Server.Mono/Native/Assemblies.cs new file mode 100644 index 000000000..eae6366e1 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/Assemblies.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Assemblies + /// + public static class Assemblies + { + /// + /// Gets the assemblies with parts. + /// + /// List{Assembly}. + public static List GetAssembliesWithParts() + { + var list = new List(); + + return list; + } + } +} diff --git a/MediaBrowser.Server.Mono/Native/Autorun.cs b/MediaBrowser.Server.Mono/Native/Autorun.cs new file mode 100644 index 000000000..ee33c5967 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/Autorun.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Autorun + /// + public static class Autorun + { + /// + /// Configures the specified autorun. + /// + /// if set to true [autorun]. + public static void Configure(bool autorun) + { + + } + } +} diff --git a/MediaBrowser.Server.Mono/Native/HttpMessageHandlerFactory.cs b/MediaBrowser.Server.Mono/Native/HttpMessageHandlerFactory.cs new file mode 100644 index 000000000..5823a7e51 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/HttpMessageHandlerFactory.cs @@ -0,0 +1,25 @@ +using System.Net; +using System.Net.Cache; +using System.Net.Http; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class HttpMessageHandlerFactory + /// + public static class HttpMessageHandlerFactory + { + /// + /// Gets the HTTP message handler. + /// + /// if set to true [enable HTTP compression]. + /// HttpMessageHandler. + public static HttpMessageHandler GetHttpMessageHandler(bool enableHttpCompression) + { + return new HttpClientHandler + { + AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None + }; + } + } +} diff --git a/MediaBrowser.Server.Mono/Native/NativeApp.cs b/MediaBrowser.Server.Mono/Native/NativeApp.cs new file mode 100644 index 000000000..bb47f6ea4 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/NativeApp.cs @@ -0,0 +1,25 @@ + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class NativeApp + /// + public static class NativeApp + { + /// + /// Shutdowns this instance. + /// + public static void Shutdown() + { + + } + + /// + /// Restarts this instance. + /// + public static void Restart() + { + + } + } +} diff --git a/MediaBrowser.Server.Mono/Native/ServerAuthorization.cs b/MediaBrowser.Server.Mono/Native/ServerAuthorization.cs new file mode 100644 index 000000000..6f43a12c0 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/ServerAuthorization.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Authorization + /// + public static class ServerAuthorization + { + /// + /// Authorizes the server. + /// + /// The HTTP server port. + /// The HTTP server URL prefix. + /// The web socket port. + /// The UDP port. + /// The temp directory. + public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int webSocketPort, int udpPort, string tempDirectory) + { + + } + } +} diff --git a/MediaBrowser.Server.Mono/Native/Sqlite.cs b/MediaBrowser.Server.Mono/Native/Sqlite.cs new file mode 100644 index 000000000..cc20952d7 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/Sqlite.cs @@ -0,0 +1,36 @@ +using System.Data; +using System.Data.SQLite; +using System.Threading.Tasks; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Sqlite + /// + public static class Sqlite + { + /// + /// Connects to db. + /// + /// The db path. + /// Task{IDbConnection}. + /// dbPath + public static async Task OpenDatabase(string dbPath) + { + var connectionstr = new SQLiteConnectionStringBuilder + { + PageSize = 4096, + CacheSize = 4096, + SyncMode = SynchronizationModes.Normal, + DataSource = dbPath, + JournalMode = SQLiteJournalModeEnum.Wal + }; + + var connection = new SQLiteConnection(connectionstr.ConnectionString); + + await connection.OpenAsync().ConfigureAwait(false); + + return connection; + } + } +} diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs new file mode 100644 index 000000000..72dee1162 --- /dev/null +++ b/MediaBrowser.Server.Mono/Program.cs @@ -0,0 +1,16 @@ +using System; +using Gtk; + +namespace MediaBrowser.Server.Mono +{ + class MainClass + { + public static void Main (string[] args) + { + Application.Init (); + MainWindow win = new MainWindow (); + win.Show (); + Application.Run (); + } + } +} diff --git a/MediaBrowser.Server.Mono/Properties/AssemblyInfo.cs b/MediaBrowser.Server.Mono/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0a2e93220 --- /dev/null +++ b/MediaBrowser.Server.Mono/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. +[assembly: AssemblyTitle ("MediaBrowser.Server.Mono")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("Luke")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. +[assembly: AssemblyVersion ("1.0.*")] +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/MediaBrowser.Server.Mono/gtk-gui/MainWindow.cs b/MediaBrowser.Server.Mono/gtk-gui/MainWindow.cs new file mode 100644 index 000000000..c481dfc8c --- /dev/null +++ b/MediaBrowser.Server.Mono/gtk-gui/MainWindow.cs @@ -0,0 +1,20 @@ + +// This file has been generated by the GUI designer. Do not modify. +public partial class MainWindow +{ + protected virtual void Build () + { + global::Stetic.Gui.Initialize (this); + // Widget MainWindow + this.Name = "MainWindow"; + this.Title = global::Mono.Unix.Catalog.GetString ("MainWindow"); + this.WindowPosition = ((global::Gtk.WindowPosition)(4)); + if ((this.Child != null)) { + this.Child.ShowAll (); + } + this.DefaultWidth = 400; + this.DefaultHeight = 300; + this.Show (); + this.DeleteEvent += new global::Gtk.DeleteEventHandler (this.OnDeleteEvent); + } +} diff --git a/MediaBrowser.Server.Mono/gtk-gui/generated.cs b/MediaBrowser.Server.Mono/gtk-gui/generated.cs new file mode 100644 index 000000000..9ef336398 --- /dev/null +++ b/MediaBrowser.Server.Mono/gtk-gui/generated.cs @@ -0,0 +1,29 @@ + +// This file has been generated by the GUI designer. Do not modify. +namespace Stetic +{ + internal class Gui + { + private static bool initialized; + + internal static void Initialize (Gtk.Widget iconRenderer) + { + if ((Stetic.Gui.initialized == false)) { + Stetic.Gui.initialized = true; + } + } + } + + internal class ActionGroups + { + public static Gtk.ActionGroup GetActionGroup (System.Type type) + { + return Stetic.ActionGroups.GetActionGroup (type.FullName); + } + + public static Gtk.ActionGroup GetActionGroup (string name) + { + return null; + } + } +} diff --git a/MediaBrowser.Server.Mono/gtk-gui/gui.stetic b/MediaBrowser.Server.Mono/gtk-gui/gui.stetic new file mode 100644 index 000000000..81685442c --- /dev/null +++ b/MediaBrowser.Server.Mono/gtk-gui/gui.stetic @@ -0,0 +1,20 @@ + + + + .. + 2.12 + + + + + + + + MainWindow + CenterOnParent + + + + + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs index 69de391a4..706206d3a 100644 --- a/MediaBrowser.ServerApplication/App.xaml.cs +++ b/MediaBrowser.ServerApplication/App.xaml.cs @@ -154,58 +154,5 @@ namespace MediaBrowser.ServerApplication { Dispatcher.Invoke(Shutdown); } - - /// - /// Opens the dashboard page. - /// - /// The page. - /// The logged in user. - /// The configuration manager. - /// The app host. - public static void OpenDashboardPage(string page, User loggedInUser, IServerConfigurationManager configurationManager, IServerApplicationHost appHost) - { - var url = "http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" + - appHost.WebApplicationName + "/dashboard/" + page; - - OpenUrl(url); - } - - /// - /// Opens the URL. - /// - /// The URL. - public static void OpenUrl(string url) - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = url - }, - - EnableRaisingEvents = true - }; - - process.Exited += ProcessExited; - - try - { - process.Start(); - } - catch (Exception ex) - { - MessageBox.Show("There was an error launching your web browser. Please check your defualt browser settings."); - } - } - - /// - /// Processes the exited. - /// - /// The sender. - /// The instance containing the event data. - static void ProcessExited(object sender, EventArgs e) - { - ((Process)sender).Dispose(); - } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 5cae99785..d0f7da73d 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -24,7 +24,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; -using MediaBrowser.IsoMounter; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; @@ -36,6 +35,7 @@ using MediaBrowser.Server.Implementations.BdInfo; using MediaBrowser.Server.Implementations.Configuration; using MediaBrowser.Server.Implementations.Drawing; using MediaBrowser.Server.Implementations.Dto; +using MediaBrowser.Server.Implementations.EntryPoints; using MediaBrowser.Server.Implementations.HttpServer; using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.Library; @@ -46,16 +46,14 @@ using MediaBrowser.Server.Implementations.Providers; using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; using MediaBrowser.Server.Implementations.WebSocket; -using MediaBrowser.ServerApplication.Implementations; +using MediaBrowser.ServerApplication.FFMpeg; +using MediaBrowser.ServerApplication.Native; using MediaBrowser.WebDashboard.Api; using System; using System.Collections.Generic; -using System.Data.SQLite; -using System.Diagnostics; +using System.Data; using System.IO; using System.Linq; -using System.Net; -using System.Net.Cache; using System.Net.Http; using System.Reflection; using System.Threading; @@ -68,8 +66,6 @@ namespace MediaBrowser.ServerApplication /// public class ApplicationHost : BaseApplicationHost, IServerApplicationHost { - internal const int UdpServerPort = 7359; - /// /// Gets the server kernel. /// @@ -142,11 +138,6 @@ namespace MediaBrowser.ServerApplication /// The provider manager. private IProviderManager ProviderManager { get; set; } /// - /// Gets or sets the zip client. - /// - /// The zip client. - private IZipClient ZipClient { get; set; } - /// /// Gets or sets the HTTP server. /// /// The HTTP server. @@ -161,6 +152,7 @@ namespace MediaBrowser.ServerApplication private IMediaEncoder MediaEncoder { get; set; } private IIsoManager IsoManager { get; set; } + private ISessionManager SessionManager { get; set; } private ILocalizationManager LocalizationManager { get; set; } @@ -174,14 +166,6 @@ namespace MediaBrowser.ServerApplication private IItemRepository ItemRepository { get; set; } private INotificationsRepository NotificationsRepository { get; set; } - /// - /// The full path to our startmenu shortcut - /// - protected override string ProductShortcutPath - { - get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Media Browser 3", "Media Browser Server.lnk"); } - } - private Task _httpServerCreationTask; /// @@ -255,8 +239,7 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(() => new BdInfoExaminer()); - ZipClient = new DotNetZipClient(); - RegisterSingleInstance(ZipClient); + var mediaEncoderTask = RegisterMediaEncoder(); UserDataRepository = new SqliteUserDataRepository(ApplicationPaths, JsonSerializer, LogManager); RegisterSingleInstance(UserDataRepository); @@ -284,10 +267,8 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager)); - await RegisterMediaEncoder().ConfigureAwait(false); - - var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository); - RegisterSingleInstance(clientConnectionManager); + SessionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository); + RegisterSingleInstance(SessionManager); HttpServer = await _httpServerCreationTask.ConfigureAwait(false); RegisterSingleInstance(HttpServer, false); @@ -310,7 +291,7 @@ namespace MediaBrowser.ServerApplication await ConfigureNotificationsRepository().ConfigureAwait(false); - await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask).ConfigureAwait(false); + await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask, mediaEncoderTask).ConfigureAwait(false); SetKernelProperties(); } @@ -406,27 +387,14 @@ namespace MediaBrowser.ServerApplication /// The db path. /// Task{IDbConnection}. /// dbPath - private static async Task ConnectToDb(string dbPath) + private static Task ConnectToDb(string dbPath) { if (string.IsNullOrEmpty(dbPath)) { throw new ArgumentNullException("dbPath"); } - var connectionstr = new SQLiteConnectionStringBuilder - { - PageSize = 4096, - CacheSize = 4096, - SyncMode = SynchronizationModes.Normal, - DataSource = dbPath, - JournalMode = SQLiteJournalModeEnum.Wal - }; - - var connection = new SQLiteConnection(connectionstr.ConnectionString); - - await connection.OpenAsync().ConfigureAwait(false); - - return connection; + return Sqlite.OpenDatabase(dbPath); } /// @@ -477,6 +445,8 @@ namespace MediaBrowser.ServerApplication IsoManager.AddParts(GetExports()); + SessionManager.AddParts(GetExports()); + ImageProcessor.AddParts(GetExports()); } @@ -527,7 +497,6 @@ namespace MediaBrowser.ServerApplication { NotifyPendingRestart(); } - } /// @@ -544,7 +513,7 @@ namespace MediaBrowser.ServerApplication Logger.ErrorException("Error sending server restart web socket message", ex); } - MainStartup.Restart(); + NativeApp.Restart(); } /// @@ -568,44 +537,44 @@ namespace MediaBrowser.ServerApplication /// IEnumerable{Assembly}. protected override IEnumerable GetComposablePartAssemblies() { + var list = Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly) + .Select(LoadAssembly) + .Where(a => a != null) + .ToList(); + // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that // This will prevent the .dll file from getting locked, and allow us to replace it when needed - foreach (var pluginAssembly in Directory - .EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly) - .Select(LoadAssembly).Where(a => a != null)) - { - yield return pluginAssembly; - } // Include composable parts in the Api assembly - yield return typeof(ApiEntryPoint).Assembly; + list.Add(typeof(ApiEntryPoint).Assembly); // Include composable parts in the Dashboard assembly - yield return typeof(DashboardInfo).Assembly; + list.Add(typeof(DashboardInfo).Assembly); // Include composable parts in the Model assembly - yield return typeof(SystemInfo).Assembly; + list.Add(typeof(SystemInfo).Assembly); // Include composable parts in the Common assembly - yield return typeof(IApplicationHost).Assembly; + list.Add(typeof(IApplicationHost).Assembly); // Include composable parts in the Controller assembly - yield return typeof(Kernel).Assembly; + list.Add(typeof(Kernel).Assembly); // Include composable parts in the Providers assembly - yield return typeof(ImagesByNameProvider).Assembly; + list.Add(typeof(ImagesByNameProvider).Assembly); // Common implementations - yield return typeof(TaskManager).Assembly; + list.Add(typeof(TaskManager).Assembly); // Server implementations - yield return typeof(ServerApplicationPaths).Assembly; + list.Add(typeof(ServerApplicationPaths).Assembly); - // Pismo - yield return typeof(PismoIsoManager).Assembly; + list.AddRange(Assemblies.GetAssembliesWithParts()); // Include composable parts in the running assembly - yield return GetType().Assembly; + list.Add(GetType().Assembly); + + return list; } private readonly string _systemId = Environment.MachineName.GetMD5().ToString(); @@ -664,7 +633,7 @@ namespace MediaBrowser.ServerApplication Logger.ErrorException("Error sending server shutdown web socket message", ex); } - MainStartup.Shutdown(); + NativeApp.Shutdown(); } /// @@ -674,36 +643,16 @@ namespace MediaBrowser.ServerApplication { Logger.Info("Requesting administrative access to authorize http server"); - // Create a temp file path to extract the bat file to - var tmpFile = Path.Combine(ConfigurationManager.CommonApplicationPaths.TempDirectory, Guid.NewGuid() + ".bat"); - - // Extract the bat file - using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.ServerApplication.RegisterServer.bat")) + try { - using (var fileStream = File.Create(tmpFile)) - { - stream.CopyTo(fileStream); - } + ServerAuthorization.AuthorizeServer(ServerConfigurationManager.Configuration.HttpServerPortNumber, + HttpServerUrlPrefix, ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber, + UdpServerEntryPoint.PortNumber, + ConfigurationManager.CommonApplicationPaths.TempDirectory); } - - var startInfo = new ProcessStartInfo + catch (Exception ex) { - FileName = tmpFile, - - Arguments = string.Format("{0} {1} {2} {3}", ServerConfigurationManager.Configuration.HttpServerPortNumber, - HttpServerUrlPrefix, - UdpServerPort, - ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber), - - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - Verb = "runas", - ErrorDialog = false - }; - - using (var process = Process.Start(startInfo)) - { - process.WaitForExit(); + Logger.ErrorException("Error authorizing server", ex); } } @@ -713,8 +662,7 @@ namespace MediaBrowser.ServerApplication /// The cancellation token. /// The progress. /// Task{CheckForUpdateResult}. - public override async Task CheckForApplicationUpdate(CancellationToken cancellationToken, - IProgress progress) + public override async Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress) { var availablePackages = await InstallationManager.GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); @@ -745,11 +693,12 @@ namespace MediaBrowser.ServerApplication /// HttpMessageHandler. protected override HttpMessageHandler GetHttpMessageHandler(bool enableHttpCompression) { - return new WebRequestHandler - { - CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate), - AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None - }; + return HttpMessageHandlerFactory.GetHttpMessageHandler(enableHttpCompression); + } + + protected override void ConfigureAutoRunAtStartup(bool autorun) + { + Autorun.Configure(autorun); } } } diff --git a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs index 87578ef84..1a5f9e2c3 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs +++ b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs @@ -3,9 +3,10 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; -using System.ComponentModel; +using System; using System.Linq; -using System.Windows; +using System.Windows.Forms; +using MediaBrowser.ServerApplication.Native; namespace MediaBrowser.ServerApplication.EntryPoints { @@ -31,9 +32,10 @@ namespace MediaBrowser.ServerApplication.EntryPoints /// /// The app host. /// The user manager. - public StartupWizard(IServerApplicationHost appHost, IUserManager userManager, IServerConfigurationManager configurationManager) + public StartupWizard(IServerApplicationHost appHost, IUserManager userManager, IServerConfigurationManager configurationManager, ILogger logger) { _appHost = appHost; + _logger = logger; _userManager = userManager; _configurationManager = configurationManager; } @@ -58,9 +60,9 @@ namespace MediaBrowser.ServerApplication.EntryPoints try { - App.OpenDashboardPage("wizardstart.html", user, _configurationManager, _appHost); + BrowserLauncher.OpenDashboardPage("wizardstart.html", user, _configurationManager, _appHost, _logger); } - catch (Win32Exception ex) + catch (Exception ex) { _logger.ErrorException("Error launching startup wizard", ex); @@ -75,4 +77,4 @@ namespace MediaBrowser.ServerApplication.EntryPoints { } } -} +} \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs new file mode 100644 index 000000000..926767f5b --- /dev/null +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs @@ -0,0 +1,302 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.ServerApplication.FFMpeg +{ + public class FFMpegDownloader + { + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + private readonly IZipClient _zipClient; + + private const string Version = "ffmpeg20130904"; + + private readonly string[] _fontUrls = new[] + { + "https://www.dropbox.com/s/pj847twf7riq0j7/ARIALUNI.7z?dl=1" + }; + + private readonly string[] _ffMpegUrls = new[] + { + "https://github.com/MediaBrowser/MediaBrowser/raw/master/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z", + + "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20130904-git-f974289-win32-static.7z", + "https://www.dropbox.com/s/a81cb2ob23fwcfs/ffmpeg-20130904-git-f974289-win32-static.7z?dl=1" + }; + + public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient) + { + _logger = logger; + _appPaths = appPaths; + _httpClient = httpClient; + _zipClient = zipClient; + } + + public async Task GetFFMpegInfo() + { + var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), Version); + + var info = new FFMpegInfo + { + ProbePath = Path.Combine(versionedDirectoryPath, "ffprobe.exe"), + Path = Path.Combine(versionedDirectoryPath, "ffmpeg.exe"), + Version = Version + }; + + if (!Directory.Exists(versionedDirectoryPath)) + { + Directory.CreateDirectory(versionedDirectoryPath); + } + + var tasks = new List(); + + if (!File.Exists(info.ProbePath) || !File.Exists(info.Path)) + { + tasks.Add(DownloadFFMpeg(info)); + } + + tasks.Add(DownloadFonts(versionedDirectoryPath)); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + return info; + } + + private async Task DownloadFFMpeg(FFMpegInfo info) + { + foreach (var url in _ffMpegUrls) + { + try + { + var tempFile = await DownloadFFMpeg(info, url).ConfigureAwait(false); + + ExtractFFMpeg(tempFile, Path.GetDirectoryName(info.Path)); + return; + } + catch (HttpException ex) + { + + } + } + + throw new ApplicationException("Unable to download required components. Please try again later."); + } + + private Task DownloadFFMpeg(FFMpegInfo info, string url) + { + return _httpClient.GetTempFile(new HttpRequestOptions + { + Url = url, + CancellationToken = CancellationToken.None, + Progress = new Progress(), + + // Make it look like a browser + // Try to hide that we're direct linking + UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.47 Safari/537.36" + }); + } + + private void ExtractFFMpeg(string tempFile, string targetFolder) + { + _logger.Debug("Extracting ffmpeg from {0}", tempFile); + + var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString()); + + if (!Directory.Exists(tempFolder)) + { + Directory.CreateDirectory(tempFolder); + } + + try + { + Extract7zArchive(tempFile, tempFolder); + + var files = Directory.EnumerateFiles(tempFolder, "*.exe", SearchOption.AllDirectories).ToList(); + + foreach (var file in files) + { + File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file))); + } + } + finally + { + DeleteFile(tempFile); + } + } + + private void Extract7zArchive(string archivePath, string targetPath) + { + _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); + } + + private void DeleteFile(string path) + { + try + { + File.Delete(path); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting temp file {0}", ex, path); + } + } + + /// + /// Extracts the fonts. + /// + /// The target path. + private async Task DownloadFonts(string targetPath) + { + try + { + var fontsDirectory = Path.Combine(targetPath, "fonts"); + + if (!Directory.Exists(fontsDirectory)) + { + Directory.CreateDirectory(fontsDirectory); + } + + const string fontFilename = "ARIALUNI.TTF"; + + var fontFile = Path.Combine(fontsDirectory, fontFilename); + + if (!File.Exists(fontFile)) + { + await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false); + } + + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); + } + catch (HttpException ex) + { + // Don't let the server crash because of this + _logger.ErrorException("Error downloading ffmpeg font files", ex); + } + catch (Exception ex) + { + // Don't let the server crash because of this + _logger.ErrorException("Error writing ffmpeg font files", ex); + } + } + + /// + /// Downloads the font file. + /// + /// The fonts directory. + /// The font filename. + /// Task. + private async Task DownloadFontFile(string fontsDirectory, string fontFilename) + { + var existingFile = Directory + .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) + .FirstOrDefault(); + + if (existingFile != null) + { + try + { + File.Copy(existingFile, Path.Combine(fontsDirectory, fontFilename), true); + return; + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error copying file", ex); + } + } + + string tempFile = null; + + foreach (var url in _fontUrls) + { + try + { + tempFile = await _httpClient.GetTempFile(new HttpRequestOptions + { + Url = url, + Progress = new Progress() + + }).ConfigureAwait(false); + + break; + } + catch (Exception ex) + { + // The core can function without the font file, so handle this + _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); + } + } + + if (string.IsNullOrEmpty(tempFile)) + { + return; + } + + Extract7zArchive(tempFile, fontsDirectory); + + try + { + File.Delete(tempFile); + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); + } + } + + /// + /// Writes the font config file. + /// + /// The fonts directory. + /// Task. + private async Task WriteFontConfigFile(string fontsDirectory) + { + const string fontConfigFilename = "fonts.conf"; + var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); + + if (!File.Exists(fontConfigFile)) + { + var contents = string.Format("{0}ArialArial Unicode MS", fontsDirectory); + + var bytes = Encoding.UTF8.GetBytes(contents); + + using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, + FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, + FileOptions.Asynchronous)) + { + await fileStream.WriteAsync(bytes, 0, bytes.Length); + } + } + } + + /// + /// Gets the media tools path. + /// + /// if set to true [create]. + /// System.String. + private string GetMediaToolsPath(bool create) + { + var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg"); + + if (create && !Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + return path; + } + } +} diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegInfo.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegInfo.cs new file mode 100644 index 000000000..147a9f771 --- /dev/null +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegInfo.cs @@ -0,0 +1,24 @@ +namespace MediaBrowser.ServerApplication.FFMpeg +{ + /// + /// Class FFMpegInfo + /// + public class FFMpegInfo + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets the probe path. + /// + /// The probe path. + public string ProbePath { get; set; } + /// + /// Gets or sets the version. + /// + /// The version. + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id b/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id new file mode 100644 index 000000000..9f83b949b --- /dev/null +++ b/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id @@ -0,0 +1 @@ +8f1dfd62d31e48c31bef4b9ccc0e514f46650a79 \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/Implementations/DotNetZipClient.cs b/MediaBrowser.ServerApplication/Implementations/DotNetZipClient.cs deleted file mode 100644 index 3b174a9b2..000000000 --- a/MediaBrowser.ServerApplication/Implementations/DotNetZipClient.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Ionic.Zip; -using MediaBrowser.Model.IO; -using System.IO; - -namespace MediaBrowser.ServerApplication.Implementations -{ - /// - /// Class DotNetZipClient - /// - public class DotNetZipClient : IZipClient - { - /// - /// Extracts all. - /// - /// The source file. - /// The target path. - /// if set to true [overwrite existing files]. - public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles) - { - using (var fileStream = File.OpenRead(sourceFile)) - { - using (var zipFile = ZipFile.Read(fileStream)) - { - zipFile.ExtractAll(targetPath, overwriteExistingFiles ? ExtractExistingFileAction.OverwriteSilently : ExtractExistingFileAction.DoNotOverwrite); - } - } - } - - /// - /// Extracts all. - /// - /// The source. - /// The target path. - /// if set to true [overwrite existing files]. - public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles) - { - using (var zipFile = ZipFile.Read(source)) - { - zipFile.ExtractAll(targetPath, overwriteExistingFiles ? ExtractExistingFileAction.OverwriteSilently : ExtractExistingFileAction.DoNotOverwrite); - } - } - } -} diff --git a/MediaBrowser.ServerApplication/Implementations/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/Implementations/FFMpegDownloader.cs deleted file mode 100644 index 7fd0acddd..000000000 --- a/MediaBrowser.ServerApplication/Implementations/FFMpegDownloader.cs +++ /dev/null @@ -1,205 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace MediaBrowser.ServerApplication.Implementations -{ - public class FFMpegDownloader - { - private readonly IZipClient _zipClient; - private readonly IHttpClient _httpClient; - private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - - public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient) - { - _logger = logger; - _appPaths = appPaths; - _httpClient = httpClient; - _zipClient = zipClient; - } - - public async Task GetFFMpegInfo() - { - var assembly = GetType().Assembly; - - var prefix = GetType().Namespace + "."; - - var srch = prefix + "ffmpeg"; - - var resource = assembly.GetManifestResourceNames().First(r => r.StartsWith(srch)); - - var filename = - resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length); - - var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), - Path.GetFileNameWithoutExtension(filename)); - - if (!Directory.Exists(versionedDirectoryPath)) - { - Directory.CreateDirectory(versionedDirectoryPath); - } - - await ExtractTools(assembly, resource, versionedDirectoryPath).ConfigureAwait(false); - - return new FFMpegInfo - { - ProbePath = Path.Combine(versionedDirectoryPath, "ffprobe.exe"), - Path = Path.Combine(versionedDirectoryPath, "ffmpeg.exe"), - Version = Path.GetFileNameWithoutExtension(versionedDirectoryPath) - }; - } - - /// - /// Extracts the tools. - /// - /// The assembly. - /// The zip file resource path. - /// The target path. - private async Task ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath) - { - using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath)) - { - _zipClient.ExtractAll(resourceStream, targetPath, false); - } - - try - { - await DownloadFonts(targetPath).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting ffmpeg font files", ex); - } - } - - private const string FontUrl = "https://www.dropbox.com/s/9nb76tybcsw5xrk/ARIALUNI.zip?dl=1"; - - /// - /// Extracts the fonts. - /// - /// The target path. - private async Task DownloadFonts(string targetPath) - { - var fontsDirectory = Path.Combine(targetPath, "fonts"); - - if (!Directory.Exists(fontsDirectory)) - { - Directory.CreateDirectory(fontsDirectory); - } - - const string fontFilename = "ARIALUNI.TTF"; - - var fontFile = Path.Combine(fontsDirectory, fontFilename); - - if (!File.Exists(fontFile)) - { - await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false); - } - - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - } - - /// - /// Downloads the font file. - /// - /// The fonts directory. - /// The font filename. - /// Task. - private async Task DownloadFontFile(string fontsDirectory, string fontFilename) - { - var existingFile = Directory - .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) - .FirstOrDefault(); - - if (existingFile != null) - { - try - { - File.Copy(existingFile, Path.Combine(fontsDirectory, fontFilename), true); - return; - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error copying file", ex); - } - } - - var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = FontUrl, - Progress = new Progress() - }); - - _zipClient.ExtractAll(tempFile, fontsDirectory, true); - - try - { - File.Delete(tempFile); - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); - } - } - - /// - /// Writes the font config file. - /// - /// The fonts directory. - /// Task. - private async Task WriteFontConfigFile(string fontsDirectory) - { - const string fontConfigFilename = "fonts.conf"; - var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); - - if (!File.Exists(fontConfigFile)) - { - var contents = string.Format("{0}ArialArial Unicode MS", fontsDirectory); - - var bytes = Encoding.UTF8.GetBytes(contents); - - using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, - FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, - FileOptions.Asynchronous)) - { - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - } - } - - /// - /// Gets the media tools path. - /// - /// if set to true [create]. - /// System.String. - private string GetMediaToolsPath(bool create) - { - var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg"); - - if (create && !Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - return path; - } - } - - public class FFMpegInfo - { - public string Path { get; set; } - public string ProbePath { get; set; } - public string Version { get; set; } - } -} diff --git a/MediaBrowser.ServerApplication/Implementations/ffmpeg20130904.zip.REMOVED.git-id b/MediaBrowser.ServerApplication/Implementations/ffmpeg20130904.zip.REMOVED.git-id deleted file mode 100644 index e99d115a4..000000000 --- a/MediaBrowser.ServerApplication/Implementations/ffmpeg20130904.zip.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -3496b2cde22e7c4cb56b480dd2da637167d51e78 \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/Implementations/readme.txt b/MediaBrowser.ServerApplication/Implementations/readme.txt deleted file mode 100644 index b32dd9aec..000000000 --- a/MediaBrowser.ServerApplication/Implementations/readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -This is the 32-bit static build of ffmpeg, located at: - -http://ffmpeg.zeranoe.com/builds/ - -The zip file contains both ffmpeg and ffprobe, and is suffixed with the date of the build. \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index e9c1fdc99..55fa60ed2 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -11,7 +11,6 @@ using System.IO; using System.Linq; using System.ServiceProcess; using System.Threading; -using System.Threading.Tasks; using System.Windows; namespace MediaBrowser.ServerApplication diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs index 4c9c065e6..c22c35be8 100644 --- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs +++ b/MediaBrowser.ServerApplication/MainWindow.xaml.cs @@ -12,6 +12,7 @@ using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Threading; +using MediaBrowser.ServerApplication.Native; namespace MediaBrowser.ServerApplication { @@ -188,19 +189,19 @@ namespace MediaBrowser.ServerApplication /// The instance containing the event data. void cmdApiDocs_Click(object sender, EventArgs e) { - App.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" + - _appHost.WebApplicationName + "/metadata"); + BrowserLauncher.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" + + _appHost.WebApplicationName + "/metadata", _logger); } void cmdSwaggerApiDocs_Click(object sender, EventArgs e) { - App.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" + - _appHost.WebApplicationName + "/swagger-ui/index.html"); + BrowserLauncher.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" + + _appHost.WebApplicationName + "/swagger-ui/index.html", _logger); } void cmdGithubWiki_Click(object sender, EventArgs e) { - App.OpenUrl("https://github.com/MediaBrowser/MediaBrowser/wiki"); + BrowserLauncher.OpenUrl("https://github.com/MediaBrowser/MediaBrowser/wiki", _logger); } /// @@ -254,7 +255,7 @@ namespace MediaBrowser.ServerApplication /// private void OpenDashboard(User loggedInUser) { - App.OpenDashboardPage("dashboard.html", loggedInUser, _configurationManager, _appHost); + BrowserLauncher.OpenDashboardPage("dashboard.html", loggedInUser, _configurationManager, _appHost, _logger); } /// @@ -264,7 +265,7 @@ namespace MediaBrowser.ServerApplication /// The instance containing the event data. private void cmVisitCT_click(object sender, RoutedEventArgs e) { - App.OpenUrl("http://community.mediabrowser.tv/"); + BrowserLauncher.OpenUrl("http://community.mediabrowser.tv/", _logger); } /// @@ -275,7 +276,7 @@ namespace MediaBrowser.ServerApplication private void cmdBrowseLibrary_click(object sender, RoutedEventArgs e) { var user = _userManager.Users.FirstOrDefault(u => u.Configuration.IsAdministrator); - App.OpenDashboardPage("index.html", user, _configurationManager, _appHost); + BrowserLauncher.OpenDashboardPage("index.html", user, _configurationManager, _appHost, _logger); } /// diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 043d5c18f..61ec19dd5 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -130,10 +130,6 @@ False ..\packages\MediaBrowser.IsoMounting.3.0.56\lib\net45\MediaBrowser.IsoMounter.dll - - False - ..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll - False ..\packages\NLog.2.0.1.2\lib\net45\NLog.dll @@ -187,7 +183,6 @@ - @@ -209,12 +204,19 @@ Component - - + + + + + + + + Component + SplashWindow.xaml @@ -242,7 +244,6 @@ Code - LibraryExplorer.xaml @@ -278,14 +279,15 @@ Resources.Designer.cs - + + SettingsSingleFileGenerator Settings.Designer.cs - + @@ -390,9 +392,6 @@ - - - if $(ConfigurationName) == Release ( diff --git a/MediaBrowser.ServerApplication/Native/Assemblies.cs b/MediaBrowser.ServerApplication/Native/Assemblies.cs new file mode 100644 index 000000000..b43dc1a10 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/Assemblies.cs @@ -0,0 +1,25 @@ +using MediaBrowser.IsoMounter; +using System.Collections.Generic; +using System.Reflection; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Assemblies + /// + public static class Assemblies + { + /// + /// Gets the assemblies with parts. + /// + /// List{Assembly}. + public static List GetAssembliesWithParts() + { + var list = new List(); + + list.Add(typeof(PismoIsoManager).Assembly); + + return list; + } + } +} diff --git a/MediaBrowser.ServerApplication/Native/Autorun.cs b/MediaBrowser.ServerApplication/Native/Autorun.cs new file mode 100644 index 000000000..d1c02db84 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/Autorun.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Autorun + /// + public static class Autorun + { + /// + /// Configures the specified autorun. + /// + /// if set to true [autorun]. + public static void Configure(bool autorun) + { + var shortcutPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Media Browser 3", "Media Browser Server.lnk"); + + if (autorun) + { + //Copy our shortut into the startup folder for this user + File.Copy(shortcutPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(shortcutPath) ?? "MBstartup.lnk"), true); + } + else + { + //Remove our shortcut from the startup folder for this user + File.Delete(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(shortcutPath) ?? "MBstartup.lnk")); + } + } + } +} diff --git a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs new file mode 100644 index 000000000..e7d041d15 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs @@ -0,0 +1,68 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Diagnostics; +using System.Windows.Forms; + +namespace MediaBrowser.ServerApplication.Native +{ + public static class BrowserLauncher + { + /// + /// Opens the dashboard page. + /// + /// The page. + /// The logged in user. + /// The configuration manager. + /// The app host. + public static void OpenDashboardPage(string page, User loggedInUser, IServerConfigurationManager configurationManager, IServerApplicationHost appHost, ILogger logger) + { + var url = "http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" + + appHost.WebApplicationName + "/dashboard/" + page; + + OpenUrl(url, logger); + } + + /// + /// Opens the URL. + /// + /// The URL. + public static void OpenUrl(string url, ILogger logger) + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = url + }, + + EnableRaisingEvents = true + }; + + process.Exited += ProcessExited; + + try + { + process.Start(); + } + catch (Exception ex) + { + logger.ErrorException("Error launching url: {0}", ex, url); + + MessageBox.Show("There was an error launching your web browser. Please check your default browser settings."); + } + } + + /// + /// Processes the exited. + /// + /// The sender. + /// The instance containing the event data. + private static void ProcessExited(object sender, EventArgs e) + { + ((Process)sender).Dispose(); + } + } +} diff --git a/MediaBrowser.ServerApplication/Native/HttpMessageHandlerFactory.cs b/MediaBrowser.ServerApplication/Native/HttpMessageHandlerFactory.cs new file mode 100644 index 000000000..4bbcc9ea0 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/HttpMessageHandlerFactory.cs @@ -0,0 +1,26 @@ +using System.Net; +using System.Net.Cache; +using System.Net.Http; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class HttpMessageHandlerFactory + /// + public static class HttpMessageHandlerFactory + { + /// + /// Gets the HTTP message handler. + /// + /// if set to true [enable HTTP compression]. + /// HttpMessageHandler. + public static HttpMessageHandler GetHttpMessageHandler(bool enableHttpCompression) + { + return new WebRequestHandler + { + CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate), + AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None + }; + } + } +} diff --git a/MediaBrowser.ServerApplication/Native/NativeApp.cs b/MediaBrowser.ServerApplication/Native/NativeApp.cs new file mode 100644 index 000000000..ea4218afc --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/NativeApp.cs @@ -0,0 +1,25 @@ + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class NativeApp + /// + public static class NativeApp + { + /// + /// Shutdowns this instance. + /// + public static void Shutdown() + { + MainStartup.Shutdown(); + } + + /// + /// Restarts this instance. + /// + public static void Restart() + { + MainStartup.Restart(); + } + } +} diff --git a/MediaBrowser.ServerApplication/RegisterServer.bat b/MediaBrowser.ServerApplication/Native/RegisterServer.bat similarity index 100% rename from MediaBrowser.ServerApplication/RegisterServer.bat rename to MediaBrowser.ServerApplication/Native/RegisterServer.bat diff --git a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs new file mode 100644 index 000000000..91f0974eb --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Authorization + /// + public static class ServerAuthorization + { + /// + /// Authorizes the server. + /// + /// The HTTP server port. + /// The HTTP server URL prefix. + /// The web socket port. + /// The UDP port. + /// The temp directory. + public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int webSocketPort, int udpPort, string tempDirectory) + { + // Create a temp file path to extract the bat file to + var tmpFile = Path.Combine(tempDirectory, Guid.NewGuid() + ".bat"); + + // Extract the bat file + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(ServerAuthorization).Namespace + ".RegisterServer.bat")) + { + using (var fileStream = File.Create(tmpFile)) + { + stream.CopyTo(fileStream); + } + } + + var startInfo = new ProcessStartInfo + { + FileName = tmpFile, + + Arguments = string.Format("{0} {1} {2} {3}", httpServerPort, + httpServerUrlPrefix, + udpPort, + webSocketPort), + + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false + }; + + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(); + } + } + } +} diff --git a/MediaBrowser.ServerApplication/Native/Sqlite.cs b/MediaBrowser.ServerApplication/Native/Sqlite.cs new file mode 100644 index 000000000..cc20952d7 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/Sqlite.cs @@ -0,0 +1,36 @@ +using System.Data; +using System.Data.SQLite; +using System.Threading.Tasks; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// Class Sqlite + /// + public static class Sqlite + { + /// + /// Connects to db. + /// + /// The db path. + /// Task{IDbConnection}. + /// dbPath + public static async Task OpenDatabase(string dbPath) + { + var connectionstr = new SQLiteConnectionStringBuilder + { + PageSize = 4096, + CacheSize = 4096, + SyncMode = SynchronizationModes.Normal, + DataSource = dbPath, + JournalMode = SQLiteJournalModeEnum.Wal + }; + + var connection = new SQLiteConnection(connectionstr.ConnectionString); + + await connection.OpenAsync().ConfigureAwait(false); + + return connection; + } + } +} diff --git a/MediaBrowser.ServerApplication/Resources/Images/mb3logo800.png b/MediaBrowser.ServerApplication/Resources/Images/mb3logo800.png index fbc769a6f..12db84679 100644 Binary files a/MediaBrowser.ServerApplication/Resources/Images/mb3logo800.png and b/MediaBrowser.ServerApplication/Resources/Images/mb3logo800.png differ diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 8c1821ca5..e680b556f 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -4,7 +4,6 @@ - diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index d139adfc3..189812a3c 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -3200,7 +3200,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi * @param {String} userId * @param {String} itemId */ - self.reportPlaybackStart = function (userId, itemId) { + self.reportPlaybackStart = function (userId, itemId, canSeek, queueableMediaTypes) { if (!userId) { throw new Error("null userId"); @@ -3210,17 +3210,26 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi throw new Error("null itemId"); } + canSeek = canSeek || false; + queueableMediaTypes = queueableMediaTypes || ''; + if (self.isWebSocketOpen()) { var deferred = $.Deferred(); - self.sendWebSocketMessage("PlaybackStart", itemId); + var msg = [itemId, canSeek, queueableMediaTypes]; + + self.sendWebSocketMessage("PlaybackStart", msg.join('|')); deferred.resolveWith(null, []); return deferred.promise(); } - var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId); + var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, { + + CanSeek: canSeek, + QueueableMediaTypes: queueableMediaTypes + }); return self.ajax({ type: "POST", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 06f930238..6a599da45 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -13,6 +13,8 @@ 512 ..\ true + 10.0.0 + 2.0 true @@ -35,24 +37,21 @@ Always - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll - - - False - ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll - - - False - ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll - + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll + + + ..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll + @@ -67,15 +66,15 @@ - {9142eefa-7570-41e1-bfcc-468bb571af2f} + {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} MediaBrowser.Controller - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model @@ -255,10 +254,10 @@ PreserveNewest - + PreserveNewest - + PreserveNewest diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 25b4f7b47..1c5a0f818 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 1d104c45b..eb846cd2f 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.206 + 3.0.208 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 0670bcacf..ba211c3d6 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.206 + 3.0.208 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index f5aa2d460..c6f801a9b 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.206 + 3.0.208 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 - +