From a55d156fd65e297b044d8ad898b1e04f659e4e60 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 24 Jun 2014 17:45:21 -0400 Subject: update translations --- MediaBrowser.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations/Session') diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index afde4af93d..f48431dc1d 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -356,7 +356,7 @@ namespace MediaBrowser.Server.Implementations.Session private string GetSessionKey(string clientType, string appVersion, string deviceId) { - return clientType + deviceId + appVersion; + return clientType + deviceId; } /// -- cgit v1.2.3 From 22fc0b442ac093e55058723782adf845f4465925 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 25 Jun 2014 11:12:39 -0400 Subject: add short overview --- MediaBrowser.Api/BaseApiService.cs | 11 +++-- MediaBrowser.Api/SessionsService.cs | 25 ++++------- MediaBrowser.Controller/Session/ISessionManager.cs | 9 ++++ MediaBrowser.Dlna/PlayTo/TransportCommands.cs | 28 ------------- MediaBrowser.Providers/Savers/XmlSaverHelpers.cs | 6 +++ .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 49 +++++++++------------- .../Session/SessionManager.cs | 6 +++ .../Session/SessionWebSocketListener.cs | 18 +++----- 8 files changed, 62 insertions(+), 90 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Session') diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index ca5b8b63a4..569e125302 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -88,9 +88,14 @@ namespace MediaBrowser.Api { var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - return sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && - string.Equals(i.Client, auth.Client) && - string.Equals(i.ApplicationVersion, auth.Version)); + var session = sessionManager.GetSession(auth.DeviceId, auth.Client, auth.Version); + + if (session == null) + { + throw new ArgumentException("Session not found."); + } + + return session; } /// diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 4cb48e9fe8..36f1d65778 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().Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlaystateCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -303,7 +303,7 @@ namespace MediaBrowser.Api ItemType = request.ItemType }; - var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendBrowseCommand(GetSession(_sessionManager).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(); + var currentSession = GetSession(_sessionManager); var command = new GeneralCommand { @@ -345,7 +345,7 @@ namespace MediaBrowser.Api Text = request.Text }; - var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendMessageCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -364,14 +364,14 @@ namespace MediaBrowser.Api StartPositionTicks = request.StartPositionTicks }; - var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlayCommand(GetSession(_sessionManager).Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } public void Post(SendGeneralCommand request) { - var currentSession = GetSession(); + var currentSession = GetSession(_sessionManager); var command = new GeneralCommand { @@ -386,7 +386,7 @@ namespace MediaBrowser.Api public void Post(SendFullGeneralCommand request) { - var currentSession = GetSession(); + var currentSession = GetSession(_sessionManager); 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().Id; + request.Id = GetSession(_sessionManager).Id; } _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities { @@ -422,14 +422,5 @@ namespace MediaBrowser.Api MessageCallbackUrl = request.MessageCallbackUrl }); } - - private SessionInfo GetSession() - { - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - - return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && - string.Equals(i.Client, auth.Client) && - string.Equals(i.ApplicationVersion, auth.Version)); - } } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 8c2ece131f..74ad1b7ee6 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -239,5 +239,14 @@ namespace MediaBrowser.Controller.Session /// /// The device identifier. void ClearTranscodingInfo(string deviceId); + + /// + /// Gets the session. + /// + /// The device identifier. + /// The client. + /// The version. + /// SessionInfo. + SessionInfo GetSession(string deviceId, string client, string version); } } \ No newline at end of file diff --git a/MediaBrowser.Dlna/PlayTo/TransportCommands.cs b/MediaBrowser.Dlna/PlayTo/TransportCommands.cs index 2f2276a873..13b9b6b189 100644 --- a/MediaBrowser.Dlna/PlayTo/TransportCommands.cs +++ b/MediaBrowser.Dlna/PlayTo/TransportCommands.cs @@ -146,34 +146,6 @@ namespace MediaBrowser.Dlna.PlayTo return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); } - public string BuildSearchPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "") - { - var stateString = string.Empty; - - foreach (var arg in action.ArgumentList) - { - if (arg.Direction == "out") - continue; - - if (arg.Name == "ObjectID") - stateString += BuildArgumentXml(arg, value.ToString()); - else if (arg.Name == "Filter") - stateString += BuildArgumentXml(arg, "*"); - else if (arg.Name == "StartingIndex") - stateString += BuildArgumentXml(arg, "0"); - else if (arg.Name == "RequestedCount") - stateString += BuildArgumentXml(arg, "200"); - else if (arg.Name == "BrowseFlag") - stateString += BuildArgumentXml(arg, null, "BrowseDirectChildren"); - else if (arg.Name == "SortCriteria") - stateString += BuildArgumentXml(arg, ""); - else - stateString += BuildArgumentXml(arg, value.ToString(), commandParameter); - } - - return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); - } - public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary dictionary) { var stateString = string.Empty; diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 6b9ebbfe93..d10422f78e 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -72,6 +72,7 @@ namespace MediaBrowser.Providers.Savers "MusicbrainzId", "Overview", + "ShortOverview", "Persons", "PlotKeywords", "PremiereDate", @@ -257,6 +258,11 @@ namespace MediaBrowser.Providers.Savers } } + if (!string.IsNullOrEmpty(item.Overview)) + { + builder.Append(""); + } + var hasShortOverview = item as IHasShortOverview; if (hasShortOverview != null) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 28d4769714..034894670d 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -67,32 +67,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio /// true if [is music album] [the specified data]; otherwise, false. public static bool IsMusicAlbum(string path, IDirectoryService directoryService) { - // If list contains at least 2 audio files or at least one and no video files consider it to contain music - var foundAudio = 0; - - foreach (var file in directoryService.GetFiles(path)) - { - var fullName = file.FullName; - - if (EntityResolutionHelper.IsAudioFile(fullName)) - { - // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) - { - continue; - } - - foundAudio++; - } - if (foundAudio >= 2) - { - return true; - } - if (EntityResolutionHelper.IsVideoFile(fullName)) return false; - } - - // or a single audio file and no video files - return foundAudio > 0; + return ContainsMusic(directoryService.GetFileSystemEntries(path)); } /// @@ -122,15 +97,31 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; - foreach (var file in list) + foreach (var fileSystemInfo in list) { - var fullName = file.FullName; + // TODO: Support disc 1, disc 2, etc + if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + continue; + } + + var fullName = fileSystemInfo.FullName; - if (EntityResolutionHelper.IsAudioFile(fullName)) foundAudio++; + if (EntityResolutionHelper.IsAudioFile(fullName)) + { + // Don't resolve these into audio files + if (string.Equals(Path.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) + { + continue; + } + + foundAudio++; + } if (foundAudio >= 2) { return true; } + if (EntityResolutionHelper.IsVideoFile(fullName)) return false; if (EntityResolutionHelper.IsVideoPlaceHolder(fullName)) return false; } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index f48431dc1d..4e2b3c7b78 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1516,5 +1516,11 @@ namespace MediaBrowser.Server.Implementations.Session { ReportTranscodingInfo(deviceId, null); } + + public SessionInfo GetSession(string deviceId, string client, string version) + { + return Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && + string.Equals(i.Client, client)); + } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index 365845f416..a50a6f02ef 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -113,21 +113,13 @@ namespace MediaBrowser.Server.Implementations.Session var version = vals[2]; var deviceName = vals.Length > 3 ? vals[3] : string.Empty; - var session = _sessionManager.Sessions - .FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && - string.Equals(i.Client, client) && - string.Equals(i.ApplicationVersion, version)); + var session = _sessionManager.GetSession(deviceId, client, version); if (session == null && !string.IsNullOrEmpty(deviceName)) { _logger.Debug("Logging session activity"); - await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false); - - session = _sessionManager.Sessions - .FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && - string.Equals(i.Client, client) && - string.Equals(i.ApplicationVersion, version)); + session = await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false); } if (session != null) @@ -197,7 +189,7 @@ namespace MediaBrowser.Server.Implementations.Session } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - + /// /// Reports the playback start. /// @@ -284,7 +276,7 @@ namespace MediaBrowser.Server.Implementations.Session _sessionManager.OnPlaybackProgress(info); } } - + /// /// Reports the playback progress. /// @@ -362,7 +354,7 @@ namespace MediaBrowser.Server.Implementations.Session _sessionManager.OnPlaybackStopped(info); } } - + /// /// Reports the playback stopped. /// -- 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.Server.Implementations/Session') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 2177f90c2f..8e6ca44014 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 8bc867a2e7..21ba47bd4d 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 9e6ca3aea9..fec6d698ae 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 e68aa73f6a..35b1b53851 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 0094282c8a..b95e18a0d9 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 ad7da8e3c5..db6c6ce53e 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 72ffa3fca6..6b8dd18f10 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 01531a7ae6..adf6a522b3 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 947da29fea..c3ef587685 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 62c7ac7c0a..4fc3e00c08 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 de01628f84..497f2d0a35 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 d3c47dfa1e..5696d8df75 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 b9e3888fda..19e47eb857 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 db02ed4280..f0bf22c5e1 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 05e6a95774..b0ee6b6d57 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 0732c951a8..34a933dee3 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 f50c87f47a..ff029d5b52 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 94ff1b62e2..112a2c5cea 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 b54b05fcf7..84b42baa38 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 9ff482a1ab..75e13f92c2 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 fa78fa0205..8a65e2b565 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 5bb6106860..0af336c55b 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 f31671b1a1..3848cb2de0 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 31463dc3f4..29cc7baf89 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 aaf14ce715..483133c102 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 e72edcc982..a6fcaf4381 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 6776cbf172..c32e672160 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 13027d30b5..07015ecae2 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 b1379ad5d6..49e979e551 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 68f0e21edc..c8c45c1bb3 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 c926c16ff2..ba07571bf2 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 538b165751..f50f0a0bd4 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 962effdeda..cf9959e535 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 4c38ba0d1f..2024d779b7 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 da12a9e3d2..d1767e7fd9 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 4dda045c06..2b300f9003 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 764a281024..64d1fcb34c 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 f62e37f798..2407a1a391 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 010caa2337..0da5f92728 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 e609204d69..d7dcb60f0e 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 74ad1b7ee6..6d3a9d20cf 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 2d19d335d2..0ed49d5f89 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 c0c886c8a2..12c87ca97a 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 5fcf3550c9..0000000000 --- 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 dd523d9478..0000000000 --- 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 d8043b0e14..0000000000 --- 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 a2eac4c2d9..0000000000 --- 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 f7b888f824..36c353479f 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 d2b0a8caf3..d758f2f394 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 0000000000..09f7072dd3 --- /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 0000000000..7d80f31774 --- /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 0000000000..34e4d32b53 --- /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 0000000000..1998d3102f --- /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 de8f4e8b86..b9bf6e7fe6 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 998aaa3a7f..8046e83c7f 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 1a7f9db283..c29a7d14e3 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 6ea77f2518..77343ab4ef 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 f9d7479cea..1e0bbc39bc 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 467bedcf13..ad1ddba881 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 b3280dac7c..41555fe82e 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 10e27bc304..a909929ae0 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 0000000000..a336eba0ed --- /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 416b29e86b..964e2cd243 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 4e2b3c7b78..2f6790a3e8 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 e8fb7bfcbf..673a9f1519 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 80f864a326..3ccf26c375 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 cbdae6d089..8f85fcb513 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.Server.Implementations/Session') diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs new file mode 100644 index 0000000000..4b49b411a8 --- /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 39fcc50d8f..291deb3b01 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 4f8c348bb8..deaefe019a 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 b95e18a0d9..993b69601c 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 77a714755d..10aa231265 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 4fc3e00c08..0000000000 --- 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 a68966b335..ca2887d19c 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 28edb61dd9..51a0801060 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 00c307a18b..f4651601b0 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 0000000000..3e692cb22f --- /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 2f0741434a..6f2e83a796 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 d1767e7fd9..55cdc8681e 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 64d1fcb34c..cda489c94a 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 46dabb0420..0000000000 --- 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 0cc4fc6b4d..dcac5e7ba6 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 f9d7cc21af..0d0555dc0d 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 524d7097ba..0428347310 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 584091b130..b886cef193 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 780181a61d..d576d90c45 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 b9630a66f4..847183fd00 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 e5a8135c2d..6404e71ec6 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 86099fdc01..619a497f5f 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 2bec9e3deb..9db91e7f22 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 23610351e7..ba1cb30436 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 692d6db909..7f1ddbce9b 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 6d3a9d20cf..7b20621827 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 0ed49d5f89..91c1508fc1 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 12c87ca97a..782e8524db 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 0000000000..737cb5c48f --- /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 36c353479f..49b731341a 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 db8b69951a..86eb40b975 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 26b0e9d9e8..6ee9f19163 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 d758f2f394..4d4ca8e20c 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 f511d41a97..6e9368f441 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 40723eff83..98df3efe55 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 0000000000..ba409c5423 --- /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 12af93dbd1..1b2e9fa6d6 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 92b4616e71..57a40741f5 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 0000000000..d6cd3424b1 --- /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 73216ca33c..62ff9f6879 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 d7186aa211..c31f462150 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 833dfc5e45..cfcbb077e9 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 77343ab4ef..94be37e956 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 79f1265116..d3030f31f9 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 412b2e7bd0..9c69e656d7 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 ad1ddba881..09793f4fc7 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 3bd0df0eb5..d6d4bbc516 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 a909929ae0..1d201e0690 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 964e2cd243..b832f3a06b 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 2f6790a3e8..2d85a3aa7f 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 46c3df07b7..7e6a252cdc 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 673a9f1519..91e92e21c5 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 4ba98e9b69..c2c64ea4d4 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 f5f9434e7d..47a4be8e36 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 3ccf26c375..36adae71c7 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 8f85fcb513..0099daaf19 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.Server.Implementations/Session') diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index ca2887d19c..6a1e45e25c 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 75e13f92c2..d8e3ee75d3 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 ffe71f4eaa..6c09f00a17 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 0000000000..ccebb912b0 --- /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 55cdc8681e..de2801dcc2 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 0900cc1eff..32d3dd5c8c 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 0428347310..d89df5f12e 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 847183fd00..8a554c1d5c 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 726f0e60e3..cdd0974eac 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 62ff9f6879..f01d973d64 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 41555fe82e..8474aa2508 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 2d85a3aa7f..784719318f 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 8f5dcc034a..cbd0ce4a18 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 0f4d25dde4..252ca62f23 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 /// The job. - /// The delete mode. - private void KillTranscodingJob(TranscodingJob job, FileDeleteMode deleteMode) + /// The delete. + private void KillTranscodingJob(TranscodingJob job, Func delete) { lock (_activeTranscodingJobs) { @@ -454,7 +456,7 @@ namespace MediaBrowser.Api } } - if (deleteMode == FileDeleteMode.All) + if (delete(job.Path)) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } @@ -593,10 +595,4 @@ namespace MediaBrowser.Api /// Hls } - - public enum FileDeleteMode - { - None, - All - } } diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index e628c5f7af..6710461ad9 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Api /// Class GetConfiguration /// [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] + [Authenticated] public class GetConfiguration : IReturn { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 8a65e2b565..8eff75533e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -22,7 +22,8 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { } @@ -103,8 +104,8 @@ namespace MediaBrowser.Api.Playback.Hls } else { - await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.All, false).ConfigureAwait(false); - + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => true, false).ConfigureAwait(false); + // If the playlist doesn't already exist, startup ffmpeg try { @@ -252,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; - + var itsOffsetMs = hlsVideoRequest == null ? 0 : hlsVideoRequest.TimeStampOffsetMs; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 6c09f00a17..07aaf5f86c 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -127,8 +127,7 @@ 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); + await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false); if (currentTranscodingIndex.HasValue) { diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 3848cb2de0..f283525885 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Api.Playback.Hls public void Delete(StopEncodingProcess request) { - var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All, true); + var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true, true); Task.WaitAll(task); } diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index f4651601b0..ed7db626f9 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -213,6 +213,7 @@ namespace MediaBrowser.Api } [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] + [Authenticated] public class PostCapabilities : IReturnVoid { /// @@ -235,6 +236,11 @@ namespace MediaBrowser.Api public bool SupportsMediaControl { get; set; } } + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] + public class ReportSessionEnded : IReturnVoid + { + } + /// /// Class SessionsService /// @@ -246,16 +252,26 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; /// /// Initializes a new instance of the class. /// /// The session manager. /// The user manager. - public SessionsService(ISessionManager sessionManager, IUserManager userManager) + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext) { _sessionManager = sessionManager; _userManager = userManager; + _authContext = authContext; + } + + + public void Post(ReportSessionEnded request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); } /// diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 6f2e83a796..f336736be8 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -15,6 +15,7 @@ namespace MediaBrowser.Api /// Class GetSystemInfo /// [Route("/System/Info", "GET", Summary = "Gets information about the server")] + [Authenticated] public class GetSystemInfo : IReturn { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index cda489c94a..bcaf80d69c 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using ServiceStack; using ServiceStack.Text.Controller; @@ -19,6 +18,7 @@ namespace MediaBrowser.Api /// Class GetUsers /// [Route("/Users", "GET", Summary = "Gets a list of users")] + [Authenticated] public class GetUsers : IReturn> { [ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -37,6 +37,7 @@ namespace MediaBrowser.Api /// Class GetUser /// [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] + [Authenticated] public class GetUser : IReturn { /// @@ -159,11 +160,6 @@ namespace MediaBrowser.Api /// public class UserService : BaseApiService, IHasAuthorization { - /// - /// The _XML serializer - /// - private readonly IXmlSerializer _xmlSerializer; - /// /// The _user manager /// @@ -176,19 +172,12 @@ namespace MediaBrowser.Api /// /// Initializes a new instance of the class. /// - /// The XML serializer. /// The user manager. /// The dto service. + /// The session mananger. /// xmlSerializer - public UserService(IXmlSerializer xmlSerializer, IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) - : base() + public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) { - if (xmlSerializer == null) - { - throw new ArgumentNullException("xmlSerializer"); - } - - _xmlSerializer = xmlSerializer; _userManager = userManager; _dtoService = dtoService; _sessionMananger = sessionMananger; @@ -196,6 +185,11 @@ namespace MediaBrowser.Api public object Get(GetPublicUsers request) { + if (!Request.IsLocal && !_sessionMananger.IsLocal(Request.RemoteIp)) + { + return ToOptimizedResult(new List()); + } + return Get(new GetUsers { IsHidden = false, @@ -368,9 +362,15 @@ namespace MediaBrowser.Api { throw new ArgumentException("There must be at least one enabled user in the system."); } + + var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); + + Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : + _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index 707807bd5a..3c7df91c1c 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Channels { public string Name { get; set; } + public string SeriesName { get; set; } + public string Id { get; set; } public ChannelItemType Type { get; set; } @@ -28,8 +30,6 @@ namespace MediaBrowser.Controller.Channels public long? RunTimeTicks { get; set; } - public bool IsInfiniteStream { get; set; } - public string ImageUrl { get; set; } public ChannelMediaType MediaType { get; set; } @@ -43,9 +43,14 @@ namespace MediaBrowser.Controller.Channels public int? ProductionYear { get; set; } public DateTime? DateCreated { get; set; } - + + public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } + public List MediaSources { get; set; } - + + public bool IsInfiniteStream { get; set; } + public ChannelItemInfo() { MediaSources = new List(); diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionEvents.cs new file mode 100644 index 0000000000..80f66a444a --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionEvents.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreatedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the options. + /// + /// The options. + public CollectionCreationOptions Options { get; set; } + } + + public class CollectionModifiedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the items changed. + /// + /// The items changed. + public List ItemsChanged { get; set; } + } +} diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index fdb2a49755..9130f68d43 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -8,6 +8,21 @@ namespace MediaBrowser.Controller.Collections { public interface ICollectionManager { + /// + /// Occurs when [collection created]. + /// + event EventHandler CollectionCreated; + + /// + /// Occurs when [items added to collection]. + /// + event EventHandler ItemsAddedToCollection; + + /// + /// Occurs when [items removed from collection]. + /// + event EventHandler ItemsRemovedFromCollection; + /// /// Creates the collection. /// diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 0d0555dc0d..434e896dad 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -50,6 +50,13 @@ namespace MediaBrowser.Controller.Dto /// ChapterInfoDto. ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item); + /// + /// Gets the user item data dto. + /// + /// The data. + /// UserItemDataDto. + UserItemDataDto GetUserItemDataDto(UserItemData data); + /// /// Gets the item by name dto. /// diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4ad3033f9e..1c60ea8e68 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -95,6 +95,7 @@ + @@ -233,6 +234,9 @@ + + + diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index d33b2c9eb0..52cd6fceac 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using System.Collections.Generic; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs new file mode 100644 index 0000000000..dd5eec1f92 --- /dev/null +++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs @@ -0,0 +1,61 @@ +using System; + +namespace MediaBrowser.Controller.Security +{ + public class AuthenticationInfo + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string AccessToken { get; set; } + + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + + /// + /// Gets or sets the name of the application. + /// + /// The name of the application. + public string AppName { get; set; } + + /// + /// Gets or sets the name of the device. + /// + /// The name of the device. + public string DeviceName { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// true if this instance is active; otherwise, false. + public bool IsActive { get; set; } + + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } + + /// + /// Gets or sets the date revoked. + /// + /// The date revoked. + public DateTime? DateRevoked { get; set; } + } +} diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs new file mode 100644 index 0000000000..3234b0350f --- /dev/null +++ b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs @@ -0,0 +1,42 @@ + +namespace MediaBrowser.Controller.Security +{ + public class AuthenticationInfoQuery + { + /// + /// Gets or sets the device identifier. + /// + /// The device identifier. + public string DeviceId { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets the access token. + /// + /// The access token. + public string AccessToken { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is active. + /// + /// null if [is active] contains no value, true if [is active]; otherwise, false. + public bool? IsActive { get; set; } + + /// + /// Gets or sets the start index. + /// + /// The start index. + public int? StartIndex { get; set; } + + /// + /// Gets or sets the limit. + /// + /// The limit. + public int? Limit { get; set; } + } +} diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs new file mode 100644 index 0000000000..219b07028d --- /dev/null +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -0,0 +1,39 @@ +using MediaBrowser.Model.Querying; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Security +{ + public interface IAuthenticationRepository + { + /// + /// Creates the specified information. + /// + /// The information. + /// The cancellation token. + /// Task. + Task Create(AuthenticationInfo info, CancellationToken cancellationToken); + + /// + /// Updates the specified information. + /// + /// The information. + /// The cancellation token. + /// Task. + Task Update(AuthenticationInfo info, CancellationToken cancellationToken); + + /// + /// Gets the specified query. + /// + /// The query. + /// QueryResult{AuthenticationInfo}. + QueryResult Get(AuthenticationInfoQuery query); + + /// + /// Gets the specified identifier. + /// + /// The identifier. + /// AuthenticationInfo. + AuthenticationInfo Get(string id); + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 7b20621827..4b30c964c8 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -259,7 +259,28 @@ namespace MediaBrowser.Controller.Session /// /// Validates the security token. /// - /// The token. - void ValidateSecurityToken(string token); + /// The access token. + void ValidateSecurityToken(string accessToken); + + /// + /// Logouts the specified access token. + /// + /// The access token. + /// Task. + Task Logout(string accessToken); + + /// + /// Revokes the user tokens. + /// + /// The user identifier. + /// Task. + Task RevokeUserTokens(string userId); + + /// + /// Determines whether the specified remote endpoint is local. + /// + /// The remote endpoint. + /// true if the specified remote endpoint is local; otherwise, false. + bool IsLocal(string remoteEndpoint); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 6f27f6cb28..58d455955a 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Controller.Session public List SupportedCommands { get; set; } public TranscodingInfo TranscodingInfo { get; set; } - + /// /// Gets a value indicating whether this instance is active. /// diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index cc9bc7bed7..62aec5ecbd 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -1,11 +1,11 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.LocalMetadata { diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 33c6f980ce..74a3314b72 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -246,7 +246,7 @@ namespace MediaBrowser.Model.ApiClient /// Gets the client session asynchronous. /// /// Task{SessionInfoDto}. - Task GetCurrentSessionAsync(); + Task GetCurrentSessionAsync(CancellationToken cancellationToken); /// /// Gets the item counts async. @@ -644,6 +644,13 @@ namespace MediaBrowser.Model.ApiClient /// Task. Task SetVolume(string sessionId, int volume); + /// + /// Stops the transcoding processes. + /// + /// The device identifier. + /// Task. + Task StopTranscodingProcesses(string deviceId); + /// /// Sets the index of the audio stream. /// @@ -984,7 +991,7 @@ namespace MediaBrowser.Model.ApiClient /// The query. /// The cancellation token. /// Task{LiveTvInfo}. - Task> GetLiveTvChannelsAsync(ChannelQuery query, CancellationToken cancellationToken); + Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken); /// /// Gets the live tv channel asynchronous. @@ -1187,5 +1194,13 @@ namespace MediaBrowser.Model.ApiClient /// The cancellation token. /// Task{QueryResult{BaseItemDto}}. Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken); + + /// + /// Gets the latest channel items. + /// + /// The query. + /// The cancellation token. + /// Task{QueryResult{BaseItemDto}}. + Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 49b731341a..d9404ce29e 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -210,6 +210,8 @@ namespace MediaBrowser.Model.Configuration public bool DefaultMetadataSettingsApplied { get; set; } + public bool EnableTokenAuthentication { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs index 8046e83c7f..97fe2ea99e 100644 --- a/MediaBrowser.Model/Users/AuthenticationResult.cs +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -21,6 +21,6 @@ namespace MediaBrowser.Model.Users /// Gets or sets the authentication token. /// /// The authentication token. - public string AuthenticationToken { get; set; } + public string AccessToken { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 95eca6ba0c..7feca2d349 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 2783fda6bc..f09890c40f 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.Providers.Manager var key = id.Key; // Don't replace existing Id's. - if (!target.ProviderIds.ContainsKey(key)) + if (replaceData || !target.ProviderIds.ContainsKey(key)) { target.ProviderIds[key] = id.Value; } diff --git a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs index 7979711ec0..b3f62005b0 100644 --- a/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbEpisodeImageProvider.cs @@ -19,7 +19,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.TV { - public class MovieDbEpisodeImageProvider : IRemoteImageProvider, IHasOrder + public class MovieDbEpisodeImageProvider/* : IRemoteImageProvider, IHasOrder*/ { private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index c6dd807585..d0ea64e0fc 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -1133,6 +1133,8 @@ namespace MediaBrowser.Server.Implementations.Channels item.CommunityRating = info.CommunityRating; item.OfficialRating = info.OfficialRating; item.Overview = info.Overview; + item.IndexNumber = info.IndexNumber; + item.ParentIndexNumber = info.ParentIndexNumber; item.People = info.People; item.PremiereDate = info.PremiereDate; item.ProductionYear = info.ProductionYear; @@ -1159,7 +1161,6 @@ namespace MediaBrowser.Server.Implementations.Channels if (channelMediaItem != null) { - channelMediaItem.IsInfiniteStream = info.IsInfiniteStream; channelMediaItem.ContentType = info.ContentType; channelMediaItem.ChannelMediaSources = info.MediaSources; diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 728b18bbf4..7bc7838c6f 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Common.IO; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; using MoreLinq; using System; using System.Collections.Generic; @@ -19,12 +21,18 @@ namespace MediaBrowser.Server.Implementations.Collections private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; + private readonly ILogger _logger; - public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor) + public event EventHandler CollectionCreated; + public event EventHandler ItemsAddedToCollection; + public event EventHandler ItemsRemovedFromCollection; + + public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor, ILogger logger) { _libraryManager = libraryManager; _fileSystem = fileSystem; _iLibraryMonitor = iLibraryMonitor; + _logger = logger; } public Folder GetCollectionsFolder(string userId) @@ -74,9 +82,16 @@ namespace MediaBrowser.Server.Implementations.Collections if (options.ItemIdList.Count > 0) { - await AddToCollection(collection.Id, options.ItemIdList); + await AddToCollection(collection.Id, options.ItemIdList, false); } + EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs + { + Collection = collection, + Options = options + + }, _logger); + return collection; } finally @@ -113,7 +128,12 @@ namespace MediaBrowser.Server.Implementations.Collections return GetCollectionsFolder(string.Empty); } - public async Task AddToCollection(Guid collectionId, IEnumerable ids) + public Task AddToCollection(Guid collectionId, IEnumerable ids) + { + return AddToCollection(collectionId, ids, true); + } + + private async Task AddToCollection(Guid collectionId, IEnumerable ids, bool fireEvent) { var collection = _libraryManager.GetItemById(collectionId) as BoxSet; @@ -123,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List(); + var itemList = new List(); var currentLinkedChildren = collection.GetLinkedChildren().ToList(); foreach (var itemId in ids) @@ -134,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentException("No item exists with the supplied Id"); } + itemList.Add(item); + if (currentLinkedChildren.Any(i => i.Id == itemId)) { throw new ArgumentException("Item already exists in collection"); @@ -165,6 +188,16 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + if (fireEvent) + { + EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + { + Collection = collection, + ItemsChanged = itemList + + }, _logger); + } } public async Task RemoveFromCollection(Guid collectionId, IEnumerable itemIds) @@ -177,6 +210,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List(); + var itemList = new List(); foreach (var itemId in itemIds) { @@ -190,6 +224,12 @@ namespace MediaBrowser.Server.Implementations.Collections list.Add(child); var childItem = _libraryManager.GetItemById(itemId); + + if (childItem != null) + { + itemList.Add(childItem); + } + var supportsGrouping = childItem as ISupportsBoxSetGrouping; if (supportsGrouping != null) @@ -221,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Collections { File.Delete(file); } - + foreach (var child in list) { collection.LinkedChildren.Remove(child); @@ -238,6 +278,13 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + { + Collection = collection, + ItemsChanged = itemList + + }, _logger); } public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user) diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index c29a7d14e3..6894d7ac7e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; @@ -13,9 +13,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { - public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext) + private readonly IServerConfigurationManager _config; + + public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config) { AuthorizationContext = authorizationContext; + _config = config; SessionManager = sessionManager; UserManager = userManager; } @@ -54,28 +57,30 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security //This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(req); - if (string.IsNullOrWhiteSpace(auth.Token)) + if (!string.IsNullOrWhiteSpace(auth.Token) || _config.Configuration.EnableTokenAuthentication) { - // Legacy - // TODO: Deprecate this in Oct 2014 - - User user = null; - - if (!string.IsNullOrWhiteSpace(auth.UserId)) - { - var userId = auth.UserId; + SessionManager.ValidateSecurityToken(auth.Token); + } - user = UserManager.GetUserById(new Guid(userId)); - } + var user = string.IsNullOrWhiteSpace(auth.UserId) + ? null + : UserManager.GetUserById(new Guid(auth.UserId)); - if (user == null || user.Configuration.IsDisabled) - { - throw new UnauthorizedAccessException("Unauthorized access."); - } + if (user != null && user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("User account has been disabled."); } - else + + if (!string.IsNullOrWhiteSpace(auth.DeviceId) && + !string.IsNullOrWhiteSpace(auth.Client) && + !string.IsNullOrWhiteSpace(auth.Device)) { - SessionManager.ValidateSecurityToken(auth.Token); + SessionManager.LogSessionActivity(auth.Client, + auth.Version, + auth.DeviceId, + auth.Device, + req.RemoteIp, + user); } } @@ -108,11 +113,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security } } - private void LogRequest() - { - - } - protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) { var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 1d201e0690..859011f6ee 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -216,6 +216,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs index df32ac021b..5d5855bf8b 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Persistence { - public class SqliteFileOrganizationRepository : IFileOrganizationRepository + public class SqliteFileOrganizationRepository : IFileOrganizationRepository, IDisposable { private IDbConnection _connection; diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs new file mode 100644 index 0000000000..5f225ddd47 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs @@ -0,0 +1,338 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using MediaBrowser.Server.Implementations.Persistence; +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Security +{ + public class AuthenticationRepository : IAuthenticationRepository + { + private IDbConnection _connection; + private readonly ILogger _logger; + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + private IDbCommand _saveInfoCommand; + + public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths) + { + _logger = logger; + _appPaths = appPaths; + } + + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db"); + + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", + "create index if not exists idx_AccessTokens on AccessTokens(Id)", + + //pragmas + "pragma temp_store = memory", + + "pragma shrink_memory" + }; + + _connection.RunQueries(queries, _logger); + + PrepareStatements(); + } + + private void PrepareStatements() + { + _saveInfoCommand = _connection.CreateCommand(); + _saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)"; + + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@Id"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AccessToken"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceId"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppName"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceName"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@UserId"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@IsActive"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateCreated"); + _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateRevoked"); + } + + public Task Create(AuthenticationInfo info, CancellationToken cancellationToken) + { + info.Id = Guid.NewGuid().ToString("N"); + + return Update(info, cancellationToken); + } + + public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id); + _saveInfoCommand.GetParameter(index++).Value = info.AccessToken; + _saveInfoCommand.GetParameter(index++).Value = info.DeviceId; + _saveInfoCommand.GetParameter(index++).Value = info.AppName; + _saveInfoCommand.GetParameter(index++).Value = info.DeviceName; + _saveInfoCommand.GetParameter(index++).Value = info.UserId; + _saveInfoCommand.GetParameter(index++).Value = info.IsActive; + _saveInfoCommand.GetParameter(index++).Value = info.DateCreated; + _saveInfoCommand.GetParameter(index++).Value = info.DateRevoked; + + _saveInfoCommand.Transaction = transaction; + + _saveInfoCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + + private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + public QueryResult Get(AuthenticationInfoQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText; + + var whereClauses = new List(); + + var startIndex = query.StartIndex ?? 0; + + if (startIndex > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens ORDER BY DateCreated LIMIT {0})", + startIndex.ToString(_usCulture))); + } + + if (!string.IsNullOrWhiteSpace(query.AccessToken)) + { + whereClauses.Add("AccessToken=@AccessToken"); + cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken; + } + + if (!string.IsNullOrWhiteSpace(query.UserId)) + { + whereClauses.Add("UserId=@UserId"); + cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId; + } + + if (!string.IsNullOrWhiteSpace(query.DeviceId)) + { + whereClauses.Add("DeviceId=@DeviceId"); + cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId; + } + + if (query.IsActive.HasValue) + { + whereClauses.Add("IsActive=@IsActive"); + cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value; + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from AccessTokens"; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(Get(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public AuthenticationInfo Get(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + var guid = new Guid(id); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText + " where Id=@Id"; + + cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + if (reader.Read()) + { + return Get(reader); + } + } + } + + return null; + } + + private AuthenticationInfo Get(IDataReader reader) + { + var s = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + var info = new AuthenticationInfo + { + Id = reader.GetGuid(0).ToString("N"), + AccessToken = reader.GetString(1) + }; + + if (!reader.IsDBNull(2)) + { + info.DeviceId = reader.GetString(2); + } + + if (!reader.IsDBNull(3)) + { + info.AppName = reader.GetString(3); + } + + if (!reader.IsDBNull(4)) + { + info.DeviceName = reader.GetString(4); + } + + if (!reader.IsDBNull(5)) + { + info.UserId = reader.GetString(5); + } + + info.IsActive = reader.GetBoolean(6); + info.DateCreated = reader.GetDateTime(7); + + if (!reader.IsDBNull(8)) + { + info.DateRevoked = reader.GetDateTime(8); + } + + return info; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private readonly object _disposeLock = new object(); + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + try + { + lock (_disposeLock) + { + if (_connection != null) + { + if (_connection.IsOpen()) + { + _connection.Close(); + } + + _connection.Dispose(); + _connection = null; + } + } + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing database", ex); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 784719318f..c3d24c0de1 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,6 +1,4 @@ -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Common.Events; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -13,12 +11,14 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Users; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,7 +27,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Users; namespace MediaBrowser.Server.Implementations.Session { @@ -62,6 +61,8 @@ namespace MediaBrowser.Server.Implementations.Session private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; + private readonly IAuthenticationRepository _authRepo; + /// /// Gets or sets the configuration manager. /// @@ -104,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Session /// The logger. /// The user repository. /// The library manager. - public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient) + public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo) { _userDataRepository = userDataRepository; _configurationManager = configurationManager; @@ -119,6 +120,7 @@ namespace MediaBrowser.Server.Implementations.Session _jsonSerializer = jsonSerializer; _appHost = appHost; _httpClient = httpClient; + _authRepo = authRepo; } /// @@ -204,7 +206,12 @@ namespace MediaBrowser.Server.Implementations.Session /// Task. /// user /// - public async Task LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + public async Task LogSessionActivity(string clientType, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { if (string.IsNullOrEmpty(clientType)) { @@ -1157,7 +1164,37 @@ namespace MediaBrowser.Server.Implementations.Session public void ValidateSecurityToken(string token) { + if (string.IsNullOrWhiteSpace(token)) + { + throw new UnauthorizedAccessException(); + } + var result = _authRepo.Get(new AuthenticationInfoQuery + { + AccessToken = token + }); + + var info = result.Items.FirstOrDefault(); + + if (info == null) + { + throw new UnauthorizedAccessException(); + } + + if (!info.IsActive) + { + throw new UnauthorizedAccessException("Access token has expired."); + } + + if (!string.IsNullOrWhiteSpace(info.UserId)) + { + var user = _userManager.GetUserById(new Guid(info.UserId)); + + if (user == null || user.Configuration.IsDisabled) + { + throw new UnauthorizedAccessException("User account has been disabled."); + } + } } /// @@ -1175,7 +1212,7 @@ namespace MediaBrowser.Server.Implementations.Session /// public async Task AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) { - var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { @@ -1185,6 +1222,8 @@ namespace MediaBrowser.Server.Implementations.Session var user = _userManager.Users .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false); + var session = await LogSessionActivity(clientType, appVersion, deviceId, @@ -1197,11 +1236,108 @@ namespace MediaBrowser.Server.Implementations.Session { User = _dtoService.GetUserDto(user), SessionInfo = GetSessionInfoDto(session), - AuthenticationToken = Guid.NewGuid().ToString("N") + AccessToken = token }; } - private bool IsLocal(string remoteEndpoint) + private async Task GetAuthorizationToken(string userId, string deviceId, string app, string deviceName) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + DeviceId = deviceId, + IsActive = true, + UserId = userId, + Limit = 1 + }); + + if (existing.Items.Length > 0) + { + _logger.Debug("Reissuing access token"); + return existing.Items[0].AccessToken; + } + + var newToken = new AuthenticationInfo + { + AppName = app, + DateCreated = DateTime.UtcNow, + DeviceId = deviceId, + DeviceName = deviceName, + UserId = userId, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N") + }; + + _logger.Debug("Creating new access token for user {0}", userId); + await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false); + + return newToken.AccessToken; + } + + public async Task Logout(string accessToken) + { + if (string.IsNullOrWhiteSpace(accessToken)) + { + throw new ArgumentNullException("accessToken"); + } + + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + Limit = 1, + AccessToken = accessToken + + }).Items.FirstOrDefault(); + + if (existing != null) + { + existing.IsActive = false; + + await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var session in sessions) + { + try + { + ReportSessionEnded(session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error reporting session ended", ex); + } + } + } + } + + public async Task RevokeUserTokens(string userId) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true, + UserId = userId + }); + + foreach (var info in existing.Items) + { + await Logout(info.AccessToken).ConfigureAwait(false); + } + } + + private bool IsLocalhost(string remoteEndpoint) + { + if (string.IsNullOrWhiteSpace(remoteEndpoint)) + { + throw new ArgumentNullException("remoteEndpoint"); + } + + return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + } + + public bool IsLocal(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) { @@ -1211,12 +1347,11 @@ namespace MediaBrowser.Server.Implementations.Session // Private address space: // http://en.wikipedia.org/wiki/Private_network - return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || + return IsLocalhost(remoteEndpoint) || remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || - remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); + remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); } /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 91e92e21c5..ca04e580f3 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -210,6 +210,8 @@ namespace MediaBrowser.ServerApplication private IUserViewManager UserViewManager { get; set; } + private IAuthenticationRepository AuthenticationRepository { get; set; } + /// /// Initializes a new instance of the class. /// @@ -586,6 +588,9 @@ namespace MediaBrowser.ServerApplication FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false); RegisterSingleInstance(FileOrganizationRepository); + AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false); + RegisterSingleInstance(AuthenticationRepository); + UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer); RegisterSingleInstance(UserManager); @@ -625,7 +630,7 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); RegisterSingleInstance(DtoService); - SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient); + SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository); RegisterSingleInstance(SessionManager); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); @@ -651,7 +656,7 @@ namespace MediaBrowser.ServerApplication var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient); RegisterSingleInstance(connectionManager); - var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager")); RegisterSingleInstance(collectionManager); LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager); @@ -678,7 +683,7 @@ namespace MediaBrowser.ServerApplication var authContext = new AuthorizationContext(); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); - RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext)); + RegisterSingleInstance(new AuthService(UserManager, SessionManager, authContext, ServerConfigurationManager)); RegisterSingleInstance(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); @@ -755,6 +760,15 @@ namespace MediaBrowser.ServerApplication return repo; } + private async Task GetAuthenticationRepository() + { + var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths); + + await repo.Initialize().ConfigureAwait(false); + + return repo; + } + /// /// Configures the repositories. /// diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 0515148f02..49851f42a7 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The cancellation token. private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken) { - using (var streamReader = new StreamReader(metadataFile, encoding)) + using (var streamReader = new StreamReader(metadataFile)) { // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, settings)) diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs index 65648e1d05..47371ea04b 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.XbmcMetadata.Providers var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -44,10 +44,10 @@ namespace MediaBrowser.XbmcMetadata.Providers { result.HasMetadata = false; } - finally - { - XmlProviderUtils.XmlParsingResourcePool.Release(); - } + //finally + //{ + // XmlProviderUtils.XmlParsingResourcePool.Release(); + //} return result; } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs index 1b6c7a3401..a96b0636f2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs @@ -1,15 +1,14 @@ -using System.Collections.Generic; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Security; using System.Text; using System.Threading; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; namespace MediaBrowser.XbmcMetadata.Savers { diff --git a/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs index 7f817f5912..7111a429ab 100644 --- a/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.XbmcMetadata/Savers/XmlSaverHelpers.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Security; -using System.Text; -using System.Xml; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -18,6 +10,14 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.XbmcMetadata.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Xml; namespace MediaBrowser.XbmcMetadata.Savers { @@ -392,9 +392,9 @@ namespace MediaBrowser.XbmcMetadata.Savers builder.Append("" + SecurityElement.Escape(person) + ""); } - if (writers.Count > 0) + foreach (var person in writers) { - builder.Append("" + SecurityElement.Escape(string.Join(" / ", writers.ToArray())) + ""); + builder.Append("" + SecurityElement.Escape(person) + ""); } var hasTrailer = item as IHasTrailers; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 8c83ffabcd..f9e8c5aad8 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.412 + 3.0.414 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 88431b72cd..89aeff0a3d 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.412 + 3.0.414 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 73f764fd5e..1696ee347f 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.412 + 3.0.414 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + -- cgit v1.2.3 From 59de5c0d14fbf0c09926e37dce0c2e6de69000dd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Jul 2014 00:27:46 -0400 Subject: update translations --- MediaBrowser.Api/ConfigurationService.cs | 53 +++++++++++++++++++- MediaBrowser.Api/UserService.cs | 10 +++- .../Configuration/IServerConfigurationManager.cs | 6 +++ MediaBrowser.Controller/Entities/BaseItem.cs | 5 ++ MediaBrowser.Controller/Session/ISessionManager.cs | 2 +- MediaBrowser.Model/Configuration/ChapterOptions.cs | 4 -- .../Configuration/SubtitleOptions.cs | 1 + .../MediaInfo/FFProbeVideoInfo.cs | 2 +- .../Subtitles/OpenSubtitleDownloader.cs | 24 +++++++++ MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs | 10 ++-- .../Channels/ChannelManager.cs | 15 ++++-- .../Configuration/ServerConfigurationManager.cs | 57 +++++++++++++++++++++- .../HttpServer/Security/AuthService.cs | 2 +- .../Localization/JavaScript/ar.json | 17 ++++++- .../Localization/JavaScript/ca.json | 17 ++++++- .../Localization/JavaScript/cs.json | 17 ++++++- .../Localization/JavaScript/da.json | 17 ++++++- .../Localization/JavaScript/de.json | 21 ++++++-- .../Localization/JavaScript/el.json | 17 ++++++- .../Localization/JavaScript/en_GB.json | 17 ++++++- .../Localization/JavaScript/en_US.json | 17 ++++++- .../Localization/JavaScript/es_ES.json | 21 ++++++-- .../Localization/JavaScript/es_MX.json | 23 +++++++-- .../Localization/JavaScript/fr.json | 21 ++++++-- .../Localization/JavaScript/he.json | 17 ++++++- .../Localization/JavaScript/it.json | 21 ++++++-- .../Localization/JavaScript/kk.json | 21 ++++++-- .../Localization/JavaScript/ms.json | 17 ++++++- .../Localization/JavaScript/nb.json | 17 ++++++- .../Localization/JavaScript/nl.json | 21 ++++++-- .../Localization/JavaScript/pl.json | 17 ++++++- .../Localization/JavaScript/pt_BR.json | 21 ++++++-- .../Localization/JavaScript/pt_PT.json | 21 ++++++-- .../Localization/JavaScript/ru.json | 23 +++++++-- .../Localization/JavaScript/sv.json | 21 ++++++-- .../Localization/JavaScript/vi.json | 17 ++++++- .../Localization/JavaScript/zh_TW.json | 17 ++++++- .../Localization/Server/ar.json | 12 ++++- .../Localization/Server/ca.json | 12 ++++- .../Localization/Server/cs.json | 24 ++++++--- .../Localization/Server/da.json | 10 +++- .../Localization/Server/de.json | 18 +++++-- .../Localization/Server/el.json | 12 ++++- .../Localization/Server/en_GB.json | 12 ++++- .../Localization/Server/en_US.json | 12 ++++- .../Localization/Server/es_ES.json | 14 ++++-- .../Localization/Server/es_MX.json | 40 +++++++++------ .../Localization/Server/fr.json | 20 +++++--- .../Localization/Server/he.json | 16 ++++-- .../Localization/Server/it.json | 18 +++++-- .../Localization/Server/kk.json | 12 ++++- .../Localization/Server/ms.json | 12 ++++- .../Localization/Server/nb.json | 12 ++++- .../Localization/Server/nl.json | 14 ++++-- .../Localization/Server/pl.json | 12 ++++- .../Localization/Server/pt_BR.json | 10 +++- .../Localization/Server/pt_PT.json | 18 +++++-- .../Localization/Server/ru.json | 16 ++++-- .../Localization/Server/server.json | 13 ++++- .../Localization/Server/sv.json | 18 +++++-- .../Localization/Server/vi.json | 12 ++++- .../Localization/Server/zh_TW.json | 12 ++++- .../Session/SessionManager.cs | 13 +++-- MediaBrowser.ServerApplication/ApplicationHost.cs | 51 +------------------ MediaBrowser.WebDashboard/Api/DashboardService.cs | 4 +- .../MediaBrowser.WebDashboard.csproj | 24 +++------ 66 files changed, 888 insertions(+), 232 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Session') diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 6710461ad9..c83028bb2d 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -1,6 +1,8 @@ -using MediaBrowser.Common.Extensions; +using System; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -65,6 +67,13 @@ namespace MediaBrowser.Api } + [Route("/System/Configuration/MetadataPlugins/Autoset", "POST")] + [Authenticated] + public class AutoSetMetadataOptions : IReturnVoid + { + + } + public class ConfigurationService : BaseApiService { /// @@ -79,13 +88,15 @@ namespace MediaBrowser.Api private readonly IFileSystem _fileSystem; private readonly IProviderManager _providerManager; + private readonly ILibraryManager _libraryManager; - public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager) + public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager) { _jsonSerializer = jsonSerializer; _configurationManager = configurationManager; _fileSystem = fileSystem; _providerManager = providerManager; + _libraryManager = libraryManager; } /// @@ -111,6 +122,44 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + public void Post(AutoSetMetadataOptions request) + { + var service = AutoDetectMetadataService(); + + Logger.Info("Setting preferred metadata format to " + service); + + _configurationManager.SetPreferredMetadataService(service); + _configurationManager.SaveConfiguration(); + } + + private string AutoDetectMetadataService() + { + const string xbmc = "Xbmc Nfo"; + const string mb = "Media Browser Xml"; + + var paths = _libraryManager.GetDefaultVirtualFolders() + .SelectMany(i => i.Locations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => new DirectoryInfo(i)) + .ToList(); + + if (paths.Select(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) + .SelectMany(i => i) + .Any()) + { + return xbmc; + } + + if (paths.Select(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) + .SelectMany(i => i) + .Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase))) + { + return mb; + } + + return xbmc; + } + /// /// Posts the specified configuraiton. /// diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index d5266038b7..0edd013df3 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -188,7 +188,11 @@ namespace MediaBrowser.Api public object Get(GetPublicUsers request) { - if (Request.IsLocal || !_config.Configuration.IsStartupWizardCompleted) + var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); + var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); + + if ((Request.IsLocal && isDashboard) || + !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers { @@ -196,7 +200,8 @@ namespace MediaBrowser.Api }); } - if (_sessionMananger.IsLocal(Request.RemoteIp)) + // TODO: Add or is authenticated + if (_sessionMananger.IsInLocalNetwork(Request.RemoteIp)) { return Get(new GetUsers { @@ -205,6 +210,7 @@ namespace MediaBrowser.Api }); } + // Return empty when external return ToOptimizedResult(new List()); } diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 6a2343a009..c3d1796f99 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -26,5 +26,11 @@ namespace MediaBrowser.Controller.Configuration /// /// The configuration. ServerConfiguration Configuration { get; } + + /// + /// Sets the preferred metadata service. + /// + /// The service. + void SetPreferredMetadataService(string service); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d89df5f12e..b891bcfb2a 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1526,6 +1526,11 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsUnplayed(User user) { + if (user == null) + { + throw new ArgumentNullException("user"); + } + var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey()); return userdata == null || !userdata.Played; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4b30c964c8..88d0e9c1a1 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -281,6 +281,6 @@ namespace MediaBrowser.Controller.Session /// /// The remote endpoint. /// true if the specified remote endpoint is local; otherwise, false. - bool IsLocal(string remoteEndpoint); + bool IsInLocalNetwork(string remoteEndpoint); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ChapterOptions.cs b/MediaBrowser.Model/Configuration/ChapterOptions.cs index 8a059a0a4a..2e882ee611 100644 --- a/MediaBrowser.Model/Configuration/ChapterOptions.cs +++ b/MediaBrowser.Model/Configuration/ChapterOptions.cs @@ -14,10 +14,6 @@ public ChapterOptions() { - EnableMovieChapterImageExtraction = true; - EnableEpisodeChapterImageExtraction = false; - EnableOtherVideoChapterImageExtraction = false; - DownloadMovieChapters = true; DisabledFetchers = new string[] { }; diff --git a/MediaBrowser.Model/Configuration/SubtitleOptions.cs b/MediaBrowser.Model/Configuration/SubtitleOptions.cs index 96e04e5115..d50dba1b2e 100644 --- a/MediaBrowser.Model/Configuration/SubtitleOptions.cs +++ b/MediaBrowser.Model/Configuration/SubtitleOptions.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Configuration public string OpenSubtitlesUsername { get; set; } public string OpenSubtitlesPasswordHash { get; set; } + public bool IsOpenSubtitleVipAccount { get; set; } public SubtitleOptions() { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 57a40741f5..d46ddf7dcc 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -259,7 +259,7 @@ namespace MediaBrowser.Providers.MediaInfo { Chapters = chapters, Video = video, - ExtractImages = false, + ExtractImages = true, SaveChapters = false }, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs index 297f058672..c690fbd843 100644 --- a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs @@ -29,6 +29,15 @@ namespace MediaBrowser.Providers.Subtitles private readonly IServerConfigurationManager _config; private readonly IEncryptionManager _encryption; + private Timer _dailyTimer; + + // This is limited to 200 per day + private int _dailyDownloadCount; + + // It's 200 but this will be in-exact so buffer a little + // And the user may restart the server + private const int MaxDownloadsPerDay = 150; + public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption) { _logger = logManager.GetLogger(GetType().Name); @@ -37,6 +46,9 @@ namespace MediaBrowser.Providers.Subtitles _encryption = encryption; _config.ConfigurationUpdating += _config_ConfigurationUpdating; + + // Reset the count every 24 hours + _dailyTimer = new Timer(state => _dailyDownloadCount = 0, null, TimeSpan.FromHours(24), TimeSpan.FromHours(24)); } private const string PasswordHashPrefix = "h:"; @@ -100,6 +112,12 @@ namespace MediaBrowser.Providers.Subtitles throw new ArgumentNullException("id"); } + if (_dailyDownloadCount >= MaxDownloadsPerDay && + !_config.Configuration.SubtitleOptions.IsOpenSubtitleVipAccount) + { + throw new InvalidOperationException("Open Subtitle's daily download limit has been exceeded. Please try again tomorrow."); + } + var idParts = id.Split(new[] { '-' }, 3); var format = idParts[0]; @@ -272,6 +290,12 @@ namespace MediaBrowser.Providers.Subtitles public void Dispose() { _config.ConfigurationUpdating -= _config_ConfigurationUpdating; + + if (_dailyTimer != null) + { + _dailyTimer.Dispose(); + _dailyTimer = null; + } } } } diff --git a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs index ec02d3add9..0546701f39 100644 --- a/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs @@ -165,7 +165,7 @@ namespace MediaBrowser.Providers.TV { cancellationToken.ThrowIfCancellationRequested(); - result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false); + result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.HasMetadata = result.Item != null; } @@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.TV return result; } - private async Task FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken) + private async Task FetchMovieData(string tmdbId, string language, CancellationToken cancellationToken) { string dataFilePath = null; RootObject seriesInfo = null; @@ -198,12 +198,12 @@ namespace MediaBrowser.Providers.TV var item = new Series(); - ProcessMainInfo(item, preferredCountryCode, seriesInfo); + ProcessMainInfo(item, seriesInfo); return item; } - private void ProcessMainInfo(Series series, string countryCode, RootObject seriesInfo) + private void ProcessMainInfo(Series series, RootObject seriesInfo) { series.Name = seriesInfo.name; series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.id.ToString(_usCulture)); @@ -231,7 +231,7 @@ namespace MediaBrowser.Providers.TV } series.HomePageUrl = seriesInfo.homepage; - + series.RunTimeTicks = seriesInfo.episode_run_time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); if (string.Equals(seriesInfo.status, "Ended", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index 91bb1cbe61..c02ad177f3 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -384,11 +384,15 @@ namespace MediaBrowser.Server.Implementations.Channels { var val = width.Value; - return list + var res = list .OrderBy(i => i.Width.HasValue && i.Width.Value <= val) - .ThenBy(i => Math.Abs(i.Width ?? 0 - val)) + .ThenBy(i => Math.Abs((i.Width ?? 0) - val)) .ThenByDescending(i => i.Width ?? 0) - .ThenBy(list.IndexOf); + .ThenBy(list.IndexOf) + .ToList(); + + + return res; } return list @@ -533,6 +537,11 @@ namespace MediaBrowser.Server.Implementations.Channels ? null : _userManager.GetUserById(new Guid(query.UserId)); + if (!string.IsNullOrWhiteSpace(query.UserId) && user == null) + { + throw new ArgumentException("User not found."); + } + var channels = _channels; if (query.ChannelIds.Length > 0) diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 59267e8569..404c333c2c 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,8 +1,13 @@ -using MediaBrowser.Common.Configuration; +using System.Linq; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Implementations.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; @@ -205,5 +210,55 @@ namespace MediaBrowser.Server.Implementations.Configuration } } } + + public void SetPreferredMetadataService(string service) + { + DisableMetadataService(typeof(Movie), Configuration, service); + DisableMetadataService(typeof(MusicAlbum), Configuration, service); + DisableMetadataService(typeof(MusicArtist), Configuration, service); + DisableMetadataService(typeof(Episode), Configuration, service); + DisableMetadataService(typeof(Season), Configuration, service); + DisableMetadataService(typeof(Series), Configuration, service); + DisableMetadataService(typeof(MusicVideo), Configuration, service); + DisableMetadataService(typeof(Trailer), Configuration, service); + DisableMetadataService(typeof(AdultVideo), Configuration, service); + DisableMetadataService(typeof(Video), Configuration, service); + } + + private void DisableMetadataService(Type type, ServerConfiguration config, string service) + { + var options = GetMetadataOptions(type, config); + + if (!options.DisabledMetadataSavers.Contains(service, StringComparer.OrdinalIgnoreCase)) + { + var list = options.DisabledMetadataSavers.ToList(); + + list.Add(service); + + options.DisabledMetadataSavers = list.ToArray(); + } + } + + private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config) + { + var options = config.MetadataOptions + .FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase)); + + if (options == null) + { + var list = config.MetadataOptions.ToList(); + + options = new MetadataOptions + { + ItemType = type.Name + }; + + list.Add(options); + + config.MetadataOptions = list.ToArray(); + } + + return options; + } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 74ec325c62..2d7c798ade 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security //ExecuteBasic(req, res, requestDto); //first check if session is authenticated //if (res.IsClosed) return; //AuthenticateAttribute already closed the request (ie auth failed) - //ValidateUser(req); + ValidateUser(req); } private void ValidateUser(IRequest req) diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json index 7e35d5d74a..294e293b7d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json index 5d06ba47e0..740d1f6e7b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json index 5fd59ee477..b9b3df10f1 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json @@ -215,5 +215,20 @@ "HeaderName": "N\u00e1zev", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kan\u00e1ly", + "HeaderMediaFolders": "Slo\u017eky m\u00e9di\u00ed", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json index 1d3f195684..21ceabda5f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kanaler", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json index 5b31baa337..546c8c1c44 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unbekannte Sprache", "ButtonMute": "Stumm", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "N\u00e4chster Track", "ButtonPause": "Pause", "ButtonPlay": "Abspielen", "ButtonEdit": "Bearbeiten", "ButtonQueue": "Warteschlange", "ButtonPlayTrailer": "Spiele Trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Vorheriger Track", "LabelEnabled": "Aktivieren", "LabelDisabled": "Deaktivieren", "ButtonMoreInformation": "mehr Informationen", @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kan\u00e4le", + "HeaderMediaFolders": "Medien Ordner", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json index 5305e1c881..4c441571d4 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json index 8f1c2cc5af..a3f0038c74 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json index ba55ada75e..9ae851d952 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json index 1073d16cf2..55fbf236d9 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_ES.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Idioma desconocido", "ButtonMute": "Silencio", "ButtonUnmute": "Activar audio", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pista siguiente", "ButtonPause": "Pausa", "ButtonPlay": "Reproducir", "ButtonEdit": "Editar", "ButtonQueue": "En cola", "ButtonPlayTrailer": "Reproducir trailer", "ButtonPlaylist": "Lista de reproducci\u00f3n", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Pista anterior", "LabelEnabled": "Activado", "LabelDisabled": "Desactivado", "ButtonMoreInformation": "M\u00e1s informaci\u00f3n", @@ -215,5 +215,20 @@ "HeaderName": "Nombre", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Artista del album", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "A\u00f1adido {0}", + "ButtonStart": "Inicio", + "HeaderChannels": "Canales", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderBlockItemsWithNoRating": "Bloquear elementos sin informaci\u00f3n de clasificaci\u00f3n:", + "OptionBlockOthers": "Otros", + "OptionBlockTvShows": "Tv Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "M\u00fasica", + "OptionBlockMovies": "Pel\u00edculas", + "OptionBlockBooks": "Libros", + "OptionBlockGames": "Juegos", + "OptionBlockLiveTvPrograms": "Programas de TV en vivo", + "OptionBlockLiveTvChannels": "Canales de Tv en vivo", + "OptionBlockChannelContent": "Contenido de canales de Internet" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json index 1dbe5993cf..3f87eeaaed 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Idioma Desconocido", "ButtonMute": "Mudo", "ButtonUnmute": "Quitar mudo", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pista Siguiente", "ButtonPause": "Pausar", "ButtonPlay": "Reproducir", "ButtonEdit": "Editar", "ButtonQueue": "A cola", "ButtonPlayTrailer": "Reproducir Avance", "ButtonPlaylist": "Lista de Reprod.", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Pista Anterior", "LabelEnabled": "Habilitado", "LabelDisabled": "Deshabilitado", "ButtonMoreInformation": "Mas Informaci\u00f3n", @@ -143,7 +143,7 @@ "HeaderSelectChannelDownloadPath": "Selecciona una ruta para la descarga del canal", "HeaderSelectChannelDownloadPathHelp": "Navega o entra en la ruta usada para almacenar los archivos temporales del canal. La carpeta debe tener permisos de escritura.", "OptionNewCollection": "Nuevo...", - "ButtonAdd": "A\u00f1adir", + "ButtonAdd": "Agregar", "ButtonRemove": "Eliminar", "LabelChapterDownloaders": "Descargadores de Cap\u00edtulos:", "LabelChapterDownloadersHelp": "Habilite y califique sus descargadores de cap\u00edtulos preferidos en orden de prioridad. Los descargadores con menor prioridad s\u00f3lo seran utilizados para completar informaci\u00f3n faltante.", @@ -215,5 +215,20 @@ "HeaderName": "Nombre", "HeaderAlbum": "\u00c1lbum", "HeaderAlbumArtist": "Artista del \u00c1lbum", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "Agregado {0}", + "ButtonStart": "Iniciar", + "HeaderChannels": "Canales", + "HeaderMediaFolders": "Carpetas de Medios", + "HeaderBlockItemsWithNoRating": "Bloquear \u00edtems sin informaci\u00f3n de clasificaci\u00f3n", + "OptionBlockOthers": "Otros", + "OptionBlockTvShows": "Programas de TV", + "OptionBlockTrailers": "Avances", + "OptionBlockMusic": "M\u00fasica", + "OptionBlockMovies": "Pel\u00edculas", + "OptionBlockBooks": "Libros", + "OptionBlockGames": "Juegos", + "OptionBlockLiveTvPrograms": "Programas de TV en Vivo", + "OptionBlockLiveTvChannels": "Canales de TV en Vivo", + "OptionBlockChannelContent": "Contenido de Canales de Internet" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json index aaf35f77cf..7a020ab305 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Langue inconnue", "ButtonMute": "Sourdine", "ButtonUnmute": "D\u00e9sactiver sourdine", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Piste suivante", "ButtonPause": "Pause", "ButtonPlay": "Lire", "ButtonEdit": "Modifier", "ButtonQueue": "En file d'attente", "ButtonPlayTrailer": "Lire bande-annonce", "ButtonPlaylist": "Liste de lecture", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Piste pr\u00e9c\u00e9dante", "LabelEnabled": "Activ\u00e9", "LabelDisabled": "D\u00e9sactiv\u00e9", "ButtonMoreInformation": "Plus d'information", @@ -215,5 +215,20 @@ "HeaderName": "Nom", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Cha\u00eenes", + "HeaderMediaFolders": "R\u00e9pertoires de m\u00e9dias", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json index 29e6dc70f5..59f4d83e9f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json @@ -215,5 +215,20 @@ "HeaderName": "\u05e9\u05dd", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "\u05e2\u05e8\u05d5\u05e6\u05d9\u05dd", + "HeaderMediaFolders": "\u05e1\u05e4\u05e8\u05d9\u05d5\u05ea \u05de\u05d3\u05d9\u05d4", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json index fcbe94376f..8ef22a7701 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "(linguaggio sconosciuto)", "ButtonMute": "Muto", "ButtonUnmute": "Togli muto", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Prossimo", "ButtonPause": "Pausa", "ButtonPlay": "Riproduci", "ButtonEdit": "Modifica", "ButtonQueue": "In coda", "ButtonPlayTrailer": "Visualizza trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Precedente", "LabelEnabled": "Abilitato", "LabelDisabled": "Disabilitato", "ButtonMoreInformation": "Maggiori informazioni", @@ -215,5 +215,20 @@ "HeaderName": "Nome", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Artista Album", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Canali", + "HeaderMediaFolders": "Cartelle dei media", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json index 36588f2ec0..22d1d91a36 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "\u0411\u0435\u043b\u0433\u0456\u0441\u0456\u0437 \u0442\u0456\u043b", "ButtonMute": "\u0414\u044b\u0431\u044b\u0441\u0442\u044b \u04e9\u0448\u0456\u0440\u0443", "ButtonUnmute": "\u0414\u044b\u0431\u044b\u0441\u0442\u044b \u049b\u043e\u0441\u0443", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "\u041a\u0435\u043b\u0435\u0441\u0456 \u0436\u043e\u043b\u0448\u044b\u049b", "ButtonPause": "\u04ae\u0437\u0456\u043b\u0456\u0441", "ButtonPlay": "\u041e\u0439\u043d\u0430\u0442\u0443", "ButtonEdit": "\u04e8\u04a3\u0434\u0435\u0443", "ButtonQueue": "\u041a\u0435\u0437\u0435\u043a", "ButtonPlayTrailer": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u0434\u0456 \u043e\u0439\u043d\u0430\u0442\u0443", "ButtonPlaylist": "\u041e\u0439\u043d\u0430\u0442\u0443 \u0442\u0456\u0437\u0456\u043c\u0456", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "\u0410\u043b\u0434\u044b\u04a3\u0493\u044b \u0436\u043e\u043b\u0448\u044b\u049b", "LabelEnabled": "\u049a\u043e\u0441\u044b\u043b\u0493\u0430\u043d", "LabelDisabled": "\u0410\u0436\u044b\u0440\u0430\u0442\u044b\u043b\u0493\u0430\u043d", "ButtonMoreInformation": "\u041a\u04e9\u0431\u0456\u0440\u0435\u043a \u0430\u049b\u043f\u0430\u0440\u0430\u0442", @@ -215,5 +215,20 @@ "HeaderName": "\u0410\u0442\u044b", "HeaderAlbum": "\u0410\u043b\u044c\u0431\u043e\u043c", "HeaderAlbumArtist": "\u0410\u043b\u044c\u0431\u043e\u043c \u043e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b\u0441\u044b", - "HeaderArtist": "\u041e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b" + "HeaderArtist": "\u041e\u0440\u044b\u043d\u0434\u0430\u0443\u0448\u044b", + "LabelAddedOnDate": "\u04ae\u0441\u0442\u0435\u043b\u0433\u0435\u043d\u0456 {0}", + "ButtonStart": "\u0411\u0430\u0441\u0442\u0430\u0443", + "HeaderChannels": "\u0410\u0440\u043d\u0430\u043b\u0430\u0440", + "HeaderMediaFolders": "\u0422\u0430\u0441\u0443\u0448\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440\u044b", + "HeaderBlockItemsWithNoRating": "\u0416\u0430\u0441\u0442\u044b\u049b \u0441\u0430\u043d\u0430\u0442\u044b \u0436\u043e\u049b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0442\u0435\u0440\u0434\u0456 \u049b\u04b1\u0440\u0441\u0430\u0443\u043b\u0430\u0443:", + "OptionBlockOthers": "\u0411\u0430\u0441\u049b\u0430\u043b\u0430\u0440", + "OptionBlockTvShows": "\u0421\u0435\u0440\u0438\u0430\u043b\u0434\u0430\u0440", + "OptionBlockTrailers": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u043b\u0435\u0440", + "OptionBlockMusic": "\u041c\u0443\u0437\u044b\u043a\u0430", + "OptionBlockMovies": "\u0424\u0438\u043b\u044c\u043c\u0434\u0435\u0440", + "OptionBlockBooks": "\u041a\u0456\u0442\u0430\u043f\u0442\u0430\u0440", + "OptionBlockGames": "\u041e\u0439\u044b\u043d\u0434\u0430\u0440", + "OptionBlockLiveTvPrograms": "\u042d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414-\u0442\u0430\u0440\u0430\u0442\u044b\u043c\u0434\u0430\u0440", + "OptionBlockLiveTvChannels": "\u042d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414-\u0430\u0440\u043d\u0430\u043b\u0430\u0440", + "OptionBlockChannelContent": "\u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json index fa186607ac..b863a6c6e8 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json index 7f9156ef52..acf1182f72 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json index 5545b69d2e..e11fd37bec 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Onbekende taal", "ButtonMute": "Dempen", "ButtonUnmute": "Dempen opheffen", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Volgend nummer", "ButtonPause": "Pauze", "ButtonPlay": "Afspelen", "ButtonEdit": "Bewerken", "ButtonQueue": "Wachtrij", "ButtonPlayTrailer": "Trailer Afspelen", "ButtonPlaylist": "Afspeellijst", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Vorig nummer", "LabelEnabled": "Ingeschakeld", "LabelDisabled": "Uitgeschakeld", "ButtonMoreInformation": "Meer informatie", @@ -215,5 +215,20 @@ "HeaderName": "Naam", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artiest", - "HeaderArtist": "Artiest" + "HeaderArtist": "Artiest", + "LabelAddedOnDate": "Toegevoegd {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kanalen", + "HeaderMediaFolders": "Media Mappen", + "HeaderBlockItemsWithNoRating": "Blokkeren van onderdelen zonder classificatiegegevens:", + "OptionBlockOthers": "Overigen", + "OptionBlockTvShows": "TV Series", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Muziek", + "OptionBlockMovies": "Films", + "OptionBlockBooks": "Boeken", + "OptionBlockGames": "Spellen", + "OptionBlockLiveTvPrograms": "Live TV Programma's", + "OptionBlockLiveTvChannels": "Live TV Kanalen", + "OptionBlockChannelContent": "Internet kanaal Inhoud" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json index b29940c31b..729e20b316 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json index 225172c813..16407aa48d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Idioma desconhecido", "ButtonMute": "Mudo", "ButtonUnmute": "Remover Mudo", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pr\u00f3xima faixa", "ButtonPause": "Pausar", "ButtonPlay": "Reproduzir", "ButtonEdit": "Editar", "ButtonQueue": "Fila", "ButtonPlayTrailer": "Reproduzir trailer", "ButtonPlaylist": "Lista reprodu\u00e7\u00e3o", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Faixa anterior", "LabelEnabled": "Ativada", "LabelDisabled": "Desativada", "ButtonMoreInformation": "Mais informa\u00e7\u00f5es", @@ -215,5 +215,20 @@ "HeaderName": "Nome", "HeaderAlbum": "\u00c1lbum", "HeaderAlbumArtist": "Artista do \u00c1lbum", - "HeaderArtist": "Artista" + "HeaderArtist": "Artista", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Canais", + "HeaderMediaFolders": "Pastas de M\u00eddia", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json index 6adf54fbd9..9ca378a91f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "Pr\u00f3xima Faixa", "ButtonPause": "Pausar", "ButtonPlay": "Reproduzir", "ButtonEdit": "Editar", "ButtonQueue": "Queue", "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "Faixa Anterior", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -215,5 +215,20 @@ "HeaderName": "Nome", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Canais", + "HeaderMediaFolders": "Pastas Multim\u00e9dia", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json index 0d6bc9e83a..20eede5412 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -38,7 +38,7 @@ "LabelEpisode": "\u042d\u043f\u0438\u0437\u043e\u0434", "LabelSeries": "\u0421\u0435\u0440\u0438\u0430\u043b", "LabelStopping": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e", - "ButtonStop": "\u0421\u0442\u043e\u043f", + "ButtonStop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c", "LabelCancelled": "(\u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e)", "LabelFailed": "(\u043d\u0435\u0443\u0434\u0430\u0447\u043d\u043e)", "LabelAbortedByServerShutdown": "(\u041f\u0440\u0435\u0440\u0432\u0430\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0430)", @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "\u041d\u0435\u043e\u043f\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u044f\u0437\u044b\u043a", "ButtonMute": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0432\u0443\u043a", "ButtonUnmute": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0432\u0443\u043a", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "ButtonPause": "\u041f\u0430\u0443\u0437\u0430", "ButtonPlay": "\u0412\u043e\u0441\u043f\u0440", "ButtonEdit": "\u041f\u0440\u0430\u0432\u0438\u0442\u044c", "ButtonQueue": "\u041e\u0447\u0435\u0440\u0435\u0434\u044c", "ButtonPlayTrailer": "\u0412\u043e\u0441\u043f\u0440 \u0442\u0440\u0435\u0439\u043b\u0435\u0440", "ButtonPlaylist": "\u0421\u043f\u0438\u0441\u043e\u043a \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "LabelEnabled": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "LabelDisabled": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "ButtonMoreInformation": "\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u0435 \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044f", @@ -215,5 +215,20 @@ "HeaderName": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "HeaderAlbum": "\u0410\u043b\u044c\u0431\u043e\u043c", "HeaderAlbumArtist": "\u0410\u043b\u044c\u0431\u043e\u043c\u043d\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c", - "HeaderArtist": "\u0418\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c" + "HeaderArtist": "\u0418\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c", + "LabelAddedOnDate": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e {0}", + "ButtonStart": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c", + "HeaderChannels": "\u041a\u0430\u043d\u0430\u043b\u044b", + "HeaderMediaFolders": "\u041c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0438", + "HeaderBlockItemsWithNoRating": "\u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0431\u0435\u0437 \u0432\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u043e\u0439 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438:", + "OptionBlockOthers": "\u0414\u0440\u0443\u0433\u0438\u0435", + "OptionBlockTvShows": "\u0421\u0435\u0440\u0438\u0430\u043b\u044b", + "OptionBlockTrailers": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u044b", + "OptionBlockMusic": "\u041c\u0443\u0437\u044b\u043a\u0430", + "OptionBlockMovies": "\u0424\u0438\u043b\u044c\u043c\u044b", + "OptionBlockBooks": "\u041a\u043d\u0438\u0433\u0438", + "OptionBlockGames": "\u0418\u0433\u0440\u044b", + "OptionBlockLiveTvPrograms": "\u042d\u0444\u0438\u0440\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438", + "OptionBlockLiveTvChannels": "\u042d\u0444\u0438\u0440\u043d\u044b\u0435 \u0422\u0412-\u043a\u0430\u043d\u0430\u043b\u044b", + "OptionBlockChannelContent": "\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u043a\u0430\u043d\u0430\u043b\u043e\u0432" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json index 845621b100..153160c4aa 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Ok\u00e4nt spr\u00e5k", "ButtonMute": "Tyst", "ButtonUnmute": "Muting av", - "ButtonNextTrack": "Next Track", + "ButtonNextTrack": "N\u00e4sta sp\u00e5r", "ButtonPause": "Paus", "ButtonPlay": "Spela upp", "ButtonEdit": "\u00c4ndra", "ButtonQueue": "K\u00f6", "ButtonPlayTrailer": "Spela upp trailer", "ButtonPlaylist": "Spellista", - "ButtonPreviousTrack": "Previous Track", + "ButtonPreviousTrack": "F\u00f6reg\u00e5ende sp\u00e5r", "LabelEnabled": "Aktiverad", "LabelDisabled": "Avaktiverad", "ButtonMoreInformation": "Mer information", @@ -215,5 +215,20 @@ "HeaderName": "Namn", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Albumartist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Kanaler", + "HeaderMediaFolders": "Mediamappar", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json index 8a10fdc558..7405894d47 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json @@ -215,5 +215,20 @@ "HeaderName": "T\u00ean", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "Channels", + "HeaderMediaFolders": "Media Folders", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json index abf7a395a9..1da78b98d0 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -215,5 +215,20 @@ "HeaderName": "Name", "HeaderAlbum": "Album", "HeaderAlbumArtist": "Album Artist", - "HeaderArtist": "Artist" + "HeaderArtist": "Artist", + "LabelAddedOnDate": "Added {0}", + "ButtonStart": "Start", + "HeaderChannels": "\u983b\u5ea6", + "HeaderMediaFolders": "\u5a92\u9ad4\u6587\u4ef6\u593e", + "HeaderBlockItemsWithNoRating": "Block items with no rating information:", + "OptionBlockOthers": "Others", + "OptionBlockTvShows": "TV Shows", + "OptionBlockTrailers": "Trailers", + "OptionBlockMusic": "Music", + "OptionBlockMovies": "Movies", + "OptionBlockBooks": "Books", + "OptionBlockGames": "Games", + "OptionBlockLiveTvPrograms": "Live TV Programs", + "OptionBlockLiveTvChannels": "Live TV Channels", + "OptionBlockChannelContent": "Internet Channel Content" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ar.json b/MediaBrowser.Server.Implementations/Localization/Server/ar.json index 53c3edfb16..ecb6abd641 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ca.json b/MediaBrowser.Server.Implementations/Localization/Server/ca.json index 65c50df853..35f607be11 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/cs.json b/MediaBrowser.Server.Implementations/Localization/Server/cs.json index 1f1bea554d..5d25ef870c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json @@ -479,7 +479,7 @@ "HeaderProgram": "Program", "HeaderClients": "Klienti", "LabelCompleted": "Hotovo", - "LabelFailed": "Chyba", + "LabelFailed": "Failed", "LabelSkipped": "P\u0159esko\u010deno", "HeaderEpisodeOrganization": "Episode Organization", "LabelSeries": "Series:", @@ -490,7 +490,7 @@ "HeaderSupportTheTeam": "Podpo\u0159te t\u00edm Media Browser", "LabelSupportAmount": "Suma (USD)", "HeaderSupportTheTeamHelp": "Pomozte zajistit pokra\u010dov\u00e1n\u00ed v\u00fdvoje tohoto projektu t\u00edm, \u017ee daruje. \u010c\u00e1st v\u0161ech dar\u016f bude pou\u017eita na dal\u0161\u00ed bezplatn\u00e9 n\u00e1stroje na kter\u00fdch jsme z\u00e1visl\u00ed.", - "ButtonEnterSupporterKey": "Enter supporter key", + "ButtonEnterSupporterKey": "Vlo\u017ete kl\u00ed\u010d sponzora", "DonationNextStep": "Once complete, please return and enter your supporter key, which you will receive by email.", "AutoOrganizeHelp": "Auto-organize monitors your download folders for new files and moves them to your media directories.", "AutoOrganizeTvHelp": "TV file organizing will only add episodes to existing series. It will not create new series folders.", @@ -529,7 +529,7 @@ "ButtonShutdown": "Vypnout", "ButtonUpdateNow": "Update Now", "PleaseUpdateManually": "Please shutdown the server and update manually.", - "NewServerVersionAvailable": "A new version of Media Browser Server is available!", + "NewServerVersionAvailable": "Je dostupn\u00e1 nov\u00e1 verze programu Media Browser!", "ServerUpToDate": "Media Browser Server is up to date", "ErrorConnectingToMediaBrowserRepository": "There was an error connecting to the remote Media Browser repository.", "LabelComponentsUpdated": "The following components have been installed or updated:", @@ -537,14 +537,14 @@ "LabelDownMixAudioScale": "Audio boost when downmixing:", "LabelDownMixAudioScaleHelp": "Boost audio when downmixing. Set to 1 to preserve original volume value.", "ButtonLinkKeys": "Link Keys", - "LabelOldSupporterKey": "Old supporter key", - "LabelNewSupporterKey": "New supporter key", + "LabelOldSupporterKey": "Star\u00fd kl\u00ed\u010d sponzora", + "LabelNewSupporterKey": "Nov\u00fd kl\u00ed\u010d sponzora", "HeaderMultipleKeyLinking": "Multiple Key Linking", "MultipleKeyLinkingHelp": "If you have more than one supporter key, use this form to link the old key's registrations with your new one.", - "LabelCurrentEmailAddress": "Current email address", + "LabelCurrentEmailAddress": "Aktu\u00e1ln\u00ed e-mailov\u00e1 adresa", "LabelCurrentEmailAddressHelp": "The current email address to which your new key was sent.", "HeaderForgotKey": "Forgot Key", - "LabelEmailAddress": "Email address", + "LabelEmailAddress": "E-mailov\u00e1 adresa", "LabelSupporterEmailAddress": "The email address that was used to purchase the key.", "ButtonRetrieveKey": "Retrieve Key", "LabelSupporterKey": "Supporter Key (paste from email)", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/da.json b/MediaBrowser.Server.Implementations/Localization/Server/da.json index c5bf75c0ff..8f7bca7868 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json index 151156fbf5..0338299860 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -479,10 +479,10 @@ "HeaderProgram": "Programm", "HeaderClients": "Clients", "LabelCompleted": "Fertiggestellt", - "LabelFailed": "Gescheitert", + "LabelFailed": "Failed", "LabelSkipped": "\u00dcbersprungen", "HeaderEpisodeOrganization": "Episodensortierung", - "LabelSeries": "Serien:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Staffelnummer", "LabelEpisodeNumber": "Episodennummer", "LabelEndingEpisodeNumber": "Ending episode number", @@ -630,8 +630,8 @@ "ButtonScenes": "Szenen", "ButtonSubtitles": "Untertitel", "ButtonAudioTracks": "Audiospuren", - "ButtonPreviousTrack": "Vorheriger Track", - "ButtonNextTrack": "N\u00e4chster Track", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pause", "LabelGroupMoviesIntoCollections": "Gruppiere Filme in Collections", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/el.json b/MediaBrowser.Server.Implementations/Localization/Server/el.json index f590a2f64f..fb43d18778 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json index 051387ea95..202a376829 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json index 60557cdca8..764685deb3 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json b/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json index fbd39b960e..5a631a8d67 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_ES.json @@ -479,10 +479,10 @@ "HeaderProgram": "Programa", "HeaderClients": "Clientes", "LabelCompleted": "Completado", - "LabelFailed": "Err\u00f3neo", + "LabelFailed": "Error", "LabelSkipped": "Omitido", "HeaderEpisodeOrganization": "Organizaci\u00f3n de episodios", - "LabelSeries": "Serie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Temporada n\u00famero:", "LabelEpisodeNumber": "Episodio n\u00famero:", "LabelEndingEpisodeNumber": "N\u00famero episodio final:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login renuncia:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente esta cantidad cada mes", - "LabelAutomaticallyDonateHelp": "Usted puede cancelar en cualquier momento desde su cuenta de PayPal." + "LabelAutomaticallyDonateHelp": "Usted puede cancelar en cualquier momento desde su cuenta de PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json index c8f3bcb05e..e5390cef5f 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -132,7 +132,7 @@ "OptionReleaseDate": "Fecha de Estreno", "OptionPlayCount": "N\u00famero de Reproducc.", "OptionDatePlayed": "Fecha de Reproducci\u00f3n", - "OptionDateAdded": "A\u00f1adido el", + "OptionDateAdded": "Agregardo el", "OptionAlbumArtist": "Artista del \u00c1lbum", "OptionArtist": "Artista", "OptionAlbum": "\u00c1lbum", @@ -234,7 +234,7 @@ "ButtonSelect": "Seleccionar", "ButtonSearch": "B\u00fasqueda", "ButtonGroupVersions": "Agrupar Versiones", - "ButtonAddToCollection": "Agregar a Colecci\u00f3n.", + "ButtonAddToCollection": "Agregar a Colecci\u00f3n", "PismoMessage": "Utilizando Pismo File Mount a trav\u00e9s de una licencia donada.", "TangibleSoftwareMessage": "Utilizando convertidores Java\/C# de Tangible Solutions por medio de una licencia donada.", "HeaderCredits": "Cr\u00e9ditos", @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "Al habilitarlo, se descargar\u00e1n autom\u00e1ticamente nuevas im\u00e1genes conforme son a\u00f1adidas a fanart.tv. Las Im\u00e1genes existentes no ser\u00e1n reemplazadas.", "LabelAutomaticUpdatesTmdbHelp": "Al habilitarlo, se descargar\u00e1n autom\u00e1ticamente nuevas im\u00e1genes conforme son a\u00f1adidas a TheMovieDB.org. Las im\u00e1genes existentes no ser\u00e1n reemplazadas.", "LabelAutomaticUpdatesTvdbHelp": "Al habilitarlo, se descargar\u00e1n autom\u00e1ticamente nuevas im\u00e1genes conforme son a\u00f1adidas a TheTVDB.com. Las im\u00e1genes existentes no ser\u00e1n reemplazadas.", - "ExtractChapterImagesHelp": "Extraer im\u00e1genes de cap\u00edtulos permite a los clientes mostrar men\u00fas gr\u00e1ficos de selecci\u00f3n de escenas. El proceso puede ser lento, intensivo en utilizaci\u00f3n del CPU y puede requerir varios gigabytes de espacio. Se ejecuta como una tarea nocturna, programada a las 4 de la ma\u00f1ana, aunque esto se puede configurar en el \u00e1rea de tareas programadas. No se recomienda ejecutar esta tarea durante las horas pico de uso.", + "ExtractChapterImagesHelp": "Extraer im\u00e1genes de cap\u00edtulos permite a los clientes mostrar men\u00fas gr\u00e1ficos de selecci\u00f3n de escenas. El proceso puede ser lento, intensivo en utilizaci\u00f3n del CPU y puede requerir varios gigabytes de espacio. Se ejecuta cuando se detectan nuevos videos, tambi\u00e9n como una tarea nocturna, programada a las 4:00 am. La programaci\u00f3n puede configurarse en el \u00e1rea de tareas programadas. No se recomienda ejecutar esta tarea durante horas pico de uso.", "LabelMetadataDownloadLanguage": "Lenguaje preferido para descargas:", "ButtonAutoScroll": "Auto-desplazamiento", "LabelImageSavingConvention": "Convenci\u00f3n de almacenamiento de im\u00e1genes:", @@ -353,9 +353,9 @@ "LabelMaxScreenshotsPerItem": "N\u00famero m\u00e1ximo de capturas de pantalla por \u00edtem:", "LabelMinBackdropDownloadWidth": "Anchura m\u00ednima de descarga de im\u00e1genes de fondo:", "LabelMinScreenshotDownloadWidth": "Anchura m\u00ednima de descarga de capturas de pantalla:", - "ButtonAddScheduledTaskTrigger": "A\u00f1adir Evento", - "HeaderAddScheduledTaskTrigger": "A\u00f1adir Evento", - "ButtonAdd": "A\u00f1adir", + "ButtonAddScheduledTaskTrigger": "Agregar Disparador de Tarea", + "HeaderAddScheduledTaskTrigger": "Agregar Disparador de Tarea", + "ButtonAdd": "Agregar", "LabelTriggerType": "Tipo de Evento:", "OptionDaily": "Diario", "OptionWeekly": "Semanal", @@ -406,7 +406,7 @@ "LabelFromHelp": "Ejemplo: D:\\Pel\u00edculas (en el servidor)", "LabelTo": "Hasta:", "LabelToHelp": "Ejemplo: \\\\MiServidor\\Pel\u00edculas (una ruta a la que los clientes pueden acceder)", - "ButtonAddPathSubstitution": "A\u00f1adir Ruta Alternativa", + "ButtonAddPathSubstitution": "Agregar Ruta Alternativa", "OptionSpecialEpisode": "Especiales", "OptionMissingEpisode": "Episodios Faltantes", "OptionUnairedEpisode": "Episodios no Emitidos", @@ -425,7 +425,7 @@ "OptionEnableDebugTranscodingLoggingHelp": "Esto crear\u00e1 archivos de bit\u00e1cora muy grandes y solo se recomienda cuando se requiera solucionar problemas.", "OptionUpscaling": "Permitir que los clientes solicitar v\u00eddeo de escala aumentada", "OptionUpscalingHelp": "En algunos casos esto resultar\u00e1 en una mejora de la calidad del video pero incrementar\u00e1 el uso del CPU.", - "EditCollectionItemsHelp": "A\u00f1adir o quitar pel\u00edculas, series, discos, libros o juegos que usted desee agrupar dentro de esta colecci\u00f3n.", + "EditCollectionItemsHelp": "Agregar o quitar pel\u00edculas, series, discos, libros o juegos que usted desee agrupar dentro de esta colecci\u00f3n.", "HeaderAddTitles": "Agregar T\u00edtulos", "LabelEnableDlnaPlayTo": "Habilitar \"Reproducir en\" por DLNA", "LabelEnableDlnaPlayToHelp": "Media Browser puede detectar dispositivos en su red y ofrecer la posibilidad de controlarlos remotamente.", @@ -587,8 +587,8 @@ "NotificationOptionGamePlaybackStopped": "Ejecuci\u00f3n de juego detenida", "NotificationOptionTaskFailed": "Falla de tarea programada", "NotificationOptionInstallationFailed": "Falla de instalaci\u00f3n", - "NotificationOptionNewLibraryContent": "Adici\u00f3n de nuevos contenidos", - "NotificationOptionNewLibraryContentMultiple": "Nuevo contenido agregado (varios).", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "NotificationOptionNewLibraryContentMultiple": "Nuevo contenido agregado (varios)", "SendNotificationHelp": "Por defecto, las notificaciones son enviadas a la bandeja de entrada del panel de control. Navegue el cat\u00e1logo de complementos para instalar opciones de notificaci\u00f3n adicionales.", "NotificationOptionServerRestartRequired": "Reinicio del servidor requerido", "LabelNotificationEnabled": "Habilitar esta notificaci\u00f3n", @@ -630,8 +630,8 @@ "ButtonScenes": "Escenas", "ButtonSubtitles": "Subt\u00edtulos", "ButtonAudioTracks": "Pistas de audio", - "ButtonPreviousTrack": "Pista Anterior", - "ButtonNextTrack": "Pista Siguiente", + "ButtonPreviousTrack": "Pista anterior", + "ButtonNextTrack": "Pista siguiente", "ButtonStop": "Detener", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Agrupar pel\u00edculas en colecciones", @@ -702,8 +702,8 @@ "LabelSerialNumber": "N\u00famero de serie:", "LabelDeviceDescription": "Descripci\u00f3n del dispositivo", "HeaderIdentificationCriteriaHelp": "Capture, al menos, un criterio de identificaci\u00f3n.", - "HeaderDirectPlayProfileHelp": "A\u00f1ada perfiles de reproducci\u00f3n directa para indicar que formatos puede manejar el dispositivo de manera nativa.", - "HeaderTranscodingProfileHelp": "A\u00f1ada perfiles de transcodificaci\u00f3n para indicar que formatos deben ser usados cuando se requiera transcodificar.", + "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducci\u00f3n directa para indicar que formatos puede manejar el dispositivo de manera nativa.", + "HeaderTranscodingProfileHelp": "Agruegue perfiles de transcodificaci\u00f3n para indicar que formatos deben ser usados cuando se requiera transcodificar.", "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la informaci\u00f3n enviada a un dispositivo cuando se reproducen ciertos tipos de medios.", "LabelXDlnaCap": "X-DLNA cap:", "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.", @@ -824,7 +824,7 @@ "LabelProtocolInfoHelp": "El valor que ser\u00e1 utilizado cuando se responde a solicitudes GetProtocolInfo desde el dispositivo.", "TabXbmcMetadata": "Xbmc", "HeaderXbmcMetadataHelp": "Media Browser incluye soporte nativo para metadatos Nfo e im\u00e1genes de Xbmc. Para habilitar o deshabilitar metadatos de Xbmc, utilice la pesta\u00f1a Avanzado para configurar opciones para sus tipos de medios.", - "LabelXbmcMetadataUser": "A\u00f1adir usuario de monitoreo de datos a los nfo\u00b4s para:", + "LabelXbmcMetadataUser": "Agregar usuario de monitoreo de datos a los nfo\u00b4s para:", "LabelXbmcMetadataUserHelp": "Habilitar esto para mantener el monitoreo de datos en sincron\u00eda entre Media Browser y Xbmc.", "LabelXbmcMetadataDateFormat": "Formato de fecha de esteno:", "LabelXbmcMetadataDateFormatHelp": "Todas las fechas en los archivos nfo's ser\u00e1n le\u00eddas y escritas empleando este formato.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Aviso de Inicio de Sesi\u00f3n:", "LabelLoginDisclaimerHelp": "Esto se mostrara al final de la pagina de inicio de sesi\u00f3n.", "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente esta cantidad cada mes", - "LabelAutomaticallyDonateHelp": "Puedes cancelarlo en cualquier momento por medio de tu cuenta PayPal." + "LabelAutomaticallyDonateHelp": "Puedes cancelarlo en cualquier momento por medio de tu cuenta PayPal.", + "OptionList": "Lista", + "TabDashboard": "Panel de Control", + "TitleServer": "Servidor", + "LabelCache": "Cach\u00e9:", + "LabelLogs": "Bit\u00e1coras:", + "LabelMetadata": "Metadatos:", + "LabelImagesByName": "Im\u00e1genes por nombre:", + "LabelTranscodingTemporaryFiles": "Archivos temporales de transcodificaci\u00f3n:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json index 05b8f568d9..90a412bfc2 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -396,7 +396,7 @@ "HeaderCastCrew": "\u00c9quipe de tournage", "HeaderAdditionalParts": "Parties Additionelles", "ButtonSplitVersionsApart": "S\u00e9parer les versions", - "ButtonPlayTrailer": "Bande-annonce", + "ButtonPlayTrailer": "Trailer", "LabelMissing": "Manquant(s)", "LabelOffline": "Hors ligne", "PathSubstitutionHelp": "Les substitutions de chemins d'acc\u00e8s sont utilis\u00e9es pour faire correspondre un chemin d'acc\u00e8s du serveur \u00e0 un chemin d'acc\u00e8s accessible par les clients. En autorisant un acc\u00e8s direct aux m\u00e9dias du serveur, les clients pourront les lire directement du r\u00e9seau et \u00e9viter l'utilisation inutiles des ressources du serveur en demandant du transcodage.", @@ -479,10 +479,10 @@ "HeaderProgram": "Programme", "HeaderClients": "Clients", "LabelCompleted": "Compl\u00e9t\u00e9", - "LabelFailed": "\u00c9chec", + "LabelFailed": "Failed", "LabelSkipped": "Saut\u00e9", "HeaderEpisodeOrganization": "Organisation d'\u00e9pisodes", - "LabelSeries": "S\u00e9ries:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Num\u00e9ro de saison", "LabelEpisodeNumber": "Num\u00e9ro d'\u00e9pisode", "LabelEndingEpisodeNumber": "Num\u00e9ro d'\u00e9pisode se terminant", @@ -630,8 +630,8 @@ "ButtonScenes": "Sc\u00e8nes", "ButtonSubtitles": "Sous-titres", "ButtonAudioTracks": "Piste audio", - "ButtonPreviousTrack": "Piste pr\u00e9c\u00e9dante", - "ButtonNextTrack": "Piste suivante", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Arr\u00eat", "ButtonPause": "Pause", "LabelGroupMoviesIntoCollections": "Grouper les films en collections", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/he.json b/MediaBrowser.Server.Implementations/Localization/Server/he.json index 55ed199e13..7ab9173aec 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/he.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json @@ -396,7 +396,7 @@ "HeaderCastCrew": "\u05e9\u05d7\u05e7\u05e0\u05d9\u05dd \u05d5\u05e6\u05d5\u05d5\u05ea", "HeaderAdditionalParts": "\u05d7\u05dc\u05e7\u05d9\u05dd \u05e0\u05d5\u05e1\u05e4\u05d9\u05dd", "ButtonSplitVersionsApart": "\u05e4\u05e6\u05dc \u05d2\u05e8\u05e1\u05d0\u05d5\u05ea \u05d1\u05e0\u05e4\u05e8\u05d3", - "ButtonPlayTrailer": "\u05d8\u05e8\u05d9\u05d9\u05dc\u05e8\u05d9\u05dd", + "ButtonPlayTrailer": "Trailer", "LabelMissing": "\u05d7\u05e1\u05e8", "LabelOffline": "\u05dc\u05d0 \u05de\u05e7\u05d5\u05d5\u05df", "PathSubstitutionHelp": "\u05e0\u05ea\u05d9\u05d1\u05d9\u05dd \u05d7\u05dc\u05d5\u05e4\u05d9\u05d9\u05dd \u05d4\u05dd \u05dc\u05e6\u05d5\u05e8\u05da \u05de\u05d9\u05e4\u05d5\u05d9 \u05e0\u05ea\u05d9\u05d1\u05d9\u05dd \u05d1\u05e9\u05e8\u05ea \u05dc\u05e0\u05ea\u05d9\u05d1\u05d9\u05dd \u05e9\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05d9\u05db\u05d5\u05dc\u05d9\u05dd \u05dc\u05d2\u05e9\u05ea \u05d0\u05dc\u05d9\u05d4\u05dd. \u05e2\u05dc \u05d9\u05d3\u05d9 \u05d4\u05e8\u05e9\u05d0\u05d4 \u05dc\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05d2\u05d9\u05e9\u05d4 \u05d9\u05e9\u05d9\u05e8\u05d4 \u05dc\u05de\u05d3\u05d9\u05d4 \u05d1\u05e9\u05e8\u05ea \u05d0\u05dd \u05d9\u05db\u05d5\u05dc\u05d9\u05dd \u05dc\u05e0\u05d2\u05df \u05d0\u05ea \u05d4\u05e7\u05d1\u05e6\u05d9\u05dd \u05d9\u05e9\u05d9\u05e8\u05d5\u05ea \u05e2\u05dc \u05d2\u05d1\u05d9 \u05d4\u05e8\u05e9\u05ea \u05d5\u05dc\u05d4\u05d9\u05de\u05e0\u05e2 \u05de\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05de\u05e9\u05d0\u05d1\u05d9 \u05d4\u05e9\u05e8\u05ea \u05dc\u05e6\u05d5\u05e8\u05da \u05e7\u05d9\u05d3\u05d5\u05d3 \u05d5\u05e9\u05d9\u05d3\u05d5\u05e8.", @@ -479,10 +479,10 @@ "HeaderProgram": "\u05ea\u05d5\u05db\u05e0\u05d4", "HeaderClients": "\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd", "LabelCompleted": "\u05d4\u05d5\u05e9\u05dc\u05dd", - "LabelFailed": "\u05e0\u05db\u05e9\u05dc", + "LabelFailed": "Failed", "LabelSkipped": "\u05d3\u05d5\u05dc\u05d2", "HeaderEpisodeOrganization": "\u05d0\u05d9\u05e8\u05d2\u05d5\u05df \u05e4\u05e8\u05e7\u05d9\u05dd", - "LabelSeries": "\u05e1\u05d3\u05e8\u05d4:", + "LabelSeries": "Series:", "LabelSeasonNumber": "\u05de\u05e1\u05e4\u05e8 \u05e2\u05d5\u05e0\u05d4:", "LabelEpisodeNumber": "\u05de\u05e1\u05e4\u05e8 \u05e4\u05e8\u05e7:", "LabelEndingEpisodeNumber": "\u05de\u05e1\u05e4\u05e8 \u05e1\u05d9\u05d5\u05dd \u05e4\u05e8\u05e7:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/it.json b/MediaBrowser.Server.Implementations/Localization/Server/it.json index 591fe98b91..026772da57 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -479,10 +479,10 @@ "HeaderProgram": "Programma", "HeaderClients": "Dispositivi", "LabelCompleted": "Completato", - "LabelFailed": "Fallito", + "LabelFailed": "Failed", "LabelSkipped": "Saltato", "HeaderEpisodeOrganization": "Organizzazione Episodi", - "LabelSeries": "Serie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "Numero Stagione:", "LabelEpisodeNumber": "Numero Episodio:", "LabelEndingEpisodeNumber": "Ultimo Episodio Numero:", @@ -630,8 +630,8 @@ "ButtonScenes": "Scene", "ButtonSubtitles": "Sottotitoli", "ButtonAudioTracks": "Traccia Audio", - "ButtonPreviousTrack": "Precedente", - "ButtonNextTrack": "Prossimo", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stop", "ButtonPause": "Pausa", "LabelGroupMoviesIntoCollections": "Raggruppa i film nelle collection", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "Questo verr\u00e0 visualizzato nella parte inferiore della pagina di accesso.", "LabelAutomaticallyDonate": "Donare automaticamente questo importo ogni mese", - "LabelAutomaticallyDonateHelp": "\u00c8 possibile annullare in qualsiasi momento tramite il vostro conto PayPal." + "LabelAutomaticallyDonateHelp": "\u00c8 possibile annullare in qualsiasi momento tramite il vostro conto PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/kk.json b/MediaBrowser.Server.Implementations/Localization/Server/kk.json index f7e73eb8fa..8ec25aea42 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -795,7 +795,7 @@ "MessageLearnHowToCustomize": "\u041e\u0441\u044b \u0431\u0435\u0442\u0442\u0456 \u0436\u0435\u043a\u0435 \u043a\u04e9\u04a3\u0456\u043b\u0433\u0435 \u04b1\u043d\u0430\u0442\u0443 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u049b\u0430\u043b\u0430\u0439 \u0442\u0435\u04a3\u0448\u0435\u0443\u0456\u043d \u04af\u0439\u0440\u0435\u043d\u0456\u04a3\u0456\u0437. \u0422\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0434\u0435\u0440\u0456\u04a3\u0456\u0437\u0434\u0456 \u049b\u0430\u0440\u0430\u0443 \u0436\u04d9\u043d\u0435 \u0436\u0430\u04a3\u0430\u0440\u0442\u0443 \u04af\u0448\u0456\u043d \u044d\u043a\u0440\u0430\u043d\u0434\u0430\u0493\u044b \u0436\u043e\u0493\u0430\u0440\u0493\u044b \u043e\u04a3 \u0431\u04b1\u0440\u044b\u0448\u044b\u043d\u0434\u0430\u0493\u044b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u044b\u049b \u0431\u0435\u043b\u0433\u0456\u0448\u0435\u04a3\u0456\u0437\u0434\u0456 \u0431\u0430\u0441\u044b\u04a3\u044b\u0437.", "ButtonEditOtherUserPreferences": "\u041e\u0441\u044b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0434\u0435\u0440\u0456\u043d \u04e9\u04a3\u0434\u0435\u0443.", "LabelChannelStreamQuality": "\u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0430\u0440\u049b\u044b\u043b\u044b \u0430\u0493\u044b\u043d\u043c\u0435\u043d \u0442\u0430\u0441\u044b\u043c\u0430\u043b\u0434\u0430\u0443 \u0442\u0435\u04a3\u0448\u0435\u043b\u0456\u043c\u0456:", - "LabelChannelStreamQualityHelp": "\u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u043b\u0430\u0440\u0434\u0430, \u0441\u0430\u043f\u0430\u043d\u044b \u0448\u0435\u043a\u0442\u0435\u0443 \u0436\u0430\u0442\u044b\u049b \u0430\u0493\u044b\u043d\u043c\u0435\u043d \u0442\u0430\u0441\u044b\u043c\u0430\u043b\u0434\u0430\u0443 \u04af\u0448\u0456\u043d \u049b\u0430\u043c\u0442\u0430\u043c\u0430\u0441\u044b\u0437 \u0435\u0442\u0433\u0435 \u043a\u04e9\u043c\u0435\u043a\u0442\u0435\u0441\u0443\u0456 \u043c\u04af\u043c\u043a\u0456\u043d.", + "LabelChannelStreamQualityHelp": "\u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u0434\u0430, \u0441\u0430\u043f\u0430\u0441\u044b\u043d \u0448\u0435\u043a\u0442\u0435\u0443\u0456 \u0436\u0430\u0442\u044b\u049b\u0442\u0430\u0443 \u0430\u0493\u044b\u043d\u0434\u044b\u049b \u043e\u0439\u043d\u0430\u0442\u0443 \u04d9\u0441\u0435\u0440\u0456\u043d \u049b\u0430\u043c\u0442\u0430\u043c\u0430\u0441\u044b\u0437 \u0435\u0442\u0443\u0456\u043d\u0435 \u043a\u04e9\u043c\u0435\u043a\u0442\u0435\u0441\u0443\u0456 \u043c\u04af\u043c\u043a\u0456\u043d.", "OptionBestAvailableStreamQuality": "\u049a\u043e\u043b \u0436\u0435\u0442\u0456\u043c\u0434\u0456 \u0435\u04a3 \u0436\u0430\u049b\u0441\u044b", "LabelEnableChannelContentDownloadingFor": "\u0411\u04b1\u043b \u04af\u0448\u0456\u043d \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u044b \u049b\u043e\u0441\u0443:", "LabelEnableChannelContentDownloadingForHelp": "\u041a\u0435\u0439\u0431\u0456\u0440 \u0430\u0440\u043d\u0430\u043b\u0430\u0440 \u049b\u0430\u0440\u0430\u0443\u0434\u044b\u04a3 \u0430\u043b\u0434\u044b\u043d\u0434\u0430 \u043c\u0430\u0437\u043c\u04af\u043d \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u044b \u049b\u043e\u043b\u0434\u0430\u0439\u0434\u044b. \u04e8\u0442\u043a\u0456\u0437\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456 \u0442\u04e9\u043c\u0435\u043d \u043e\u0440\u0442\u0430\u043b\u0430\u0440\u0434\u0430 \u0430\u0440\u043d\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d\u044b\u043d \u0436\u04b1\u043c\u044b\u0441\u0442\u0430\u043d \u0431\u043e\u0441 \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043d\u044b \u049b\u043e\u0441\u044b\u04a3\u044b\u0437. \u041c\u0430\u0437\u043c\u04af\u043d \u0430\u0440\u043d\u0430 \u0436\u04af\u043a\u0442\u0435\u0443 \u0436\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0493\u0430\u043d \u0442\u0430\u043f\u0441\u044b\u0440\u043c\u0430\u0441\u044b \u0431\u04e9\u043b\u0456\u0433\u0456 \u0440\u0435\u0442\u0456\u043d\u0434\u0435 \u0436\u04af\u043a\u0442\u0435\u043b\u0435\u0434\u0456.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "\u041a\u0456\u0440\u0433\u0435\u043d\u0434\u0435\u0433\u0456 \u0435\u0441\u043a\u0435\u0440\u0442\u0443:", "LabelLoginDisclaimerHelp": "\u0411\u04b1\u043b \u043a\u0456\u0440\u0443 \u0431\u0435\u0442\u0456\u043d\u0456\u04a3 \u0442\u04e9\u043c\u0435\u043d\u0456\u043d\u0434\u0435 \u0431\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0435\u0434\u0456.", "LabelAutomaticallyDonate": "\u041e\u0441\u044b \u0441\u043e\u043c\u0430\u043d\u044b \u0430\u0439 \u0441\u0430\u0439\u044b\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u0441\u044b\u0439\u043b\u0430\u0443", - "LabelAutomaticallyDonateHelp": "PayPal \u0435\u0441\u0435\u043f \u0448\u043e\u0442\u044b\u04a3\u044b\u0437 \u0430\u0440\u049b\u044b\u043b\u044b \u043a\u0435\u0437 \u043a\u0435\u043b\u0433\u0435\u043d \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0434\u043e\u0493\u0430\u0440\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456\u04a3\u0456\u0437 \u0431\u0430\u0440." + "LabelAutomaticallyDonateHelp": "PayPal \u0435\u0441\u0435\u043f \u0448\u043e\u0442\u044b\u04a3\u044b\u0437 \u0430\u0440\u049b\u044b\u043b\u044b \u043a\u0435\u0437 \u043a\u0435\u043b\u0433\u0435\u043d \u0443\u0430\u049b\u044b\u0442\u0442\u0430 \u0434\u043e\u0493\u0430\u0440\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u0433\u0456\u04a3\u0456\u0437 \u0431\u0430\u0440.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ms.json b/MediaBrowser.Server.Implementations/Localization/Server/ms.json index b790d5660a..cbde8c27ce 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nb.json b/MediaBrowser.Server.Implementations/Localization/Server/nb.json index 394769b2a1..1bf329cd26 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nl.json b/MediaBrowser.Server.Implementations/Localization/Server/nl.json index 6c58426588..e7094e2888 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -630,8 +630,8 @@ "ButtonScenes": "Scenes", "ButtonSubtitles": "Ondertitels", "ButtonAudioTracks": "Audio tracks", - "ButtonPreviousTrack": "Vorig nummer", - "ButtonNextTrack": "Volgend nummer", + "ButtonPreviousTrack": "Vorige track", + "ButtonNextTrack": "Volgende track", "ButtonStop": "Stop", "ButtonPause": "Pauze", "LabelGroupMoviesIntoCollections": "Groepeer films in verzamelingen", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Aanmeld vrijwaring:", "LabelLoginDisclaimerHelp": "Dit wordt onderaan de login pagina weergegeven.", "LabelAutomaticallyDonate": "Doneer dit bedrag automatisch per maand", - "LabelAutomaticallyDonateHelp": "U kunt dit via uw PayPal account op elk moment annuleren." + "LabelAutomaticallyDonateHelp": "U kunt dit via uw PayPal account op elk moment annuleren.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pl.json b/MediaBrowser.Server.Implementations/Localization/Server/pl.json index b4d49e5b3b..aad8274bff 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json index 96c3429c96..ea1ed04008 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Aviso legal no login:", "LabelLoginDisclaimerHelp": "Isto ser\u00e1 exibido na parte inferior da p\u00e1gina de login.", "LabelAutomaticallyDonate": "Doar automaticamente esta quantidade a cada m\u00eas", - "LabelAutomaticallyDonateHelp": "Voc\u00ea pode cancelar a qualquer hora atrav\u00e9s de sua conta do PayPal." + "LabelAutomaticallyDonateHelp": "Voc\u00ea pode cancelar a qualquer hora atrav\u00e9s de sua conta do PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json index 87c24c117d..dfc818e7d2 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -479,10 +479,10 @@ "HeaderProgram": "Programa", "HeaderClients": "Clientes", "LabelCompleted": "Terminado", - "LabelFailed": "Falhou", + "LabelFailed": "Failed", "LabelSkipped": "Ignorado", "HeaderEpisodeOrganization": "Organiza\u00e7\u00e3o dos Epis\u00f3dios", - "LabelSeries": "S\u00e9rie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "N\u00famero da temporada", "LabelEpisodeNumber": "N\u00famero do epis\u00f3dio", "LabelEndingEpisodeNumber": "N\u00famero do epis\u00f3dio final", @@ -630,8 +630,8 @@ "ButtonScenes": "Cenas", "ButtonSubtitles": "Legendas", "ButtonAudioTracks": "Faixas de \u00e1udio", - "ButtonPreviousTrack": "Faixa Anterior", - "ButtonNextTrack": "Pr\u00f3xima Faixa", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Parar", "ButtonPause": "Pausar", "LabelGroupMoviesIntoCollections": "Group movies into collections", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json index c1150bc2b3..eb58b98bce 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -479,7 +479,7 @@ "HeaderProgram": "\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430", "HeaderClients": "\u041a\u043b\u0438\u0435\u043d\u0442\u044b", "LabelCompleted": "\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e", - "LabelFailed": "\u041d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e", + "LabelFailed": "\u041d\u0435\u0443\u0434\u0430\u0447\u0430", "LabelSkipped": "\u041e\u0442\u043b\u043e\u0436\u0435\u043d\u043e", "HeaderEpisodeOrganization": "\u0420\u0435\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u044d\u043f\u0438\u0437\u043e\u0434\u0430", "LabelSeries": "\u0421\u0435\u0440\u0438\u0430\u043b:", @@ -632,7 +632,7 @@ "ButtonAudioTracks": "\u0410\u0443\u0434\u0438\u043e\u0434\u043e\u0440\u043e\u0436\u043a\u0438", "ButtonPreviousTrack": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", "ButtonNextTrack": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u043a\u0430", - "ButtonStop": "\u0421\u0442\u043e\u043f", + "ButtonStop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c", "ButtonPause": "\u041f\u0430\u0443\u0437\u0430", "LabelGroupMoviesIntoCollections": "\u0413\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0438\u043b\u044c\u043c\u044b \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439", "LabelGroupMoviesIntoCollectionsHelp": "\u041f\u0440\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0444\u0438\u043b\u044c\u043c\u043e\u0432, \u0444\u0438\u043b\u044c\u043c\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u043a\u0430\u043a \u0435\u0434\u0438\u043d\u044b\u0439 \u0441\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442.", @@ -795,7 +795,7 @@ "MessageLearnHowToCustomize": "\u0423\u0437\u043d\u0430\u0439\u0442\u0435, \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c\u0443 \u0432\u043a\u0443\u0441\u0443. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u0430\u0432\u0430\u0442\u0430\u0440\u0443 \u0432 \u043f\u0440\u0430\u0432\u043e\u043c \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0443\u0433\u043b\u0443 \u044d\u043a\u0440\u0430\u043d\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", "ButtonEditOtherUserPreferences": "\u041f\u0440\u0430\u0432\u0438\u0442\u044c \u043b\u0438\u0447\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "LabelChannelStreamQuality": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442:", - "LabelChannelStreamQualityHelp": "\u0412 \u0441\u0440\u0435\u0434\u0435 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043b\u0430\u0432\u043d\u0443\u044e \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438.", + "LabelChannelStreamQualityHelp": "\u0412 \u0441\u0440\u0435\u0434\u0435 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f.", "OptionBestAvailableStreamQuality": "\u041d\u0430\u0438\u043b\u0443\u0447\u0448\u0435\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0435", "LabelEnableChannelContentDownloadingFor": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430 \u0434\u043b\u044f:", "LabelEnableChannelContentDownloadingForHelp": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043a\u0430\u043d\u0430\u043b\u0430\u043c\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435. \u0412\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435 \u043f\u0440\u0438 \u0441\u0440\u0435\u0434\u0430\u0445 \u0441 \u043d\u0438\u0437\u043a\u043e\u0439 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043d\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0432\u0440\u0435\u043c\u044f. \u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u0430\u043d\u0430\u043b\u043e\u0432.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "\u041e\u0433\u043e\u0432\u043e\u0440\u043a\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0432\u0445\u043e\u0434\u0430:", "LabelLoginDisclaimerHelp": "\u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 \u043d\u0438\u0436\u043d\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443.", "LabelAutomaticallyDonate": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u0430\u0440\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u0435\u0441\u044f\u0446", - "LabelAutomaticallyDonateHelp": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c PayPal." + "LabelAutomaticallyDonateHelp": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c PayPal.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index e7e1685628..bc350bb38c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -861,5 +861,14 @@ "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", - "OptionList": "List" + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:", + "HeaderLatestMusic": "Latest Music", + "HeaderBranding": "Branding" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index c2e49294f1..8106294223 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -479,10 +479,10 @@ "HeaderProgram": "Program", "HeaderClients": "Klienter", "LabelCompleted": "Klar", - "LabelFailed": "Misslyckades", + "LabelFailed": "Failed", "LabelSkipped": "Hoppades \u00f6ver", "HeaderEpisodeOrganization": "Katalogisering av avsnitt", - "LabelSeries": "Serie:", + "LabelSeries": "Series:", "LabelSeasonNumber": "S\u00e4songsnummer:", "LabelEpisodeNumber": "Avsnittsnummer:", "LabelEndingEpisodeNumber": "Avslutande avsnittsnummer:", @@ -630,8 +630,8 @@ "ButtonScenes": "Scener", "ButtonSubtitles": "Undertexter", "ButtonAudioTracks": "Ljudsp\u00e5r", - "ButtonPreviousTrack": "F\u00f6reg\u00e5ende sp\u00e5r", - "ButtonNextTrack": "N\u00e4sta sp\u00e5r", + "ButtonPreviousTrack": "Previous track", + "ButtonNextTrack": "Next track", "ButtonStop": "Stopp", "ButtonPause": "Paus", "LabelGroupMoviesIntoCollections": "Gruppera filmer i samlingsboxar", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Ansvarsbegr\u00e4nsning vid inloggning:", "LabelLoginDisclaimerHelp": "Detta visas l\u00e4ngst ned p\u00e5 inloggningssidan.", "LabelAutomaticallyDonate": "Donera automatiskt detta belopp varje m\u00e5nad", - "LabelAutomaticallyDonateHelp": "Du kan avbryta n\u00e4r som helst via ditt PayPal-konto." + "LabelAutomaticallyDonateHelp": "Du kan avbryta n\u00e4r som helst via ditt PayPal-konto.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/vi.json b/MediaBrowser.Server.Implementations/Localization/Server/vi.json index 269d7a8bc1..0da205c7b7 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -279,7 +279,7 @@ "LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.", "LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.", "LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.", - "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", + "ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs when videos are discovered, and also as a nightly scheduled task at 4am. The schedule is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelMetadataDownloadLanguage": "Preferred download language:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Image saving convention:", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json index 2868ad3d94..78c7689c35 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -396,7 +396,7 @@ "HeaderCastCrew": "\u62cd\u651d\u4eba\u54e1\u53ca\u6f14\u54e1", "HeaderAdditionalParts": "\u9644\u52a0\u90e8\u4efd", "ButtonSplitVersionsApart": "Split Versions Apart", - "ButtonPlayTrailer": "\u9810\u544a", + "ButtonPlayTrailer": "Trailer", "LabelMissing": "\u7f3a\u5c11", "LabelOffline": "\u96e2\u7dda", "PathSubstitutionHelp": "Path substitutions are used for mapping a path on the server to a path that clients are able to access. By allowing clients direct access to media on the server they may be able to play them directly over the network and avoid using server resources to stream and transcode them.", @@ -846,5 +846,13 @@ "LabelLoginDisclaimer": "Login disclaimer:", "LabelLoginDisclaimerHelp": "This will be displayed at the bottom of the login page.", "LabelAutomaticallyDonate": "Automatically donate this amount each month", - "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account." + "LabelAutomaticallyDonateHelp": "You can cancel at any time via your PayPal account.", + "OptionList": "List", + "TabDashboard": "Dashboard", + "TitleServer": "Server", + "LabelCache": "Cache:", + "LabelLogs": "Logs:", + "LabelMetadata": "Metadata:", + "LabelImagesByName": "Images by name:", + "LabelTranscodingTemporaryFiles": "Transcoding temporary files:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index c3d24c0de1..338bf89598 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1210,9 +1210,16 @@ namespace MediaBrowser.Server.Implementations.Session /// Task{SessionInfo}. /// Invalid user or password entered. /// - public async Task AuthenticateNewSession(string username, 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 = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = (IsLocalhost(remoteEndPoint) && string.Equals(clientType, "Dashboard", StringComparison.OrdinalIgnoreCase)) || + await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) { @@ -1337,7 +1344,7 @@ namespace MediaBrowser.Server.Implementations.Session remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase); } - public bool IsLocal(string remoteEndpoint) + public bool IsInLocalNetwork(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) { diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 688c07639c..96e38b2d4e 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -17,9 +17,6 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -45,7 +42,6 @@ using MediaBrowser.LocalMetadata.Providers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.MediaEncoding.Subtitles; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; @@ -343,58 +339,13 @@ namespace MediaBrowser.ServerApplication ? "Xbmc Nfo" : "Media Browser Xml"; - DisableMetadataService(typeof(Movie), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(MusicAlbum), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(MusicArtist), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Episode), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Season), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Series), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(MusicVideo), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Trailer), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(AdultVideo), ServerConfigurationManager.Configuration, service); - DisableMetadataService(typeof(Video), ServerConfigurationManager.Configuration, service); + ServerConfigurationManager.SetPreferredMetadataService(service); } ServerConfigurationManager.Configuration.DefaultMetadataSettingsApplied = true; ServerConfigurationManager.SaveConfiguration(); } - private void DisableMetadataService(Type type, ServerConfiguration config, string service) - { - var options = GetMetadataOptions(type, config); - - if (!options.DisabledMetadataSavers.Contains(service, StringComparer.OrdinalIgnoreCase)) - { - var list = options.DisabledMetadataSavers.ToList(); - - list.Add(service); - - options.DisabledMetadataSavers = list.ToArray(); - } - } - - private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config) - { - var options = config.MetadataOptions - .FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase)); - - if (options == null) - { - var list = config.MetadataOptions.ToList(); - - options = new MetadataOptions - { - ItemType = type.Name - }; - - list.Add(options); - - config.MetadataOptions = list.ToArray(); - } - - return options; - } - private void DeleteDeprecatedModules() { try diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 36adae71c7..186cf9fe34 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -521,7 +521,6 @@ namespace MediaBrowser.WebDashboard.Api "mediacontroller.js", "chromecast.js", "backdrops.js", - "branding.js", "mediaplayer.js", "mediaplayer-video.js", @@ -544,7 +543,6 @@ namespace MediaBrowser.WebDashboard.Api "channelitems.js", "channelsettings.js", "dashboardgeneral.js", - "dashboardinfo.js", "dashboardpage.js", "directorybrowser.js", "dlnaprofile.js", @@ -627,6 +625,7 @@ namespace MediaBrowser.WebDashboard.Api "scheduledtaskpage.js", "scheduledtaskspage.js", "search.js", + "serversecurity.js", "songs.js", "supporterkeypage.js", "supporterpage.js", @@ -645,7 +644,6 @@ namespace MediaBrowser.WebDashboard.Api "userprofilespage.js", "userparentalcontrol.js", "wizardfinishpage.js", - "wizardimagesettings.js", "wizardservice.js", "wizardstartpage.js", "wizardsettings.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 2906d1c34c..e08da4e004 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -98,9 +98,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -302,9 +299,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -620,9 +614,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -641,9 +632,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -794,6 +782,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -803,9 +794,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -815,6 +803,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1442,9 +1433,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest -- cgit v1.2.3 From b5641013cea8542d3a992fb11811347e761a1f50 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Jul 2014 22:31:08 -0400 Subject: Add api key functions --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 ++-- MediaBrowser.Api/SessionsService.cs | 56 +++++++++++++++++++++- MediaBrowser.Controller/Session/ISessionManager.cs | 7 +++ .../MediaInfo/FFProbeVideoInfo.cs | 22 ++++++++- .../Localization/JavaScript/javascript.json | 5 +- .../Localization/Server/server.json | 16 ++++++- .../Security/AuthenticationRepository.cs | 4 +- .../Session/SessionManager.cs | 19 +++++--- .../Session/WebSocketController.cs | 26 +++++++--- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 21 ++++++-- .../Savers/EpisodeNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs | 4 +- MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs | 3 +- MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs | 4 +- 16 files changed, 168 insertions(+), 39 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Session') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 094a9034da..5d772527c1 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using MediaBrowser.Api.Playback.Hls; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -317,7 +318,7 @@ namespace MediaBrowser.Api.Playback switch (qualitySetting) { case EncodingQuality.HighSpeed: - param = "-preset ultrafast"; + param = "-preset superfast"; break; case EncodingQuality.HighQuality: param = "-preset superfast"; @@ -945,7 +946,8 @@ namespace MediaBrowser.Api.Playback } // Allow a small amount of time to buffer a little - if (state.IsInputVideo) + // But not with HLS because it already has it's own wait + if (state.IsInputVideo && TranscodingJobType != TranscodingJobType.Hls) { await Task.Delay(500, cancellationTokenSource.Token).ConfigureAwait(false); } diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index ed7db626f9..8017f35231 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Session; using ServiceStack; @@ -241,6 +242,25 @@ namespace MediaBrowser.Api { } + [Route("/Auth/Keys", "GET")] + public class GetApiKeys + { + } + + [Route("/Auth/Keys/{Key}", "DELETE")] + public class RevokeKey + { + [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Key { get; set; } + } + + [Route("/Auth/Keys", "POST")] + public class CreateKey + { + [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string App { get; set; } + } + /// /// Class SessionsService /// @@ -253,19 +273,43 @@ namespace MediaBrowser.Api private readonly IUserManager _userManager; private readonly IAuthorizationContext _authContext; + private readonly IAuthenticationRepository _authRepo; /// /// Initializes a new instance of the class. /// /// The session manager. /// The user manager. - public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext) + /// The authentication context. + /// The authentication repo. + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) { _sessionManager = sessionManager; _userManager = userManager; _authContext = authContext; + _authRepo = authRepo; + } + + public void Delete(RevokeKey request) + { + var task = _sessionManager.RevokeToken(request.Key); + + Task.WaitAll(task); } + public void Post(CreateKey request) + { + var task = _authRepo.Create(new AuthenticationInfo + { + AppName = request.App, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N"), + DateCreated = DateTime.UtcNow + + }, CancellationToken.None); + + Task.WaitAll(task); + } public void Post(ReportSessionEnded request) { @@ -274,6 +318,16 @@ namespace MediaBrowser.Api _sessionManager.Logout(auth.Token); } + public object Get(GetApiKeys request) + { + var result = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true + }); + + return ToOptimizedResult(result); + } + /// /// Gets the specified request. /// diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 88d0e9c1a1..c8ae0e6a75 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -276,6 +276,13 @@ namespace MediaBrowser.Controller.Session /// Task. Task RevokeUserTokens(string userId); + /// + /// Revokes the token. + /// + /// The identifier. + /// Task. + Task RevokeToken(string id); + /// /// Determines whether the specified remote endpoint is local. /// diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index d46ddf7dcc..c0313561ae 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -255,6 +255,8 @@ namespace MediaBrowser.Providers.MediaInfo AddDummyChapters(video, chapters); } + NormalizeChapterNames(chapters); + await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions { Chapters = chapters, @@ -268,6 +270,25 @@ namespace MediaBrowser.Providers.MediaInfo } } + private void NormalizeChapterNames(List chapters) + { + var index = 1; + + foreach (var chapter in chapters) + { + TimeSpan time; + + // Check if the name is empty and/or if the name is a time + // Some ripping programs do that. + if (string.IsNullOrWhiteSpace(chapter.Name) || + TimeSpan.TryParse(chapter.Name, out time)) + { + chapter.Name = string.Format(_localization.GetLocalizedString("LabelChapterName"), index.ToString(CultureInfo.InvariantCulture)); + } + index++; + } + } + private ChapterInfo GetChapterInfo(MediaChapter chapter) { var info = new ChapterInfo(); @@ -570,7 +591,6 @@ namespace MediaBrowser.Providers.MediaInfo { chapters.Add(new ChapterInfo { - Name = "Chapter " + index, StartPositionTicks = currentChapterTicks }); diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 85cc51f538..a69a7b124d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -233,5 +233,8 @@ "OptionBlockGames": "Games", "OptionBlockLiveTvPrograms": "Live TV Programs", "OptionBlockLiveTvChannels": "Live TV Channels", - "OptionBlockChannelContent": "Internet Channel Content" + "OptionBlockChannelContent": "Internet Channel Content", + "ButtonRevoke": "Revoke", + "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Media Browser will be abruptly terminated.", + "HeaderConfirmRevokeApiKey": "Revoke Api Key" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index bc350bb38c..96b80d1435 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -849,7 +849,7 @@ "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", @@ -870,5 +870,17 @@ "LabelImagesByName": "Images by name:", "LabelTranscodingTemporaryFiles": "Transcoding temporary files:", "HeaderLatestMusic": "Latest Music", - "HeaderBranding": "Branding" + "HeaderBranding": "Branding", + "HeaderApiKeys": "Api Keys", + "HeaderApiKeysHelp": "External applications are required to have an Api key in order to communicate with Media Browser. Keys are issued by logging in with a Media Browser account, or by manually granting the application a key.", + "HeaderApiKey": "Api Key", + "HeaderApp": "App", + "HeaderDevice": "Device", + "HeaderUser": "User", + "HeaderDateIssued": "Date Issued", + "LabelChapterName": "Chapter {0}", + "HeaderNewApiKey": "New Api Key", + "LabelAppName": "App name", + "LabelAppNameExample": "Example: Sickbeard, NzbDrone", + "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Media Browser." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs index 5f225ddd47..43a960ea29 100644 --- a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs +++ b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs @@ -283,11 +283,11 @@ namespace MediaBrowser.Server.Implementations.Security } info.IsActive = reader.GetBoolean(6); - info.DateCreated = reader.GetDateTime(7); + info.DateCreated = reader.GetDateTime(7).ToUniversalTime(); if (!reader.IsDBNull(8)) { - info.DateRevoked = reader.GetDateTime(8); + info.DateRevoked = reader.GetDateTime(8).ToUniversalTime(); } return info; diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 338bf89598..1d1910e40e 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1210,15 +1210,15 @@ namespace MediaBrowser.Server.Implementations.Session /// Task{SessionInfo}. /// Invalid user or password entered. /// - public async Task AuthenticateNewSession(string username, - string password, - string clientType, - string appVersion, - string deviceId, - string deviceName, + public async Task AuthenticateNewSession(string username, + string password, + string clientType, + string appVersion, + string deviceId, + string deviceName, string remoteEndPoint) { - var result = (IsLocalhost(remoteEndPoint) && string.Equals(clientType, "Dashboard", StringComparison.OrdinalIgnoreCase)) || + var result = (IsLocalhost(remoteEndPoint) && string.Equals(clientType, "Dashboard", StringComparison.OrdinalIgnoreCase)) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); if (!result) @@ -1332,6 +1332,11 @@ namespace MediaBrowser.Server.Implementations.Session } } + public Task RevokeToken(string token) + { + return Logout(token); + } + private bool IsLocalhost(string remoteEndpoint) { if (string.IsNullOrWhiteSpace(remoteEndpoint)) diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs index 5fc28e81b0..e1d77c2682 100644 --- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -62,14 +62,28 @@ namespace MediaBrowser.Server.Implementations.Session void connection_Closed(object sender, EventArgs e) { - var capabilities = new SessionCapabilities + if (!GetActiveSockets().Any()) { - PlayableMediaTypes = Session.PlayableMediaTypes, - SupportedCommands = Session.SupportedCommands, - SupportsMediaControl = SupportsMediaControl - }; + try + { + _sessionManager.ReportSessionEnded(Session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error reporting session ended.", ex); + } + } + else + { + var capabilities = new SessionCapabilities + { + PlayableMediaTypes = Session.PlayableMediaTypes, + SupportedCommands = Session.SupportedCommands, + SupportsMediaControl = SupportsMediaControl + }; - _sessionManager.ReportCapabilities(Session.Id, capabilities); + _sessionManager.ReportCapabilities(Session.Id, capabilities); + } } private IWebSocketConnection GetActiveSocket() diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index 800cb8e108..79b262fc31 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Globalization; @@ -14,8 +15,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class AlbumNfoSaver : BaseNfoSaver { - public AlbumNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) - : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + public AlbumNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs index a76eaef839..f4ffc35fbd 100644 --- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; using MediaBrowser.XbmcMetadata.Configuration; using System.Collections.Generic; using System.Globalization; @@ -14,8 +15,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class ArtistNfoSaver : BaseNfoSaver { - public ArtistNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) - : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + public ArtistNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index cb9137ad09..f795eb8b3b 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using MediaBrowser.XbmcMetadata.Configuration; using System; using System.Collections.Generic; @@ -98,8 +99,9 @@ namespace MediaBrowser.XbmcMetadata.Savers }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - protected BaseNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) + protected BaseNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) { + Logger = logger; UserDataManager = userDataManager; UserManager = userManager; LibraryManager = libraryManager; @@ -112,6 +114,7 @@ namespace MediaBrowser.XbmcMetadata.Savers protected ILibraryManager LibraryManager { get; private set; } protected IUserManager UserManager { get; private set; } protected IUserDataManager UserDataManager { get; private set; } + protected ILogger Logger { get; private set; } public string Name { @@ -232,11 +235,11 @@ namespace MediaBrowser.XbmcMetadata.Savers try { - AddCustomTags(xmlPath, tagsUsed, writer); + AddCustomTags(xmlPath, tagsUsed, writer, Logger); } catch (FileNotFoundException) { - + } writer.WriteEndElement(); @@ -950,7 +953,7 @@ namespace MediaBrowser.XbmcMetadata.Savers return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); } - private static void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer) + private static void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer, ILogger logger) { var settings = new XmlReaderSettings { @@ -965,7 +968,15 @@ namespace MediaBrowser.XbmcMetadata.Savers // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, settings)) { - reader.MoveToContent(); + try + { + reader.MoveToContent(); + } + catch (Exception ex) + { + logger.ErrorException("Error reading existing xml tags from {0}.", ex, path); + return; + } // Loop through each element while (reader.Read()) diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index 381faedf4a..f5dc0558bd 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; using MediaBrowser.XbmcMetadata.Configuration; using System.Collections.Generic; using System.Globalization; @@ -13,8 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class EpisodeNfoSaver : BaseNfoSaver { - public EpisodeNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) - : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + public EpisodeNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index 9c8442e167..b23473295c 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -14,8 +15,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class MovieNfoSaver : BaseNfoSaver { - public MovieNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) - : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + public MovieNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs index fd5819b3d2..715337a015 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -12,7 +13,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class SeasonNfoSaver : BaseNfoSaver { - public SeasonNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + public SeasonNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs index 473b5f46bb..8a67b04182 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -13,8 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { public class SeriesNfoSaver : BaseNfoSaver { - public SeriesNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager) - : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager) + public SeriesNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } -- cgit v1.2.3 From baf44b2718162c7f1d4f3061e438efbafc22201b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 13 Jul 2014 00:55:56 -0400 Subject: 3.0.5306.42925 --- MediaBrowser.Api/ConfigurationService.cs | 24 ++++---- MediaBrowser.Api/ItemUpdateService.cs | 11 +++- MediaBrowser.Api/UserService.cs | 71 +++++++++++++++++++--- .../Updates/InstallationManager.cs | 24 +++++--- .../Configuration/IServerConfigurationManager.cs | 2 +- .../Resolvers/EntityResolutionHelper.cs | 15 +++++ MediaBrowser.Controller/Session/ISessionManager.cs | 11 +--- .../MediaBrowser.Model.Portable.csproj | 21 +++++++ MediaBrowser.Model.Portable/app.config | 15 +++++ MediaBrowser.Model.Portable/packages.config | 3 + MediaBrowser.Model/ApiClient/IApiClient.cs | 6 ++ MediaBrowser.Model/Entities/CollectionType.cs | 3 +- .../Configuration/ServerConfigurationManager.cs | 2 +- .../Library/CoreResolutionIgnoreRule.cs | 17 +----- .../Library/Resolvers/Movies/MovieResolver.cs | 14 ++--- .../Session/SessionManager.cs | 37 ++--------- MediaBrowser.ServerApplication/ApplicationHost.cs | 2 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 12 ++-- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 21 files changed, 191 insertions(+), 109 deletions(-) create mode 100644 MediaBrowser.Model.Portable/app.config (limited to 'MediaBrowser.Server.Implementations/Session') diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index c83028bb2d..29848cfc04 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -122,42 +122,44 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + const string XbmcMetadata = "Xbmc Nfo"; + const string MediaBrowserMetadata = "Media Browser Xml"; + public void Post(AutoSetMetadataOptions request) { var service = AutoDetectMetadataService(); Logger.Info("Setting preferred metadata format to " + service); - _configurationManager.SetPreferredMetadataService(service); + var serviceToDisable = string.Equals(service, XbmcMetadata) ? + MediaBrowserMetadata : + XbmcMetadata; + + _configurationManager.DisableMetadataService(serviceToDisable); _configurationManager.SaveConfiguration(); } private string AutoDetectMetadataService() { - const string xbmc = "Xbmc Nfo"; - const string mb = "Media Browser Xml"; - var paths = _libraryManager.GetDefaultVirtualFolders() .SelectMany(i => i.Locations) .Distinct(StringComparer.OrdinalIgnoreCase) .Select(i => new DirectoryInfo(i)) .ToList(); - if (paths.Select(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) - .SelectMany(i => i) + if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) .Any()) { - return xbmc; + return XbmcMetadata; } - if (paths.Select(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) - .SelectMany(i => i) + if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) .Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase))) { - return mb; + return MediaBrowserMetadata; } - return xbmc; + return XbmcMetadata; } /// diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index db6c6ce53e..1fa1a5509d 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -65,6 +65,11 @@ namespace MediaBrowser.Api } } + private DateTime NormalizeDateTime(DateTime val) + { + return DateTime.SpecifyKind(val, DateTimeKind.Utc); + } + private void UpdateItem(BaseItemDto request, BaseItem item) { item.Name = request.Name; @@ -140,11 +145,11 @@ namespace MediaBrowser.Api if (request.DateCreated.HasValue) { - item.DateCreated = request.DateCreated.Value.ToUniversalTime(); + item.DateCreated = NormalizeDateTime(request.DateCreated.Value); } - item.EndDate = request.EndDate.HasValue ? request.EndDate.Value.ToUniversalTime() : (DateTime?)null; - item.PremiereDate = request.PremiereDate.HasValue ? request.PremiereDate.Value.ToUniversalTime() : (DateTime?)null; + item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null; + item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null; item.ProductionYear = request.ProductionYear; item.OfficialRating = request.OfficialRating; item.CustomRating = request.CustomRating; diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 178199b220..df4bc06bab 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -11,6 +12,7 @@ using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; namespace MediaBrowser.Api @@ -168,6 +170,7 @@ namespace MediaBrowser.Api private readonly IDtoService _dtoService; private readonly ISessionManager _sessionMananger; private readonly IServerConfigurationManager _config; + private readonly INetworkManager _networkManager; public IAuthorizationContext AuthorizationContext { get; set; } @@ -178,12 +181,13 @@ namespace MediaBrowser.Api /// The dto service. /// The session mananger. /// xmlSerializer - public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config) + public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager) { _userManager = userManager; _dtoService = dtoService; _sessionMananger = sessionMananger; _config = config; + _networkManager = networkManager; } public object Get(GetPublicUsers request) @@ -200,18 +204,65 @@ namespace MediaBrowser.Api }); } - // TODO: Add or is authenticated - if (_sessionMananger.IsInLocalNetwork(Request.RemoteIp)) + // TODO: Uncomment this once all clients can handle an empty user list. + return Get(new GetUsers { - return Get(new GetUsers + IsHidden = false, + IsDisabled = false + }); + + //// TODO: Add or is authenticated + //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp)) + //{ + // return Get(new GetUsers + // { + // IsHidden = false, + // IsDisabled = false + // }); + //} + + //// Return empty when external + //return ToOptimizedResult(new List()); + } + + private bool IsInLocalNetwork(string remoteEndpoint) + { + if (string.IsNullOrWhiteSpace(remoteEndpoint)) + { + throw new ArgumentNullException("remoteEndpoint"); + } + + IPAddress address; + if (!IPAddress.TryParse(remoteEndpoint, out address)) + { + return true; + } + + const int lengthMatch = 4; + + if (remoteEndpoint.Length >= lengthMatch) + { + var prefix = remoteEndpoint.Substring(0, lengthMatch); + + if (_networkManager.GetLocalIpAddresses() + .Any(i => i.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { - IsHidden = false, - IsDisabled = false - }); + return true; + } } - // Return empty when external - return ToOptimizedResult(new List()); + // Private address space: + // http://en.wikipedia.org/wiki/Private_network + + return + + // If url was requested with computer name, we may see this + remoteEndpoint.IndexOf("::", StringComparison.OrdinalIgnoreCase) != -1 || + + remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || + remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); } /// @@ -307,7 +358,7 @@ namespace MediaBrowser.Api var auth = AuthorizationContext.GetAuthorizationInfo(Request); var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp).Result; + auth.DeviceId, auth.Device, Request.RemoteIp, Request.IsLocal).Result; return ToOptimizedResult(result); } diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs index 751ff55a57..e4c88e6568 100644 --- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs +++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs @@ -177,17 +177,27 @@ namespace MediaBrowser.Common.Implementations.Updates { if (_lastPackageListResult != null) { - // Let dev users get results more often for testing purposes - var cacheLength = _config.CommonConfiguration.SystemUpdateLevel == PackageVersionClass.Dev - ? TimeSpan.FromMinutes(3) - : TimeSpan.FromHours(6); + TimeSpan cacheLength; + + switch (_config.CommonConfiguration.SystemUpdateLevel) + { + case PackageVersionClass.Beta: + cacheLength = TimeSpan.FromMinutes(30); + break; + case PackageVersionClass.Dev: + cacheLength = TimeSpan.FromMinutes(3); + break; + default: + cacheLength = TimeSpan.FromHours(6); + break; + } if ((DateTime.UtcNow - _lastPackageListResult.Item2) < cacheLength) { return _lastPackageListResult.Item1; } } - + using (var json = await _httpClient.Get(Constants.Constants.MbAdminUrl + "service/MB3Packages.json", cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); @@ -274,7 +284,7 @@ namespace MediaBrowser.Common.Implementations.Updates { var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); - var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) + var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) ?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (package == null) @@ -310,7 +320,7 @@ namespace MediaBrowser.Common.Implementations.Updates /// PackageVersionInfo. public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release) { - var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) + var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) ?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (package == null) diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index c3d1796f99..aac8cda2eb 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -31,6 +31,6 @@ namespace MediaBrowser.Controller.Configuration /// Sets the preferred metadata service. /// /// The service. - void SetPreferredMetadataService(string service); + void DisableMetadataService(string service); } } diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs index 42178c44c1..ff94ceff36 100644 --- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs +++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs @@ -14,6 +14,21 @@ namespace MediaBrowser.Controller.Resolvers /// public static class EntityResolutionHelper { + /// + /// Any folder named in this list will be ignored - can be added to at runtime for extensibility + /// + public static readonly List IgnoreFolders = new List + { + "metadata", + "ps3_update", + "ps3_vprm", + "extrafanart", + "extrathumbs", + ".actors", + ".wd_tv" + + }; + /// /// Any extension in this list is considered a video file - can be added to at runtime for extensibility /// diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index c8ae0e6a75..e37a139233 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -218,6 +218,7 @@ namespace MediaBrowser.Controller.Session /// The device identifier. /// Name of the device. /// The remote end point. + /// if set to true [is local]. /// Task{SessionInfo}. Task AuthenticateNewSession(string username, string password, @@ -225,7 +226,8 @@ namespace MediaBrowser.Controller.Session string appVersion, string deviceId, string deviceName, - string remoteEndPoint); + string remoteEndPoint, + bool isLocal); /// /// Reports the capabilities. @@ -282,12 +284,5 @@ namespace MediaBrowser.Controller.Session /// The identifier. /// Task. Task RevokeToken(string id); - - /// - /// Determines whether the specified remote endpoint is local. - /// - /// The remote endpoint. - /// true if the specified remote endpoint is local; otherwise, false. - bool IsInLocalNetwork(string remoteEndpoint); } } \ 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 bb919edb82..baa9f933d3 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -57,14 +57,30 @@ + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\portable-net40+sl4+win8+wp71+wpa81\Microsoft.Threading.Tasks.dll + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\portable-net40+sl4+win8+wp71+wpa81\Microsoft.Threading.Tasks.Extensions.dll + ..\packages\PropertyChanged.Fody.1.41.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\PropertyChanged.dll False + + ..\packages\Microsoft.Bcl.1.1.8\lib\portable-net40+sl5+win8+wp8+wpa81\System.IO.dll + + + ..\packages\Microsoft.Bcl.1.1.8\lib\portable-net40+sl5+win8+wp8+wpa81\System.Runtime.dll + + + ..\packages\Microsoft.Bcl.1.1.8\lib\portable-net40+sl5+win8+wp8+wpa81\System.Threading.Tasks.dll + @@ -914,6 +930,11 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\portable\" /y /d /r /i + + + + + - + \ No newline at end of file diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs new file mode 100644 index 0000000000..e6b525e534 --- /dev/null +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -0,0 +1,116 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Session +{ + /// + /// Class SessionInfoWebSocketListener + /// + class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "Sessions"; } + } + + /// + /// The _kernel + /// + private readonly ISessionManager _sessionManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The session manager. + public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) + : base(logger) + { + _sessionManager = sessionManager; + + _sessionManager.SessionStarted += _sessionManager_SessionStarted; + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; + _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; + _sessionManager.SessionActivity += _sessionManager_SessionActivity; + } + + void _sessionManager_SessionActivity(object sender, SessionEventArgs e) + { + SendData(false); + } + + void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) + { + SendData(true); + } + + void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) + { + SendData(false); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + SendData(true); + } + + void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + { + SendData(true); + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + SendData(true); + } + + void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + { + SendData(true); + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task> GetDataToSend(WebSocketListenerState state) + { + return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); + } + + protected override bool SendOnTimer + { + get + { + return false; + } + } + + protected override void Dispose(bool dispose) + { + _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; + _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; + _sessionManager.SessionActivity -= _sessionManager_SessionActivity; + + base.Dispose(dispose); + } + } +} diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs new file mode 100644 index 0000000000..e2c95eba9c --- /dev/null +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -0,0 +1,506 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Session +{ + /// + /// 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")] + public Guid? ControllableByUserId { get; set; } + + [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + } + + /// + /// Class DisplayContent + /// + [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] + [Authenticated] + public class DisplayContent : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// + /// The type of the item. + [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemType { get; set; } + + /// + /// Artist name, genre name, item Id, etc + /// + /// The item identifier. + [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemId { get; set; } + + /// + /// Gets or sets the name of the item. + /// + /// The name of the item. + [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemName { get; set; } + } + + [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] + [Authenticated] + public class Play : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// + /// The type of the item. + [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string ItemIds { get; set; } + + /// + /// Gets or sets the start position ticks that the first item should be played at + /// + /// The start position ticks. + [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? StartPositionTicks { get; set; } + + /// + /// Gets or sets the play command. + /// + /// The play command. + [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public PlayCommand PlayCommand { get; set; } + } + + [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] + [Authenticated] + public class SendPlaystateCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the position to seek to + /// + [ApiMember(Name = "SeekPositionTicks", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? SeekPositionTicks { get; set; } + + /// + /// Gets or sets the play command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek, fullscreen.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public PlaystateCommand Command { get; set; } + } + + [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendSystemCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendGeneralCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendFullGeneralCommand : GeneralCommand, IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + } + + [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] + [Authenticated] + public class SendMessageCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Text { get; set; } + + [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Header { get; set; } + + [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? TimeoutMs { get; set; } + } + + [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")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + } + + [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")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + } + + [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] + [Authenticated] + public class PostCapabilities : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayableMediaTypes { get; set; } + + [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string SupportedCommands { get; set; } + + [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MessageCallbackUrl { get; set; } + + [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool SupportsMediaControl { get; set; } + } + + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] + public class ReportSessionEnded : IReturnVoid + { + } + + [Route("/Auth/Keys", "GET")] + public class GetApiKeys + { + } + + [Route("/Auth/Keys/{Key}", "DELETE")] + public class RevokeKey + { + [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Key { get; set; } + } + + [Route("/Auth/Keys", "POST")] + public class CreateKey + { + [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string App { get; set; } + } + + /// + /// Class SessionsService + /// + public class SessionsService : BaseApiService + { + /// + /// The _session manager + /// + private readonly ISessionManager _sessionManager; + + private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; + private readonly IAuthenticationRepository _authRepo; + + /// + /// Initializes a new instance of the class. + /// + /// The session manager. + /// The user manager. + /// The authentication context. + /// The authentication repo. + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) + { + _sessionManager = sessionManager; + _userManager = userManager; + _authContext = authContext; + _authRepo = authRepo; + } + + public void Delete(RevokeKey request) + { + var task = _sessionManager.RevokeToken(request.Key); + + Task.WaitAll(task); + } + + public void Post(CreateKey request) + { + var task = _authRepo.Create(new AuthenticationInfo + { + AppName = request.App, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N"), + DateCreated = DateTime.UtcNow + + }, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(ReportSessionEnded request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); + } + + public object Get(GetApiKeys request) + { + var result = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true + }); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSessions request) + { + var result = _sessionManager.Sessions.Where(i => i.IsActive); + + if (!string.IsNullOrEmpty(request.DeviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (request.ControllableByUserId.HasValue) + { + result = result.Where(i => i.SupportsMediaControl); + + var user = _userManager.GetUserById(request.ControllableByUserId.Value); + + if (!user.Configuration.EnableRemoteControlOfOtherUsers) + { + result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId.Value)); + } + } + + return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList()); + } + + public void Post(SendPlaystateCommand request) + { + var command = new PlaystateRequest + { + Command = request.Command, + SeekPositionTicks = request.SeekPositionTicks + }; + + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(DisplayContent request) + { + var command = new BrowseRequest + { + ItemId = request.ItemId, + ItemName = request.ItemName, + ItemType = request.ItemType + }; + + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendSystemCommand request) + { + GeneralCommandType commandType; + + if (Enum.TryParse(request.Command, true, out commandType)) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = commandType.ToString(), + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendMessageCommand request) + { + var command = new MessageCommand + { + Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, + TimeoutMs = request.TimeoutMs, + Text = request.Text + }; + + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(Play request) + { + var command = new PlayRequest + { + ItemIds = request.ItemIds.Split(',').ToArray(), + + PlayCommand = request.PlayCommand, + StartPositionTicks = request.StartPositionTicks + }; + + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendGeneralCommand request) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = request.Command, + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendFullGeneralCommand request) + { + var currentSession = GetSession(); + + request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(AddUserToSession request) + { + _sessionManager.AddAdditionalUser(request.Id, request.UserId); + } + + public void Delete(RemoveUserFromSession request) + { + _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); + } + + public void Post(PostCapabilities request) + { + if (string.IsNullOrWhiteSpace(request.Id)) + { + request.Id = GetSession().Id; + } + _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities + { + PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + + SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + + SupportsMediaControl = request.SupportsMediaControl, + + MessageCallbackUrl = request.MessageCallbackUrl + }); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs deleted file mode 100644 index 8017f35231..0000000000 --- a/MediaBrowser.Api/SessionsService.cs +++ /dev/null @@ -1,506 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Session; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -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")] - public Guid? ControllableByUserId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - } - - /// - /// Class DisplayContent - /// - [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] - [Authenticated] - public class DisplayContent : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// - /// The type of the item. - [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemType { get; set; } - - /// - /// Artist name, genre name, item Id, etc - /// - /// The item identifier. - [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - - /// - /// Gets or sets the name of the item. - /// - /// The name of the item. - [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemName { get; set; } - } - - [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] - [Authenticated] - public class Play : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// - /// The type of the item. - [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string ItemIds { get; set; } - - /// - /// Gets or sets the start position ticks that the first item should be played at - /// - /// The start position ticks. - [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? StartPositionTicks { get; set; } - - /// - /// Gets or sets the play command. - /// - /// The play command. - [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayCommand PlayCommand { get; set; } - } - - [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] - [Authenticated] - public class SendPlaystateCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the position to seek to - /// - [ApiMember(Name = "SeekPositionTicks", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? SeekPositionTicks { get; set; } - - /// - /// Gets or sets the play command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek, fullscreen.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public PlaystateCommand Command { get; set; } - } - - [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendSystemCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendGeneralCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendFullGeneralCommand : GeneralCommand, IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] - [Authenticated] - public class SendMessageCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Text { get; set; } - - [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Header { get; set; } - - [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? TimeoutMs { get; set; } - } - - [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")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - } - - [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")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - } - - [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] - [Authenticated] - public class PostCapabilities : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayableMediaTypes { get; set; } - - [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SupportedCommands { get; set; } - - [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MessageCallbackUrl { get; set; } - - [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsMediaControl { get; set; } - } - - [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] - public class ReportSessionEnded : IReturnVoid - { - } - - [Route("/Auth/Keys", "GET")] - public class GetApiKeys - { - } - - [Route("/Auth/Keys/{Key}", "DELETE")] - public class RevokeKey - { - [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Key { get; set; } - } - - [Route("/Auth/Keys", "POST")] - public class CreateKey - { - [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string App { get; set; } - } - - /// - /// Class SessionsService - /// - public class SessionsService : BaseApiService - { - /// - /// The _session manager - /// - private readonly ISessionManager _sessionManager; - - private readonly IUserManager _userManager; - private readonly IAuthorizationContext _authContext; - private readonly IAuthenticationRepository _authRepo; - - /// - /// Initializes a new instance of the class. - /// - /// The session manager. - /// The user manager. - /// The authentication context. - /// The authentication repo. - public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) - { - _sessionManager = sessionManager; - _userManager = userManager; - _authContext = authContext; - _authRepo = authRepo; - } - - public void Delete(RevokeKey request) - { - var task = _sessionManager.RevokeToken(request.Key); - - Task.WaitAll(task); - } - - public void Post(CreateKey request) - { - var task = _authRepo.Create(new AuthenticationInfo - { - AppName = request.App, - IsActive = true, - AccessToken = Guid.NewGuid().ToString("N"), - DateCreated = DateTime.UtcNow - - }, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(ReportSessionEnded request) - { - var auth = _authContext.GetAuthorizationInfo(Request); - - _sessionManager.Logout(auth.Token); - } - - public object Get(GetApiKeys request) - { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - IsActive = true - }); - - return ToOptimizedResult(result); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSessions request) - { - var result = _sessionManager.Sessions.Where(i => i.IsActive); - - if (!string.IsNullOrEmpty(request.DeviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (request.ControllableByUserId.HasValue) - { - result = result.Where(i => i.SupportsMediaControl); - - var user = _userManager.GetUserById(request.ControllableByUserId.Value); - - if (!user.Configuration.EnableRemoteControlOfOtherUsers) - { - result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId.Value)); - } - } - - return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList()); - } - - public void Post(SendPlaystateCommand request) - { - var command = new PlaystateRequest - { - Command = request.Command, - SeekPositionTicks = request.SeekPositionTicks - }; - - var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(DisplayContent request) - { - var command = new BrowseRequest - { - ItemId = request.ItemId, - ItemName = request.ItemName, - ItemType = request.ItemType - }; - - var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(SendSystemCommand request) - { - GeneralCommandType commandType; - - if (Enum.TryParse(request.Command, true, out commandType)) - { - var currentSession = GetSession(); - - var command = new GeneralCommand - { - Name = commandType.ToString(), - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null - }; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(SendMessageCommand request) - { - var command = new MessageCommand - { - Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, - TimeoutMs = request.TimeoutMs, - Text = request.Text - }; - - var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(Play request) - { - var command = new PlayRequest - { - ItemIds = request.ItemIds.Split(',').ToArray(), - - PlayCommand = request.PlayCommand, - StartPositionTicks = request.StartPositionTicks - }; - - var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(SendGeneralCommand request) - { - var currentSession = GetSession(); - - var command = new GeneralCommand - { - Name = request.Command, - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null - }; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(SendFullGeneralCommand request) - { - var currentSession = GetSession(); - - request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(AddUserToSession request) - { - _sessionManager.AddAdditionalUser(request.Id, request.UserId); - } - - public void Delete(RemoveUserFromSession request) - { - _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); - } - - public void Post(PostCapabilities request) - { - if (string.IsNullOrWhiteSpace(request.Id)) - { - request.Id = GetSession().Id; - } - _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities - { - PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), - - SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), - - SupportsMediaControl = request.SupportsMediaControl, - - MessageCallbackUrl = request.MessageCallbackUrl - }); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs new file mode 100644 index 0000000000..0ccc28c6ff --- /dev/null +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using ServiceStack; + +namespace MediaBrowser.Api.System +{ + [Route("/System/ActivityLog/Entries", "GET", Summary = "Gets activity log entries")] + public class GetActivityLogs : IReturn> + { + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + [Authenticated] + public class ActivityLogService : BaseApiService + { + private readonly IActivityManager _activityManager; + + public ActivityLogService(IActivityManager activityManager) + { + _activityManager = activityManager; + } + + public object Get(GetActivityLogs request) + { + var result = _activityManager.GetActivityLogEntries(request.StartIndex, request.Limit); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs new file mode 100644 index 0000000000..c20cef3b38 --- /dev/null +++ b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs @@ -0,0 +1,49 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.System +{ + /// + /// Class SystemInfoWebSocketListener + /// + public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "SystemInfo"; } + } + + /// + /// The _kernel + /// + private readonly IServerApplicationHost _appHost; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The app host. + public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost) + : base(logger) + { + _appHost = appHost; + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task GetDataToSend(WebSocketListenerState state) + { + return Task.FromResult(_appHost.GetSystemInfo()); + } + } +} diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs new file mode 100644 index 0000000000..3913275eee --- /dev/null +++ b/MediaBrowser.Api/System/SystemService.cs @@ -0,0 +1,178 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.System; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.System +{ + /// + /// Class GetSystemInfo + /// + [Route("/System/Info", "GET", Summary = "Gets information about the server")] + [Authenticated] + public class GetSystemInfo : IReturn + { + + } + + [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] + public class GetPublicSystemInfo : IReturn + { + + } + + /// + /// 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 + /// + public class SystemService : BaseApiService + { + /// + /// 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. + /// The application paths. + /// The file system. + /// jsonSerializer + 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", global::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, global::System.StringComparison.OrdinalIgnoreCase)); + + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSystemInfo request) + { + var result = _appHost.GetSystemInfo(); + + return ToOptimizedResult(result); + } + + public object Get(GetPublicSystemInfo request) + { + var result = _appHost.GetSystemInfo(); + + var publicInfo = new PublicSystemInfo + { + Id = result.Id, + ServerName = result.ServerName, + Version = result.Version + }; + + return ToOptimizedResult(publicInfo); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(RestartApplication request) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Restart().ConfigureAwait(false); + }); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(ShutdownApplication request) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Shutdown().ConfigureAwait(false); + }); + } + + } +} diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs deleted file mode 100644 index 259b1d8921..0000000000 --- a/MediaBrowser.Api/SystemService.cs +++ /dev/null @@ -1,177 +0,0 @@ -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 -{ - /// - /// Class GetSystemInfo - /// - [Route("/System/Info", "GET", Summary = "Gets information about the server")] - [Authenticated] - public class GetSystemInfo : IReturn - { - - } - - [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] - public class GetPublicSystemInfo : IReturn - { - - } - - /// - /// 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 - /// - public class SystemService : BaseApiService - { - /// - /// 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. - /// The application paths. - /// The file system. - /// jsonSerializer - 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); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSystemInfo request) - { - var result = _appHost.GetSystemInfo(); - - return ToOptimizedResult(result); - } - - public object Get(GetPublicSystemInfo request) - { - var result = _appHost.GetSystemInfo(); - - var publicInfo = new PublicSystemInfo - { - Id = result.Id, - ServerName = result.ServerName, - Version = result.Version - }; - - return ToOptimizedResult(publicInfo); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(RestartApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Restart().ConfigureAwait(false); - }); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(ShutdownApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); - }); - } - - } -} diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index f5a1f54cbb..4ffe5b3911 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -195,7 +195,7 @@ namespace MediaBrowser.Api var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); - if ((Request.IsLocal && isDashboard) || + if ((Request.IsLocal && isDashboard) || !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers @@ -327,7 +327,7 @@ namespace MediaBrowser.Api var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); Task.WaitAll(revokeTask); - + var task = _userManager.DeleteUser(user); Task.WaitAll(task); @@ -374,8 +374,17 @@ namespace MediaBrowser.Api auth.DeviceId = "Unknown device id"; } - var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp, Request.IsLocal).Result; + var result = _sessionMananger.AuthenticateNewSession(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device, + Password = request.Password, + RemoteEndPoint = Request.RemoteIp, + Username = request.Username + + }, Request.IsLocal).Result; return ToOptimizedResult(result); } @@ -457,8 +466,8 @@ namespace MediaBrowser.Api Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? - _userManager.UpdateUser(user) : + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs deleted file mode 100644 index 600d9e405a..0000000000 --- a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs +++ /dev/null @@ -1,116 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Session; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class SessionInfoWebSocketListener - /// - class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "Sessions"; } - } - - /// - /// The _kernel - /// - private readonly ISessionManager _sessionManager; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The session manager. - public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) - : base(logger) - { - _sessionManager = sessionManager; - - _sessionManager.SessionStarted += _sessionManager_SessionStarted; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity += _sessionManager_SessionActivity; - } - - void _sessionManager_SessionActivity(object sender, SessionEventArgs e) - { - SendData(false); - } - - void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) - { - SendData(true); - } - - void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) - { - SendData(false); - } - - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) - { - SendData(true); - } - - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) - { - SendData(true); - } - - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) - { - SendData(true); - } - - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) - { - SendData(true); - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state) - { - return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); - } - - protected override bool SendOnTimer - { - get - { - return false; - } - } - - protected override void Dispose(bool dispose) - { - _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity -= _sessionManager_SessionActivity; - - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs deleted file mode 100644 index 2940bcef06..0000000000 --- a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.System; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class SystemInfoWebSocketListener - /// - public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "SystemInfo"; } - } - - /// - /// The _kernel - /// - private readonly IServerApplicationHost _appHost; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The app host. - public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost) - : base(logger) - { - _appHost = appHost; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task GetDataToSend(WebSocketListenerState state) - { - return Task.FromResult(_appHost.GetSystemInfo()); - } - } -} diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs index 60abc14f1a..cb6121c9ff 100644 --- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs +++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Common.Implementations.Configuration /// public event EventHandler ConfigurationUpdated; + /// + /// Occurs when [configuration updating]. + /// + public event EventHandler NamedConfigurationUpdating; + /// /// Occurs when [named configuration updated]. /// @@ -217,6 +222,13 @@ namespace MediaBrowser.Common.Implementations.Configuration throw new ArgumentException("Expected configuration type is " + configurationType.Name); } + EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs + { + Key = key, + NewConfiguration = configuration + + }, Logger); + _configurations.AddOrUpdate(key, configuration, (k, v) => configuration); var path = GetConfigurationFile(key); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 68222d8436..0dc67f8c00 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -547,6 +547,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks if (ex != null) { result.ErrorMessage = ex.Message; + result.LongErrorMessage = ex.StackTrace; } var path = GetHistoryFilePath(); diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs index 25698d9729..d826a3ee78 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -6,6 +6,11 @@ namespace MediaBrowser.Common.Configuration { public interface IConfigurationManager { + /// + /// Occurs when [configuration updating]. + /// + event EventHandler NamedConfigurationUpdating; + /// /// Occurs when [configuration updated]. /// diff --git a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs index fc69630709..6989dea06b 100644 --- a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs @@ -13,4 +13,9 @@ /// true if this instance is enabled; otherwise, false. bool IsEnabled { get; } } + + public interface IScheduledTaskActivityLog + { + bool IsActivityLogged { get; } + } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Activity/IActivityManager.cs b/MediaBrowser.Controller/Activity/IActivityManager.cs new file mode 100644 index 0000000000..0c565ae36c --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityManager.cs @@ -0,0 +1,17 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Querying; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityManager + { + event EventHandler> EntryCreated; + + Task Create(ActivityLogEntry entry); + + QueryResult GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Activity/IActivityRepository.cs b/MediaBrowser.Controller/Activity/IActivityRepository.cs new file mode 100644 index 0000000000..29e60ff1f0 --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityRepository.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityRepository + { + Task Create(ActivityLogEntry entry); + + QueryResult GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index aac8cda2eb..13c9f8d84b 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,7 +1,5 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Events; -using System; namespace MediaBrowser.Controller.Configuration { @@ -10,11 +8,6 @@ namespace MediaBrowser.Controller.Configuration /// public interface IServerConfigurationManager : IConfigurationManager { - /// - /// Occurs when [configuration updating]. - /// - event EventHandler> ConfigurationUpdating; - /// /// Gets the application paths. /// diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 5e6bd97079..19c9601671 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -102,17 +102,9 @@ namespace MediaBrowser.Controller.Entities.Movies var totalItems = items.Count; var percentages = new Dictionary(totalItems); - var tasks = new List(); - // Refresh songs foreach (var item in items) { - if (tasks.Count >= 3) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - } - cancellationToken.ThrowIfCancellationRequested(); var innerProgress = new ActionableProgress(); @@ -132,13 +124,9 @@ namespace MediaBrowser.Controller.Entities.Movies }); // Avoid implicitly captured closure - var taskChild = item; - tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken)); + await RefreshItem(item, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false); } - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - // Refresh current item await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 0da5f92728..c6bbf02ae7 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -31,6 +31,7 @@ namespace MediaBrowser.Controller.Library event EventHandler> UserCreated; event EventHandler> UserConfigurationUpdated; + event EventHandler> UserPasswordChanged; /// /// Updates the configuration. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index d8d836597b..34486182b7 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -269,7 +269,7 @@ namespace MediaBrowser.Controller.Library if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { - logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); + //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); continue; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5243e1a2ae..28e1ffb1c8 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -68,6 +68,8 @@ Properties\SharedVersion.cs + + @@ -241,6 +243,7 @@ + @@ -320,6 +323,7 @@ + @@ -360,4 +364,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i --> - + \ No newline at end of file diff --git a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs index 87b89e79c8..254e56e059 100644 --- a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs +++ b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs @@ -16,10 +16,6 @@ namespace MediaBrowser.Controller.Notifications /// event EventHandler NotificationAdded; /// - /// Occurs when [notification updated]. - /// - event EventHandler NotificationUpdated; - /// /// Occurs when [notifications marked read]. /// event EventHandler NotificationsMarkedRead; @@ -37,14 +33,6 @@ namespace MediaBrowser.Controller.Notifications /// NotificationResult. NotificationResult GetNotifications(NotificationQuery query); - /// - /// Gets the notification. - /// - /// The id. - /// The user id. - /// Notification. - Notification GetNotification(string id, string userId); - /// /// Adds the notification. /// diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs new file mode 100644 index 0000000000..38871e8147 --- /dev/null +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -0,0 +1,14 @@ + +namespace MediaBrowser.Controller.Session +{ + public class AuthenticationRequest + { + public string Username { get; set; } + public string Password { get; set; } + public string App { get; set; } + public string AppVersion { get; set; } + public string DeviceId { get; set; } + public string DeviceName { get; set; } + public string RemoteEndPoint { get; set; } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index e37a139233..f715ce7703 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; using System; @@ -46,6 +47,16 @@ namespace MediaBrowser.Controller.Session /// Occurs when [capabilities changed]. /// event EventHandler CapabilitiesChanged; + + /// + /// Occurs when [authentication failed]. + /// + event EventHandler> AuthenticationFailed; + + /// + /// Occurs when [authentication succeeded]. + /// + event EventHandler> AuthenticationSucceeded; /// /// Gets the sessions. @@ -211,23 +222,10 @@ namespace MediaBrowser.Controller.Session /// /// Authenticates the new session. /// - /// The username. - /// The password. - /// Type of the client. - /// The application version. - /// The device identifier. - /// Name of the device. - /// The remote end point. + /// The request. /// if set to true [is local]. /// Task{SessionInfo}. - Task AuthenticateNewSession(string username, - string password, - string clientType, - string appVersion, - string deviceId, - string deviceName, - string remoteEndPoint, - bool isLocal); + Task AuthenticateNewSession(AuthenticationRequest request, bool isLocal); /// /// Reports the capabilities. diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 1d66d1505b..0c814c0d4c 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Providers; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,6 +9,16 @@ namespace MediaBrowser.Controller.Subtitles { public interface ISubtitleManager { + /// + /// Occurs when [subtitle download failure]. + /// + event EventHandler SubtitleDownloadFailure; + + /// + /// Occurs when [subtitles downloaded]. + /// + event EventHandler SubtitlesDownloaded; + /// /// Adds the parts. /// @@ -31,7 +42,7 @@ namespace MediaBrowser.Controller.Subtitles /// The request. /// The cancellation token. /// Task{IEnumerable{RemoteSubtitleInfo}}. - Task> SearchSubtitles(SubtitleSearchRequest request, + Task> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken); /// @@ -41,8 +52,8 @@ namespace MediaBrowser.Controller.Subtitles /// The subtitle identifier. /// The cancellation token. /// Task. - Task DownloadSubtitles(Video video, - string subtitleId, + Task DownloadSubtitles(Video video, + string subtitleId, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs new file mode 100644 index 0000000000..1d204f2cbd --- /dev/null +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Subtitles +{ + public class SubtitleDownloadEventArgs + { + public BaseItem Item { get; set; } + + public string Format { get; set; } + + public string Language { get; set; } + + public bool IsForced { get; set; } + + public string Provider { get; set; } + } + + public class SubtitleDownloadFailureEventArgs + { + public BaseItem Item { get; set; } + + public string Provider { get; set; } + + public Exception Exception { get; set; } + } +} diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index 62aec5ecbd..25778d036b 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.LocalMetadata var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -46,7 +46,7 @@ namespace MediaBrowser.LocalMetadata } finally { - XmlProviderUtils.XmlParsingResourcePool.Release(); + //XmlProviderUtils.XmlParsingResourcePool.Release(); } return result; diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ca48b88896..2a99076d42 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -83,6 +83,9 @@ + + Activity\ActivityLogEntry.cs + ApiClient\ApiClientExtensions.cs @@ -173,9 +176,6 @@ Configuration\ServerConfiguration.cs - - Configuration\SubtitleOptions.cs - Configuration\SubtitlePlaybackMode.cs @@ -728,6 +728,9 @@ Providers\RemoteSubtitleInfo.cs + + Providers\SubtitleOptions.cs + Querying\AllThemeMediaResult.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 1adf83d36a..72414d454c 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -52,6 +52,9 @@ + + Activity\ActivityLogEntry.cs + ApiClient\GeneralCommandEventArgs.cs @@ -136,9 +139,6 @@ Configuration\ServerConfiguration.cs - - Configuration\SubtitleOptions.cs - Configuration\SubtitlePlaybackMode.cs @@ -685,6 +685,9 @@ Providers\RemoteSubtitleInfo.cs + + Providers\SubtitleOptions.cs + Querying\AllThemeMediaResult.cs diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs new file mode 100644 index 0000000000..8fad574618 --- /dev/null +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Model.Logging; +using System; + +namespace MediaBrowser.Model.Activity +{ + public class ActivityLogEntry + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the overview. + /// + /// The overview. + public string Overview { get; set; } + + /// + /// Gets or sets the short overview. + /// + /// The short overview. + public string ShortOverview { get; set; } + + /// + /// Gets or sets the type. + /// + /// The type. + public string Type { get; set; } + + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public string ItemId { get; set; } + + /// + /// Gets or sets the date. + /// + /// The date. + public DateTime Date { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets the log severity. + /// + /// The log severity. + public LogSeverity Severity { get; set; } + } +} diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 363500954a..98b765e6bd 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -623,7 +623,7 @@ namespace MediaBrowser.Model.ApiClient Task ReportPlaybackStoppedAsync(PlaybackStopInfo info); /// - /// Instructs antoher client to browse to a library item. + /// Instructs another client to browse to a library item. /// /// The session id. /// The id of the item to browse to. diff --git a/MediaBrowser.Model/Channels/ChannelItemQuery.cs b/MediaBrowser.Model/Channels/ChannelItemQuery.cs index a76c6cd2d3..4aacc16194 100644 --- a/MediaBrowser.Model/Channels/ChannelItemQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelItemQuery.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using System.Collections.Generic; namespace MediaBrowser.Model.Channels { @@ -39,13 +38,13 @@ namespace MediaBrowser.Model.Channels public SortOrder? SortOrder { get; set; } public string[] SortBy { get; set; } public ItemFilter[] Filters { get; set; } - public List Fields { get; set; } + public ItemFields[] Fields { get; set; } public ChannelItemQuery() { Filters = new ItemFilter[] { }; SortBy = new string[] { }; - Fields = new List(); + Fields = new ItemFields[] { }; } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 4734e2af7d..6600a3e912 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Model.Configuration { @@ -285,8 +286,6 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions(0, 1280) {ItemType = "Season"} }; - - SubtitleOptions = new SubtitleOptions(); } } } diff --git a/MediaBrowser.Model/Configuration/SubtitleOptions.cs b/MediaBrowser.Model/Configuration/SubtitleOptions.cs deleted file mode 100644 index d50dba1b2e..0000000000 --- a/MediaBrowser.Model/Configuration/SubtitleOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public class SubtitleOptions - { - public bool SkipIfGraphicalSubtitlesPresent { get; set; } - public bool SkipIfAudioTrackMatches { get; set; } - public string[] DownloadLanguages { get; set; } - public bool DownloadMovieSubtitles { get; set; } - public bool DownloadEpisodeSubtitles { get; set; } - - public string OpenSubtitlesUsername { get; set; } - public string OpenSubtitlesPasswordHash { get; set; } - public bool IsOpenSubtitleVipAccount { get; set; } - - public SubtitleOptions() - { - DownloadLanguages = new string[] { }; - - SkipIfAudioTrackMatches = true; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs index 5a83419e19..3c558577ad 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/MediaBrowser.Model/Events/GenericEventArgs.cs @@ -13,5 +13,21 @@ namespace MediaBrowser.Model.Events /// /// The argument. public T Argument { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The argument. + public GenericEventArgs(T arg) + { + Argument = arg; + } + + /// + /// Initializes a new instance of the class. + /// + public GenericEventArgs() + { + } } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 042828887f..75694cb04d 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -59,6 +59,7 @@ Properties\SharedVersion.cs + @@ -99,7 +100,7 @@ - + @@ -378,4 +379,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i --> - + \ No newline at end of file diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs new file mode 100644 index 0000000000..84f01e0b7a --- /dev/null +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -0,0 +1,22 @@ +namespace MediaBrowser.Model.Providers +{ + public class SubtitleOptions + { + public bool SkipIfGraphicalSubtitlesPresent { get; set; } + public bool SkipIfAudioTrackMatches { get; set; } + public string[] DownloadLanguages { get; set; } + public bool DownloadMovieSubtitles { get; set; } + public bool DownloadEpisodeSubtitles { get; set; } + + public string OpenSubtitlesUsername { get; set; } + public string OpenSubtitlesPasswordHash { get; set; } + public bool IsOpenSubtitleVipAccount { get; set; } + + public SubtitleOptions() + { + DownloadLanguages = new string[] { }; + + SkipIfAudioTrackMatches = true; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs index e73b4c9a11..956d68ae4d 100644 --- a/MediaBrowser.Model/Tasks/TaskResult.cs +++ b/MediaBrowser.Model/Tasks/TaskResult.cs @@ -42,5 +42,11 @@ namespace MediaBrowser.Model.Tasks /// /// The error message. public string ErrorMessage { get; set; } + + /// + /// Gets or sets the long error message. + /// + /// The long error message. + public string LongErrorMessage { get; set; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 76a1e52f5b..66188f796a 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -152,6 +152,7 @@ + @@ -213,4 +214,4 @@ --> - + \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index a2e1ba05a7..f48707582c 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -13,10 +13,12 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; @@ -464,6 +466,11 @@ namespace MediaBrowser.Providers.MediaInfo } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration("subtitles"); + } + /// /// Adds the external subtitles. /// @@ -484,9 +491,11 @@ namespace MediaBrowser.Providers.MediaInfo var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; - if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && + var subtitleOptions = GetOptions(); + + if (enableSubtitleDownloading && (subtitleOptions.DownloadEpisodeSubtitles && video is Episode) || - (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && + (subtitleOptions.DownloadMovieSubtitles && video is Movie)) { var downloadedLanguages = await new SubtitleDownloader(_logger, @@ -494,9 +503,9 @@ namespace MediaBrowser.Providers.MediaInfo .DownloadSubtitles(video, currentStreams, externalSubtitleStreams, - _config.Configuration.SubtitleOptions.SkipIfGraphicalSubtitlesPresent, - _config.Configuration.SubtitleOptions.SkipIfAudioTrackMatches, - _config.Configuration.SubtitleOptions.DownloadLanguages, + subtitleOptions.SkipIfGraphicalSubtitlesPresent, + subtitleOptions.SkipIfAudioTrackMatches, + subtitleOptions.DownloadLanguages, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 361cc317c2..63df3f50d5 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -1,10 +1,12 @@ -using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -12,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.MediaInfo { @@ -45,8 +48,15 @@ namespace MediaBrowser.Providers.MediaInfo get { return "Library"; } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration("subtitles"); + } + public async Task Execute(CancellationToken cancellationToken, IProgress progress) { + var options = GetOptions(); + var videos = _libraryManager.RootFolder .RecursiveChildren .OfType