From 3bef6ead9cec4c33d43b6348ae4fc33c9b70316a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 1 Jul 2014 17:13:32 -0400 Subject: #514 - Support HLS seeking --- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Api/UserLibrary/UserLibraryService.cs') diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 96bbd6dff..a1625d052 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -541,7 +541,8 @@ namespace MediaBrowser.Api.UserLibrary if (series != null) { var dtos = series - .GetRecursiveChildren(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) + .GetRecursiveChildren() + .Where(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) .OrderBy(i => { if (i.PremiereDate.HasValue) -- cgit v1.2.3 From 389390b82ecfbb48e0486f8f132046ddf8624e00 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 2 Jul 2014 00:57:18 -0400 Subject: fixes #789 - Security Issue: API allows access to any folder of the PC running MediaBrowser --- MediaBrowser.Api/AppThemeService.cs | 2 + .../AuthorizationRequestFilterAttribute.cs | 190 --------------------- MediaBrowser.Api/BaseApiService.cs | 13 +- MediaBrowser.Api/ChannelService.cs | 4 +- MediaBrowser.Api/DisplayPreferencesService.cs | 4 +- MediaBrowser.Api/EnvironmentService.cs | 2 + MediaBrowser.Api/GamesService.cs | 2 + MediaBrowser.Api/Library/LibraryService.cs | 2 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - MediaBrowser.Api/Playback/BaseStreamingService.cs | 7 - MediaBrowser.Api/SessionsService.cs | 16 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 10 +- MediaBrowser.Api/UserService.cs | 7 +- MediaBrowser.Controller/Entities/Folder.cs | 21 +-- .../MediaBrowser.Controller.csproj | 8 + .../Net/AuthenticatedAttribute.cs | 41 +++++ MediaBrowser.Controller/Net/AuthorizationInfo.cs | 32 ++++ MediaBrowser.Controller/Net/IAuthService.cs | 9 + .../Net/IAuthorizationContext.cs | 14 ++ MediaBrowser.Controller/Net/IHasAuthorization.cs | 12 ++ MediaBrowser.Controller/Net/IHasResultFactory.cs | 3 +- MediaBrowser.Controller/Net/IHasSession.cs | 12 ++ MediaBrowser.Controller/Net/IRestfulService.cs | 1 + MediaBrowser.Controller/Net/ISessionContext.cs | 13 ++ MediaBrowser.Controller/Net/LoggedAttribute.cs | 73 ++++++++ MediaBrowser.Providers/Manager/MetadataService.cs | 10 +- .../Collections/ManualCollectionsFolder.cs | 8 +- .../HttpServer/HttpListenerHost.cs | 34 ++-- .../HttpServer/RangeRequestWriter.cs | 2 + .../HttpServer/Security/AuthService.cs | 113 ++++++++++++ .../HttpServer/Security/AuthorizationContext.cs | 96 +++++++++++ .../HttpServer/Security/SessionAuthProvider.cs | 35 ++++ .../HttpServer/Security/SessionContext.cs | 36 ++++ .../MediaBrowser.Server.Implementations.csproj | 4 + MediaBrowser.ServerApplication/ApplicationHost.cs | 8 +- .../Native/BrowserLauncher.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 3 + .../Providers/SeriesNfoProvider.cs | 2 +- 39 files changed, 587 insertions(+), 267 deletions(-) delete mode 100644 MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs create mode 100644 MediaBrowser.Controller/Net/AuthenticatedAttribute.cs create mode 100644 MediaBrowser.Controller/Net/AuthorizationInfo.cs create mode 100644 MediaBrowser.Controller/Net/IAuthService.cs create mode 100644 MediaBrowser.Controller/Net/IAuthorizationContext.cs create mode 100644 MediaBrowser.Controller/Net/IHasAuthorization.cs create mode 100644 MediaBrowser.Controller/Net/IHasSession.cs create mode 100644 MediaBrowser.Controller/Net/ISessionContext.cs create mode 100644 MediaBrowser.Controller/Net/LoggedAttribute.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs create mode 100644 MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs (limited to 'MediaBrowser.Api/UserLibrary/UserLibraryService.cs') diff --git a/MediaBrowser.Api/AppThemeService.cs b/MediaBrowser.Api/AppThemeService.cs index 4d8eed7dd..0c8a0aaa6 100644 --- a/MediaBrowser.Api/AppThemeService.cs +++ b/MediaBrowser.Api/AppThemeService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Themes; using MediaBrowser.Model.Themes; using ServiceStack; @@ -47,6 +48,7 @@ namespace MediaBrowser.Api { } + [Authenticated] public class AppThemeService : BaseApiService { private readonly IAppThemeManager _themeManager; diff --git a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs deleted file mode 100644 index 6c56083cb..000000000 --- a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs +++ /dev/null @@ -1,190 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using ServiceStack.Web; -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(IRequest request, IResponse response, object requestDto) - { - //This code is executed before the service - var auth = GetAuthorizationDictionary(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)) - { - var remoteEndPoint = request.RemoteIp; - - SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); - } - } - } - - /// - /// Gets the auth. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - private static Dictionary GetAuthorizationDictionary(IRequest httpReq) - { - var auth = httpReq.Headers["Authorization"]; - - return GetAuthorization(auth); - } - - public static User GetCurrentUser(IRequest httpReq, IUserManager userManager) - { - var info = GetAuthorization(httpReq); - - return string.IsNullOrEmpty(info.UserId) ? null : - userManager.GetUserById(new Guid(info.UserId)); - } - - /// - /// Gets the authorization. - /// - /// The HTTP req. - /// Dictionary{System.StringSystem.String}. - public static AuthorizationInfo GetAuthorization(IRequest httpReq) - { - var auth = GetAuthorizationDictionary(httpReq); - - string userId = null; - string deviceId = null; - string device = null; - string client = null; - string version = null; - - if (auth != null) - { - 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 f1d596213..09eb1ea41 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -14,8 +14,7 @@ namespace MediaBrowser.Api /// /// Class BaseApiService /// - [AuthorizationRequestFilter] - public class BaseApiService : IHasResultFactory, IRestfulService + public class BaseApiService : IHasResultFactory, IRestfulService, IHasSession { /// /// Gets or sets the logger. @@ -35,6 +34,8 @@ namespace MediaBrowser.Api /// The request context. public IRequest Request { get; set; } + public ISessionContext SessionContext { get; set; } + public string GetHeader(string name) { return Request.Headers[name]; @@ -82,13 +83,11 @@ namespace MediaBrowser.Api /// /// Gets the session. /// - /// The session manager. /// SessionInfo. - protected SessionInfo GetSession(ISessionManager sessionManager) + /// Session not found. + protected SessionInfo GetSession() { - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - - var session = sessionManager.GetSession(auth.DeviceId, auth.Client, auth.Version); + var session = SessionContext.GetSession(Request); if (session == null) { diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index d71db929f..2cc046f1d 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -172,7 +173,8 @@ namespace MediaBrowser.Api [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } } - + + [Authenticated] public class ChannelService : BaseApiService { private readonly IChannelManager _channelManager; diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 206f5bf7b..9f3a6134e 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using ServiceStack; @@ -48,6 +49,7 @@ namespace MediaBrowser.Api /// /// Class DisplayPreferencesService /// + [Authenticated] public class DisplayPreferencesService : BaseApiService { /// diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 56f71fc00..590deff5a 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using ServiceStack; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api /// /// Class EnvironmentService /// + [Authenticated] public class EnvironmentService : BaseApiService { const char UncSeparator = '\\'; diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index ff2771ce1..9aba2b065 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using ServiceStack; @@ -51,6 +52,7 @@ namespace MediaBrowser.Api /// /// Class GamesService /// + [Authenticated] public class GamesService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index ddb2dc943..77a714755 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -470,7 +470,7 @@ namespace MediaBrowser.Api.Library { var item = _libraryManager.GetItemById(request.Id); - var session = GetSession(_sessionManager); + var session = GetSession(); if (!session.UserId.HasValue || !_userManager.GetUserById(session.UserId.Value).Configuration.EnableContentDeletion) { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 6973a233a..de01628f8 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -280,7 +280,7 @@ namespace MediaBrowser.Api.LiveTv private void AssertUserCanManageLiveTv() { - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, _userManager); + var user = SessionContext.GetUser(Request); if (user == null) { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 3f1d9fe67..a68966b33 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -79,7 +79,6 @@ - diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 380ece2f2..9ff482a1a 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1386,8 +1386,6 @@ namespace MediaBrowser.Api.Playback ParseParams(request); } - var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager); - var url = Request.PathInfo; if (string.IsNullOrEmpty(request.AudioCodec)) @@ -1409,11 +1407,6 @@ namespace MediaBrowser.Api.Playback var item = LibraryManager.GetItemById(request.Id); - if (user != null && item.GetPlayAccess(user) != PlayAccess.Full) - { - throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name)); - } - List mediaStreams = null; state.ItemType = item.GetType().Name; diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 36f1d6577..00c307a18 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -285,7 +285,7 @@ namespace MediaBrowser.Api SeekPositionTicks = request.SeekPositionTicks }; - var task = _sessionManager.SendPlaystateCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -303,7 +303,7 @@ namespace MediaBrowser.Api ItemType = request.ItemType }; - var task = _sessionManager.SendBrowseCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -318,7 +318,7 @@ namespace MediaBrowser.Api if (Enum.TryParse(request.Command, true, out commandType)) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); var command = new GeneralCommand { @@ -345,7 +345,7 @@ namespace MediaBrowser.Api Text = request.Text }; - var task = _sessionManager.SendMessageCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -364,14 +364,14 @@ namespace MediaBrowser.Api StartPositionTicks = request.StartPositionTicks }; - var task = _sessionManager.SendPlayCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } public void Post(SendGeneralCommand request) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); var command = new GeneralCommand { @@ -386,7 +386,7 @@ namespace MediaBrowser.Api public void Post(SendFullGeneralCommand request) { - var currentSession = GetSession(_sessionManager); + var currentSession = GetSession(); request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; @@ -409,7 +409,7 @@ namespace MediaBrowser.Api { if (string.IsNullOrWhiteSpace(request.Id)) { - request.Id = GetSession(_sessionManager).Id; + request.Id = GetSession().Id; } _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities { diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index a1625d052..da12a9e3d 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.UserLibrary datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var session = GetSession(_sessionManager); + var session = GetSession(); var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); @@ -826,7 +826,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStart request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackStart(request); @@ -854,7 +854,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackProgress request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackProgress(request); @@ -877,7 +877,7 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStopped request) { - request.SessionId = GetSession(_sessionManager).Id; + request.SessionId = GetSession().Id; var task = _sessionManager.OnPlaybackStopped(request); @@ -899,7 +899,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var session = GetSession(_sessionManager); + var session = GetSession(); var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 1932c8f93..764a28102 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Serialization; @@ -152,7 +153,7 @@ namespace MediaBrowser.Api /// /// Class UsersService /// - public class UserService : BaseApiService + public class UserService : BaseApiService, IHasAuthorization { /// /// The _XML serializer @@ -166,6 +167,8 @@ namespace MediaBrowser.Api private readonly IDtoService _dtoService; private readonly ISessionManager _sessionMananger; + public IAuthorizationContext AuthorizationContext { get; set; } + /// /// Initializes a new instance of the class. /// @@ -295,7 +298,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); + var auth = AuthorizationContext.GetAuthorizationInfo(Request); // Login in the old way if the header is missing if (string.IsNullOrEmpty(auth.Client) || diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 52c414ae8..584091b13 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -797,9 +797,8 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// The list. /// if set to true [recursive]. - /// The filter. /// true if XXXX, false otherwise - private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive, Func filter) + private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive) { var hasLinkedChildren = false; @@ -807,19 +806,16 @@ namespace MediaBrowser.Controller.Entities { if (child.IsVisible(user)) { - if (filter == null || filter(child)) + if (!child.IsHiddenFromUser(user)) { - if (!child.IsHiddenFromUser(user)) - { - list.Add(child); - } + list.Add(child); } if (recursive && child.IsFolder) { var folder = (Folder)child; - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, true)) { hasLinkedChildren = true; } @@ -831,11 +827,6 @@ namespace MediaBrowser.Controller.Entities { foreach (var child in GetLinkedChildren()) { - if (filter != null && !filter(child)) - { - continue; - } - if (child.IsVisible(user)) { hasLinkedChildren = true; @@ -864,7 +855,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1310e7797..ae7118be9 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -195,10 +195,18 @@ + + + + + + + + diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs new file mode 100644 index 000000000..567d20f39 --- /dev/null +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -0,0 +1,41 @@ +using ServiceStack.Web; +using System; + +namespace MediaBrowser.Controller.Net +{ + public class AuthenticatedAttribute : Attribute, IHasRequestFilter + { + public IAuthService AuthService { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IRequest request, IResponse response, object requestDto) + { + AuthService.Authenticate(request, response, requestDto); + } + + /// + /// 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.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs new file mode 100644 index 000000000..e609204d6 --- /dev/null +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -0,0 +1,32 @@ + +namespace MediaBrowser.Controller.Net +{ + public class AuthorizationInfo + { + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + /// + /// Gets or sets the device. + /// + /// The device. + public string Device { get; set; } + /// + /// Gets or sets the client. + /// + /// The client. + public string Client { get; set; } + /// + /// Gets or sets the version. + /// + /// The version. + public string Version { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs new file mode 100644 index 000000000..41859395b --- /dev/null +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -0,0 +1,9 @@ +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface IAuthService + { + void Authenticate(IRequest request, IResponse response, object requestDto); + } +} diff --git a/MediaBrowser.Controller/Net/IAuthorizationContext.cs b/MediaBrowser.Controller/Net/IAuthorizationContext.cs new file mode 100644 index 000000000..9cf562370 --- /dev/null +++ b/MediaBrowser.Controller/Net/IAuthorizationContext.cs @@ -0,0 +1,14 @@ +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface IAuthorizationContext + { + /// + /// Gets the authorization information. + /// + /// The request context. + /// AuthorizationInfo. + AuthorizationInfo GetAuthorizationInfo(IRequest requestContext); + } +} diff --git a/MediaBrowser.Controller/Net/IHasAuthorization.cs b/MediaBrowser.Controller/Net/IHasAuthorization.cs new file mode 100644 index 000000000..6fc70159d --- /dev/null +++ b/MediaBrowser.Controller/Net/IHasAuthorization.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Net +{ + public interface IHasAuthorization + { + /// + /// Gets or sets the authorization context. + /// + /// The authorization context. + IAuthorizationContext AuthorizationContext { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IHasResultFactory.cs b/MediaBrowser.Controller/Net/IHasResultFactory.cs index a87113d1f..3988b8d61 100644 --- a/MediaBrowser.Controller/Net/IHasResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHasResultFactory.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Net; -using ServiceStack.Web; +using ServiceStack.Web; namespace MediaBrowser.Controller.Net { diff --git a/MediaBrowser.Controller/Net/IHasSession.cs b/MediaBrowser.Controller/Net/IHasSession.cs new file mode 100644 index 000000000..e762c1e84 --- /dev/null +++ b/MediaBrowser.Controller/Net/IHasSession.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Controller.Net +{ + public interface IHasSession + { + /// + /// Gets or sets the session context. + /// + /// The session context. + ISessionContext SessionContext { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IRestfulService.cs b/MediaBrowser.Controller/Net/IRestfulService.cs index f55012b73..7d07bb094 100644 --- a/MediaBrowser.Controller/Net/IRestfulService.cs +++ b/MediaBrowser.Controller/Net/IRestfulService.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Controller.Net /// /// Interface IRestfulService /// + [Logged] public interface IRestfulService : IService { } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs new file mode 100644 index 000000000..31ccd5373 --- /dev/null +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; +using ServiceStack.Web; + +namespace MediaBrowser.Controller.Net +{ + public interface ISessionContext + { + SessionInfo GetSession(IRequest requestContext); + + User GetUser(IRequest requestContext); + } +} diff --git a/MediaBrowser.Controller/Net/LoggedAttribute.cs b/MediaBrowser.Controller/Net/LoggedAttribute.cs new file mode 100644 index 000000000..6df72f7a7 --- /dev/null +++ b/MediaBrowser.Controller/Net/LoggedAttribute.cs @@ -0,0 +1,73 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using ServiceStack.Web; +using System; + +namespace MediaBrowser.Controller.Net +{ + public class LoggedAttribute : Attribute, IHasRequestFilter + { + public ILogger Logger { get; set; } + public IUserManager UserManager { get; set; } + public ISessionManager SessionManager { get; set; } + public IAuthorizationContext AuthorizationContext { get; set; } + + /// + /// The request filter is executed before the service. + /// + /// The http request wrapper + /// The http response wrapper + /// The request DTO + public void RequestFilter(IRequest request, IResponse response, object requestDto) + { + //This code is executed before the service + var auth = AuthorizationContext.GetAuthorizationInfo(request); + + if (auth != null) + { + User user = null; + + if (!string.IsNullOrWhiteSpace(auth.UserId)) + { + var userId = auth.UserId; + + user = UserManager.GetUserById(new Guid(userId)); + } + + string deviceId = auth.DeviceId; + string device = auth.Device; + string client = auth.Client; + string version = auth.Version; + + if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + { + var remoteEndPoint = request.RemoteIp; + + SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); + } + } + } + + /// + /// 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.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 717b93524..95eca6ba0 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -340,13 +340,17 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { + Logger.ErrorException("Error in {0}", ex, provider.Name); + // If a local provider fails, consider that a failure refreshResult.Status = ProviderRefreshStatus.Failure; refreshResult.ErrorMessage = ex.Message; - Logger.ErrorException("Error in {0}", ex, provider.Name); - // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost - return refreshResult; + if (options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh) + { + // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost + return refreshResult; + } } } diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index 739b42192..07c86ed31 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -12,12 +12,8 @@ namespace MediaBrowser.Server.Implementations.Collections public override bool IsVisible(User user) { - if (!GetChildren(user, true).Any()) - { - return false; - } - - return base.IsVisible(user); + return GetChildren(user, true).Any() && + base.IsVisible(user); } public override bool IsHidden diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 0fc9265f6..833dfc5e4 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,13 +1,13 @@ -using System.Net.Sockets; -using System.Runtime.Serialization; -using Funq; +using Funq; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.HttpServer.Security; using ServiceStack; using ServiceStack.Api.Swagger; +using ServiceStack.Auth; using ServiceStack.Host; using ServiceStack.Host.Handlers; using ServiceStack.Host.HttpListener; @@ -27,7 +27,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer { public class HttpListenerHost : ServiceStackHost, IHttpServer { - private string ServerName { get; set; } private string HandlerPath { get; set; } private string DefaultRedirectPath { get; set; } @@ -59,7 +58,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer : base(serviceName, assembliesWithServices) { DefaultRedirectPath = defaultRedirectPath; - ServerName = serviceName; HandlerPath = handlerPath; _logger = logManager.GetLogger("HttpServer"); @@ -95,7 +93,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer container.Adapter = _containerAdapter; Plugins.Add(new SwaggerFeature()); - Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); + Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); + + Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { + new SessionAuthProvider(_containerAdapter.Resolve()), + })); + HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } @@ -112,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer Config.HandlerFactoryPath = string.IsNullOrEmpty(HandlerPath) ? null - : HandlerPath; + : "/" + HandlerPath; Config.MetadataRedirectPath = string.IsNullOrEmpty(HandlerPath) ? "metadata" @@ -161,8 +164,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer if (Listener == null) Listener = new HttpListener(); - HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First()); - foreach (var prefix in UrlPrefixes) { _logger.Info("Adding HttpListener prefix " + prefix); @@ -172,6 +173,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer IsStarted = true; _logger.Info("Starting HttpListner"); Listener.Start(); + _logger.Info("HttpListener started"); for (var i = 0; i < _autoResetEvents.Count; i++) { @@ -263,27 +265,27 @@ namespace MediaBrowser.Server.Implementations.HttpServer var localPath = request.Url.LocalPath; - if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + HandlerPath + "/", StringComparison.OrdinalIgnoreCase)) { context.Response.Redirect(DefaultRedirectPath); context.Response.Close(); return; } - if (string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/" + HandlerPath, StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect("mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) { - context.Response.Redirect("mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect(HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } if (string.IsNullOrEmpty(localPath)) { - context.Response.Redirect("/mediabrowser/" + DefaultRedirectPath); + context.Response.Redirect("/" + HandlerPath + "/" + DefaultRedirectPath); context.Response.Close(); return; } @@ -410,6 +412,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None); req.RequestAttributes = req.GetAttributes(); + return req; } @@ -442,7 +445,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; + //var pathInfo = httpReq.PathInfo; + var handler = HttpHandlerFactory.GetHandler(httpReq); + //var handler = HttpHandlerFactory.GetHandlerForPathInfo(httpReq.HttpMethod, pathInfo, pathInfo, httpReq.GetPhysicalPath()); var serviceStackHandler = handler as IServiceStackHandler; if (serviceStackHandler != null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 1ff199eb4..9de6972f8 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -228,5 +228,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer } public string StatusDescription { get; set; } + + public int PaddingLength { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs new file mode 100644 index 000000000..ddb583f5d --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -0,0 +1,113 @@ +using MediaBrowser.Controller.Net; +using ServiceStack; +using ServiceStack.Auth; +using ServiceStack.Web; +using System; +using System.Collections.Specialized; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class AuthService : IAuthService + { + /// + /// Restrict authentication to a specific . + /// For example, if this attribute should only permit access + /// if the user is authenticated with , + /// you should set this property to . + /// + public string Provider { get; set; } + + /// + /// Redirect the client to a specific URL if authentication failed. + /// If this property is null, simply `401 Unauthorized` is returned. + /// + public string HtmlRedirect { get; set; } + + public void Authenticate(IRequest req, IResponse res, object requestDto) + { + if (HostContext.HasValidAuthSecret(req)) + return; + + ExecuteBasic(req, res, requestDto); //first check if session is authenticated + if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) + + ValidateUser(req); + } + + private void ValidateUser(IRequest req) + { + var user = req.TryResolve().GetUser(req); + + if (user == null || user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("Unauthorized access."); + } + } + + private void ExecuteBasic(IRequest req, IResponse res, object requestDto) + { + if (AuthenticateService.AuthProviders == null) + throw new InvalidOperationException( + "The AuthService must be initialized by calling AuthService.Init to use an authenticate attribute"); + + var matchingOAuthConfigs = AuthenticateService.AuthProviders.Where(x => + this.Provider.IsNullOrEmpty() + || x.Provider == this.Provider).ToList(); + + if (matchingOAuthConfigs.Count == 0) + { + res.WriteError(req, requestDto, "No OAuth Configs found matching {0} provider" + .Fmt(this.Provider ?? "any")); + res.EndRequest(); + } + + matchingOAuthConfigs.OfType() + .Each(x => x.PreAuthenticate(req, res)); + + var session = req.GetSession(); + if (session == null || !matchingOAuthConfigs.Any(x => session.IsAuthorized(x.Provider))) + { + if (this.DoHtmlRedirectIfConfigured(req, res, true)) return; + + AuthProvider.HandleFailedAuth(matchingOAuthConfigs[0], session, req, res); + } + } + + protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) + { + var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; + if (htmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html)) + { + DoHtmlRedirect(htmlRedirect, req, res, includeRedirectParam); + return true; + } + return false; + } + + public static void DoHtmlRedirect(string redirectUrl, IRequest req, IResponse res, bool includeRedirectParam) + { + var url = req.ResolveAbsoluteUrl(redirectUrl); + if (includeRedirectParam) + { + var absoluteRequestPath = req.ResolveAbsoluteUrl("~" + req.PathInfo + ToQueryString(req.QueryString)); + url = url.AddQueryParam(HostContext.ResolveLocalizedString(LocalizedStrings.Redirect), absoluteRequestPath); + } + + res.RedirectToUrl(url); + } + + private static string ToQueryString(INameValueCollection queryStringCollection) + { + return ToQueryString((NameValueCollection)queryStringCollection.Original); + } + + private static string ToQueryString(NameValueCollection queryStringCollection) + { + if (queryStringCollection == null || queryStringCollection.Count == 0) + return String.Empty; + + return "?" + queryStringCollection.ToFormUrlEncoded(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs new file mode 100644 index 000000000..6ea77f251 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Net; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class AuthorizationContext : IAuthorizationContext + { + public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) + { + return GetAuthorization(requestContext); + } + + /// + /// Gets the authorization. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + private static AuthorizationInfo GetAuthorization(IRequest httpReq) + { + var auth = GetAuthorizationDictionary(httpReq); + + string userId = null; + string deviceId = null; + string device = null; + string client = null; + string version = null; + + if (auth != null) + { + 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 auth. + /// + /// The HTTP req. + /// Dictionary{System.StringSystem.String}. + private static Dictionary GetAuthorizationDictionary(IRequest httpReq) + { + var auth = httpReq.Headers["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; + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs new file mode 100644 index 000000000..7c3173101 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionAuthProvider.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Controller.Net; +using ServiceStack; +using ServiceStack.Auth; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class SessionAuthProvider : CredentialsAuthProvider + { + private readonly ISessionContext _sessionContext; + + public SessionAuthProvider(ISessionContext sessionContext) + { + _sessionContext = sessionContext; + } + + public override bool TryAuthenticate(IServiceBase authService, string userName, string password) + { + return true; + } + + public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null) + { + return true; + } + + protected override void SaveUserAuth(IServiceBase authService, IAuthSession session, IAuthRepository authRepo, IAuthTokens tokens) + { + } + + public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request) + { + return base.Authenticate(authService, session, request); + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs new file mode 100644 index 000000000..f67c643c8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -0,0 +1,36 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using ServiceStack.Web; + +namespace MediaBrowser.Server.Implementations.HttpServer.Security +{ + public class SessionContext : ISessionContext + { + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + private readonly IAuthorizationContext _authContext; + + public SessionContext(IUserManager userManager, IAuthorizationContext authContext, ISessionManager sessionManager) + { + _userManager = userManager; + _authContext = authContext; + _sessionManager = sessionManager; + } + + public SessionInfo GetSession(IRequest requestContext) + { + var authorization = _authContext.GetAuthorizationInfo(requestContext); + + return _sessionManager.GetSession(authorization.DeviceId, authorization.Client, authorization.Version); + } + + public User GetUser(IRequest requestContext) + { + var session = GetSession(requestContext); + + return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 340107851..10e27bc30 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -133,6 +133,7 @@ + @@ -141,9 +142,12 @@ + + + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 87f18e4cb..dbca94065 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -62,6 +62,7 @@ using MediaBrowser.Server.Implementations.Dto; using MediaBrowser.Server.Implementations.EntryPoints; using MediaBrowser.Server.Implementations.FileOrganization; using MediaBrowser.Server.Implementations.HttpServer; +using MediaBrowser.Server.Implementations.HttpServer.Security; using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.Library; using MediaBrowser.Server.Implementations.LiveTv; @@ -598,7 +599,7 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); - HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html"); + HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", WebApplicationName, "dashboard/index.html"); RegisterSingleInstance(HttpServer, false); progress.Report(10); @@ -667,6 +668,11 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); + var authContext = new AuthorizationContext(); + RegisterSingleInstance(authContext); + RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); + RegisterSingleInstance(new AuthService()); + RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); diff --git a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs index 77a543ad7..4ba98e9b6 100644 --- a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs +++ b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.ServerApplication.Native 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; + appHost.WebApplicationName + "/web/" + page; OpenUrl(url, logger); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 2ca0669cd..80f864a32 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.WebDashboard.Api /// Class GetDashboardConfigurationPages /// [Route("/dashboard/ConfigurationPages", "GET")] + [Route("/web/ConfigurationPages", "GET")] public class GetDashboardConfigurationPages : IReturn> { /// @@ -38,6 +39,7 @@ namespace MediaBrowser.WebDashboard.Api /// Class GetDashboardConfigurationPage /// [Route("/dashboard/ConfigurationPage", "GET")] + [Route("/web/ConfigurationPage", "GET")] public class GetDashboardConfigurationPage { /// @@ -50,6 +52,7 @@ namespace MediaBrowser.WebDashboard.Api /// /// Class GetDashboardResource /// + [Route("/web/{ResourceName*}", "GET")] [Route("/dashboard/{ResourceName*}", "GET")] public class GetDashboardResource { diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index 4a71d9fd3..9db0cc8fc 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.XbmcMetadata.Providers protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - return directoryService.GetFile(Path.Combine(info.Path, "series.nfo")); + return directoryService.GetFile(Path.Combine(info.Path, "tvshow.nfo")); } } } -- cgit v1.2.3 From 59dc591f66c20b6417aa2baa9503a154585386f9 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 2 Jul 2014 14:34:08 -0400 Subject: update to jquery mobile 1.4.3 --- MediaBrowser.Api/ApiEntryPoint.cs | 60 ++- .../DefaultTheme/DefaultThemeService.cs | 2 + MediaBrowser.Api/Dlna/DlnaService.cs | 2 + MediaBrowser.Api/ItemLookupService.cs | 2 + MediaBrowser.Api/ItemRefreshService.cs | 2 + MediaBrowser.Api/ItemUpdateService.cs | 6 +- MediaBrowser.Api/Library/ChapterService.cs | 2 + .../Library/FileOrganizationService.cs | 2 + .../Library/LibraryStructureService.cs | 28 +- MediaBrowser.Api/Library/SubtitleService.cs | 2 + MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 + MediaBrowser.Api/LocalizationService.cs | 2 + MediaBrowser.Api/Movies/CollectionService.cs | 2 + MediaBrowser.Api/Movies/MoviesService.cs | 2 + MediaBrowser.Api/Movies/TrailersService.cs | 2 + MediaBrowser.Api/Music/AlbumsService.cs | 3 +- MediaBrowser.Api/Music/InstantMixService.cs | 2 + MediaBrowser.Api/PackageReviewService.cs | 2 + MediaBrowser.Api/PackageService.cs | 2 + MediaBrowser.Api/Playback/BaseStreamingService.cs | 7 - MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 12 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 10 +- MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs | 4 +- MediaBrowser.Api/PluginService.cs | 2 + .../ScheduledTasks/ScheduledTaskService.cs | 2 + MediaBrowser.Api/SearchService.cs | 2 + MediaBrowser.Api/TvShowsService.cs | 2 + MediaBrowser.Api/UserLibrary/ArtistsService.cs | 2 + MediaBrowser.Api/UserLibrary/GameGenresService.cs | 2 + MediaBrowser.Api/UserLibrary/GenresService.cs | 2 + MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 + MediaBrowser.Api/UserLibrary/MusicGenresService.cs | 2 + MediaBrowser.Api/UserLibrary/PersonsService.cs | 2 + MediaBrowser.Api/UserLibrary/StudiosService.cs | 2 + MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 2 + MediaBrowser.Api/UserLibrary/YearsService.cs | 2 + MediaBrowser.Api/UserService.cs | 63 +--- MediaBrowser.Api/VideosService.cs | 2 + MediaBrowser.Controller/Library/IUserManager.cs | 4 +- MediaBrowser.Controller/Net/AuthorizationInfo.cs | 5 + MediaBrowser.Controller/Session/ISessionManager.cs | 13 +- .../MediaBrowser.Model.Portable.csproj | 24 +- .../MediaBrowser.Model.net35.csproj | 24 +- .../Configuration/NotificationOption.cs | 54 --- .../Configuration/NotificationOptions.cs | 124 ------- .../Configuration/NotificationType.cs | 23 -- MediaBrowser.Model/Configuration/SendToUserType.cs | 9 - .../Configuration/ServerConfiguration.cs | 8 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 8 +- .../Notifications/NotificationOption.cs | 56 +++ .../Notifications/NotificationOptions.cs | 125 +++++++ .../Notifications/NotificationType.cs | 23 ++ MediaBrowser.Model/Notifications/SendToUserType.cs | 9 + MediaBrowser.Model/Updates/PackageVersionInfo.cs | 32 +- MediaBrowser.Model/Users/AuthenticationResult.cs | 6 + .../HttpServer/Security/AuthService.cs | 25 +- .../HttpServer/Security/AuthorizationContext.cs | 3 +- .../Library/UserManager.cs | 11 +- .../LiveTv/LiveTvManager.cs | 125 ++++--- .../Localization/JavaScript/javascript.json | 18 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../NotificationConfigurationFactory.cs | 21 ++ .../Notifications/NotificationManager.cs | 16 +- .../Session/SessionManager.cs | 31 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 7 + MediaBrowser.WebDashboard/Api/DashboardService.cs | 4 +- .../MediaBrowser.WebDashboard.csproj | 409 +++++++++++---------- 67 files changed, 828 insertions(+), 641 deletions(-) delete mode 100644 MediaBrowser.Model/Configuration/NotificationOption.cs delete mode 100644 MediaBrowser.Model/Configuration/NotificationOptions.cs delete mode 100644 MediaBrowser.Model/Configuration/NotificationType.cs delete mode 100644 MediaBrowser.Model/Configuration/SendToUserType.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationOption.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationOptions.cs create mode 100644 MediaBrowser.Model/Notifications/NotificationType.cs create mode 100644 MediaBrowser.Model/Notifications/SendToUserType.cs create mode 100644 MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs (limited to 'MediaBrowser.Api/UserLibrary/UserLibraryService.cs') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 2177f90c2..8e6ca4401 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; + public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1,1); + /// /// Initializes a new instance of the class. /// @@ -301,8 +303,9 @@ namespace MediaBrowser.Api /// /// The device id. /// The delete mode. + /// if set to true [acquire lock]. /// sourcePath - internal void KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode) + internal async Task KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -318,9 +321,29 @@ namespace MediaBrowser.Api jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase))); } - foreach (var job in jobs) + if (jobs.Count == 0) + { + return; + } + + if (acquireLock) { - KillTranscodingJob(job, deleteMode); + await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + } + + try + { + foreach (var job in jobs) + { + KillTranscodingJob(job, deleteMode); + } + } + finally + { + if (acquireLock) + { + TranscodingStartLock.Release(); + } } } @@ -328,10 +351,11 @@ namespace MediaBrowser.Api /// Kills the transcoding jobs. /// /// The device identifier. - /// The output path. + /// The type. /// The delete mode. + /// if set to true [acquire lock]. /// deviceId - internal void KillTranscodingJobs(string deviceId, string outputPath, FileDeleteMode deleteMode) + internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, FileDeleteMode deleteMode, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -344,12 +368,32 @@ namespace MediaBrowser.Api { // This is really only needed for HLS. // Progressive streams can stop on their own reliably - jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase) && string.Equals(outputPath, i.Path, StringComparison.OrdinalIgnoreCase))); + jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase) && i.Type == type)); } - foreach (var job in jobs) + if (jobs.Count == 0) { - KillTranscodingJob(job, deleteMode); + return; + } + + if (acquireLock) + { + await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + } + + try + { + foreach (var job in jobs) + { + KillTranscodingJob(job, deleteMode); + } + } + finally + { + if (acquireLock) + { + TranscodingStartLock.Release(); + } } } diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index 8bc867a2e..21ba47bd4 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api.DefaultTheme public Guid UserId { get; set; } } + [Authenticated] public class DefaultThemeService : BaseApiService { private readonly IUserManager _userManager; diff --git a/MediaBrowser.Api/Dlna/DlnaService.cs b/MediaBrowser.Api/Dlna/DlnaService.cs index 9e6ca3aea..fec6d698a 100644 --- a/MediaBrowser.Api/Dlna/DlnaService.cs +++ b/MediaBrowser.Api/Dlna/DlnaService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using ServiceStack; using System.Collections.Generic; @@ -42,6 +43,7 @@ namespace MediaBrowser.Api.Dlna { } + [Authenticated] public class DlnaService : BaseApiService { private readonly IDlnaManager _dlnaManager; diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index e68aa73f6..35b1b5385 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -104,6 +105,7 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Authenticated] public class ItemLookupService : BaseApiService { private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 0094282c8..b95e18a0d 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using ServiceStack; using System; @@ -30,6 +31,7 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Authenticated] public class ItemRefreshService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index ad7da8e3c..db6c6ce53 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using ServiceStack; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,7 @@ namespace MediaBrowser.Api public string ItemId { get; set; } } + [Authenticated] public class ItemUpdateService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs index 72ffa3fca..6b8dd18f1 100644 --- a/MediaBrowser.Api/Library/ChapterService.cs +++ b/MediaBrowser.Api/Library/ChapterService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Chapters; +using MediaBrowser.Controller.Net; using ServiceStack; using System.Linq; @@ -9,6 +10,7 @@ namespace MediaBrowser.Api.Library { } + [Authenticated] public class ChapterService : BaseApiService { private readonly IChapterManager _chapterManager; diff --git a/MediaBrowser.Api/Library/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs index 01531a7ae..adf6a522b 100644 --- a/MediaBrowser.Api/Library/FileOrganizationService.cs +++ b/MediaBrowser.Api/Library/FileOrganizationService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.FileOrganization; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.Querying; using ServiceStack; @@ -78,6 +79,7 @@ namespace MediaBrowser.Api.Library public bool RememberCorrection { get; set; } } + [Authenticated] public class FileOrganizationService : BaseApiService { private readonly IFileOrganizationService _iFileOrganizationService; diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 947da29fe..c3ef58768 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using ServiceStack; @@ -130,36 +131,11 @@ namespace MediaBrowser.Api.Library /// true if [refresh library]; otherwise, false. public bool RefreshLibrary { get; set; } } - - [Route("/Library/Downloaded", "POST")] - public class ReportContentDownloaded : IReturnVoid - { - [ApiMember(Name = "Path", Description = "The path being downloaded to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Path { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ImageUrl { get; set; } - - [ApiMember(Name = "Name", Description = "The name of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - } - - [Route("/Library/Downloading", "POST")] - public class ReportContentDownloading : IReturnVoid - { - [ApiMember(Name = "Path", Description = "The path being downloaded to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Path { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ImageUrl { get; set; } - - [ApiMember(Name = "Name", Description = "The name of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - } /// /// Class LibraryStructureService /// + [Authenticated] public class LibraryStructureService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/SubtitleService.cs b/MediaBrowser.Api/Library/SubtitleService.cs index 62c7ac7c0..4fc3e00c0 100644 --- a/MediaBrowser.Api/Library/SubtitleService.cs +++ b/MediaBrowser.Api/Library/SubtitleService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Entities; @@ -86,6 +87,7 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } + [Authenticated] public class SubtitleService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index de01628f8..497f2d0a3 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -267,6 +268,7 @@ namespace MediaBrowser.Api.LiveTv public string UserId { get; set; } } + [Authenticated] public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index d3c47dfa1..5696d8df7 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using ServiceStack; @@ -42,6 +43,7 @@ namespace MediaBrowser.Api /// /// Class CulturesService /// + [Authenticated] public class LocalizationService : BaseApiService { /// diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs index b9e3888fd..19e47eb85 100644 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using ServiceStack; using System; @@ -45,6 +46,7 @@ namespace MediaBrowser.Api.Movies public Guid Id { get; set; } } + [Authenticated] public class CollectionService : BaseApiService { private readonly ICollectionManager _collectionManager; diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index db02ed428..f0bf22c5e 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -64,6 +65,7 @@ namespace MediaBrowser.Api.Movies /// /// Class MoviesService /// + [Authenticated] public class MoviesService : BaseApiService { /// diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 05e6a9577..b0ee6b6d5 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using ServiceStack; @@ -18,6 +19,7 @@ namespace MediaBrowser.Api.Movies /// /// Class TrailersService /// + [Authenticated] public class TrailersService : BaseApiService { /// diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index 0732c951a..34a933dee 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -2,10 +2,10 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using ServiceStack; using System; -using System.Collections.Generic; using System.Linq; namespace MediaBrowser.Api.Music @@ -15,6 +15,7 @@ namespace MediaBrowser.Api.Music { } + [Authenticated] public class AlbumsService : BaseApiService { /// diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index f50c87f47..ff029d5b5 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using ServiceStack; using System.Collections.Generic; @@ -33,6 +34,7 @@ namespace MediaBrowser.Api.Music public string Name { get; set; } } + [Authenticated] public class InstantMixService : BaseApiService { private readonly IUserManager _userManager; diff --git a/MediaBrowser.Api/PackageReviewService.cs b/MediaBrowser.Api/PackageReviewService.cs index 94ff1b62e..112a2c5ce 100644 --- a/MediaBrowser.Api/PackageReviewService.cs +++ b/MediaBrowser.Api/PackageReviewService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Constants; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; using ServiceStack; @@ -96,6 +97,7 @@ namespace MediaBrowser.Api } + [Authenticated] public class PackageReviewService : BaseApiService { private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index b54b05fcf..84b42baa3 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Updates; using ServiceStack; using System; @@ -121,6 +122,7 @@ namespace MediaBrowser.Api /// /// Class PackageService /// + [Authenticated] public class PackageService : BaseApiService { private readonly IInstallationManager _installationManager; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 9ff482a1a..75e13f92c 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -13,7 +13,6 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Library; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using System; @@ -1955,12 +1954,6 @@ namespace MediaBrowser.Api.Playback /// The video request. private void EnforceResolutionLimit(StreamState state, VideoStreamRequest videoRequest) { - // If enabled, allow whatever the client asks for - if (ServerConfigurationManager.Configuration.AllowVideoUpscaling) - { - return; - } - // Switch the incoming params to be ceilings rather than fixed values videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width; videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index fa78fa020..8a65e2b56 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -66,7 +66,6 @@ namespace MediaBrowser.Api.Playback.Hls return ProcessRequestAsync(request, isLive).Result; } - private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); /// /// Processes the request async. /// @@ -82,6 +81,11 @@ namespace MediaBrowser.Api.Playback.Hls var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); + if (isLive) + { + state.Request.StartTimeTicks = null; + } + var playlist = state.OutputFilePath; if (File.Exists(playlist)) @@ -90,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls } else { - await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(playlist)) @@ -99,6 +103,8 @@ namespace MediaBrowser.Api.Playback.Hls } else { + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.All, false).ConfigureAwait(false); + // If the playlist doesn't already exist, startup ffmpeg try { @@ -116,7 +122,7 @@ namespace MediaBrowser.Api.Playback.Hls } finally { - FfmpegStartLock.Release(); + ApiEntryPoint.Instance.TranscodingStartLock.Release(); } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 5bb610686..0af336c55 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -65,7 +65,6 @@ namespace MediaBrowser.Api.Playback.Hls return GetDynamicSegment(request).Result; } - private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); private async Task GetDynamicSegment(GetDynamicHlsVideoSegment request) { if ((request.StartTimeTicks ?? 0) > 0) @@ -90,7 +89,7 @@ namespace MediaBrowser.Api.Playback.Hls return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false); } - await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(segmentPath)) @@ -107,10 +106,11 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { + // TODO: Delete files from other jobs, but not this one + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.None, false).ConfigureAwait(false); + if (currentTranscodingIndex.HasValue) { - ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, playlistPath, FileDeleteMode.None); - DeleteLastFile(playlistPath, 0); } @@ -131,7 +131,7 @@ namespace MediaBrowser.Api.Playback.Hls } finally { - FfmpegStartLock.Release(); + ApiEntryPoint.Instance.TranscodingStartLock.Release(); } Logger.Info("waiting for {0}", segmentPath); diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index f31671b1a..3848cb2de 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -74,7 +74,9 @@ namespace MediaBrowser.Api.Playback.Hls public void Delete(StopEncodingProcess request) { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All); + var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All, true); + + Task.WaitAll(task); } /// diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index 31463dc3f..29cc7baf8 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; @@ -100,6 +101,7 @@ namespace MediaBrowser.Api /// /// Class PluginsService /// + [Authenticated] public class PluginService : BaseApiService { /// diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index aaf14ce71..483133c10 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Tasks; using ServiceStack; using ServiceStack.Text.Controller; @@ -78,6 +79,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class ScheduledTasksService /// + [Authenticated] public class ScheduledTaskService : BaseApiService { /// diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index e72edcc98..a6fcaf438 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Search; using ServiceStack; @@ -79,6 +80,7 @@ namespace MediaBrowser.Api /// /// Class SearchService /// + [Authenticated] public class SearchService : BaseApiService { /// diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 6776cbf17..c32e67216 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -175,6 +176,7 @@ namespace MediaBrowser.Api /// /// Class TvShowsService /// + [Authenticated] public class TvShowsService : BaseApiService { /// diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 13027d30b..07015ecae 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -46,6 +47,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class ArtistsService /// + [Authenticated] public class ArtistsService : BaseItemsByNameService { /// diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index b1379ad5d..49e979e55 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -41,6 +42,7 @@ namespace MediaBrowser.Api.UserLibrary public Guid? UserId { get; set; } } + [Authenticated] public class GameGenresService : BaseItemsByNameService { public GameGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 68f0e21ed..c8c45c1bb 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -46,6 +47,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class GenresService /// + [Authenticated] public class GenresService : BaseItemsByNameService { public GenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index c926c16ff..ba07571bf 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -246,6 +247,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class ItemsService /// + [Authenticated] public class ItemsService : BaseApiService { /// diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index 538b16575..f50f0a0bd 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -41,6 +42,7 @@ namespace MediaBrowser.Api.UserLibrary public Guid? UserId { get; set; } } + [Authenticated] public class MusicGenresService : BaseItemsByNameService { public MusicGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 962effded..cf9959e53 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -51,6 +52,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class PersonsService /// + [Authenticated] public class PersonsService : BaseItemsByNameService { /// diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index 4c38ba0d1..2024d779b 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -43,6 +44,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class StudiosService /// + [Authenticated] public class StudiosService : BaseItemsByNameService { public StudiosService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index da12a9e3d..d1767e7fd 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -424,6 +425,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class UserLibraryService /// + [Authenticated] public class UserLibraryService : BaseApiService { /// diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index 4dda045c0..2b300f900 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -43,6 +44,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class YearsService /// + [Authenticated] public class YearsService : BaseItemsByNameService { public YearsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 764a28102..64d1fcb34 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -268,28 +268,6 @@ namespace MediaBrowser.Api /// /// The request. public object Post(AuthenticateUser request) - { - // No response needed. Will throw an exception on failure. - var result = AuthenticateUser(request).Result; - - return result; - } - - public object Post(AuthenticateUserByName request) - { - var user = _userManager.Users.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); - - if (user == null) - { - throw new ArgumentException(string.Format("User {0} not found.", request.Username)); - } - - var result = AuthenticateUser(new AuthenticateUser { Id = user.Id, Password = request.Password }).Result; - - return ToOptimizedResult(result); - } - - private async Task AuthenticateUser(AuthenticateUser request) { var user = _userManager.GetUserById(request.Id); @@ -298,38 +276,21 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - - // Login in the old way if the header is missing - if (string.IsNullOrEmpty(auth.Client) || - string.IsNullOrEmpty(auth.Device) || - string.IsNullOrEmpty(auth.DeviceId) || - string.IsNullOrEmpty(auth.Version)) + return Post(new AuthenticateUserByName { - var success = await _userManager.AuthenticateUser(user, request.Password).ConfigureAwait(false); - - if (!success) - { - // Unauthorized - throw new UnauthorizedAccessException("Invalid user or password entered."); - } - - return new AuthenticationResult - { - User = _dtoService.GetUserDto(user) - }; - } + Username = user.Name, + Password = request.Password + }); + } - var session = await _sessionMananger.AuthenticateNewSession(user, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp).ConfigureAwait(false); + public object Post(AuthenticateUserByName request) + { + var auth = AuthorizationContext.GetAuthorizationInfo(Request); - var result = new AuthenticationResult - { - User = _dtoService.GetUserDto(user), - SessionInfo = _sessionMananger.GetSessionInfoDto(session) - }; + var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, + auth.DeviceId, auth.Device, Request.RemoteIp).Result; - return result; + return ToOptimizedResult(result); } /// @@ -353,7 +314,7 @@ namespace MediaBrowser.Api } else { - var success = _userManager.AuthenticateUser(user, request.CurrentPassword).Result; + var success = _userManager.AuthenticateUser(user.Name, request.CurrentPassword).Result; if (!success) { diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index f62e37f79..2407a1a39 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Querying; using ServiceStack; @@ -41,6 +42,7 @@ namespace MediaBrowser.Api public string Ids { get; set; } } + [Authenticated] public class VideosService : BaseApiService { private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 010caa233..0da5f9272 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -50,11 +50,11 @@ namespace MediaBrowser.Controller.Library /// /// Authenticates a User and returns a result indicating whether or not it succeeded /// - /// The user. + /// The username. /// The password. /// Task{System.Boolean}. /// user - Task AuthenticateUser(User user, string password); + Task AuthenticateUser(string username, string password); /// /// Refreshes metadata for each user diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index e609204d6..d7dcb60f0 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -28,5 +28,10 @@ namespace MediaBrowser.Controller.Net /// /// The version. public string Version { get; set; } + /// + /// Gets or sets the token. + /// + /// The token. + public string Token { get; set; } } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 74ad1b7ee..6d3a9d20c 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Users; using System; using System.Collections.Generic; using System.Threading; @@ -206,11 +207,11 @@ namespace MediaBrowser.Controller.Session /// The session identifier. /// The item. void ReportNowViewingItem(string sessionId, BaseItemInfo item); - + /// /// Authenticates the new session. /// - /// The user. + /// The username. /// The password. /// Type of the client. /// The application version. @@ -218,7 +219,7 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// Task{SessionInfo}. - Task AuthenticateNewSession(User user, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint); + Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint); /// /// Reports the capabilities. @@ -248,5 +249,11 @@ namespace MediaBrowser.Controller.Session /// The version. /// SessionInfo. SessionInfo GetSession(string deviceId, string client, string version); + + /// + /// Validates the security token. + /// + /// The token. + void ValidateSecurityToken(string token); } } \ No newline at end of file diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 2d19d335d..0ed49d5f8 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -137,21 +137,9 @@ Configuration\MetadataPluginType.cs - - Configuration\NotificationOption.cs - - - Configuration\NotificationOptions.cs - - - Configuration\NotificationType.cs - Configuration\PathSubstitution.cs - - Configuration\SendToUserType.cs - Configuration\ServerConfiguration.cs @@ -638,6 +626,12 @@ Notifications\NotificationLevel.cs + + Notifications\NotificationOption.cs + + + Notifications\NotificationOptions.cs + Notifications\NotificationQuery.cs @@ -653,9 +647,15 @@ Notifications\NotificationsSummary.cs + + Notifications\NotificationType.cs + Notifications\NotificationTypeInfo.cs + + Notifications\SendToUserType.cs + Plugins\BasePluginConfiguration.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index c0c886c8a..12c87ca97 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -124,21 +124,9 @@ Configuration\MetadataPluginType.cs - - Configuration\NotificationOption.cs - - - Configuration\NotificationOptions.cs - - - Configuration\NotificationType.cs - Configuration\PathSubstitution.cs - - Configuration\SendToUserType.cs - Configuration\ServerConfiguration.cs @@ -619,6 +607,12 @@ Notifications\NotificationLevel.cs + + Notifications\NotificationOption.cs + + + Notifications\NotificationOptions.cs + Notifications\NotificationQuery.cs @@ -634,9 +628,15 @@ Notifications\NotificationsSummary.cs + + Notifications\NotificationType.cs + Notifications\NotificationTypeInfo.cs + + Notifications\SendToUserType.cs + Plugins\BasePluginConfiguration.cs diff --git a/MediaBrowser.Model/Configuration/NotificationOption.cs b/MediaBrowser.Model/Configuration/NotificationOption.cs deleted file mode 100644 index 5fcf3550c..000000000 --- a/MediaBrowser.Model/Configuration/NotificationOption.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public class NotificationOption - { - public string Type { get; set; } - - /// - /// User Ids to not monitor (it's opt out) - /// - public string[] DisabledMonitorUsers { get; set; } - - /// - /// User Ids to send to (if SendToUserMode == Custom) - /// - public string[] SendToUsers { get; set; } - - /// - /// Gets or sets a value indicating whether this is enabled. - /// - /// true if enabled; otherwise, false. - public bool Enabled { get; set; } - - /// - /// Gets or sets the title format string. - /// - /// The title format string. - public string Title { get; set; } - - /// - /// Gets or sets the description. - /// - /// The description. - public string Description { get; set; } - - /// - /// Gets or sets the disabled services. - /// - /// The disabled services. - public string[] DisabledServices { get; set; } - - /// - /// Gets or sets the send to user mode. - /// - /// The send to user mode. - public SendToUserType SendToUserMode { get; set; } - - public NotificationOption() - { - DisabledServices = new string[] { }; - DisabledMonitorUsers = new string[] { }; - SendToUsers = new string[] { }; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/NotificationOptions.cs b/MediaBrowser.Model/Configuration/NotificationOptions.cs deleted file mode 100644 index dd523d947..000000000 --- a/MediaBrowser.Model/Configuration/NotificationOptions.cs +++ /dev/null @@ -1,124 +0,0 @@ -using MediaBrowser.Model.Extensions; - -namespace MediaBrowser.Model.Configuration -{ - public class NotificationOptions - { - public NotificationOption[] Options { get; set; } - - public NotificationOptions() - { - Options = new[] - { - new NotificationOption - { - Type = NotificationType.TaskFailed.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.ServerRestartRequired.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.ApplicationUpdateAvailable.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.ApplicationUpdateInstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginUpdateInstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginUninstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.InstallationFailed.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginInstalled.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - }, - new NotificationOption - { - Type = NotificationType.PluginError.ToString(), - Enabled = true, - SendToUserMode = SendToUserType.Admins - } - }; - } - - public NotificationOption GetOptions(string type) - { - foreach (NotificationOption i in Options) - { - if (StringHelper.EqualsIgnoreCase(type, i.Type)) return i; - } - return null; - } - - public bool IsEnabled(string type) - { - NotificationOption opt = GetOptions(type); - - return opt != null && opt.Enabled; - } - - public bool IsServiceEnabled(string service, string notificationType) - { - NotificationOption opt = GetOptions(notificationType); - - return opt == null || - !ListHelper.ContainsIgnoreCase(opt.DisabledServices, service); - } - - public bool IsEnabledToMonitorUser(string type, string userId) - { - NotificationOption opt = GetOptions(type); - - return opt != null && opt.Enabled && - !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); - } - - public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig) - { - NotificationOption opt = GetOptions(type); - - if (opt != null && opt.Enabled) - { - if (opt.SendToUserMode == SendToUserType.All) - { - return true; - } - - if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) - { - return true; - } - - return ListHelper.ContainsIgnoreCase(opt.SendToUsers, userId); - } - - return false; - } - } -} diff --git a/MediaBrowser.Model/Configuration/NotificationType.cs b/MediaBrowser.Model/Configuration/NotificationType.cs deleted file mode 100644 index d8043b0e1..000000000 --- a/MediaBrowser.Model/Configuration/NotificationType.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public enum NotificationType - { - ApplicationUpdateAvailable, - ApplicationUpdateInstalled, - AudioPlayback, - GamePlayback, - VideoPlayback, - AudioPlaybackStopped, - GamePlaybackStopped, - VideoPlaybackStopped, - InstallationFailed, - PluginError, - PluginInstalled, - PluginUpdateInstalled, - PluginUninstalled, - NewLibraryContent, - NewLibraryContentMultiple, - ServerRestartRequired, - TaskFailed - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/SendToUserType.cs b/MediaBrowser.Model/Configuration/SendToUserType.cs deleted file mode 100644 index a2eac4c2d..000000000 --- a/MediaBrowser.Model/Configuration/SendToUserType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public enum SendToUserType - { - All = 0, - Admins = 1, - Custom = 2 - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f7b888f82..36c353479 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Weather; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Weather; using System; namespace MediaBrowser.Model.Configuration @@ -178,8 +179,6 @@ namespace MediaBrowser.Model.Configuration /// The encoding quality. public EncodingQuality MediaEncodingQuality { get; set; } - public bool AllowVideoUpscaling { get; set; } - public MetadataOptions[] MetadataOptions { get; set; } public bool EnableDebugEncodingLogging { get; set; } @@ -268,10 +267,7 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions(0, 1280) {ItemType = "Season"} }; - NotificationOptions = new NotificationOptions(); - SubtitleOptions = new SubtitleOptions(); - LiveTvOptions = new LiveTvOptions(); TvFileOrganizationOptions = new TvFileOrganizationOptions(); } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index d2b0a8caf..d758f2f39 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -88,11 +88,11 @@ - - - + + + - + diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs new file mode 100644 index 000000000..09f7072dd --- /dev/null +++ b/MediaBrowser.Model/Notifications/NotificationOption.cs @@ -0,0 +1,56 @@ +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Model.Notifications +{ + public class NotificationOption + { + public string Type { get; set; } + + /// + /// User Ids to not monitor (it's opt out) + /// + public string[] DisabledMonitorUsers { get; set; } + + /// + /// User Ids to send to (if SendToUserMode == Custom) + /// + public string[] SendToUsers { get; set; } + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + /// true if enabled; otherwise, false. + public bool Enabled { get; set; } + + /// + /// Gets or sets the title format string. + /// + /// The title format string. + public string Title { get; set; } + + /// + /// Gets or sets the description. + /// + /// The description. + public string Description { get; set; } + + /// + /// Gets or sets the disabled services. + /// + /// The disabled services. + public string[] DisabledServices { get; set; } + + /// + /// Gets or sets the send to user mode. + /// + /// The send to user mode. + public SendToUserType SendToUserMode { get; set; } + + public NotificationOption() + { + DisabledServices = new string[] { }; + DisabledMonitorUsers = new string[] { }; + SendToUsers = new string[] { }; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs new file mode 100644 index 000000000..7d80f3177 --- /dev/null +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -0,0 +1,125 @@ +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Extensions; + +namespace MediaBrowser.Model.Notifications +{ + public class NotificationOptions + { + public NotificationOption[] Options { get; set; } + + public NotificationOptions() + { + Options = new[] + { + new NotificationOption + { + Type = NotificationType.TaskFailed.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.ServerRestartRequired.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.ApplicationUpdateAvailable.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.ApplicationUpdateInstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginUpdateInstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginUninstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.InstallationFailed.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginInstalled.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + }, + new NotificationOption + { + Type = NotificationType.PluginError.ToString(), + Enabled = true, + SendToUserMode = SendToUserType.Admins + } + }; + } + + public NotificationOption GetOptions(string type) + { + foreach (NotificationOption i in Options) + { + if (StringHelper.EqualsIgnoreCase(type, i.Type)) return i; + } + return null; + } + + public bool IsEnabled(string type) + { + NotificationOption opt = GetOptions(type); + + return opt != null && opt.Enabled; + } + + public bool IsServiceEnabled(string service, string notificationType) + { + NotificationOption opt = GetOptions(notificationType); + + return opt == null || + !ListHelper.ContainsIgnoreCase(opt.DisabledServices, service); + } + + public bool IsEnabledToMonitorUser(string type, string userId) + { + NotificationOption opt = GetOptions(type); + + return opt != null && opt.Enabled && + !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); + } + + public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig) + { + NotificationOption opt = GetOptions(type); + + if (opt != null && opt.Enabled) + { + if (opt.SendToUserMode == SendToUserType.All) + { + return true; + } + + if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) + { + return true; + } + + return ListHelper.ContainsIgnoreCase(opt.SendToUsers, userId); + } + + return false; + } + } +} diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs new file mode 100644 index 000000000..34e4d32b5 --- /dev/null +++ b/MediaBrowser.Model/Notifications/NotificationType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.Notifications +{ + public enum NotificationType + { + ApplicationUpdateAvailable, + ApplicationUpdateInstalled, + AudioPlayback, + GamePlayback, + VideoPlayback, + AudioPlaybackStopped, + GamePlaybackStopped, + VideoPlaybackStopped, + InstallationFailed, + PluginError, + PluginInstalled, + PluginUpdateInstalled, + PluginUninstalled, + NewLibraryContent, + NewLibraryContentMultiple, + ServerRestartRequired, + TaskFailed + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Notifications/SendToUserType.cs b/MediaBrowser.Model/Notifications/SendToUserType.cs new file mode 100644 index 000000000..1998d3102 --- /dev/null +++ b/MediaBrowser.Model/Notifications/SendToUserType.cs @@ -0,0 +1,9 @@ +namespace MediaBrowser.Model.Notifications +{ + public enum SendToUserType + { + All = 0, + Admins = 1, + Custom = 2 + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs index de8f4e8b8..b9bf6e7fe 100644 --- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs +++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs @@ -1,4 +1,6 @@ - +using System; +using System.Runtime.Serialization; + namespace MediaBrowser.Model.Updates { /// @@ -24,6 +26,32 @@ namespace MediaBrowser.Model.Updates /// The version STR. public string versionStr { get; set; } + /// + /// The _version + /// + private Version _version; + /// + /// Gets or sets the version. + /// Had to make this an interpreted property since Protobuf can't handle Version + /// + /// The version. + [IgnoreDataMember] + public Version version + { + get { return _version ?? (_version = new Version(ValueOrDefault(versionStr, "0.0.0.1"))); } + } + + /// + /// Values the or default. + /// + /// The STR. + /// The def. + /// System.String. + private static string ValueOrDefault(string str, string def) + { + return string.IsNullOrEmpty(str) ? def : str; + } + /// /// Gets or sets the classification. /// @@ -60,4 +88,4 @@ namespace MediaBrowser.Model.Updates /// The target filename. public string targetFilename { get; set; } } -} +} \ No newline at end of file diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs index 998aaa3a7..8046e83c7 100644 --- a/MediaBrowser.Model/Users/AuthenticationResult.cs +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -16,5 +16,11 @@ namespace MediaBrowser.Model.Users /// /// The session information. public SessionInfoDto SessionInfo { get; set; } + + /// + /// Gets or sets the authentication token. + /// + /// The authentication token. + public string AuthenticationToken { get; set; } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 1a7f9db28..c29a7d14e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -51,13 +51,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security private void ValidateUser(IRequest req) { - User user = null; - //This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(req); - if (auth != null) + if (string.IsNullOrWhiteSpace(auth.Token)) { + // Legacy + // TODO: Deprecate this in Oct 2014 + + User user = null; + if (!string.IsNullOrWhiteSpace(auth.UserId)) { var userId = auth.UserId; @@ -65,22 +68,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security user = UserManager.GetUserById(new Guid(userId)); } - string deviceId = auth.DeviceId; - string device = auth.Device; - string client = auth.Client; - string version = auth.Version; - - if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version)) + if (user == null || user.Configuration.IsDisabled) { - var remoteEndPoint = req.RemoteIp; - - SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user); + throw new UnauthorizedAccessException("Unauthorized access."); } } - - if (user == null || user.Configuration.IsDisabled) + else { - throw new UnauthorizedAccessException("Unauthorized access."); + SessionManager.ValidateSecurityToken(auth.Token); } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 6ea77f251..77343ab4e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -42,7 +42,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security Device = device, DeviceId = deviceId, UserId = userId, - Version = version + Version = version, + Token = httpReq.Headers["X-AUTH-TOKEN"] }; } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index f9d7479ce..1e0bbc39b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -121,17 +121,20 @@ namespace MediaBrowser.Server.Implementations.Library /// /// Authenticates a User and returns a result indicating whether or not it succeeded /// - /// The user. + /// The username. /// The password. /// Task{System.Boolean}. /// user - public async Task AuthenticateUser(User user, string password) + /// + public async Task AuthenticateUser(string username, string password) { - if (user == null) + if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException("username"); } + var user = Users.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + if (user.Configuration.IsDisabled) { throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 467bedcf1..ad1ddba88 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -49,8 +49,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly List _services = new List(); - private readonly ConcurrentDictionary _openStreams = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _openStreams = + new ConcurrentDictionary(); private List _channelIdList = new List(); private Dictionary _programs = new Dictionary(); @@ -291,66 +291,69 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); public async Task GetRecordingStream(string id, CancellationToken cancellationToken) + { + return await GetLiveStream(id, false, cancellationToken).ConfigureAwait(false); + } + + public async Task GetChannelStream(string id, CancellationToken cancellationToken) + { + return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false); + } + + private async Task GetLiveStream(string id, bool isChannel, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var service = ActiveService; - - var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); - - var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id)); - - var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false); - - Sanitize(result); + // Avoid implicitly captured closure + var itemId = id; - _logger.Debug("Live stream info: " + _json.SerializeToString(result)); + var stream = _openStreams + .Where(i => string.Equals(i.Value.ItemId, itemId) && isChannel == i.Value.IsChannel) + .Take(1) + .Select(i => i.Value) + .FirstOrDefault(); - if (!string.IsNullOrEmpty(result.Id)) + if (stream != null) { - _openStreams.AddOrUpdate(result.Id, result, (key, info) => result); + stream.ConsumerCount++; + _logger.Debug("Returning existing live tv stream"); + return stream.Info; } - return result; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting recording stream", ex); - - throw; - } - finally - { - _liveStreamSemaphore.Release(); - } - } - - public async Task GetChannelStream(string id, CancellationToken cancellationToken) - { - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { var service = ActiveService; + LiveStreamInfo info; - var channel = GetInternalChannel(id); - - _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); + if (isChannel) + { + var channel = GetInternalChannel(id); + _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); - var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false); + info = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false); + } + else + { + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id)); - Sanitize(result); + _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.Id); + info = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false); + } - _logger.Debug("Live stream info: " + _json.SerializeToString(result)); + Sanitize(info); - if (!string.IsNullOrEmpty(result.Id)) + var data = new LiveStreamData { - _openStreams.AddOrUpdate(result.Id, result, (key, info) => result); - } + Info = info, + ConsumerCount = 1, + IsChannel = isChannel, + ItemId = id + }; - return result; + _openStreams.AddOrUpdate(info.Id, data, (key, i) => data); + + return info; } catch (Exception ex) { @@ -1597,20 +1600,38 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } + class LiveStreamData + { + internal LiveStreamInfo Info; + internal int ConsumerCount; + internal string ItemId; + internal bool IsChannel; + } + public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - var service = ActiveService; - - _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); - try { - await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); + var service = ActiveService; + + LiveStreamData data; + if (_openStreams.TryGetValue(id, out data)) + { + if (data.ConsumerCount > 1) + { + data.ConsumerCount--; + _logger.Info("Decrementing live stream client count."); + return; + } - LiveStreamInfo removed; - _openStreams.TryRemove(id, out removed); + } + _openStreams.TryRemove(id, out data); + + _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); + + await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -1662,7 +1683,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { foreach (var stream in _openStreams.Values.ToList()) { - var task = CloseLiveStream(stream.Id, CancellationToken.None); + var task = CloseLiveStream(stream.Info.Id, CancellationToken.None); Task.WaitAll(task); } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index b3280dac7..41555fe82 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -201,5 +201,21 @@ "ButtonOpenInNewTab": "Open in new tab", "ButtonShuffle": "Shuffle", "ButtonInstantMix": "Instant mix", - "ButtonResume": "Resume" + "ButtonResume": "Resume", + "HeaderScenes": "Scenes", + "HeaderAudioTracks": "Audio Tracks", + "LabelUnknownLanguage": "Unknown language", + "HeaderSubtitles": "Subtitles", + "HeaderVideoQuality": "Video Quality", + "MessageErrorPlayingVideo": "There was an error playing the video.", + "MessageEnsureOpenTuner": "Please ensure there is an open tuner availalble.", + "ButtonHome": "Home", + "ButtonDashboard": "Dashboard", + "ButtonReports": "Reports", + "ButtonMetadataManager": "Metadata Manager", + "HeaderTime": "Time", + "HeaderName": "Name", + "HeaderAlbum": "Album", + "HeaderAlbumArtist": "Album Artist", + "HeaderArtist": "Artist" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 10e27bc30..a909929ae 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -200,6 +200,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs new file mode 100644 index 000000000..a336eba0e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationConfigurationFactory.cs @@ -0,0 +1,21 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Notifications; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Notifications +{ + public class NotificationConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new List + { + new ConfigurationStore + { + Key = "notifications", + ConfigurationType = typeof (NotificationOptions) + } + }; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs index 416b29e86..964e2cd24 100644 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -30,13 +31,18 @@ namespace MediaBrowser.Server.Implementations.Notifications _logger = logManager.GetLogger(GetType().Name); } + private NotificationOptions GetConfiguration() + { + return _config.GetConfiguration("notifications"); + } + public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken) { var notificationType = request.NotificationType; var options = string.IsNullOrWhiteSpace(notificationType) ? null : - _config.Configuration.NotificationOptions.GetOptions(notificationType); + GetConfiguration().GetOptions(notificationType); var users = GetUserIds(request, options) .Except(request.ExcludeUserIds) @@ -223,7 +229,7 @@ namespace MediaBrowser.Server.Implementations.Notifications private bool IsEnabled(INotificationService service, string notificationType) { return string.IsNullOrEmpty(notificationType) || - _config.Configuration.NotificationOptions.IsServiceEnabled(service.Name, notificationType); + GetConfiguration().IsServiceEnabled(service.Name, notificationType); } public void AddParts(IEnumerable services, IEnumerable notificationTypeFactories) @@ -248,9 +254,11 @@ namespace MediaBrowser.Server.Implementations.Notifications }).SelectMany(i => i).ToList(); + var config = GetConfiguration(); + foreach (var i in list) { - i.Enabled = _config.Configuration.NotificationOptions.IsEnabled(i.Type); + i.Enabled = config.IsEnabled(i.Type); } return list; diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 4e2b3c7b7..2f6790a3e 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -25,6 +25,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Users; namespace MediaBrowser.Server.Implementations.Session { @@ -1138,10 +1139,15 @@ namespace MediaBrowser.Server.Implementations.Session } } + public void ValidateSecurityToken(string token) + { + + } + /// /// Authenticates the new session. /// - /// The user. + /// The username. /// The password. /// Type of the client. /// The application version. @@ -1149,17 +1155,34 @@ namespace MediaBrowser.Server.Implementations.Session /// Name of the device. /// The remote end point. /// Task{SessionInfo}. + /// Invalid user or password entered. /// - public async Task AuthenticateNewSession(User user, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) + public async Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) { - var result = await _userManager.AuthenticateUser(user, password).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { throw new UnauthorizedAccessException("Invalid user or password entered."); } - return await LogSessionActivity(clientType, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + var user = _userManager.Users + .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + + var session = await LogSessionActivity(clientType, + appVersion, + deviceId, + deviceName, + remoteEndPoint, + user) + .ConfigureAwait(false); + + return new AuthenticationResult + { + User = _dtoService.GetUserDto(user), + SessionInfo = GetSessionInfoDto(session), + AuthenticationToken = Guid.NewGuid().ToString("N") + }; } /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e8fb7bfcb..673a9f151 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -318,6 +318,13 @@ namespace MediaBrowser.ServerApplication saveConfig = true; } + if (ServerConfigurationManager.Configuration.NotificationOptions != null) + { + ServerConfigurationManager.SaveConfiguration("notifications", ServerConfigurationManager.Configuration.NotificationOptions); + ServerConfigurationManager.Configuration.NotificationOptions = null; + saveConfig = true; + } + if (saveConfig) { ServerConfigurationManager.SaveConfiguration(); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 80f864a32..3ccf26c37 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -403,7 +403,7 @@ namespace MediaBrowser.WebDashboard.Api var files = new[] { - "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.css", + "thirdparty/jquerymobile-1.4.3/jquery.mobile-1.4.3.min.css", "css/all.css" + versionString }; @@ -449,7 +449,7 @@ namespace MediaBrowser.WebDashboard.Api // jQuery + jQuery mobile await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false); - await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.js", newLineBytes).ConfigureAwait(false); + await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.3/jquery.mobile-1.4.3.min.js", newLineBytes).ConfigureAwait(false); await AppendLocalization(memoryStream).ConfigureAwait(false); await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index cbdae6d08..8f85fcb51 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -815,613 +815,613 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -2144,6 +2144,9 @@ PreserveNewest + + PreserveNewest + Designer -- cgit v1.2.3 From 7fa9b14f56eabbb06e38726879b3cddc47b8e8fb Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Jul 2014 22:22:57 -0400 Subject: fixes #762 - Marking unwatched doesn't update display --- MediaBrowser.Api/BrandingService.cs | 28 +++ MediaBrowser.Api/ConfigurationService.cs | 7 + MediaBrowser.Api/Images/ImageService.cs | 7 + MediaBrowser.Api/ItemRefreshService.cs | 22 ++- MediaBrowser.Api/Library/LibraryService.cs | 2 + MediaBrowser.Api/Library/SubtitleService.cs | 184 -------------------- MediaBrowser.Api/MediaBrowser.Api.csproj | 4 +- MediaBrowser.Api/NotificationsService.cs | 2 + MediaBrowser.Api/SessionsService.cs | 12 +- MediaBrowser.Api/Subtitles/SubtitleService.cs | 189 +++++++++++++++++++++ MediaBrowser.Api/SystemService.cs | 70 +++++++- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 10 +- MediaBrowser.Api/UserService.cs | 4 + .../WebSocket/LogFileWebSocketListener.cs | 149 ---------------- MediaBrowser.Common/Net/MimeTypes.cs | 4 + MediaBrowser.Controller/Dto/IDtoService.cs | 7 - MediaBrowser.Controller/Entities/BaseItem.cs | 15 ++ MediaBrowser.Controller/Entities/Folder.cs | 80 ++++++++- MediaBrowser.Controller/Entities/IHasUserData.cs | 18 +- MediaBrowser.Controller/Entities/TV/Episode.cs | 2 +- MediaBrowser.Controller/Entities/UserRootFolder.cs | 6 + MediaBrowser.Controller/Entities/UserView.cs | 2 +- .../Library/IUserDataManager.cs | 9 + MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs | 4 +- .../Providers/MetadataRefreshOptions.cs | 39 +++-- MediaBrowser.Controller/Session/ISessionManager.cs | 8 +- .../MediaBrowser.Model.Portable.csproj | 6 + .../MediaBrowser.Model.net35.csproj | 6 + MediaBrowser.Model/Branding/BrandingOptions.cs | 12 ++ .../Configuration/ServerConfiguration.cs | 3 - .../Configuration/XbmcMetadataOptions.cs | 3 + MediaBrowser.Model/Dto/UserItemDataDto.cs | 16 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + .../Notifications/NotificationRequest.cs | 3 +- MediaBrowser.Model/Session/SessionInfoDto.cs | 32 +--- MediaBrowser.Model/System/LogFile.cs | 31 ++++ MediaBrowser.Providers/Manager/ImageSaver.cs | 28 ++- .../MediaInfo/FFProbeVideoInfo.cs | 4 +- .../Branding/BrandingConfigurationFactory.cs | 21 +++ .../Dto/DtoService.cs | 52 +++--- .../EntryPoints/UserDataChangeNotifier.cs | 45 +++-- .../HttpServer/HttpListenerHost.cs | 26 +-- .../HttpServer/Security/AuthorizationContext.cs | 9 +- .../Library/UserDataManager.cs | 37 ++++ .../LiveTv/LiveTvDtoService.cs | 11 +- .../LiveTv/LiveTvManager.cs | 6 +- .../Localization/Server/server.json | 17 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Notifications/NotificationManager.cs | 5 +- .../Session/SessionManager.cs | 27 ++- .../Sorting/AirTimeComparer.cs | 33 +++- MediaBrowser.ServerApplication/ApplicationHost.cs | 2 +- .../Native/BrowserLauncher.cs | 12 -- MediaBrowser.ServerApplication/ServerNotifyIcon.cs | 15 -- MediaBrowser.WebDashboard/Api/DashboardService.cs | 3 +- .../MediaBrowser.WebDashboard.csproj | 22 +-- 56 files changed, 821 insertions(+), 553 deletions(-) create mode 100644 MediaBrowser.Api/BrandingService.cs delete mode 100644 MediaBrowser.Api/Library/SubtitleService.cs create mode 100644 MediaBrowser.Api/Subtitles/SubtitleService.cs delete mode 100644 MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs create mode 100644 MediaBrowser.Model/Branding/BrandingOptions.cs create mode 100644 MediaBrowser.Model/System/LogFile.cs create mode 100644 MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs (limited to 'MediaBrowser.Api/UserLibrary/UserLibraryService.cs') diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs new file mode 100644 index 000000000..4b49b411a --- /dev/null +++ b/MediaBrowser.Api/BrandingService.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Branding; +using ServiceStack; + +namespace MediaBrowser.Api +{ + [Route("/Branding/Configuration", "GET", Summary = "Gets branding configuration")] + public class GetBrandingOptions : IReturn + { + } + + public class BrandingService : BaseApiService + { + private readonly IConfigurationManager _config; + + public BrandingService(IConfigurationManager config) + { + _config = config; + } + + public object Get(GetBrandingOptions request) + { + var result = _config.GetConfiguration("branding"); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 39fcc50d8..291deb3b0 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; @@ -27,6 +28,7 @@ namespace MediaBrowser.Api } [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")] + [Authenticated] public class GetNamedConfiguration { [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -37,11 +39,13 @@ namespace MediaBrowser.Api /// Class UpdateConfiguration /// [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] + [Authenticated] public class UpdateConfiguration : ServerConfiguration, IReturnVoid { } [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")] + [Authenticated] public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream { [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -51,18 +55,21 @@ namespace MediaBrowser.Api } [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")] + [Authenticated] public class GetDefaultMetadataOptions : IReturn { } [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")] + [Authenticated] public class GetMetadataPlugins : IReturn> { } [Route("/System/Configuration/VideoImageExtraction", "POST", Summary = "Updates image extraction for all types")] + [Authenticated] public class UpdateVideoImageExtraction : IReturnVoid { public bool Enabled { get; set; } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 4f8c348bb..deaefe019 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; @@ -26,6 +27,7 @@ namespace MediaBrowser.Api.Images /// [Route("/Items/{Id}/Images", "GET")] [Api(Description = "Gets information about an item's images")] + [Authenticated] public class GetItemImageInfos : IReturn> { /// @@ -56,6 +58,7 @@ namespace MediaBrowser.Api.Images /// [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST")] [Api(Description = "Updates the index for an item image")] + [Authenticated] public class UpdateItemImageIndex : IReturnVoid { /// @@ -137,6 +140,7 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "DELETE")] [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")] [Api(Description = "Deletes an item image")] + [Authenticated] public class DeleteItemImage : DeleteImageRequest, IReturnVoid { /// @@ -153,6 +157,7 @@ namespace MediaBrowser.Api.Images [Route("/Users/{Id}/Images/{Type}", "DELETE")] [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] [Api(Description = "Deletes a user image")] + [Authenticated] public class DeleteUserImage : DeleteImageRequest, IReturnVoid { /// @@ -169,6 +174,7 @@ namespace MediaBrowser.Api.Images [Route("/Users/{Id}/Images/{Type}", "POST")] [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")] [Api(Description = "Posts a user image")] + [Authenticated] public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid { /// @@ -191,6 +197,7 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "POST")] [Route("/Items/{Id}/Images/{Type}/{Index}", "POST")] [Api(Description = "Posts an item image")] + [Authenticated] public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid { /// diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index b95e18a0d..993b69601 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -13,10 +13,16 @@ namespace MediaBrowser.Api { public class BaseRefreshRequest : IReturnVoid { - [ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool Forced { get; set; } + [ApiMember(Name = "MetadataRefreshMode", Description = "Specifies the metadata refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public MetadataRefreshMode MetadataRefreshMode { get; set; } - [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced during the refresh.", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "ImageRefreshMode", Description = "Specifies the image refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public ImageRefreshMode ImageRefreshMode { get; set; } + + [ApiMember(Name = "ReplaceAllMetadata", Description = "Determines if metadata should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public bool ReplaceAllMetadata { get; set; } + + [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool ReplaceAllImages { get; set; } } @@ -93,7 +99,7 @@ namespace MediaBrowser.Api private async Task RefreshItem(RefreshItem request, BaseItem item) { var options = GetRefreshOptions(request); - + try { await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); @@ -148,10 +154,10 @@ namespace MediaBrowser.Api { return new MetadataRefreshOptions { - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = ImageRefreshMode.FullRefresh, - ReplaceAllMetadata = request.Forced, - ReplaceAllImages = request.ReplaceAllImages + MetadataRefreshMode = request.MetadataRefreshMode, + ImageRefreshMode = request.ImageRefreshMode, + ReplaceAllImages = request.ReplaceAllImages, + ReplaceAllMetadata = request.ReplaceAllMetadata }; } } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 77a714755..10aa23126 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; @@ -226,6 +227,7 @@ namespace MediaBrowser.Api.Library /// /// Class LibraryService /// + [Authenticated] public class LibraryService : BaseApiService { /// diff --git a/MediaBrowser.Api/Library/SubtitleService.cs b/MediaBrowser.Api/Library/SubtitleService.cs deleted file mode 100644 index 4fc3e00c0..000000000 --- a/MediaBrowser.Api/Library/SubtitleService.cs +++ /dev/null @@ -1,184 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.Library -{ - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")] - public class GetSubtitle - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - - [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Format { get; set; } - - [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public long StartPositionTicks { get; set; } - } - - [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] - public class DeleteSubtitle - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] - public int Index { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] - public class SearchRemoteSubtitles : IReturn> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Language { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")] - public class GetSubtitleProviders : IReturn> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] - public class DownloadRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SubtitleId { get; set; } - } - - [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")] - public class GetRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Authenticated] - public class SubtitleService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly ISubtitleManager _subtitleManager; - private readonly ISubtitleEncoder _subtitleEncoder; - - public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder) - { - _libraryManager = libraryManager; - _subtitleManager = subtitleManager; - _subtitleEncoder = subtitleEncoder; - } - - public object Get(SearchRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result; - - return ToOptimizedResult(response); - } - public object Get(GetSubtitle request) - { - if (string.IsNullOrEmpty(request.Format)) - { - var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - - var mediaSource = item.GetMediaSources(false) - .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); - - return ToStaticFileResult(subtitleStream.Path); - } - - var stream = GetSubtitles(request).Result; - - return ResultFactory.GetResult(stream, Common.Net.MimeTypes.GetMimeType("file." + request.Format)); - } - - private async Task GetSubtitles(GetSubtitle request) - { - return await _subtitleEncoder.GetSubtitles(request.Id, - request.MediaSourceId, - request.Index, - request.Format, - request.StartPositionTicks, - CancellationToken.None).ConfigureAwait(false); - } - - public void Delete(DeleteSubtitle request) - { - var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index); - - Task.WaitAll(task); - } - - public object Get(GetSubtitleProviders request) - { - var result = _subtitleManager.GetProviders(request.Id); - - return ToOptimizedResult(result); - } - - public object Get(GetRemoteSubtitles request) - { - var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result; - - return ResultFactory.GetResult(result.Stream, Common.Net.MimeTypes.GetMimeType("file." + result.Format)); - } - - public void Post(DownloadRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - Task.Run(async () => - { - try - { - await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) - .ConfigureAwait(false); - - await video.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error downloading subtitles", ex); - } - - }); - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a68966b33..ca2887d19 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -65,11 +65,12 @@ Properties\SharedVersion.cs + - + @@ -139,7 +140,6 @@ - diff --git a/MediaBrowser.Api/NotificationsService.cs b/MediaBrowser.Api/NotificationsService.cs index 28edb61dd..51a080106 100644 --- a/MediaBrowser.Api/NotificationsService.cs +++ b/MediaBrowser.Api/NotificationsService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; using MediaBrowser.Model.Notifications; using ServiceStack; @@ -82,6 +83,7 @@ namespace MediaBrowser.Api public string Ids { get; set; } } + [Authenticated] public class NotificationsService : BaseApiService { private readonly INotificationsRepository _notificationsRepo; diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 00c307a18..f4651601b 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Session; using ServiceStack; @@ -14,6 +15,7 @@ namespace MediaBrowser.Api /// Class GetSessions /// [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] + [Authenticated] public class GetSessions : IReturn> { [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -27,6 +29,7 @@ namespace MediaBrowser.Api /// Class DisplayContent /// [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] + [Authenticated] public class DisplayContent : IReturnVoid { /// @@ -59,6 +62,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] + [Authenticated] public class Play : IReturnVoid { /// @@ -91,6 +95,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] + [Authenticated] public class SendPlaystateCommand : IReturnVoid { /// @@ -115,6 +120,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] public class SendSystemCommand : IReturnVoid { /// @@ -133,6 +139,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] public class SendGeneralCommand : IReturnVoid { /// @@ -151,6 +158,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] + [Authenticated] public class SendFullGeneralCommand : GeneralCommand, IReturnVoid { /// @@ -162,6 +170,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] + [Authenticated] public class SendMessageCommand : IReturnVoid { /// @@ -182,6 +191,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] + [Authenticated] public class AddUserToSession : IReturnVoid { [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] @@ -192,6 +202,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] + [Authenticated] public class RemoveUserFromSession : IReturnVoid { [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] @@ -202,7 +213,6 @@ namespace MediaBrowser.Api } [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] - [Route("/Sessions/{Id}/Capabilities", "POST", Summary = "Updates capabilities for a device")] public class PostCapabilities : IReturnVoid { /// diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs new file mode 100644 index 000000000..3e692cb22 --- /dev/null +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -0,0 +1,189 @@ +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Subtitles +{ + [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] + [Authenticated] + public class DeleteSubtitle + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + + [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] + public int Index { get; set; } + } + + [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] + [Authenticated] + public class SearchRemoteSubtitles : IReturn> + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Language { get; set; } + } + + [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")] + [Authenticated] + public class GetSubtitleProviders : IReturn> + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] + [Authenticated] + public class DownloadRemoteSubtitles : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SubtitleId { get; set; } + } + + [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")] + [Authenticated] + public class GetRemoteSubtitles : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")] + public class GetSubtitle + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string MediaSourceId { get; set; } + + [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] + public int Index { get; set; } + + [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Format { get; set; } + + [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public long StartPositionTicks { get; set; } + } + + public class SubtitleService : BaseApiService + { + private readonly ILibraryManager _libraryManager; + private readonly ISubtitleManager _subtitleManager; + private readonly ISubtitleEncoder _subtitleEncoder; + + public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, ISubtitleEncoder subtitleEncoder) + { + _libraryManager = libraryManager; + _subtitleManager = subtitleManager; + _subtitleEncoder = subtitleEncoder; + } + + public object Get(GetSubtitle request) + { + if (string.IsNullOrEmpty(request.Format)) + { + var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); + + var mediaSource = item.GetMediaSources(false) + .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id)); + + var subtitleStream = mediaSource.MediaStreams + .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); + + return ToStaticFileResult(subtitleStream.Path); + } + + var stream = GetSubtitles(request).Result; + + return ResultFactory.GetResult(stream, Common.Net.MimeTypes.GetMimeType("file." + request.Format)); + } + + private async Task GetSubtitles(GetSubtitle request) + { + return await _subtitleEncoder.GetSubtitles(request.Id, + request.MediaSourceId, + request.Index, + request.Format, + request.StartPositionTicks, + CancellationToken.None).ConfigureAwait(false); + } + + public object Get(SearchRemoteSubtitles request) + { + var video = (Video)_libraryManager.GetItemById(request.Id); + + var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result; + + return ToOptimizedResult(response); + } + + public void Delete(DeleteSubtitle request) + { + var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index); + + Task.WaitAll(task); + } + + public object Get(GetSubtitleProviders request) + { + var result = _subtitleManager.GetProviders(request.Id); + + return ToOptimizedResult(result); + } + + public object Get(GetRemoteSubtitles request) + { + var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result; + + return ResultFactory.GetResult(result.Stream, Common.Net.MimeTypes.GetMimeType("file." + result.Format)); + } + + public void Post(DownloadRemoteSubtitles request) + { + var video = (Video)_libraryManager.GetItemById(request.Id); + + Task.Run(async () => + { + try + { + await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) + .ConfigureAwait(false); + + await video.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error downloading subtitles", ex); + } + + }); + } + } +} diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 2f0741434..6f2e83a79 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -1,6 +1,12 @@ -using MediaBrowser.Controller; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.System; using ServiceStack; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Api @@ -18,15 +24,30 @@ namespace MediaBrowser.Api /// Class RestartApplication /// [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] + [Authenticated] public class RestartApplication { } [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] + [Authenticated] public class ShutdownApplication { } + [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] + [Authenticated] + public class GetServerLogs : IReturn> + { + } + + [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] + public class GetLogFile + { + [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Name { get; set; } + } + /// /// Class SystemInfoService /// @@ -36,16 +57,59 @@ namespace MediaBrowser.Api /// The _app host /// private readonly IServerApplicationHost _appHost; - + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// /// The app host. /// jsonSerializer - public SystemService(IServerApplicationHost appHost) + public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) { _appHost = appHost; + _appPaths = appPaths; + _fileSystem = fileSystem; + } + + public object Get(GetServerLogs request) + { + List files; + + try + { + files = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => string.Equals(i.Extension, ".txt", System.StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + catch (DirectoryNotFoundException) + { + files = new List(); + } + + var result = files.Select(i => new LogFile + { + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i), + Name = i.Name, + Size = i.Length + + }).OrderByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ThenBy(i => i.Name) + .ToList(); + + return ToOptimizedResult(result); + } + + public object Get(GetLogFile request) + { + var file = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .First(i => string.Equals(i.Name, request.Name, System.StringComparison.OrdinalIgnoreCase)); + + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); } /// diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index d1767e7fd..55cdc8681 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -717,9 +717,7 @@ namespace MediaBrowser.Api.UserLibrary await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false); - data = _userDataRepository.GetUserData(user.Id, key); - - return _dtoService.GetUserItemDataDto(data); + return _userDataRepository.GetUserDataDto(item, user); } /// @@ -766,9 +764,7 @@ namespace MediaBrowser.Api.UserLibrary await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false); - data = _userDataRepository.GetUserData(user.Id, key); - - return _dtoService.GetUserItemDataDto(data); + return _userDataRepository.GetUserDataDto(item, user); } /// @@ -936,7 +932,7 @@ namespace MediaBrowser.Api.UserLibrary await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false); } - return _dtoService.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey())); + return _userDataRepository.GetUserDataDto(item, user); } } } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 64d1fcb34..cda489c94 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -51,6 +51,7 @@ namespace MediaBrowser.Api /// Class DeleteUser /// [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")] + [Authenticated] public class DeleteUser : IReturnVoid { /// @@ -107,6 +108,7 @@ namespace MediaBrowser.Api /// Class UpdateUserPassword /// [Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")] + [Authenticated] public class UpdateUserPassword : IReturnVoid { /// @@ -138,6 +140,7 @@ namespace MediaBrowser.Api /// Class UpdateUser /// [Route("/Users/{Id}", "POST", Summary = "Updates a user")] + [Authenticated] public class UpdateUser : UserDto, IReturnVoid { } @@ -146,6 +149,7 @@ namespace MediaBrowser.Api /// Class CreateUser /// [Route("/Users", "POST", Summary = "Creates a user")] + [Authenticated] public class CreateUser : UserDto, IReturn { } diff --git a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs deleted file mode 100644 index 46dabb042..000000000 --- a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs +++ /dev/null @@ -1,149 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class ScheduledTasksWebSocketListener - /// - public class LogFileWebSocketListener : BasePeriodicWebSocketListener, LogFileWebSocketState> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "LogFile"; } - } - - /// - /// The _kernel - /// - private readonly ILogManager _logManager; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The log manager. - public LogFileWebSocketListener(ILogger logger, ILogManager logManager, IFileSystem fileSystem) - : base(logger) - { - _logManager = logManager; - _fileSystem = fileSystem; - _logManager.LoggerLoaded += kernel_LoggerLoaded; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// IEnumerable{System.String}. - protected override async Task> GetDataToSend(LogFileWebSocketState state) - { - if (!string.Equals(_logManager.LogFilePath, state.LastLogFilePath)) - { - state.LastLogFilePath = _logManager.LogFilePath; - state.StartLine = 0; - } - - var lines = await GetLogLines(state.LastLogFilePath, state.StartLine, _fileSystem).ConfigureAwait(false); - - state.StartLine += lines.Count; - - return lines; - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) - { - if (dispose) - { - _logManager.LoggerLoaded -= kernel_LoggerLoaded; - } - base.Dispose(dispose); - } - - /// - /// Handles the LoggerLoaded event of the kernel control. - /// - /// The source of the event. - /// The instance containing the event data. - void kernel_LoggerLoaded(object sender, EventArgs e) - { - // Reset the startline for each connection whenever the logger reloads - lock (ActiveConnections) - { - foreach (var connection in ActiveConnections) - { - connection.Item4.StartLine = 0; - } - } - } - - /// - /// Gets the log lines. - /// - /// The log file path. - /// The start line. - /// Task{IEnumerable{System.String}}. - internal static async Task> GetLogLines(string logFilePath, int startLine, IFileSystem fileSystem) - { - var lines = new List(); - - using (var fs = fileSystem.GetFileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) - { - using (var reader = new StreamReader(fs)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - if (line.IndexOf("Info -", StringComparison.OrdinalIgnoreCase) != -1 || - line.IndexOf("Warn -", StringComparison.OrdinalIgnoreCase) != -1 || - line.IndexOf("Error -", StringComparison.OrdinalIgnoreCase) != -1) - { - lines.Add(line); - } - } - } - } - - if (startLine > 0) - { - lines = lines.Skip(startLine).ToList(); - } - - return lines; - } - } - - /// - /// Class LogFileWebSocketState - /// - public class LogFileWebSocketState : WebSocketListenerState - { - /// - /// Gets or sets the last log file path. - /// - /// The last log file path. - public string LastLogFilePath { get; set; } - /// - /// Gets or sets the start line. - /// - /// The start line. - public int StartLine { get; set; } - } -} diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs index 0cc4fc6b4..dcac5e7ba 100644 --- a/MediaBrowser.Common/Net/MimeTypes.cs +++ b/MediaBrowser.Common/Net/MimeTypes.cs @@ -199,6 +199,10 @@ namespace MediaBrowser.Common.Net { return "application/x-javascript"; } + if (ext.Equals(".map", StringComparison.OrdinalIgnoreCase)) + { + return "application/x-javascript"; + } if (ext.Equals(".woff", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index f9d7cc21a..0d0555dc0 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -25,13 +25,6 @@ namespace MediaBrowser.Controller.Dto /// System.String. string GetDtoId(BaseItem item); - /// - /// Gets the user item data dto. - /// - /// The data. - /// UserItemDataDto. - UserItemDataDto GetUserItemDataDto(UserItemData data); - /// /// Attaches the primary image aspect ratio. /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 524d7097b..042834731 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; @@ -1571,5 +1572,19 @@ namespace MediaBrowser.Controller.Entities return path; } + + public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) + { + if (RunTimeTicks.HasValue) + { + double pct = RunTimeTicks.Value; + + if (pct > 0) + { + pct = userData.PlaybackPositionTicks / pct; + dto.PlayedPercentage = 100 * pct; + } + } + } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 584091b13..b886cef19 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MoreLinq; using System; @@ -769,6 +770,11 @@ namespace MediaBrowser.Controller.Entities /// IEnumerable{BaseItem}. /// public virtual IEnumerable GetChildren(User user, bool includeLinkedChildren) + { + return GetChildren(user, includeLinkedChildren, false); + } + + internal IEnumerable GetChildren(User user, bool includeLinkedChildren, bool includeHidden) { if (user == null) { @@ -780,7 +786,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, includeHidden, false); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -796,9 +802,10 @@ namespace MediaBrowser.Controller.Entities /// The user. /// if set to true [include linked children]. /// The list. + /// if set to true [include hidden]. /// if set to true [recursive]. /// true if XXXX, false otherwise - private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool recursive) + private bool AddChildrenToList(User user, bool includeLinkedChildren, List list, bool includeHidden, bool recursive) { var hasLinkedChildren = false; @@ -806,7 +813,7 @@ namespace MediaBrowser.Controller.Entities { if (child.IsVisible(user)) { - if (!child.IsHiddenFromUser(user)) + if (includeHidden || !child.IsHiddenFromUser(user)) { list.Add(child); } @@ -815,7 +822,7 @@ namespace MediaBrowser.Controller.Entities { var folder = (Folder)child; - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true)) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, includeHidden, true)) { hasLinkedChildren = true; } @@ -855,7 +862,7 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, true); return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -1069,5 +1076,68 @@ namespace MediaBrowser.Controller.Entities return GetRecursiveChildren(user).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual) .All(i => i.IsUnplayed(user)); } + + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) + { + var recursiveItemCount = 0; + var unplayed = 0; + + double totalPercentPlayed = 0; + + IEnumerable children; + var folder = this; + + var season = folder as Season; + + if (season != null) + { + children = season.GetEpisodes(user).Where(i => i.LocationType != LocationType.Virtual); + } + else + { + children = folder.GetRecursiveChildren(user) + .Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual); + } + + // Loop through each recursive child + foreach (var child in children) + { + recursiveItemCount++; + + var isUnplayed = true; + + var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey()); + + // Incrememt totalPercentPlayed + if (itemUserData != null) + { + if (itemUserData.Played) + { + totalPercentPlayed += 100; + + isUnplayed = false; + } + else if (itemUserData.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0) + { + double itemPercent = itemUserData.PlaybackPositionTicks; + itemPercent /= child.RunTimeTicks.Value; + totalPercentPlayed += itemPercent; + } + } + + if (isUnplayed) + { + unplayed++; + } + } + + dto.UnplayedItemCount = unplayed; + + if (recursiveItemCount > 0) + { + dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount; + dto.Played = dto.PlayedPercentage.Value >= 100; + } + } } } diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs index 780181a61..d576d90c4 100644 --- a/MediaBrowser.Controller/Entities/IHasUserData.cs +++ b/MediaBrowser.Controller/Entities/IHasUserData.cs @@ -1,4 +1,6 @@ - +using MediaBrowser.Model.Dto; +using System; + namespace MediaBrowser.Controller.Entities { /// @@ -6,10 +8,24 @@ namespace MediaBrowser.Controller.Entities /// public interface IHasUserData { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + Guid Id { get; set; } + /// /// Gets the user data key. /// /// System.String. string GetUserDataKey(); + + /// + /// Fills the user data dto values. + /// + /// The dto. + /// The user data. + /// The user. + void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user); } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index b9630a66f..847183fd0 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return FindParent(); + return Season; } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index e5a8135c2..6404e71ec 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; using System; using System.Collections.Generic; using System.Linq; @@ -51,5 +52,10 @@ namespace MediaBrowser.Controller.Entities LibraryManager.RegisterItem(item); } } + + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user) + { + // Nothing meaninful here and will only waste resources + } } } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 86099fdc0..619a497f5 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList(); return user.RootFolder - .GetChildren(user, true) + .GetChildren(user, true, true) .OfType() .Where(i => !excludeFolderIds.Contains(i.Id) && !IsExcludedFromGrouping(i)); } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 2bec9e3de..9db91e7f2 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using System; using System.Threading; @@ -34,5 +35,13 @@ namespace MediaBrowser.Controller.Library /// The key. /// Task{UserItemData}. UserItemData GetUserData(Guid userId, string key); + + /// + /// Gets the user data dto. + /// + /// The item. + /// The user. + /// UserItemDataDto. + UserItemDataDto GetUserDataDto(IHasUserData item, User user); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index 23610351e..ba1cb3043 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.LiveTv { - public interface ILiveTvRecording : IHasImages, IHasMediaSources + public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData { string ServiceName { get; set; } @@ -20,8 +20,6 @@ namespace MediaBrowser.Controller.LiveTv string GetClientTypeName(); - string GetUserDataKey(); - bool IsParentalAllowed(User user); Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 692d6db90..7f1ddbce9 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -1,6 +1,6 @@ -using System; +using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Providers { @@ -18,6 +18,11 @@ namespace MediaBrowser.Controller.Providers /// [Obsolete] public bool ForceSave { get; set; } + + public MetadataRefreshOptions() + { + MetadataRefreshMode = MetadataRefreshMode.Default; + } } public class ImageRefreshOptions @@ -38,48 +43,54 @@ namespace MediaBrowser.Controller.Providers public bool IsReplacingImage(ImageType type) { - return ReplaceAllImages || ReplaceImages.Contains(type); + return ImageRefreshMode == ImageRefreshMode.FullRefresh && + (ReplaceAllImages || ReplaceImages.Contains(type)); } } public enum MetadataRefreshMode { /// - /// Providers will be executed based on default rules + /// The none /// - EnsureMetadata = 0, + None = 0, /// - /// No providers will be executed + /// The validation only /// - None = 1, + ValidationOnly = 1, /// - /// All providers will be executed to search for new metadata + /// Providers will be executed based on default rules /// - FullRefresh = 2, + Default = 2, /// - /// The validation only + /// All providers will be executed to search for new metadata /// - ValidationOnly = 3 + FullRefresh = 3 } public enum ImageRefreshMode { + /// + /// The none + /// + None = 0, + /// /// The default /// - Default = 0, + Default = 1, /// /// Existing images will be validated /// - ValidationOnly = 1, + ValidationOnly = 2, /// /// All providers will be executed to search for new metadata /// - FullRefresh = 2 + FullRefresh = 3 } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6d3a9d20c..7b2062182 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -219,7 +219,13 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// Task{SessionInfo}. - Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint); + Task AuthenticateNewSession(string username, + string password, + string clientType, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint); /// /// Reports the capabilities. diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 0ed49d5f8..91c1508fc 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -77,6 +77,9 @@ ApiClient\SessionUpdatesEventArgs.cs + + Branding\BrandingOptions.cs + Channels\ChannelFeatures.cs @@ -815,6 +818,9 @@ Session\UserDataChangeInfo.cs + + System\LogFile.cs + System\SystemInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 12c87ca97..782e8524d 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -64,6 +64,9 @@ ApiClient\SessionUpdatesEventArgs.cs + + Branding\BrandingOptions.cs + Channels\ChannelFeatures.cs @@ -796,6 +799,9 @@ Session\UserDataChangeInfo.cs + + System\LogFile.cs + System\SystemInfo.cs diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs new file mode 100644 index 000000000..737cb5c48 --- /dev/null +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Model.Branding +{ + public class BrandingOptions + { + /// + /// Gets or sets the login disclaimer. + /// + /// The login disclaimer. + public string LoginDisclaimer { get; set; } + } +} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 36c353479..49b731341 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -158,9 +158,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableTmdbUpdates { get; set; } public bool EnableFanArtUpdates { get; set; } - public bool RequireMobileManualLogin { get; set; } - public bool RequireNonMobileManualLogin { get; set; } - /// /// Gets or sets the image saving convention. /// diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs index db8b69951..86eb40b97 100644 --- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs @@ -10,12 +10,15 @@ namespace MediaBrowser.Model.Configuration public bool SaveImagePathsInNfo { get; set; } public bool EnablePathSubstitution { get; set; } + public bool EnableExtraThumbsDuplication { get; set; } + public XbmcMetadataOptions() { ReleaseDateFormat = "yyyy-MM-dd"; SaveImagePathsInNfo = true; EnablePathSubstitution = true; + EnableExtraThumbsDuplication = true; } } } diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs index 26b0e9d9e..6ee9f1916 100644 --- a/MediaBrowser.Model/Dto/UserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs @@ -1,6 +1,6 @@ -using System; +using MediaBrowser.Model.Extensions; +using System; using System.ComponentModel; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Dto { @@ -15,6 +15,18 @@ namespace MediaBrowser.Model.Dto /// The rating. public double? Rating { get; set; } + /// + /// Gets or sets the played percentage. + /// + /// The played percentage. + public double? PlayedPercentage { get; set; } + + /// + /// Gets or sets the unplayed item count. + /// + /// The unplayed item count. + public int? UnplayedItemCount { get; set; } + /// /// Gets or sets the playback position ticks. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index d758f2f39..4d4ca8e20 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -65,6 +65,7 @@ + @@ -294,6 +295,7 @@ + diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs index f511d41a9..6e9368f44 100644 --- a/MediaBrowser.Model/Notifications/NotificationRequest.cs +++ b/MediaBrowser.Model/Notifications/NotificationRequest.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Configuration; -using System; +using System; using System.Collections.Generic; namespace MediaBrowser.Model.Notifications diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs index 40723eff8..98df3efe5 100644 --- a/MediaBrowser.Model/Session/SessionInfoDto.cs +++ b/MediaBrowser.Model/Session/SessionInfoDto.cs @@ -1,32 +1,20 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Session { [DebuggerDisplay("Client = {Client}, Username = {UserName}")] public class SessionInfoDto : IHasPropertyChangedEvent { - /// - /// 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 supported commands. /// /// The supported commands. public List SupportedCommands { get; set; } - - /// - /// Gets or sets the remote end point. - /// - /// The remote end point. - public string RemoteEndPoint { get; set; } /// /// Gets or sets the queueable media types. @@ -99,18 +87,6 @@ namespace MediaBrowser.Model.Session /// /// The name of the device. public string DeviceName { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is paused. - /// - /// true if this instance is paused; otherwise, false. - public bool IsPaused { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is muted. - /// - /// true if this instance is muted; otherwise, false. - public bool IsMuted { get; set; } /// /// Gets or sets the now playing item. @@ -118,12 +94,6 @@ namespace MediaBrowser.Model.Session /// The now playing item. public BaseItemInfo NowPlayingItem { get; set; } - /// - /// Gets or sets the now playing position ticks. - /// - /// The now playing position ticks. - public long? NowPlayingPositionTicks { get; set; } - /// /// Gets or sets the device id. /// diff --git a/MediaBrowser.Model/System/LogFile.cs b/MediaBrowser.Model/System/LogFile.cs new file mode 100644 index 000000000..ba409c542 --- /dev/null +++ b/MediaBrowser.Model/System/LogFile.cs @@ -0,0 +1,31 @@ +using System; + +namespace MediaBrowser.Model.System +{ + public class LogFile + { + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + + /// + /// Gets or sets the date modified. + /// + /// The date modified. + public DateTime DateModified { get; set; } + + /// + /// Gets or sets the size. + /// + /// The size. + public long Size { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + } +} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 12af93dbd..1b2e9fa6d 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -441,11 +442,16 @@ namespace MediaBrowser.Providers.Manager var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex); - return new[] - { - Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension), - Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension) - }; + var list = new List + { + Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension) + }; + + if (EnableExtraThumbsDuplication) + { + list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); + } + return list.ToArray(); } if (type == ImageType.Primary) @@ -528,6 +534,16 @@ namespace MediaBrowser.Providers.Manager return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) }; } + private bool EnableExtraThumbsDuplication + { + get + { + var config = _config.GetConfiguration("xbmcmetadata"); + + return config.EnableExtraThumbsDuplication; + } + } + /// /// Gets the save path for item in mixed folder. /// diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 92b4616e7..57a40741f 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -234,7 +234,7 @@ namespace MediaBrowser.Providers.MediaInfo await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || - options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata) + options.MetadataRefreshMode == MetadataRefreshMode.Default) { try { @@ -460,7 +460,7 @@ namespace MediaBrowser.Providers.MediaInfo var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, options.DirectoryService, false).ToList(); - var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.EnsureMetadata || + var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && diff --git a/MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs new file mode 100644 index 000000000..d6cd3424b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Branding/BrandingConfigurationFactory.cs @@ -0,0 +1,21 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Branding; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Branding +{ + public class BrandingConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new[] + { + new ConfigurationStore + { + ConfigurationType = typeof(BrandingOptions), + Key = "branding" + } + }; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 73216ca33..62ff9f687 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -212,6 +212,12 @@ namespace MediaBrowser.Server.Implementations.Dto { if (item.IsFolder) { + var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()); + + // Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice + // TODO: Improve in future + dto.UserData = GetUserItemDataDto(userData); + var folder = (Folder)item; dto.ChildCount = GetChildCount(folder, user); @@ -220,15 +226,15 @@ namespace MediaBrowser.Server.Implementations.Dto { SetSpecialCounts(folder, user, dto, fields); } - } - var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()); - - dto.UserData = GetUserItemDataDto(userData); + dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100; + dto.UserData.PlayedPercentage = dto.PlayedPercentage; + dto.UserData.UnplayedItemCount = dto.RecursiveUnplayedItemCount; + } - if (item.IsFolder) + else { - dto.UserData.Played = dto.PlayedPercentage.HasValue && dto.PlayedPercentage.Value >= 100; + dto.UserData = _userDataRepository.GetUserDataDto(item, user); } dto.PlayAccess = item.GetPlayAccess(user); @@ -1110,16 +1116,17 @@ namespace MediaBrowser.Server.Implementations.Dto if (episode != null) { - series = item.FindParent(); + series = episode.Series; - dto.SeriesId = GetDtoId(series); - dto.SeriesName = series.Name; - dto.AirTime = series.AirTime; - dto.SeriesStudio = series.Studios.FirstOrDefault(); - - dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb); - - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + if (series != null) + { + dto.SeriesId = GetDtoId(series); + dto.SeriesName = series.Name; + dto.AirTime = series.AirTime; + dto.SeriesStudio = series.Studios.FirstOrDefault(); + dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb); + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + } } // Add SeasonInfo @@ -1127,14 +1134,17 @@ namespace MediaBrowser.Server.Implementations.Dto if (season != null) { - series = item.FindParent(); + series = season.Series; - dto.SeriesId = GetDtoId(series); - dto.SeriesName = series.Name; - dto.AirTime = series.AirTime; - dto.SeriesStudio = series.Studios.FirstOrDefault(); + if (series != null) + { + dto.SeriesId = GetDtoId(series); + dto.SeriesName = series.Name; + dto.AirTime = series.AirTime; + dto.SeriesStudio = series.Studios.FirstOrDefault(); - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + } } var game = item as Game; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index d7186aa21..c31f46215 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,10 +1,11 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; +using MoreLinq; using System; using System.Collections.Generic; using System.Linq; @@ -17,21 +18,21 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { private readonly ISessionManager _sessionManager; private readonly ILogger _logger; - private readonly IDtoService _dtoService; private readonly IUserDataManager _userDataManager; + private readonly IUserManager _userManager; private readonly object _syncLock = new object(); private Timer UpdateTimer { get; set; } private const int UpdateDuration = 500; - private readonly Dictionary> _changedKeys = new Dictionary>(); + private readonly Dictionary> _changedItems = new Dictionary>(); - public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IDtoService dtoService, ILogger logger) + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager) { _userDataManager = userDataManager; _sessionManager = sessionManager; - _dtoService = dtoService; _logger = logger; + _userManager = userManager; } public void Run() @@ -58,15 +59,28 @@ namespace MediaBrowser.Server.Implementations.EntryPoints UpdateTimer.Change(UpdateDuration, Timeout.Infinite); } - List keys; + List keys; - if (!_changedKeys.TryGetValue(e.UserId, out keys)) + if (!_changedItems.TryGetValue(e.UserId, out keys)) { - keys = new List(); - _changedKeys[e.UserId] = keys; + keys = new List(); + _changedItems[e.UserId] = keys; } - keys.Add(e.Key); + keys.Add(e.Item); + + var baseItem = e.Item as BaseItem; + + // Go up one level for indicators + if (baseItem != null) + { + var parent = baseItem.Parent; + + if (parent != null) + { + keys.Add(parent); + } + } } } @@ -75,8 +89,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints lock (_syncLock) { // Remove dupes in case some were saved multiple times - var changes = _changedKeys.ToList(); - _changedKeys.Clear(); + var changes = _changedItems.ToList(); + _changedItems.Clear(); SendNotifications(changes, CancellationToken.None); @@ -88,7 +102,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints } } - private async Task SendNotifications(IEnumerable>> changes, CancellationToken cancellationToken) + private async Task SendNotifications(IEnumerable>> changes, CancellationToken cancellationToken) { foreach (var pair in changes) { @@ -99,8 +113,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints if (userSessions.Count > 0) { + var user = _userManager.GetUserById(userId); + var dtoList = pair.Value - .Select(i => _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(userId, i))) + .DistinctBy(i => i.Id) + .Select(i => _userDataManager.GetUserDataDto(i, user)) .ToList(); var info = new UserDataChangeInfo diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 833dfc5e4..cfcbb077e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -363,19 +363,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer { try { - var errorResponse = new ErrorResponse - { - ResponseStatus = new ResponseStatus - { - ErrorCode = ex.GetType().GetOperationName(), - Message = ex.Message, - StackTrace = ex.StackTrace, - } - }; - var operationName = context.Request.GetOperationName(); var httpReq = GetRequest(context, operationName); var httpRes = httpReq.Response; + + if (httpRes.IsClosed) + { + return; + } + var contentType = httpReq.ResponseContentType; var serializer = HostContext.ContentTypes.GetResponseSerializer(contentType); @@ -398,6 +394,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer httpRes.ContentType = contentType; + var errorResponse = new ErrorResponse + { + ResponseStatus = new ResponseStatus + { + ErrorCode = ex.GetType().GetOperationName(), + Message = ex.Message, + StackTrace = ex.StackTrace, + } + }; + serializer(httpReq, errorResponse, httpRes); httpRes.Close(); diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 77343ab4e..94be37e95 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -36,6 +36,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security auth.TryGetValue("Version", out version); } + var token = httpReq.Headers["X-MediaBrowser-Token"]; + + if (string.IsNullOrWhiteSpace(token)) + { + token = httpReq.QueryString["api_key"]; + } + return new AuthorizationInfo { Client = client, @@ -43,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security DeviceId = deviceId, UserId = userId, Version = version, - Token = httpReq.Headers["X-AUTH-TOKEN"] + Token = token }; } diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs index 79f126511..d3030f31f 100644 --- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -125,5 +126,41 @@ namespace MediaBrowser.Server.Implementations.Library { return userId + key; } + + public UserItemDataDto GetUserDataDto(IHasUserData item, User user) + { + var userData = GetUserData(user.Id, item.GetUserDataKey()); + var dto = GetUserItemDataDto(userData); + + item.FillUserDataDtoValues(dto, userData, user); + + return dto; + } + + /// + /// Converts a UserItemData to a DTOUserItemData + /// + /// The data. + /// DtoUserItemData. + /// + private UserItemDataDto GetUserItemDataDto(UserItemData data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + Key = data.Key + }; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 412b2e7bd..9c69e656d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; @@ -23,15 +22,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; - private readonly IItemRepository _itemRepo; - public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger, IItemRepository itemRepo) + public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger) { _dtoService = dtoService; _userDataManager = userDataManager; _imageProcessor = imageProcessor; _logger = logger; - _itemRepo = itemRepo; } public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel) @@ -249,7 +246,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, recording.GetUserDataKey())); + dto.UserData = _userDataManager.GetUserDataDto(recording, user); dto.PlayAccess = recording.GetPlayAccess(user); } @@ -322,7 +319,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey())); + dto.UserData = _userDataManager.GetUserDataDto(info, user); dto.PlayAccess = info.GetPlayAccess(user); } @@ -401,7 +398,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (user != null) { - dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, item.GetUserDataKey())); + dto.UserData = _userDataManager.GetUserDataDto(item, user); dto.PlayAccess = item.GetPlayAccess(user); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index ad1ddba88..09793f4fc 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -40,7 +40,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; - private readonly IJsonSerializer _json; private readonly IDtoService _dtoService; private readonly ILocalizationManager _localization; @@ -58,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1); - public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, IJsonSerializer json, ILocalizationManager localization) + public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization) { _config = config; _fileSystem = fileSystem; @@ -67,12 +66,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv _userManager = userManager; _libraryManager = libraryManager; _taskManager = taskManager; - _json = json; _localization = localization; _dtoService = dtoService; _userDataManager = userDataManager; - _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo); + _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger); } /// diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 3bd0df0eb..d6d4bbc51 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -258,11 +258,11 @@ "LabelCachePath": "Cache path:", "LabelCachePathHelp": "This folder contains server cache files, such as images.", "LabelImagesByNamePath": "Images by name path:", - "LabelImagesByNamePathHelp": "This folder contains actor, artist, genre and studio images.", + "LabelImagesByNamePathHelp": "This folder contains downloaded actor, artist, genre and studio images.", "LabelMetadataPath": "Metadata path:", "LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.", "LabelTranscodingTempPath": "Transcoding temporary path:", - "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder.", + "LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder. Specify a custom path, or leave empty to use the default within the server's data folder.", "TabBasics": "Basics", "TabTV": "TV", "TabGames": "Games", @@ -284,7 +284,7 @@ "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", "LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.", - "OptionImageSavingCompatible": "Compatible - Media Browser/Plex/Xbmc", + "OptionImageSavingCompatible": "Compatible - Media Browser/Xbmc/Plex", "OptionImageSavingStandard": "Standard - MB2", "ButtonSignIn": "Sign In", "TitleSignIn": "Sign In", @@ -849,5 +849,14 @@ "LabelXbmcMetadataEnablePathSubstitutionHelp2": "See path substitution.", "LabelGroupChannelsIntoViews": "Display the following channels directly within my views:", "LabelGroupChannelsIntoViewsHelp": "If enabled, these channels will be displayed directly alongside other views. If disabled, they'll be displayed within a separate Channels view.", - "LabelDisplayCollectionsView": "Display a Collections view to show movie collections" + "LabelDisplayCollectionsView": "Display a Collections view to show movie collections", + "LabelXbmcMetadataEnableExtraThumbs": "Copy extrafanart into extrathumbs", + "LabelXbmcMetadataEnableExtraThumbsHelp": "When downloading images they can be saved into both extrafanart and extrathumbs for maximum Xbmc skin compatibility.", + "TabServices": "Services", + "TabLogs": "Logs", + "HeaderServerLogFiles": "Server log files:", + "TabBranding": "Branding", + "HeaderBrandingHelp": "Customize the appearance of Media Browser to fit the needs of your group or organization.", + "LabelLoginDisclaimer": "Login disclaimer:", + "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a909929ae..1d201e069 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -101,6 +101,7 @@ Properties\SharedVersion.cs + diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs index 964e2cd24..b832f3a06 100644 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Notifications; using System; @@ -93,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.Notifications if (options != null && !string.IsNullOrWhiteSpace(request.NotificationType)) { - return _userManager.Users.Where(i => _config.Configuration.NotificationOptions.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration)) + var config = GetConfiguration(); + + return _userManager.Users.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration)) .Select(i => i.Id.ToString("N")); } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 2f6790a3e..2d85a3aa7 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Common.Events; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -1185,6 +1187,24 @@ namespace MediaBrowser.Server.Implementations.Session }; } + private bool IsLocal(string remoteEndpoint) + { + if (string.IsNullOrWhiteSpace(remoteEndpoint)) + { + throw new ArgumentNullException("remoteEndpoint"); + } + + // Private address space: + // http://en.wikipedia.org/wiki/Private_network + + return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + } + /// /// Reports the capabilities. /// @@ -1283,15 +1303,10 @@ namespace MediaBrowser.Server.Implementations.Session DeviceName = session.DeviceName, Id = session.Id, LastActivityDate = session.LastActivityDate, - NowPlayingPositionTicks = session.PlayState.PositionTicks, - IsPaused = session.PlayState.IsPaused, - IsMuted = session.PlayState.IsMuted, NowViewingItem = session.NowViewingItem, ApplicationVersion = session.ApplicationVersion, - CanSeek = session.PlayState.CanSeek, QueueableMediaTypes = session.QueueableMediaTypes, PlayableMediaTypes = session.PlayableMediaTypes, - RemoteEndPoint = session.RemoteEndPoint, AdditionalUsers = session.AdditionalUsers, SupportedCommands = session.SupportedCommands, UserName = session.UserName, diff --git a/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs index 46c3df07b..7e6a252cd 100644 --- a/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/AirTimeComparer.cs @@ -26,13 +26,36 @@ namespace MediaBrowser.Server.Implementations.Sorting /// System.String. private DateTime GetValue(BaseItem x) { - var series = (x as Series) ?? x.FindParent(); + var series = x as Series; - DateTime result; - if (series != null && DateTime.TryParse(series.AirTime, out result)) + if (series == null) { - return result; - } + var season = x as Season; + + if (season != null) + { + series = season.Series; + } + else + { + var episode = x as Episode; + + if (episode != null) + { + series = episode.Series; + } + } + } + + if (series != null) + { + DateTime result; + if (DateTime.TryParse(series.AirTime, out result)) + { + return result; + } + } + return DateTime.MinValue; } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 673a9f151..91e92e21c 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -654,7 +654,7 @@ namespace MediaBrowser.ServerApplication var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); RegisterSingleInstance(collectionManager); - LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, JsonSerializer, LocalizationManager); + LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager); RegisterSingleInstance(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager); diff --git a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs index 4ba98e9b6..c2c64ea4d 100644 --- a/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs +++ b/MediaBrowser.ServerApplication/Native/BrowserLauncher.cs @@ -86,18 +86,6 @@ namespace MediaBrowser.ServerApplication.Native appHost.WebApplicationName + "/swagger-ui/index.html", logger); } - /// - /// Opens the standard API documentation. - /// - /// The configuration manager. - /// The app host. - /// The logger. - public static void OpenStandardApiDocumentation(IServerConfigurationManager configurationManager, IServerApplicationHost appHost, ILogger logger) - { - OpenUrl("http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" + - appHost.WebApplicationName + "/metadata", logger); - } - /// /// Opens the URL. /// diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index f5f9434e7..47a4be8e3 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -29,7 +29,6 @@ namespace MediaBrowser.ServerApplication private System.Windows.Forms.ToolStripMenuItem cmdLogWindow; private System.Windows.Forms.ToolStripMenuItem cmdCommunity; private System.Windows.Forms.ToolStripMenuItem cmdApiDocs; - private System.Windows.Forms.ToolStripMenuItem cmdStandardDocs; private System.Windows.Forms.ToolStripMenuItem cmdSwagger; private System.Windows.Forms.ToolStripMenuItem cmdGtihub; @@ -90,7 +89,6 @@ namespace MediaBrowser.ServerApplication cmdConfigure = new System.Windows.Forms.ToolStripMenuItem(); cmdBrowse = new System.Windows.Forms.ToolStripMenuItem(); cmdApiDocs = new System.Windows.Forms.ToolStripMenuItem(); - cmdStandardDocs = new System.Windows.Forms.ToolStripMenuItem(); cmdSwagger = new System.Windows.Forms.ToolStripMenuItem(); cmdGtihub = new System.Windows.Forms.ToolStripMenuItem(); @@ -169,17 +167,11 @@ namespace MediaBrowser.ServerApplication // cmdApiDocs // cmdApiDocs.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - cmdStandardDocs, cmdSwagger, cmdGtihub}); cmdApiDocs.Name = "cmdApiDocs"; cmdApiDocs.Size = new System.Drawing.Size(208, 22); // - // cmdStandardDocs - // - cmdStandardDocs.Name = "cmdStandardDocs"; - cmdStandardDocs.Size = new System.Drawing.Size(136, 22); - // // cmdSwagger // cmdSwagger.Name = "cmdSwagger"; @@ -199,7 +191,6 @@ namespace MediaBrowser.ServerApplication cmdLibraryExplorer.Click += cmdLibraryExplorer_Click; cmdSwagger.Click += cmdSwagger_Click; - cmdStandardDocs.Click += cmdStandardDocs_Click; cmdGtihub.Click += cmdGtihub_Click; LoadLogWindow(null, EventArgs.Empty); @@ -224,7 +215,6 @@ namespace MediaBrowser.ServerApplication cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity"); cmdGtihub.Text = _localization.GetLocalizedString("LabelGithubWiki"); cmdSwagger.Text = _localization.GetLocalizedString("LabelSwagger"); - cmdStandardDocs.Text = _localization.GetLocalizedString("LabelStandard"); cmdApiDocs.Text = _localization.GetLocalizedString("LabelViewApiDocumentation"); cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary"); cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureMediaBrowser"); @@ -346,11 +336,6 @@ namespace MediaBrowser.ServerApplication BrowserLauncher.OpenGithub(_logger); } - void cmdStandardDocs_Click(object sender, EventArgs e) - { - BrowserLauncher.OpenStandardApiDocumentation(_configurationManager, _appHost, _logger); - } - void cmdSwagger_Click(object sender, EventArgs e) { BrowserLauncher.OpenSwagger(_configurationManager, _appHost, _logger); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 3ccf26c37..36adae71c 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -521,6 +521,7 @@ namespace MediaBrowser.WebDashboard.Api "mediacontroller.js", "chromecast.js", "backdrops.js", + "branding.js", "mediaplayer.js", "mediaplayer-video.js", @@ -529,7 +530,6 @@ namespace MediaBrowser.WebDashboard.Api "ratingdialog.js", "aboutpage.js", - "allusersettings.js", "alphapicker.js", "addpluginpage.js", "advancedconfigurationpage.js", @@ -537,7 +537,6 @@ namespace MediaBrowser.WebDashboard.Api "advancedserversettings.js", "metadataadvanced.js", "appsplayback.js", - "appsweather.js", "autoorganizetv.js", "autoorganizelog.js", "channels.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 8f85fcb51..0099daaf1 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -98,6 +98,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -392,9 +395,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -617,6 +617,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1499,9 +1502,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -1748,11 +1748,6 @@ PreserveNewest - - - PreserveNewest - - PreserveNewest @@ -1814,11 +1809,6 @@ PreserveNewest - - - PreserveNewest - - PreserveNewest -- cgit v1.2.3 From ed5bf546c1c1498601ea41bd24c12f9cbf7c84e7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 5 Jul 2014 01:21:13 -0400 Subject: fixes #689 - Support grouping latest items --- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Api/Playback/BaseStreamingService.cs | 9 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 4 +- MediaBrowser.Api/UserLibrary/PlaystateService.cs | 388 ++++++++++++++++ MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 500 ++++++--------------- MediaBrowser.Controller/Entities/Audio/Audio.cs | 22 +- MediaBrowser.Controller/Entities/BaseItem.cs | 6 + MediaBrowser.Controller/Entities/TV/Episode.cs | 8 + MediaBrowser.Controller/Providers/NameParser.cs | 4 +- .../Dto/DtoService.cs | 1 - .../Localization/JavaScript/javascript.json | 3 +- .../Session/SessionManager.cs | 14 + .../Providers/MovieDbProviderTests.cs | 11 + MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs | 3 +- 14 files changed, 584 insertions(+), 390 deletions(-) create mode 100644 MediaBrowser.Api/UserLibrary/PlaystateService.cs (limited to 'MediaBrowser.Api/UserLibrary/UserLibraryService.cs') diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index ca2887d19..6a1e45e25 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -134,6 +134,7 @@ + diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 75e13f92c..d8e3ee75d 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -467,11 +467,13 @@ namespace MediaBrowser.Api.Playback /// /// The state. /// The output video codec. + /// if set to true [allow time stamp copy]. /// The cancellation token. /// System.String. protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + bool allowTimeStampCopy = true) { // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ @@ -564,7 +566,10 @@ namespace MediaBrowser.Api.Playback filters.Add(subParam); - output += " -copyts"; + if (allowTimeStampCopy) + { + output += " -copyts"; + } } if (filters.Count > 0) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index ffe71f4ea..6c09f00a1 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -489,7 +489,7 @@ namespace MediaBrowser.Api.Playback.Hls // Add resolution params, if specified if (!hasGraphicalSubs) { - args += GetOutputSizeParam(state, codec, CancellationToken.None); + args += GetOutputSizeParam(state, codec, CancellationToken.None, false); } // This is for internal graphical subs @@ -517,7 +517,7 @@ namespace MediaBrowser.Api.Playback.Hls // If isEncoding is true we're actually starting ffmpeg var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0"; - var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", + var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", inputModifier, GetInputArgument(state), threads, diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs new file mode 100644 index 000000000..ccebb912b --- /dev/null +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -0,0 +1,388 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Session; +using ServiceStack; +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.UserLibrary +{ + /// + /// Class MarkPlayedItem + /// + [Route("/Users/{UserId}/PlayedItems/{Id}", "POST")] + [Api(Description = "Marks an item as played")] + public class MarkPlayedItem : IReturn + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + + [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string DatePlayed { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + } + + /// + /// Class MarkUnplayedItem + /// + [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE")] + [Api(Description = "Marks an item as unplayed")] + public class MarkUnplayedItem : IReturn + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public Guid UserId { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + } + + [Route("/Sessions/Playing", "POST")] + [Api(Description = "Reports playback has started within a session")] + public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid + { + } + + [Route("/Sessions/Playing/Progress", "POST")] + [Api(Description = "Reports playback progress within a session")] + public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid + { + } + + [Route("/Sessions/Playing/Stopped", "POST")] + [Api(Description = "Reports playback has stopped within a session")] + public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid + { + } + + /// + /// Class OnPlaybackStart + /// + [Route("/Users/{UserId}/PlayingItems/{Id}", "POST")] + [Api(Description = "Reports that a user has begun playing an item")] + public class OnPlaybackStart : IReturnVoid + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MediaSourceId { 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; } + + [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? AudioStreamIndex { get; set; } + + [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? SubtitleStreamIndex { get; set; } + } + + /// + /// Class OnPlaybackProgress + /// + [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST")] + [Api(Description = "Reports a user's playback progress")] + public class OnPlaybackProgress : IReturnVoid + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MediaSourceId { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public long? PositionTicks { get; set; } + + [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public bool IsPaused { get; set; } + + [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] + public bool IsMuted { get; set; } + + [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? AudioStreamIndex { get; set; } + + [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? SubtitleStreamIndex { get; set; } + + [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? VolumeLevel { get; set; } + } + + /// + /// Class OnPlaybackStopped + /// + [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE")] + [Api(Description = "Reports that a user has stopped playing an item")] + public class OnPlaybackStopped : IReturnVoid + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public Guid UserId { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string MediaSourceId { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")] + public long? PositionTicks { get; set; } + } + + [Authenticated] + public class PlaystateService : BaseApiService + { + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataRepository; + private readonly ILibraryManager _libraryManager; + private readonly ISessionManager _sessionManager; + + public PlaystateService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, ISessionManager sessionManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _sessionManager = sessionManager; + } + + /// + /// Posts the specified request. + /// + /// The request. + public object Post(MarkPlayedItem request) + { + var result = MarkPlayed(request).Result; + + return ToOptimizedResult(result); + } + + private async Task MarkPlayed(MarkPlayedItem request) + { + var user = _userManager.GetUserById(request.UserId); + + DateTime? datePlayed = null; + + if (!string.IsNullOrEmpty(request.DatePlayed)) + { + datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); + } + + var session = GetSession(); + + var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); + + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); + + await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false); + } + + return dto; + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(OnPlaybackStart request) + { + var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty); + + Post(new ReportPlaybackStart + { + CanSeek = request.CanSeek, + ItemId = request.Id, + QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(), + MediaSourceId = request.MediaSourceId, + AudioStreamIndex = request.AudioStreamIndex, + SubtitleStreamIndex = request.SubtitleStreamIndex + }); + } + + public void Post(ReportPlaybackStart request) + { + request.SessionId = GetSession().Id; + + var task = _sessionManager.OnPlaybackStart(request); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(OnPlaybackProgress request) + { + Post(new ReportPlaybackProgress + { + ItemId = request.Id, + PositionTicks = request.PositionTicks, + IsMuted = request.IsMuted, + IsPaused = request.IsPaused, + MediaSourceId = request.MediaSourceId, + AudioStreamIndex = request.AudioStreamIndex, + SubtitleStreamIndex = request.SubtitleStreamIndex, + VolumeLevel = request.VolumeLevel + }); + } + + public void Post(ReportPlaybackProgress request) + { + request.SessionId = GetSession().Id; + + var task = _sessionManager.OnPlaybackProgress(request); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Delete(OnPlaybackStopped request) + { + Post(new ReportPlaybackStopped + { + ItemId = request.Id, + PositionTicks = request.PositionTicks, + MediaSourceId = request.MediaSourceId + }); + } + + public void Post(ReportPlaybackStopped request) + { + request.SessionId = GetSession().Id; + + var task = _sessionManager.OnPlaybackStopped(request); + + Task.WaitAll(task); + } + + /// + /// Deletes the specified request. + /// + /// The request. + public object Delete(MarkUnplayedItem request) + { + var task = MarkUnplayed(request); + + return ToOptimizedResult(task.Result); + } + + private async Task MarkUnplayed(MarkUnplayedItem request) + { + var user = _userManager.GetUserById(request.UserId); + + var session = GetSession(); + + var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); + + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); + + await UpdatePlayedStatus(additionalUser, request.Id, false, null).ConfigureAwait(false); + } + + return dto; + } + + /// + /// Updates the played status. + /// + /// The user. + /// The item id. + /// if set to true [was played]. + /// The date played. + /// Task. + private async Task UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed) + { + var item = _libraryManager.GetItemById(itemId); + + if (wasPlayed) + { + await item.MarkPlayed(user, datePlayed, _userDataRepository).ConfigureAwait(false); + } + else + { + await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false); + } + + return _userDataRepository.GetUserDataDto(item, user); + } + } +} diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 55cdc8681..de2801dcc 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -4,16 +4,13 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Session; using ServiceStack; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -189,261 +186,97 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class MarkPlayedItem - /// - [Route("/Users/{UserId}/PlayedItems/{Id}", "POST")] - [Api(Description = "Marks an item as played")] - public class MarkPlayedItem : IReturn - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string DatePlayed { get; set; } - - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// - /// Class MarkUnplayedItem - /// - [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE")] - [Api(Description = "Marks an item as unplayed")] - public class MarkUnplayedItem : IReturn - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Sessions/Playing", "POST")] - [Api(Description = "Reports playback has started within a session")] - public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid - { - } - - [Route("/Sessions/Playing/Progress", "POST")] - [Api(Description = "Reports playback progress within a session")] - public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid - { - } - - [Route("/Sessions/Playing/Stopped", "POST")] - [Api(Description = "Reports playback has stopped within a session")] - public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid - { - } - - /// - /// Class OnPlaybackStart + /// Class GetLocalTrailers /// - [Route("/Users/{UserId}/PlayingItems/{Id}", "POST")] - [Api(Description = "Reports that a user has begun playing an item")] - public class OnPlaybackStart : IReturnVoid + [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET")] + [Api(Description = "Gets local trailers for an item")] + public class GetLocalTrailers : IReturn> { /// /// Gets or sets the user id. /// /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaSourceId { 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; } - - [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } } /// - /// Class OnPlaybackProgress + /// Class GetSpecialFeatures /// - [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST")] - [Api(Description = "Reports a user's playback progress")] - public class OnPlaybackProgress : IReturnVoid + [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET")] + [Api(Description = "Gets special features for an item")] + public class GetSpecialFeatures : IReturn> { /// /// Gets or sets the user id. /// /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Id", Description = "Movie Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaSourceId { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public long? PositionTicks { get; set; } - - [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool IsPaused { get; set; } - - [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool IsMuted { get; set; } - - [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } - - [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? VolumeLevel { get; set; } } - /// - /// Class OnPlaybackStopped - /// - [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE")] - [Api(Description = "Reports that a user has stopped playing an item")] - public class OnPlaybackStopped : IReturnVoid + [Route("/Users/{UserId}/Items/Latest", "GET", Summary = "Gets latest media")] + public class GetLatestMedia : IReturn>, IHasItemFields { /// /// Gets or sets the user id. /// /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + [ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int Limit { get; set; } - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string MediaSourceId { get; set; } + [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ParentId { get; set; } - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")] - public long? PositionTicks { get; set; } - } + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } - /// - /// Class GetLocalTrailers - /// - [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET")] - [Api(Description = "Gets local trailers for an item")] - public class GetLocalTrailers : IReturn> - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } + [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string IncludeItemTypes { get; set; } - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } + [ApiMember(Name = "IsFolder", Description = "Filter by items that are folders, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsFolder { get; set; } - /// - /// Class GetSpecialFeatures - /// - [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET")] - [Api(Description = "Gets special features for an item")] - public class GetSpecialFeatures : IReturn> - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } + [ApiMember(Name = "IsPlayed", Description = "Filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsPlayed { get; set; } - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Movie Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + [ApiMember(Name = "GroupItems", Description = "Whether or not to group items into a parent container.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool GroupItems { get; set; } + + public GetLatestMedia() + { + Limit = 20; + GroupItems = true; + } } - /// /// Class UserLibraryService /// [Authenticated] public class UserLibraryService : BaseApiService { - /// - /// The _user manager - /// private readonly IUserManager _userManager; - /// - /// The _user data repository - /// private readonly IUserDataManager _userDataRepository; - /// - /// The _library manager - /// private readonly ILibraryManager _libraryManager; - - private readonly ISessionManager _sessionManager; private readonly IDtoService _dtoService; - private readonly IUserViewManager _userViewManager; /// @@ -452,15 +285,14 @@ namespace MediaBrowser.Api.UserLibrary /// The user manager. /// The library manager. /// The user data repository. - /// The session manager. /// The dto service. + /// The user view manager. /// jsonSerializer - public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, ISessionManager sessionManager, IDtoService dtoService, IUserViewManager userViewManager) + public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager) { _userManager = userManager; _libraryManager = libraryManager; _userDataRepository = userDataRepository; - _sessionManager = sessionManager; _dtoService = dtoService; _userViewManager = userViewManager; } @@ -477,6 +309,96 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedSerializedResultUsingCache(result); } + public object Get(GetLatestMedia request) + { + var user = _userManager.GetUserById(request.UserId); + + // Avoid implicitly captured closure + var libraryItems = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId) + .OrderByDescending(i => i.DateCreated) + .Where(i => i.LocationType != LocationType.Virtual); + + if (request.IsFolder.HasValue) + { + var val = request.IsFolder.Value; + libraryItems = libraryItems.Where(f => f.IsFolder == val); + } + + if (!string.IsNullOrEmpty(request.IncludeItemTypes)) + { + var vals = request.IncludeItemTypes.Split(','); + libraryItems = libraryItems.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)); + } + + var currentUser = user; + + if (request.IsPlayed.HasValue) + { + var takeLimit = request.Limit * 20; + + var val = request.IsPlayed.Value; + libraryItems = libraryItems.Where(f => f.IsPlayed(currentUser) == val) + .Take(takeLimit); + } + + // Avoid implicitly captured closure + var items = libraryItems + .ToList(); + + var list = new List>>(); + + foreach (var item in items) + { + // Only grab the index container for media + var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer; + + if (container == null) + { + list.Add(new Tuple>(null, new List { item })); + } + else + { + var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id == container.Id); + + if (current != null) + { + current.Item2.Add(item); + } + else + { + list.Add(new Tuple>(container, new List { item })); + } + } + + if (list.Count >= request.Limit) + { + break; + } + } + + var fields = request.GetItemFields().ToList(); + + var dtos = list.Select(i => + { + var item = i.Item2[0]; + var childCount = 0; + + if (i.Item1 != null && i.Item2.Count > 0) + { + item = i.Item1; + childCount = i.Item2.Count; + } + + var dto = _dtoService.GetBaseItemDto(item, fields, user); + + dto.ChildCount = childCount; + + return dto; + }); + + return ToOptimizedResult(dtos.ToList()); + } + public object Get(GetUserViews request) { var user = _userManager.GetUserById(new Guid(request.UserId)); @@ -766,173 +688,5 @@ namespace MediaBrowser.Api.UserLibrary return _userDataRepository.GetUserDataDto(item, user); } - - /// - /// Posts the specified request. - /// - /// The request. - public object Post(MarkPlayedItem request) - { - var result = MarkPlayed(request).Result; - - return ToOptimizedResult(result); - } - - private async Task MarkPlayed(MarkPlayedItem request) - { - var user = _userManager.GetUserById(request.UserId); - - DateTime? datePlayed = null; - - if (!string.IsNullOrEmpty(request.DatePlayed)) - { - datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); - } - - var session = GetSession(); - - var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); - - foreach (var additionalUserInfo in session.AdditionalUsers) - { - var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); - - await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false); - } - - return dto; - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(OnPlaybackStart request) - { - var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty); - - Post(new ReportPlaybackStart - { - CanSeek = request.CanSeek, - ItemId = request.Id, - QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(), - MediaSourceId = request.MediaSourceId, - AudioStreamIndex = request.AudioStreamIndex, - SubtitleStreamIndex = request.SubtitleStreamIndex - }); - } - - public void Post(ReportPlaybackStart request) - { - request.SessionId = GetSession().Id; - - var task = _sessionManager.OnPlaybackStart(request); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(OnPlaybackProgress request) - { - Post(new ReportPlaybackProgress - { - ItemId = request.Id, - PositionTicks = request.PositionTicks, - IsMuted = request.IsMuted, - IsPaused = request.IsPaused, - MediaSourceId = request.MediaSourceId, - AudioStreamIndex = request.AudioStreamIndex, - SubtitleStreamIndex = request.SubtitleStreamIndex, - VolumeLevel = request.VolumeLevel - }); - } - - public void Post(ReportPlaybackProgress request) - { - request.SessionId = GetSession().Id; - - var task = _sessionManager.OnPlaybackProgress(request); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Delete(OnPlaybackStopped request) - { - Post(new ReportPlaybackStopped - { - ItemId = request.Id, - PositionTicks = request.PositionTicks, - MediaSourceId = request.MediaSourceId - }); - } - - public void Post(ReportPlaybackStopped request) - { - request.SessionId = GetSession().Id; - - var task = _sessionManager.OnPlaybackStopped(request); - - Task.WaitAll(task); - } - - /// - /// Deletes the specified request. - /// - /// The request. - public object Delete(MarkUnplayedItem request) - { - var task = MarkUnplayed(request); - - return ToOptimizedResult(task.Result); - } - - private async Task MarkUnplayed(MarkUnplayedItem request) - { - var user = _userManager.GetUserById(request.UserId); - - var session = GetSession(); - - var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); - - foreach (var additionalUserInfo in session.AdditionalUsers) - { - var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); - - await UpdatePlayedStatus(additionalUser, request.Id, false, null).ConfigureAwait(false); - } - - return dto; - } - - /// - /// Updates the played status. - /// - /// The user. - /// The item id. - /// if set to true [was played]. - /// The date played. - /// Task. - private async Task UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed) - { - var item = _libraryManager.GetItemById(itemId); - - if (wasPlayed) - { - await item.MarkPlayed(user, datePlayed, _userDataRepository).ConfigureAwait(false); - } - else - { - await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false); - } - - return _userDataRepository.GetUserDataDto(item, user); - } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 0900cc1ef..32d3dd5c8 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -14,11 +14,11 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// Class Audio /// - public class Audio : BaseItem, - IHasAlbumArtist, - IHasArtist, - IHasMusicGenres, - IHasLookupInfo, + public class Audio : BaseItem, + IHasAlbumArtist, + IHasArtist, + IHasMusicGenres, + IHasLookupInfo, IHasTags, IHasMediaSources { @@ -64,7 +64,15 @@ namespace MediaBrowser.Controller.Entities.Audio { get { - return Parents.OfType().FirstOrDefault() ?? new MusicAlbum { Name = "" }; + return LatestItemsIndexContainer ?? new MusicAlbum { Name = "Unknown Album" }; + } + } + + public override Folder LatestItemsIndexContainer + { + get + { + return Parents.OfType().FirstOrDefault(); } } @@ -204,7 +212,7 @@ namespace MediaBrowser.Controller.Entities.Audio private static MediaSourceInfo GetVersionInfo(Audio i, bool enablePathSubstituion) { var locationType = i.LocationType; - + var info = new MediaSourceInfo { Id = i.Id.ToString("N"), diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 042834731..d89df5f12 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -796,6 +796,12 @@ namespace MediaBrowser.Controller.Entities get { return null; } } + [IgnoreDataMember] + public virtual Folder LatestItemsIndexContainer + { + get { return null; } + } + /// /// Gets the user data key. /// diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 847183fd0..8a554c1d5 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -95,6 +95,14 @@ namespace MediaBrowser.Controller.Entities.TV } } + public override Folder LatestItemsIndexContainer + { + get + { + return Series; + } + } + /// /// Gets the user data key. /// diff --git a/MediaBrowser.Controller/Providers/NameParser.cs b/MediaBrowser.Controller/Providers/NameParser.cs index 726f0e60e..cdd0974ea 100644 --- a/MediaBrowser.Controller/Providers/NameParser.cs +++ b/MediaBrowser.Controller/Providers/NameParser.cs @@ -5,13 +5,13 @@ namespace MediaBrowser.Controller.Providers { public static class NameParser { - static readonly Regex[] NameMatches = new[] { + static readonly Regex[] NameMatches = + { new Regex(@"(?.*)\((?\d{4})\)"), // matches "My Movie (2001)" and gives us the name and the year new Regex(@"(?.*)(\.(?\d{4})(\.|$)).*$"), new Regex(@"(?.*)") // last resort matches the whole string as the name }; - /// /// Parses the name. /// diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 62ff9f687..f01d973d6 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -460,7 +460,6 @@ namespace MediaBrowser.Server.Implementations.Dto return 10; }) - .ThenBy(i => i.Name) .ToList(); // Attach People by transforming them into BaseItemPerson (DTO) diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 41555fe82..8474aa250 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -217,5 +217,6 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 2d85a3aa7..784719318 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -616,6 +616,20 @@ namespace MediaBrowser.Server.Implementations.Session info.MediaSourceId = info.ItemId; } + if (!string.IsNullOrWhiteSpace(info.ItemId) && libraryItem != null) + { + var current = session.NowPlayingItem; + + if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) + { + info.Item = GetItemInfo(libraryItem, libraryItem, info.MediaSourceId); + } + else + { + info.Item = current; + } + } + RemoveNowPlayingItem(session); var users = GetUsers(session); diff --git a/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs b/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs index 8f5dcc034..cbd0ce4a1 100644 --- a/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs +++ b/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs @@ -9,24 +9,35 @@ namespace MediaBrowser.Tests.Providers { public void TestNameMatches() { var name = string.Empty; int? year = null; + NameParser.ParseName("My Movie (2013)", out name, out year); Assert.AreEqual("My Movie", name); Assert.AreEqual(2013, year); + name = string.Empty; year = null; NameParser.ParseName("My Movie 2 (2013)", out name, out year); Assert.AreEqual("My Movie 2", name); Assert.AreEqual(2013, year); + + name = string.Empty; + year = null; + NameParser.ParseName("2013 - My Movie 2", out name, out year); + Assert.AreEqual(2013, year); + Assert.AreEqual("My Movie 2", name); + name = string.Empty; year = null; NameParser.ParseName("My Movie 2001 (2013)", out name, out year); Assert.AreEqual("My Movie 2001", name); Assert.AreEqual(2013, year); + name = string.Empty; year = null; NameParser.ParseName("My Movie - 2 (2013)", out name, out year); Assert.AreEqual("My Movie - 2", name); Assert.AreEqual(2013, year); + name = string.Empty; year = null; NameParser.ParseName("curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", out name, out year); diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs index 0f4d25dde..252ca62f2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumXmlSaver.cs @@ -56,8 +56,7 @@ namespace MediaBrowser.XbmcMetadata.Savers XmlSaverHelpers.AddCommonNodes(album, builder, _libraryManager, _userManager, _userDataRepo, _fileSystem, _config); - var tracks = album.RecursiveChildren - .OfType