From 43943657b7ea0c607f998c6397d3878e3fb05c5f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 1 Jul 2014 00:06:28 -0400 Subject: add additional view settings --- .../Collections/CollectionManager.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index a69f9e94bd..d9323573d4 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -4,13 +4,13 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MoreLinq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MoreLinq; namespace MediaBrowser.Server.Implementations.Collections { @@ -27,6 +27,12 @@ namespace MediaBrowser.Server.Implementations.Collections _iLibraryMonitor = iLibraryMonitor; } + public Folder GetCollectionsFolder(string userId) + { + return _libraryManager.RootFolder.Children.Concat(_libraryManager.RootFolder.GetHiddenChildren()).OfType() + .FirstOrDefault(); + } + public async Task CreateCollection(CollectionCreationOptions options) { var name = options.Name; @@ -104,8 +110,7 @@ namespace MediaBrowser.Server.Implementations.Collections } } - return _libraryManager.RootFolder.Children.OfType().FirstOrDefault() ?? - _libraryManager.RootFolder.GetHiddenChildren().OfType().FirstOrDefault(); + return GetCollectionsFolder(string.Empty); } public async Task AddToCollection(Guid collectionId, IEnumerable ids) -- cgit v1.2.3 From 970504abdf237a2c404024c6978d5353ea915d03 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 1 Jul 2014 00:26:50 -0400 Subject: update translations --- MediaBrowser.Api/Library/LibraryService.cs | 10 ++++++- MediaBrowser.Controller/Entities/Folder.cs | 7 +---- .../Collections/CollectionManager.cs | 2 +- .../Collections/ManualCollectionsFolder.cs | 2 +- .../Localization/JavaScript/ar.json | 14 +++++++--- .../Localization/JavaScript/ca.json | 14 +++++++--- .../Localization/JavaScript/cs.json | 14 +++++++--- .../Localization/JavaScript/da.json | 14 +++++++--- .../Localization/JavaScript/de.json | 8 +++++- .../Localization/JavaScript/el.json | 14 +++++++--- .../Localization/JavaScript/en_GB.json | 14 +++++++--- .../Localization/JavaScript/en_US.json | 14 +++++++--- .../Localization/JavaScript/es.json | 8 +++++- .../Localization/JavaScript/es_MX.json | 8 +++++- .../Localization/JavaScript/fr.json | 8 +++++- .../Localization/JavaScript/he.json | 14 +++++++--- .../Localization/JavaScript/it.json | 16 +++++++---- .../Localization/JavaScript/kk.json | 8 +++++- .../Localization/JavaScript/ms.json | 14 +++++++--- .../Localization/JavaScript/nb.json | 14 +++++++--- .../Localization/JavaScript/nl.json | 8 +++++- .../Localization/JavaScript/pl.json | 14 +++++++--- .../Localization/JavaScript/pt_BR.json | 8 +++++- .../Localization/JavaScript/pt_PT.json | 10 +++++-- .../Localization/JavaScript/ru.json | 8 +++++- .../Localization/JavaScript/sv.json | 8 +++++- .../Localization/JavaScript/vi.json | 14 +++++++--- .../Localization/JavaScript/zh_TW.json | 14 +++++++--- .../Localization/Server/ar.json | 16 ++++++++++- .../Localization/Server/ca.json | 16 ++++++++++- .../Localization/Server/cs.json | 18 ++++++++++-- .../Localization/Server/da.json | 16 ++++++++++- .../Localization/Server/de.json | 24 ++++++++++++---- .../Localization/Server/el.json | 16 ++++++++++- .../Localization/Server/en_GB.json | 16 ++++++++++- .../Localization/Server/en_US.json | 16 ++++++++++- .../Localization/Server/es.json | 20 ++++++++++++-- .../Localization/Server/es_MX.json | 20 ++++++++++++-- .../Localization/Server/fr.json | 26 ++++++++++++++---- .../Localization/Server/he.json | 22 ++++++++++++--- .../Localization/Server/it.json | 32 ++++++++++++++++------ .../Localization/Server/kk.json | 16 ++++++++++- .../Localization/Server/ms.json | 16 ++++++++++- .../Localization/Server/nb.json | 16 ++++++++++- .../Localization/Server/nl.json | 20 ++++++++++++-- .../Localization/Server/pl.json | 16 ++++++++++- .../Localization/Server/pt_BR.json | 16 ++++++++++- .../Localization/Server/pt_PT.json | 24 ++++++++++++---- .../Localization/Server/ru.json | 18 ++++++++++-- .../Localization/Server/sv.json | 24 ++++++++++++---- .../Localization/Server/vi.json | 16 ++++++++++- .../Localization/Server/zh_TW.json | 18 ++++++++++-- 52 files changed, 621 insertions(+), 138 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index e1494700c8..ddb2dc943e 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -210,7 +210,8 @@ namespace MediaBrowser.Api.Library [Api(Description = "Gets all user media folders.")] public class GetMediaFolders : IReturn { - + [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? IsHidden { get; set; } } [Route("/Library/Series/Added", "POST")] @@ -259,6 +260,13 @@ namespace MediaBrowser.Api.Library { var items = _libraryManager.GetUserRootFolder().Children.OrderBy(i => i.SortName).ToList(); + if (request.IsHidden.HasValue) + { + var val = request.IsHidden.Value; + + items = items.Where(i => i.IsHidden == val).ToList(); + } + // Get everything var fields = Enum.GetNames(typeof(ItemFields)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index e54e72cd8d..c712d50afa 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public IEnumerable Children { - get { return ActualChildren.Where(i => !i.IsHidden); } + get { return ActualChildren; } } /// @@ -1101,10 +1101,5 @@ namespace MediaBrowser.Controller.Entities return GetRecursiveChildren(user).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual) .All(i => i.IsUnplayed(user)); } - - public IEnumerable GetHiddenChildren() - { - return ActualChildren.Where(i => i.IsHidden); - } } } diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index d9323573d4..728b18bbf4 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Server.Implementations.Collections public Folder GetCollectionsFolder(string userId) { - return _libraryManager.RootFolder.Children.Concat(_libraryManager.RootFolder.GetHiddenChildren()).OfType() + return _libraryManager.RootFolder.Children.Concat(_libraryManager.RootFolder).OfType() .FirstOrDefault(); } diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index 03479d838f..739b42192b 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Server.Implementations.Collections { get { - return !ActualChildren.Any() || base.IsHidden; + return true; } } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json index 9d900131fb..77478dd48b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 b3480f4a28..de4323e1ec 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 9d0c2d6fba..8d831d02c0 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "P\u0159ehr\u00e1t", "ButtonEdit": "Upravit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 a545648641..7b5c19ab5a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Afspil", "ButtonEdit": "Rediger", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 9fa6b0ca23..355107e18a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 719f63e086..d88b82f10e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 246d872751..1fa2b959dc 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 6c5cf65dff..6a4d442be4 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json index 3873a10fc2..3caaa8d5e7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Ruta actual:", "HeaderSelectMediaPath": "Seleccionar la ruta para Medios", "ButtonNetwork": "Red", - "MessageDirectoryPickerInstruction": "Rutas de red pueden ser introducidas manualmente en el caso de que el bot\u00f3n de la red no pueda localizar sus dispositivos. Por ejemplo, {0} o {1}." + "MessageDirectoryPickerInstruction": "Rutas de red pueden ser introducidas manualmente en el caso de que el bot\u00f3n de la red no pueda localizar sus dispositivos. Por ejemplo, {0} o {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 d72c295e38..4a9a6e5774 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Trayectoria actual:", "HeaderSelectMediaPath": "Seleccionar trayectoria a medios", "ButtonNetwork": "Red", - "MessageDirectoryPickerInstruction": "Las trayectorias de red pueden ser ingresadas manualmente en caso de que el bot\u00f3n de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}." + "MessageDirectoryPickerInstruction": "Las trayectorias de red pueden ser ingresadas manualmente en caso de que el bot\u00f3n de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 a452eb02d5..ba06845d59 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Chemin actuel:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "R\u00e9seau", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 c3f5fecc17..1683bd4b04 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "\u05e0\u05d2\u05df", "ButtonEdit": "\u05e2\u05e8\u05d5\u05da", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 4c5bfe46ee..c7ac54e50e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json @@ -190,9 +190,15 @@ "HeaderLatestTvRecordings": "Ultime registrazioni", "ButtonOk": "OK", "ButtonCancel": "Annulla", - "ButtonRefresh": "Refresh", - "LabelCurrentPath": "Current path:", - "HeaderSelectMediaPath": "Select Media Path", - "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "ButtonRefresh": "Aggiorna", + "LabelCurrentPath": "Percorso Corrente:", + "HeaderSelectMediaPath": "Seleziona il percorso", + "ButtonNetwork": "Rete", + "MessageDirectoryPickerInstruction": "Percorsi di rete possono essere inseriti manualmente nel caso in cui il pulsante Rete non riesce a individuare i vostri dispositivi. Ad esempio, {0} o {1}", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 5191e82e96..aa7222e62f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "\u0410\u0493\u044b\u043c\u0434\u044b\u049b \u0436\u043e\u043b:", "HeaderSelectMediaPath": "\u0422\u0430\u0441\u0443\u0448\u044b \u0436\u043e\u043b\u044b\u043d \u0442\u0430\u04a3\u0434\u0430\u0443", "ButtonNetwork": "\u0416\u0435\u043b\u0456", - "MessageDirectoryPickerInstruction": "\u0416\u0435\u043b\u0456 \u0442\u04af\u0439\u043c\u0435\u0448\u0456\u0433\u0456 \u0431\u0430\u0441\u044b\u043b\u0493\u0430\u043d\u0434\u0430 \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440 \u043e\u0440\u043d\u044b \u0442\u0430\u0431\u044b\u043b\u043c\u0430\u0441\u0430, \u0436\u0435\u043b\u0456\u043b\u0456\u043a \u0436\u043e\u043b\u0434\u0430\u0440 \u049b\u043e\u043b\u043c\u0435\u043d \u0435\u043d\u0433\u0456\u0437\u0456\u043b\u0443\u0456 \u043c\u04af\u043c\u043a\u0456\u043d. \u041c\u044b\u0441\u0430\u043b\u044b, {0} \u043d\u0435\u043c\u0435\u0441\u0435 {1}." + "MessageDirectoryPickerInstruction": "\u0416\u0435\u043b\u0456 \u0442\u04af\u0439\u043c\u0435\u0448\u0456\u0433\u0456 \u0431\u0430\u0441\u044b\u043b\u0493\u0430\u043d\u0434\u0430 \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440 \u043e\u0440\u043d\u044b \u0442\u0430\u0431\u044b\u043b\u043c\u0430\u0441\u0430, \u0436\u0435\u043b\u0456\u043b\u0456\u043a \u0436\u043e\u043b\u0434\u0430\u0440 \u049b\u043e\u043b\u043c\u0435\u043d \u0435\u043d\u0433\u0456\u0437\u0456\u043b\u0443\u0456 \u043c\u04af\u043c\u043a\u0456\u043d. \u041c\u044b\u0441\u0430\u043b\u044b, {0} \u043d\u0435\u043c\u0435\u0441\u0435 {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 57cb41fe94..fb6dcff78d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 52e3f2b4ae..dbf708f6e7 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 9f54ce9b62..5a5e81c2f8 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Huidige pad:", "HeaderSelectMediaPath": "Selecteer Media Pad", "ButtonNetwork": "Netwerk", - "MessageDirectoryPickerInstruction": "Netwerk paden kunnen handmatig worden ingevoerd in het geval de Netwerk knop faalt om uw apparatuur te lokaliseren . Bijvoorbeeld: {0} of {1}." + "MessageDirectoryPickerInstruction": "Netwerk paden kunnen handmatig worden ingevoerd in het geval de Netwerk knop faalt om uw apparatuur te lokaliseren . Bijvoorbeeld: {0} of {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 b3ab8d3b90..81326f875d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 5443451d60..34fdc4f681 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Caminho atual:", "HeaderSelectMediaPath": "Selecionar o Caminho da M\u00eddia", "ButtonNetwork": "Rede", - "MessageDirectoryPickerInstruction": "Os caminhos da rede podem ser digitados manualmente caso o bot\u00e3o de Rede n\u00e3o consiga localizar seus dispositivos. Por exemplo, {0} ou {1}." + "MessageDirectoryPickerInstruction": "Os caminhos da rede podem ser digitados manualmente caso o bot\u00e3o de Rede n\u00e3o consiga localizar seus dispositivos. Por exemplo, {0} ou {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 b996b70753..64720463e5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -63,7 +63,7 @@ "ButtonPlay": "Reproduzir", "ButtonEdit": "Editar", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", "ButtonPreviousTrack": "Faixa Anterior", "LabelEnabled": "Enabled", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 afabb17e70..824353427f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "\u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u043f\u0443\u0442\u044c:", "HeaderSelectMediaPath": "\u0412\u044b\u0431\u043e\u0440 \u043f\u0443\u0442\u0438 \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044f", "ButtonNetwork": "\u0421\u0435\u0442\u044c", - "MessageDirectoryPickerInstruction": "\u0421\u0435\u0442\u0435\u0432\u044b\u0435 \u043f\u0443\u0442\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u0421\u0435\u0442\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0431\u043e\u0439 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, {0} \u0438\u043b\u0438 {1}." + "MessageDirectoryPickerInstruction": "\u0421\u0435\u0442\u0435\u0432\u044b\u0435 \u043f\u0443\u0442\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u0421\u0435\u0442\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0431\u043e\u0439 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, {0} \u0438\u043b\u0438 {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 b41dba8107..2dc9a559ef 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json @@ -194,5 +194,11 @@ "LabelCurrentPath": "Aktuell s\u00f6kv\u00e4g:", "HeaderSelectMediaPath": "V\u00e4lj s\u00f6kv\u00e4g till media", "ButtonNetwork": "N\u00e4tverk", - "MessageDirectoryPickerInstruction": "N\u00e4tverkss\u00f6kv\u00e4gar kan anges manuellt om \"N\u00e4tverk\" inte hittar dina enheter. T ex {0} eller {1}." + "MessageDirectoryPickerInstruction": "N\u00e4tverkss\u00f6kv\u00e4gar kan anges manuellt om \"N\u00e4tverk\" inte hittar dina enheter. T ex {0} eller {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 9feeb51832..b4869f2602 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "Play", "ButtonEdit": "Edit", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 7bd50e2ca9..3e76fe9d16 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -58,14 +58,14 @@ "LabelUnknownLanguage": "Unknown language", "ButtonMute": "Mute", "ButtonUnmute": "Unmute", - "ButtonNextTrack": "Next track", + "ButtonNextTrack": "Next Track", "ButtonPause": "Pause", "ButtonPlay": "\u64ad\u653e", "ButtonEdit": "\u7de8\u8f2f", "ButtonQueue": "Queue", - "ButtonPlayTrailer": "PlayTrailer", + "ButtonPlayTrailer": "Play trailer", "ButtonPlaylist": "Playlist", - "ButtonPreviousTrack": "Previous track", + "ButtonPreviousTrack": "Previous Track", "LabelEnabled": "Enabled", "LabelDisabled": "Disabled", "ButtonMoreInformation": "More Information", @@ -194,5 +194,11 @@ "LabelCurrentPath": "Current path:", "HeaderSelectMediaPath": "Select Media Path", "ButtonNetwork": "Network", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}." + "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", + "HeaderMenu": "Menu", + "ButtonOpen": "Open", + "ButtonOpenInNewTab": "Open in new tab", + "ButtonShuffle": "Shuffle", + "ButtonInstantMix": "Instant mix", + "ButtonResume": "Resume" } \ 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 82a5cb4228..a638ed6309 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 13e14f7b44..db7911515a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 b9f7fba6fe..a2a8e8fa71 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": "Skipped", "HeaderEpisodeOrganization": "Episode Organization", "LabelSeries": "Series:", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 ac070a3520..0062e8d6d5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 05e6e7202f..6b982e0d3e 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", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 fd439fdc4e..97ed4b27ff 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 4d4a24ce1b..ca1dfd1639 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 91588bdaab..4d650c135b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es.json b/MediaBrowser.Server.Implementations/Localization/Server/es.json index 125693f340..fbbb9d1e16 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/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:", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Control remoto", "OptionLatestTvRecordings": "\u00daltimas grabaciones", "LabelProtocolInfo": "Informaci\u00f3n de protocolo:", - "LabelProtocolInfoHelp": "El valor que se utilizar\u00e1 cuando se responde a una solicitud GetProtocolInfo desde el dispositivo." + "LabelProtocolInfoHelp": "El valor que se utilizar\u00e1 cuando se responde a una solicitud GetProtocolInfo desde el dispositivo.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser incluye soporte nativo para XBMC, Nfo, metadatos e im\u00e1genes. Para activar o desactivar los metadatos XBMC, utilice la ficha Avanzadas para configurar opciones para sus tipos de medios.", + "LabelXbmcMetadataUser": "A\u00f1adir datos de reproducciones de usuario a los nfo\u00b4s para:", + "LabelXbmcMetadataUserHelp": "Activar esto para mantener sincronizados los datos de reproducci\u00f3n entre Media Browser y Xbmc.", + "LabelXbmcMetadataDateFormat": "Formato de fecha de estreno:", + "LabelXbmcMetadataDateFormatHelp": "Todas las fechas dentro de los nfo se leer\u00e1n y se escribir\u00e1n usando este formato.", + "LabelXbmcMetadataSaveImagePaths": "Grabar las rutas de las im\u00e1genes en los archivos nfo", + "LabelXbmcMetadataSaveImagePathsHelp": "\nEsto se recomienda si usted tiene los nombres de archivo de imagen que no se ajusten a las directrices de XBMC.", + "LabelXbmcMetadataEnablePathSubstitution": "Habilitar rutas de sustituci\u00f3n", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Permite la sustituci\u00f3n de las rutas de im\u00e1genes utilizando la configuraci\u00f3n de rutas de sustituci\u00f3n en las opciones del servidor.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Ver rutas de sustituci\u00f3n.", + "LabelGroupChannelsIntoViews": "Visualice los siguientes canales dentro de mis vistas:", + "LabelGroupChannelsIntoViewsHelp": "Si est\u00e1 activado, estos canales se mostrar\u00e1n directamente junto a Mis Vistas. Si est\u00e1 desactivada, ser\u00e1n mostrados separadamente en la vista de Canales.", + "LabelDisplayCollectionsView": "Display a Collections view to show movie collections" } \ 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 7ddd4c9872..1709538026 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -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", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Control Remoto", "OptionLatestTvRecordings": "\u00daltimas grabaciones", "LabelProtocolInfo": "Informaci\u00f3n del protocolo:", - "LabelProtocolInfoHelp": "El valor que ser\u00e1 utilizado cuando se responde a solicitudes GetProtocolInfo desde el dispositivo." + "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:", + "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.", + "LabelXbmcMetadataSaveImagePaths": "Guardar trayectorias de im\u00e1genes dentro de los archivos nfo.", + "LabelXbmcMetadataSaveImagePathsHelp": "Esto se recomienda si usted tiene nombres de im\u00e1genes que no se ajustan a los lineamientos de Xbmc.", + "LabelXbmcMetadataEnablePathSubstitution": "Habilitar sustituci\u00f3n de trayectorias.", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Habilita la susituci\u00f3n de trayectorias de las trayectorias de im\u00e1genes usando la configuraci\u00f3n de sustituci\u00f3n de trayectorias del servidor.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Ver sustituci\u00f3n de trayectorias.", + "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" } \ 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 69f75dd949..ded4dbc061 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", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Acc\u00e8s \u00e0 distance", "OptionLatestTvRecordings": "Les plus r\u00e9cents enregistrements", "LabelProtocolInfo": "Infos sur le protocol:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 28bb4a4bfc..8284fbbd93 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:", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 1ef8205ad0..e604b1c4af 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", @@ -771,9 +771,9 @@ "LabelHomePageSection2": "Sezione 2 pagina iniziale", "LabelHomePageSection3": "Sezione 3 pagina iniziale", "LabelHomePageSection4": "Sezione Home page sezione quattro:", - "OptionMyViewsButtons": "My views (buttons)", - "OptionMyViews": "My views", - "OptionMyViewsSmall": "My views (small)", + "OptionMyViewsButtons": "Mie Viste", + "OptionMyViews": "Mie Viste", + "OptionMyViewsSmall": "Mie Viste", "OptionResumablemedia": "Riprendi", "OptionLatestMedia": "Ultimo media", "OptionLatestChannelMedia": "Ultime voci del canale", @@ -820,6 +820,20 @@ "OptionLibraryFolders": "Vista", "TitleRemoteControl": "Telecomando", "OptionLatestTvRecordings": "Ultime registrazioni", - "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfo": "Info.protocollo:", + "LabelProtocolInfoHelp": "Il valore che verr\u00e0 utilizzato quando si risponde a GetProtocolInfo richieste dal dispositivo.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser include il supporto nativo per i metadati XBMC Nfo e immagini. Per attivare o disattivare i metadati XBMC, utilizzare la scheda Avanzate per configurare le opzioni per i tipi di media.", + "LabelXbmcMetadataUser": "Aggiungere i dati di orologi utenti per nfo di per:", + "LabelXbmcMetadataUserHelp": "Attivare questa opzione per mantenere i dati di orologi sincronizzati tra Media Browser e XBMC.", + "LabelXbmcMetadataDateFormat": "Formato Data di rilascio:", + "LabelXbmcMetadataDateFormatHelp": "Tutte le date all'interno del nfo verranno letti e scritti utilizzando questo formato.", + "LabelXbmcMetadataSaveImagePaths": "Salvare percorsi delle immagini all'interno di file nfo", + "LabelXbmcMetadataSaveImagePathsHelp": "Questo \u00e8 consigliato se si dispone di nomi di file di immagine che non sono conformi alle linee guida XBMC.", + "LabelXbmcMetadataEnablePathSubstitution": "Abilita sostituzione di percorso", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Consente percorso sostituzione dei percorsi delle immagini utilizzando le impostazioni di sostituzione percorso del server.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Vedere sostituzione di percorso.", + "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" } \ 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 6e8abc47da..1497be46a9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "\u049a\u0430\u0448\u044b\u049b\u0442\u0430\u043d \u0431\u0430\u0441\u049b\u0430\u0440\u0443", "OptionLatestTvRecordings": "\u0415\u04a3 \u043a\u0435\u0439\u0456\u043d\u0433\u0456 \u0436\u0430\u0437\u0431\u0430\u043b\u0430\u0440", "LabelProtocolInfo": "\u041f\u0440\u043e\u0442\u043e\u049b\u043e\u043b \u0430\u049b\u043f\u0430\u0440\u0430\u0442\u044b:", - "LabelProtocolInfoHelp": "\u0411\u04b1\u043b \u043c\u04d9\u043d \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u044b\u04a3 GetProtocolInfo \u0441\u04b1\u0440\u0430\u043d\u044b\u0441\u0442\u0430\u0440\u044b\u043d\u0430 \u0436\u0430\u0443\u0430\u043f \u0431\u0435\u0440\u0433\u0435\u043d\u0434\u0435 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b." + "LabelProtocolInfoHelp": "\u0411\u04b1\u043b \u043c\u04d9\u043d \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u044b\u04a3 GetProtocolInfo \u0441\u04b1\u0440\u0430\u043d\u044b\u0441\u0442\u0430\u0440\u044b\u043d\u0430 \u0436\u0430\u0443\u0430\u043f \u0431\u0435\u0440\u0433\u0435\u043d\u0434\u0435 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser \u0431\u0430\u0493\u0434\u0430\u0440\u043b\u0430\u043c\u0430\u0441\u044b Xbmc Nfo \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u0436\u04d9\u043d\u0435 \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0456\u043d\u0456\u04a3 \u043a\u0456\u0440\u0456\u043a\u0442\u0456\u0440\u043c\u0435 \u049b\u043e\u043b\u0434\u0430\u0443\u044b\u043d \u049b\u0430\u043c\u0442\u0438\u0434\u044b.", + "LabelXbmcMetadataUser": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043d\u044b\u04a3 \u049b\u0430\u0440\u0430\u0443 \u043a\u04af\u0439\u0456\u043d nfo \u04af\u0448\u0456\u043d \u043c\u044b\u043d\u0430\u0493\u0430\u043d \u049b\u043e\u0441\u0443:", + "LabelXbmcMetadataUserHelp": "\u041a\u04e9\u0440\u0456\u043b\u0433\u0435\u043d \u043a\u04af\u0439\u0434\u0456 Media Browser \u0436\u04d9\u043d\u0435 Xbmc \u0430\u0440\u0430\u0441\u044b\u043d\u0434\u0430 \u04af\u0439\u043b\u0435\u0441\u0442\u0456\u0440\u0456\u043f \u0442\u04b1\u0440\u0443 \u04af\u0448\u0456\u043d \u0431\u04b1\u043d\u044b \u049b\u043e\u0441\u044b\u04a3\u044b\u0437.", + "LabelXbmcMetadataDateFormat": "\u0416\u044b\u0493\u0430\u0440\u0443 \u043a\u04af\u043d-\u0430\u0439\u044b\u043d\u044b\u04a3 \u043f\u0456\u0448\u0456\u043c\u0456:", + "LabelXbmcMetadataDateFormatHelp": "\u041e\u0441\u044b \u043f\u0456\u0448\u0456\u043c\u0434\u0456 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043f nfo \u0456\u0448\u0456\u043d\u0434\u0435\u0433\u0456 \u0431\u0430\u0440\u043b\u044b\u049b \u043a\u04af\u043d-\u0430\u0439\u043b\u0430\u0440 \u043e\u049b\u044b\u043b\u0430\u0434\u044b \u0436\u04d9\u043d\u0435 \u0436\u0430\u0437\u044b\u043b\u0430\u0434\u044b.", + "LabelXbmcMetadataSaveImagePaths": "\u0421\u0443\u0440\u0435\u0442 \u0436\u043e\u043b\u044b\u043d nfo \u0444\u0430\u0439\u043b\u0434\u0430\u0440\u044b\u043d\u0434\u0430 \u0441\u0430\u049b\u0442\u0430\u0443", + "LabelXbmcMetadataSaveImagePathsHelp": "\u0415\u0433\u0435\u0440 \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0434\u0456\u04a3 Xbmc \u043d\u04b1\u0441\u049b\u0430\u0443\u043b\u044b\u049b \u04b1\u0441\u0442\u0430\u043d\u044b\u043c\u0434\u0430\u0440\u044b\u043d\u0430 \u0441\u0430\u0439 \u043a\u0435\u043b\u043c\u0435\u0433\u0435\u043d \u0430\u0442\u0430\u0443\u043b\u0430\u0440\u044b \u0431\u043e\u043b\u0441\u0430, \u0431\u04b1\u043b \u04b1\u0441\u044b\u043d\u044b\u043b\u0430\u0434\u044b.", + "LabelXbmcMetadataEnablePathSubstitution": "\u0416\u043e\u043b \u0430\u043b\u043c\u0430\u0441\u0442\u044b\u0440\u0443\u0434\u044b \u049b\u043e\u0441\u0443", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u0436\u043e\u043b \u0430\u043b\u043c\u0430\u0441\u0442\u044b\u0440\u0443 \u0442\u0435\u04a3\u0448\u0435\u0443\u0456\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043f \u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440\u0434\u0456\u04a3 \u0436\u043e\u043b \u0430\u043b\u043c\u0430\u0441\u0442\u044b\u0440\u0443\u044b\u043d \u049b\u043e\u0441\u0430\u0434\u044b.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "\u0416\u043e\u043b \u0430\u043b\u043c\u0430\u0441\u0442\u044b\u0440\u0443\u0434\u044b \u049b\u0430\u0440\u0430\u0443.", + "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" } \ 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 2e32a8aaf1..29fb57e4af 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 2eb1f0c189..4d6862d867 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 1e92d46d68..74a241ee8e 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", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Beheer op afstand", "OptionLatestTvRecordings": "Nieuwste opnames", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "De waarde die wordt gebruikt bij het reageren op GetProtocolInfo verzoeken van het apparaat." + "LabelProtocolInfoHelp": "De waarde die wordt gebruikt bij het reageren op GetProtocolInfo verzoeken van het apparaat.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser omvat native ondersteuning voor XBMC Nfo metadata en afbeeldingen. Of uit te schakelen XBMC metadata, gebruikt u het tabblad Geavanceerd om opties voor uw mediatypen configureren.", + "LabelXbmcMetadataUser": "Voeg gekeken gegevens toe aan NFO's voor (gebruiker):", + "LabelXbmcMetadataUserHelp": "Activeer dit om gekeken gegevens sync te houden tussen Media Browser en XBMC.", + "LabelXbmcMetadataDateFormat": "Release datum formaat:", + "LabelXbmcMetadataDateFormatHelp": "Alle datums binnen nfo zullen worden gelezen en geschreven en gebruik maken van dit formaat.", + "LabelXbmcMetadataSaveImagePaths": "Bewaar afbeeldingen paden in nfo-bestanden", + "LabelXbmcMetadataSaveImagePathsHelp": "Dit wordt aanbevolen als je bestandsnamen hbt die niet voldoen aan XBMC richtlijnen.", + "LabelXbmcMetadataEnablePathSubstitution": "Pad vervanging inschakelen", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Stelt pad vervanging in voor afbeeldings paden en maakt gebruik van de pad vervangings instellingen van de server", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Zie pad vervanging.", + "LabelGroupChannelsIntoViews": "Toon de volgende kanalen binnen mijn weergaven:", + "LabelGroupChannelsIntoViewsHelp": "Indien ingeschakeld, zullen deze kanalen direct naast andere weergaven worden weergegeven. Indien uitgeschakeld, zullen ze worden weergegeven in een aparte kanalen weergave.", + "LabelDisplayCollectionsView": "Display a Collections view to show movie collections" } \ 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 4970fdccc6..f6a8508888 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 9dbd946fe4..482313b856 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Controle Remoto", "OptionLatestTvRecordings": "\u00daltimas grava\u00e7\u00f5es", "LabelProtocolInfo": "Informa\u00e7\u00e3o do protocolo:", - "LabelProtocolInfoHelp": "O valor que ser\u00e1 usado ao responder os pedidos GetProtocolInfo do dispositivo." + "LabelProtocolInfoHelp": "O valor que ser\u00e1 usado ao responder os pedidos GetProtocolInfo do dispositivo.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "O Media Browser inclui suporte nativo aos metadados Nfo e Imagens do Xbmc. Para ativar ou desativar os metadados do Xbmc, use a aba Avan\u00e7ado para configurar as op\u00e7\u00f5es para seus tipos de m\u00eddias.", + "LabelXbmcMetadataUser": "Adicionar dados de monitora\u00e7\u00e3o do usu\u00e1rio para nfo`s para:", + "LabelXbmcMetadataUserHelp": "Ativar esta op\u00e7\u00e3o para manter dados de monitora\u00e7\u00e3o em sincronia entre o Media Browser e o Xbmc.", + "LabelXbmcMetadataDateFormat": "Formato da data de lan\u00e7amento:", + "LabelXbmcMetadataDateFormatHelp": "Todas as datas dentro dos nfo`s ser\u00e3o lidas e gravadas para usar este formato.", + "LabelXbmcMetadataSaveImagePaths": "Salvar o caminho da imagem dentro dos arquivos nfo.", + "LabelXbmcMetadataSaveImagePathsHelp": "Esta op\u00e7\u00e3o \u00e9 recomendada se possuir nomes de arquivos de imagem que n\u00e3o est\u00e3o de acordo com as recomenda\u00e7\u00f5es do Xbmc.", + "LabelXbmcMetadataEnablePathSubstitution": "Ativar substitui\u00e7\u00e3o de caminho", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Ativa a substitui\u00e7\u00e3o do caminho da imagem usando as configura\u00e7\u00f5es de suvbstitui\u00e7\u00e3o de caminho do servidor.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "Ver substitui\u00e7\u00e3o de caminho.", + "LabelGroupChannelsIntoViews": "Exibir os seguintes canais dentro de minhas visualiza\u00e7\u00f5es:", + "LabelGroupChannelsIntoViewsHelp": "Se ativado, estes canais ser\u00e3o exibidos imediatamente ao lado de outras visualiza\u00e7\u00f5es. Se desativado, eles ser\u00e3o exibidos dentro de uma visualiza\u00e7\u00e3o separada de Canais.", + "LabelDisplayCollectionsView": "Display a Collections view to show movie collections" } \ 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 412b681221..7f7eed0dd8 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", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 e92a234179..40f53ac1d3 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:", @@ -821,5 +821,19 @@ "TitleRemoteControl": "\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435", "OptionLatestTvRecordings": "\u041d\u043e\u0432\u0438\u043d\u043a\u0438 \u0437\u0430\u043f\u0438\u0441\u0435\u0439", "LabelProtocolInfo": "\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0435:", - "LabelProtocolInfoHelp": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u043b\u0438\u043a\u0435 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u044b GetProtocolInfo \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430." + "LabelProtocolInfoHelp": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u043b\u0438\u043a\u0435 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u044b GetProtocolInfo \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0434\u043b\u044f Xbmc Nfo. \u0414\u043b\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 Xbmc, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0435, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e \u0442\u0438\u043f\u0430\u043c \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u0435\u0439.", + "LabelXbmcMetadataUser": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 nfo \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043b\u044f:", + "LabelXbmcMetadataUserHelp": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 Media Browser \u0438 Xbmc \u0445\u0440\u0430\u043d\u0438\u043c\u044b\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430.", + "LabelXbmcMetadataDateFormat": "\u0424\u043e\u0440\u043c\u0430\u0442 \u0434\u0430\u0442\u044b \u0432\u044b\u043f\u0443\u0441\u043a\u0430:", + "LabelXbmcMetadataDateFormatHelp": "\u0412\u0441\u0435 \u0434\u0430\u0442\u044b \u0432 nfo \u0431\u0443\u0434\u0443\u0442 \u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430.", + "LabelXbmcMetadataSaveImagePaths": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u0443\u0442\u0438 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0432 nfo \u0444\u0430\u0439\u043b\u0430\u0445", + "LabelXbmcMetadataSaveImagePathsHelp": "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0438\u043c\u0435\u043d\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u044f\u0449\u0438\u043c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c Xbmc.", + "LabelXbmcMetadataEnablePathSubstitution": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "\u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439 \u043a \u0440\u0438\u0441\u0443\u043d\u043a\u0430\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a \u043f\u0443\u0442\u0435\u0439 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.", + "LabelXbmcMetadataEnablePathSubstitutionHelp2": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0439.", + "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" } \ 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 79f1f58266..8005836247 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", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Fj\u00e4rrkontroll", "OptionLatestTvRecordings": "Senaste inspelningar", "LabelProtocolInfo": "Protokollinfo:", - "LabelProtocolInfoHelp": "V\u00e4rde att anv\u00e4nda vid svar p\u00e5 GetProtocolInfo-beg\u00e4ran fr\u00e5n enheter." + "LabelProtocolInfoHelp": "V\u00e4rde att anv\u00e4nda vid svar p\u00e5 GetProtocolInfo-beg\u00e4ran fr\u00e5n enheter.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 5298bd8b65..09de248168 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ 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 1642c22201..60b62f8bb2 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.", @@ -821,5 +821,19 @@ "TitleRemoteControl": "Remote Control", "OptionLatestTvRecordings": "Latest recordings", "LabelProtocolInfo": "Protocol info:", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device." + "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", + "TabXbmcMetadata": "Xbmc", + "HeaderXbmcMetadataHelp": "Media Browser includes native support for Xbmc Nfo metadata and images. To enable or disable Xbmc metadata, use the Advanced tab to configure options for your media types.", + "LabelXbmcMetadataUser": "Add user watch data to nfo's for:", + "LabelXbmcMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Xbmc.", + "LabelXbmcMetadataDateFormat": "Release date format:", + "LabelXbmcMetadataDateFormatHelp": "All dates within nfo's will be read and written to using this format.", + "LabelXbmcMetadataSaveImagePaths": "Save image paths within nfo files", + "LabelXbmcMetadataSaveImagePathsHelp": "This is recommended if you have image file names that don't conform to Xbmc guidelines.", + "LabelXbmcMetadataEnablePathSubstitution": "Enable path substitution", + "LabelXbmcMetadataEnablePathSubstitutionHelp": "Enables path substitution of image paths using the server's path substitution settings.", + "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" } \ No newline at end of file -- cgit v1.2.3 From c02e917f5657db4bd76fc6ca17c535fc441c641c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 7 Jul 2014 21:41:03 -0400 Subject: completed auth database --- MediaBrowser.Api/ApiEntryPoint.cs | 34 +-- MediaBrowser.Api/ConfigurationService.cs | 1 + MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 9 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 3 +- MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs | 2 +- MediaBrowser.Api/SessionsService.cs | 18 +- MediaBrowser.Api/SystemService.cs | 1 + MediaBrowser.Api/UserService.cs | 32 +- .../Channels/ChannelItemInfo.cs | 13 +- .../Collections/CollectionEvents.cs | 37 +++ .../Collections/ICollectionManager.cs | 15 + MediaBrowser.Controller/Dto/IDtoService.cs | 7 + .../MediaBrowser.Controller.csproj | 4 + .../Providers/IMetadataProvider.cs | 1 + .../Security/AuthenticationInfo.cs | 61 ++++ .../Security/AuthenticationInfoQuery.cs | 42 +++ .../Security/IAuthenticationRepository.cs | 39 +++ MediaBrowser.Controller/Session/ISessionManager.cs | 25 +- MediaBrowser.Controller/Session/SessionInfo.cs | 2 +- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 10 +- MediaBrowser.Model/ApiClient/IApiClient.cs | 19 +- .../Configuration/ServerConfiguration.cs | 2 + MediaBrowser.Model/Users/AuthenticationResult.cs | 2 +- MediaBrowser.Providers/Manager/MetadataService.cs | 1 - MediaBrowser.Providers/Manager/ProviderUtils.cs | 2 +- .../TV/MovieDbEpisodeImageProvider.cs | 2 +- .../Channels/ChannelManager.cs | 3 +- .../Collections/CollectionManager.cs | 57 +++- .../HttpServer/Security/AuthService.cs | 48 +-- .../MediaBrowser.Server.Implementations.csproj | 1 + .../SqliteFileOrganizationRepository.cs | 2 +- .../Security/AuthenticationRepository.cs | 338 +++++++++++++++++++++ .../Session/SessionManager.cs | 159 +++++++++- MediaBrowser.ServerApplication/ApplicationHost.cs | 20 +- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 2 +- .../Providers/BaseNfoProvider.cs | 10 +- MediaBrowser.XbmcMetadata/Savers/SeasonXmlSaver.cs | 13 +- .../Savers/XmlSaverHelpers.cs | 22 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 41 files changed, 933 insertions(+), 136 deletions(-) create mode 100644 MediaBrowser.Controller/Collections/CollectionEvents.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfo.cs create mode 100644 MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs create mode 100644 MediaBrowser.Controller/Security/IAuthenticationRepository.cs create mode 100644 MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 8e6ca44014..f255339bc3 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; - public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1,1); + public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1); /// /// Initializes a new instance of the class. @@ -102,7 +102,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, FileDeleteMode.All)); + Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, path => true)); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -295,17 +295,18 @@ namespace MediaBrowser.Api { var job = (TranscodingJob)state; - KillTranscodingJob(job, FileDeleteMode.All); + KillTranscodingJob(job, path => true); } /// /// Kills the single transcoding job. /// /// The device id. - /// The delete mode. + /// The delete. /// if set to true [acquire lock]. + /// Task. /// sourcePath - internal async Task KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode, bool acquireLock) + internal async Task KillTranscodingJobs(string deviceId, Func delete, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -330,12 +331,12 @@ namespace MediaBrowser.Api { await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); } - + try { foreach (var job in jobs) { - KillTranscodingJob(job, deleteMode); + KillTranscodingJob(job, delete); } } finally @@ -352,10 +353,11 @@ namespace MediaBrowser.Api /// /// The device identifier. /// The type. - /// The delete mode. + /// The delete. /// if set to true [acquire lock]. + /// Task. /// deviceId - internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, FileDeleteMode deleteMode, bool acquireLock) + internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, Func delete, bool acquireLock) { if (string.IsNullOrEmpty(deviceId)) { @@ -385,7 +387,7 @@ namespace MediaBrowser.Api { foreach (var job in jobs) { - KillTranscodingJob(job, deleteMode); + KillTranscodingJob(job, delete); } } finally @@ -401,8 +403,8 @@ namespace MediaBrowser.Api /// Kills the transcoding job. /// /// 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 798505f21cf2bf2c1d2448929e0a48438655b449 Mon Sep 17 00:00:00 2001 From: Eric Reed Date: Wed, 16 Jul 2014 16:01:38 -0400 Subject: Fix ItemsAddedToCollection not firing --- MediaBrowser.Server.Implementations/Collections/CollectionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 7bc7838c6f..8e51e1d7db 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -191,7 +191,7 @@ namespace MediaBrowser.Server.Implementations.Collections if (fireEvent) { - EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs + EventHelper.FireEventIfNotNull(ItemsAddedToCollection, this, new CollectionModifiedEventArgs { Collection = collection, ItemsChanged = itemList -- cgit v1.2.3 From f0464dfa17d146f34849e45097085b891e51ebc8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Aug 2014 22:34:45 -0400 Subject: improve poster sizing --- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Api/Movies/CollectionService.cs | 6 +- MediaBrowser.Api/PlaylistService.cs | 84 +++++++++++++ .../MediaBrowser.Controller.csproj | 3 + .../Playlists/IPlaylistManager.cs | 47 +++++++ MediaBrowser.Controller/Playlists/Playlist.cs | 20 +++ .../Playlists/PlaylistCreationOptions.cs | 16 +++ .../MediaBrowser.Model.Portable.csproj | 6 + .../MediaBrowser.Model.net35.csproj | 6 + .../Collections/CollectionCreationResult.cs | 8 ++ MediaBrowser.Model/Entities/CollectionType.cs | 1 + MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + .../Playlists/PlaylistCreationResult.cs | 8 ++ .../MediaBrowser.Providers.csproj | 1 - .../Music/SoundtrackPostScanTask.cs | 32 ----- .../Collections/CollectionManager.cs | 2 +- .../Library/UserViewManager.cs | 15 ++- .../MediaBrowser.Server.Implementations.csproj | 2 + .../Playlists/ManualPlaylistsFolder.cs | 62 ++++++++++ .../Playlists/PlaylistManager.cs | 137 +++++++++++++++++++++ MediaBrowser.ServerApplication/ApplicationHost.cs | 7 +- 21 files changed, 422 insertions(+), 44 deletions(-) create mode 100644 MediaBrowser.Api/PlaylistService.cs create mode 100644 MediaBrowser.Controller/Playlists/IPlaylistManager.cs create mode 100644 MediaBrowser.Controller/Playlists/Playlist.cs create mode 100644 MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs create mode 100644 MediaBrowser.Model/Collections/CollectionCreationResult.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistCreationResult.cs delete mode 100644 MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs create mode 100644 MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs create mode 100644 MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index f3857ce30a..f2f0ec790c 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -70,6 +70,7 @@ + diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs index 19e47eb857..e3816aa518 100644 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Collections; using MediaBrowser.Model.Querying; using ServiceStack; using System; @@ -92,9 +93,4 @@ namespace MediaBrowser.Api.Movies Task.WaitAll(task); } } - - public class CollectionCreationResult - { - public string Id { get; set; } - } } diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs new file mode 100644 index 0000000000..475183dea9 --- /dev/null +++ b/MediaBrowser.Api/PlaylistService.cs @@ -0,0 +1,84 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.Playlists; +using MediaBrowser.Model.Querying; +using ServiceStack; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api +{ + [Route("/Playlists", "POST", Summary = "Creates a new playlist")] + public class CreatePlaylist : IReturn + { + [ApiMember(Name = "Name", Description = "The name of the new playlist.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Name { get; set; } + + [ApiMember(Name = "Ids", Description = "Item Ids to add to the playlist", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string Ids { get; set; } + } + + [Route("/Playlists/{Id}/Items", "POST", Summary = "Adds items to a playlist")] + public class AddToPlaylist : IReturnVoid + { + [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Ids { get; set; } + + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + } + + [Route("/Playlists/{Id}/Items", "DELETE", Summary = "Removes items from a playlist")] + public class RemoveFromPlaylist : IReturnVoid + { + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + } + + [Authenticated] + public class PlaylistService : BaseApiService + { + private readonly IPlaylistManager _playlistManager; + private readonly IDtoService _dtoService; + + public PlaylistService(IDtoService dtoService, IPlaylistManager playlistManager) + { + _dtoService = dtoService; + _playlistManager = playlistManager; + } + + public object Post(CreatePlaylist request) + { + var task = _playlistManager.CreatePlaylist(new PlaylistCreationOptions + { + Name = request.Name, + ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList() + }); + + var item = task.Result; + + var dto = _dtoService.GetBaseItemDto(item, new List()); + + return ToOptimizedResult(new PlaylistCreationResult + { + Id = dto.Id + }); + } + + public void Post(AddToPlaylist request) + { + var task = _playlistManager.AddToPlaylist(request.Id, request.Ids.Split(',')); + + Task.WaitAll(task); + } + + public void Delete(RemoveFromPlaylist request) + { + //var task = _playlistManager.RemoveFromPlaylist(request.Id, request.Ids.Split(',').Select(i => new Guid(i))); + + //Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index aee118f9ac..b4b7b3650a 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -217,6 +217,9 @@ + + + diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs new file mode 100644 index 0000000000..2923c11c51 --- /dev/null +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -0,0 +1,47 @@ +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Playlists +{ + public interface IPlaylistManager + { + /// + /// Gets the playlists. + /// + /// The user identifier. + /// IEnumerable<Playlist>. + IEnumerable GetPlaylists(string userId); + + /// + /// Creates the playlist. + /// + /// The options. + /// Task<Playlist>. + Task CreatePlaylist(PlaylistCreationOptions options); + + /// + /// Adds to playlist. + /// + /// The playlist identifier. + /// The item ids. + /// Task. + Task AddToPlaylist(string playlistId, IEnumerable itemIds); + + /// + /// Removes from playlist. + /// + /// The playlist identifier. + /// The indeces. + /// Task. + Task RemoveFromPlaylist(string playlistId, IEnumerable indeces); + + /// + /// Gets the playlists folder. + /// + /// The user identifier. + /// Folder. + Folder GetPlaylistsFolder(string userId); + + } +} diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs new file mode 100644 index 0000000000..e20387eba3 --- /dev/null +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -0,0 +1,20 @@ +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Playlists +{ + public class Playlist : Folder + { + public List ItemIds { get; set; } + + public Playlist() + { + ItemIds = new List(); + } + + public override IEnumerable GetChildren(User user, bool includeLinkedChildren) + { + return base.GetChildren(user, includeLinkedChildren); + } + } +} diff --git a/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs b/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs new file mode 100644 index 0000000000..a62cbe12e5 --- /dev/null +++ b/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Playlists +{ + public class PlaylistCreationOptions + { + public string Name { get; set; } + + public List ItemIdList { get; set; } + + public PlaylistCreationOptions() + { + ItemIdList = new List(); + } + } +} diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index f374ed5297..ca48b88896 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -131,6 +131,9 @@ Chapters\RemoteChapterResult.cs + + Collections\CollectionCreationResult.cs + Configuration\BaseApplicationConfiguration.cs @@ -692,6 +695,9 @@ Notifications\SendToUserType.cs + + Playlists\PlaylistCreationResult.cs + Plugins\BasePluginConfiguration.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 5385ee036c..1adf83d36a 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -94,6 +94,9 @@ Chapters\RemoteChapterResult.cs + + Collections\CollectionCreationResult.cs + Configuration\BaseApplicationConfiguration.cs @@ -649,6 +652,9 @@ Notifications\SendToUserType.cs + + Playlists\PlaylistCreationResult.cs + Plugins\BasePluginConfiguration.cs diff --git a/MediaBrowser.Model/Collections/CollectionCreationResult.cs b/MediaBrowser.Model/Collections/CollectionCreationResult.cs new file mode 100644 index 0000000000..ad7ac95535 --- /dev/null +++ b/MediaBrowser.Model/Collections/CollectionCreationResult.cs @@ -0,0 +1,8 @@ + +namespace MediaBrowser.Model.Collections +{ + public class CollectionCreationResult + { + public string Id { get; set; } + } +} diff --git a/MediaBrowser.Model/Entities/CollectionType.cs b/MediaBrowser.Model/Entities/CollectionType.cs index 5a9526d953..31cda8303e 100644 --- a/MediaBrowser.Model/Entities/CollectionType.cs +++ b/MediaBrowser.Model/Entities/CollectionType.cs @@ -23,5 +23,6 @@ public const string Games = "games"; public const string Channels = "channels"; public const string LiveTv = "livetv"; + public const string Playlists = "playlists"; } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index efd8c2a0a2..94629048c7 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -75,6 +75,7 @@ + @@ -204,6 +205,7 @@ + diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs new file mode 100644 index 0000000000..bbab8a18d2 --- /dev/null +++ b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs @@ -0,0 +1,8 @@ + +namespace MediaBrowser.Model.Playlists +{ + public class PlaylistCreationResult + { + public string Id { get; set; } + } +} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 9a26101a1f..5fb7ef11b7 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -140,7 +140,6 @@ - diff --git a/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs b/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs deleted file mode 100644 index dc94460f4e..0000000000 --- a/MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MediaBrowser.Controller.Library; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Music -{ - public class SoundtrackPostScanTask : ILibraryPostScanTask - { - private readonly ILibraryManager _libraryManager; - - public SoundtrackPostScanTask(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - private readonly Task _cachedTask = Task.FromResult(true); - public Task Run(IProgress progress, CancellationToken cancellationToken) - { - RunInternal(progress, cancellationToken); - - return _cachedTask; - } - - private void RunInternal(IProgress progress, CancellationToken cancellationToken) - { - // Reimplement this when more kinds of associations are supported. - - progress.Report(100); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 8e51e1d7db..6a87453ab6 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Server.Implementations.Collections public Folder GetCollectionsFolder(string userId) { - return _libraryManager.RootFolder.Children.Concat(_libraryManager.RootFolder).OfType() + return _libraryManager.RootFolder.Children.OfType() .FirstOrDefault(); } diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index b8cb955fc9..0d54d94e8c 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -1,5 +1,4 @@ -using System.IO; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; @@ -10,16 +9,17 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.Configuration; namespace MediaBrowser.Server.Implementations.Library { @@ -33,8 +33,9 @@ namespace MediaBrowser.Server.Implementations.Library private readonly IChannelManager _channelManager; private readonly ILiveTvManager _liveTvManager; private readonly IServerApplicationPaths _appPaths; + private readonly IPlaylistManager _playlists; - public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IFileSystem fileSystem, IUserManager userManager, IChannelManager channelManager, ILiveTvManager liveTvManager, IServerApplicationPaths appPaths) + public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IFileSystem fileSystem, IUserManager userManager, IChannelManager channelManager, ILiveTvManager liveTvManager, IServerApplicationPaths appPaths, IPlaylistManager playlists) { _libraryManager = libraryManager; _localizationManager = localizationManager; @@ -43,6 +44,7 @@ namespace MediaBrowser.Server.Implementations.Library _channelManager = channelManager; _liveTvManager = liveTvManager; _appPaths = appPaths; + _playlists = playlists; } public async Task> GetUserViews(UserViewQuery query, CancellationToken cancellationToken) @@ -94,6 +96,11 @@ namespace MediaBrowser.Server.Implementations.Library list.Add(await GetUserView(CollectionType.BoxSets, user, CollectionType.BoxSets, cancellationToken).ConfigureAwait(false)); } + if (recursiveChildren.OfType().Any()) + { + list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N"))); + } + if (query.IncludeExternalContent) { var channelResult = await _channelManager.GetChannels(new ChannelQuery diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 214d4c6c77..8bfbc08550 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -221,6 +221,8 @@ + + diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs new file mode 100644 index 0000000000..3b46191b1d --- /dev/null +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Playlists +{ + public class PlaylistsFolder : BasePluginFolder + { + public PlaylistsFolder() + { + Name = "Playlists"; + } + + public override bool IsVisible(User user) + { + return GetChildren(user, true).Any() && + base.IsVisible(user); + } + + public override bool IsHidden + { + get + { + return true; + } + } + + public override bool IsHiddenFromUser(User user) + { + return false; + } + + public override string CollectionType + { + get { return Model.Entities.CollectionType.Playlists; } + } + } + + public class PlaylistssDynamicFolder : IVirtualFolderCreator + { + private readonly IApplicationPaths _appPaths; + + public PlaylistssDynamicFolder(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + + public BasePluginFolder GetFolder() + { + var path = Path.Combine(_appPaths.DataPath, "playlists"); + + Directory.CreateDirectory(path); + + return new PlaylistsFolder + { + Path = path + }; + } + } +} + diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs new file mode 100644 index 0000000000..92f01305cb --- /dev/null +++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs @@ -0,0 +1,137 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Playlists +{ + public class PlaylistManager : IPlaylistManager + { + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryMonitor _iLibraryMonitor; + private readonly ILogger _logger; + private readonly IUserManager _userManager; + + public PlaylistManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor, ILogger logger, IUserManager userManager) + { + _libraryManager = libraryManager; + _fileSystem = fileSystem; + _iLibraryMonitor = iLibraryMonitor; + _logger = logger; + _userManager = userManager; + } + + public IEnumerable GetPlaylists(string userId) + { + var user = _userManager.GetUserById(new Guid(userId)); + + return GetPlaylistsFolder(userId).GetChildren(user, true).OfType(); + } + + public async Task CreatePlaylist(PlaylistCreationOptions options) + { + var name = options.Name; + + // Need to use the [boxset] suffix + // If internet metadata is not found, or if xml saving is off there will be no collection.xml + // This could cause it to get re-resolved as a plain folder + var folderName = _fileSystem.GetValidFilename(name) + " [playlist]"; + + var parentFolder = GetPlaylistsFolder(null); + + if (parentFolder == null) + { + throw new ArgumentException(); + } + + var path = Path.Combine(parentFolder.Path, folderName); + + _iLibraryMonitor.ReportFileSystemChangeBeginning(path); + + try + { + Directory.CreateDirectory(path); + + var collection = new Playlist + { + Name = name, + Parent = parentFolder, + Path = path + }; + + await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) + .ConfigureAwait(false); + + if (options.ItemIdList.Count > 0) + { + await AddToPlaylist(collection.Id.ToString("N"), options.ItemIdList); + } + + return collection; + } + finally + { + // Refresh handled internally + _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); + } + } + + public async Task AddToPlaylist(string playlistId, IEnumerable itemIds) + { + var collection = _libraryManager.GetItemById(playlistId) as Playlist; + + if (collection == null) + { + throw new ArgumentException("No Playlist exists with the supplied Id"); + } + + var list = new List(); + var itemList = new List(); + + foreach (var itemId in itemIds) + { + var item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + throw new ArgumentException("No item exists with the supplied Id"); + } + + itemList.Add(item); + + list.Add(new LinkedChild + { + Type = LinkedChildType.Manual, + ItemId = item.Id + }); + } + + collection.LinkedChildren.AddRange(list); + + await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + } + + public Task RemoveFromPlaylist(string playlistId, IEnumerable indeces) + { + throw new NotImplementedException(); + } + + public Folder GetPlaylistsFolder(string userId) + { + return _libraryManager.RootFolder.Children.OfType() + .FirstOrDefault(); + } + } +} diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 7dc70627bd..993cc4e1a6 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -26,6 +26,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.News; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; @@ -68,6 +69,7 @@ using MediaBrowser.Server.Implementations.Localization; using MediaBrowser.Server.Implementations.MediaEncoder; using MediaBrowser.Server.Implementations.Notifications; using MediaBrowser.Server.Implementations.Persistence; +using MediaBrowser.Server.Implementations.Playlists; using MediaBrowser.Server.Implementations.Security; using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; @@ -612,10 +614,13 @@ namespace MediaBrowser.ServerApplication var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager")); RegisterSingleInstance(collectionManager); + var playlistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager); + RegisterSingleInstance(playlistManager); + 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, ApplicationPaths); + UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager, ApplicationPaths, playlistManager); RegisterSingleInstance(UserViewManager); var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, UserViewManager, ChannelManager); -- cgit v1.2.3 From 2714127d2b663b735048da6d9def08efa38f2b5f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Aug 2014 22:16:37 -0400 Subject: fixes #791 - Support server-side playlists --- MediaBrowser.Api/PlaylistService.cs | 81 +++++++++++++++- MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs | 6 +- MediaBrowser.Controller/Entities/BaseItem.cs | 24 ++++- .../Entities/BasePluginFolder.cs | 5 - MediaBrowser.Controller/Entities/Folder.cs | 8 +- MediaBrowser.Controller/Entities/LinkedChild.cs | 13 +++ MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 12 ++- MediaBrowser.Controller/Entities/TV/Season.cs | 9 ++ MediaBrowser.Controller/Entities/TV/Series.cs | 9 ++ MediaBrowser.Controller/Entities/UserView.cs | 1 - MediaBrowser.Controller/Playlists/Playlist.cs | 77 ++++++++++++++- .../Playlists/PlaylistCreationOptions.cs | 4 + .../Providers/BaseItemXmlParser.cs | 72 ++++++++++++++ .../ContentDirectory/ControlHandler.cs | 2 +- .../MediaBrowser.LocalMetadata.csproj | 1 + .../Parsers/BoxSetXmlParser.cs | 54 ----------- .../Savers/FolderXmlSaver.cs | 14 +-- .../Savers/PlaylistXmlSaver.cs | 68 +++++++++++++ .../Savers/XmlSaverHelpers.cs | 31 ++++-- MediaBrowser.Model/Dto/BaseItemDto.cs | 14 ++- .../Channels/ChannelDownloadScheduledTask.cs | 2 +- .../Collections/CollectionManager.cs | 8 +- .../Collections/ManualCollectionsFolder.cs | 1 + .../Dto/DtoService.cs | 47 ++++++++- .../FileOrganization/OrganizerScheduledTask.cs | 2 +- .../Library/Resolvers/PlaylistResolver.cs | 38 ++++++++ .../Localization/JavaScript/javascript.json | 11 ++- .../Localization/Server/server.json | 6 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Playlists/ManualPlaylistsFolder.cs | 17 +++- .../Playlists/PlaylistManager.cs | 105 +++++++++++++++++---- MediaBrowser.WebDashboard/Api/DashboardService.cs | 5 +- .../MediaBrowser.WebDashboard.csproj | 18 +++- 33 files changed, 636 insertions(+), 130 deletions(-) create mode 100644 MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs create mode 100644 MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 475183dea9..b4d2e2f0f2 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -1,9 +1,12 @@ using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Querying; using ServiceStack; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -18,6 +21,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "Ids", Description = "Item Ids to add to the playlist", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] public string Ids { get; set; } + + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string UserId { get; set; } } [Route("/Playlists/{Id}/Items", "POST", Summary = "Adds items to a playlist")] @@ -37,16 +43,55 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")] + public class GetPlaylistItems : IReturn>, IHasItemFields + { + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid? UserId { get; set; } + + /// + /// 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; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [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; } + } + [Authenticated] public class PlaylistService : BaseApiService { private readonly IPlaylistManager _playlistManager; private readonly IDtoService _dtoService; + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; - public PlaylistService(IDtoService dtoService, IPlaylistManager playlistManager) + public PlaylistService(IDtoService dtoService, IPlaylistManager playlistManager, IUserManager userManager, ILibraryManager libraryManager) { _dtoService = dtoService; _playlistManager = playlistManager; + _userManager = userManager; + _libraryManager = libraryManager; } public object Post(CreatePlaylist request) @@ -54,7 +99,8 @@ namespace MediaBrowser.Api var task = _playlistManager.CreatePlaylist(new PlaylistCreationOptions { Name = request.Name, - ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList() + ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(), + UserId = request.UserId }); var item = task.Result; @@ -80,5 +126,36 @@ namespace MediaBrowser.Api //Task.WaitAll(task); } + + public object Get(GetPlaylistItems request) + { + var playlist = (Playlist)_libraryManager.GetItemById(request.Id); + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + var items = playlist.GetManageableItems().ToArray(); + + var count = items.Length; + + if (request.StartIndex.HasValue) + { + items = items.Skip(request.StartIndex.Value).ToArray(); + } + + if (request.Limit.HasValue) + { + items = items.Take(request.Limit.Value).ToArray(); + } + + var dtos = items + .Select(i => _dtoService.GetBaseItemDto(i, request.GetItemFields().ToList(), user)) + .ToArray(); + + var result = new ItemsResult + { + Items = dtos, + TotalRecordCount = count + }; + + return ToOptimizedResult(result); + } } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 7cd518a187..f236100147 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Api.UserLibrary { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2bdbab0848..36e65f5f53 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1006,6 +1006,18 @@ namespace MediaBrowser.Controller.Entities private BaseItem FindLinkedChild(LinkedChild info) { + if (!string.IsNullOrWhiteSpace(info.ItemName)) + { + if (string.Equals(info.ItemType, "musicgenre", StringComparison.OrdinalIgnoreCase)) + { + return LibraryManager.GetMusicGenre(info.ItemName); + } + if (string.Equals(info.ItemType, "musicartist", StringComparison.OrdinalIgnoreCase)) + { + return LibraryManager.GetArtist(info.ItemName); + } + } + if (!string.IsNullOrEmpty(info.Path)) { var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); @@ -1028,7 +1040,17 @@ namespace MediaBrowser.Controller.Entities { if (info.ItemYear.HasValue) { - return info.ItemYear.Value == (i.ProductionYear ?? -1); + if (info.ItemYear.Value != (i.ProductionYear ?? -1)) + { + return false; + } + } + if (info.ItemIndexNumber.HasValue) + { + if (info.ItemIndexNumber.Value != (i.IndexNumber ?? -1)) + { + return false; + } } return true; } diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index fa2b49a60b..b30bd81b96 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -7,11 +7,6 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BasePluginFolder : Folder, ICollectionFolder, IByReferenceItem { - protected BasePluginFolder() - { - DisplayMediaType = "CollectionFolder"; - } - public virtual string CollectionType { get { return null; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 12afe26b63..2013b926cc 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -38,6 +38,12 @@ namespace MediaBrowser.Controller.Entities Tags = new List(); } + [IgnoreDataMember] + public virtual bool IsPreSorted + { + get { return false; } + } + /// /// Gets a value indicating whether this instance is folder. /// @@ -855,7 +861,7 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// IEnumerable{BaseItem}. /// - public IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) + public virtual IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) { if (user == null) { diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 1ae04e40f9..3fc0ab716e 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -12,12 +12,25 @@ namespace MediaBrowser.Controller.Entities public string ItemName { get; set; } public string ItemType { get; set; } public int? ItemYear { get; set; } + public int? ItemIndexNumber { get; set; } /// /// Serves as a cache /// [IgnoreDataMember] public Guid? ItemId { get; set; } + + public static LinkedChild Create(BaseItem item) + { + return new LinkedChild + { + ItemName = item.Name, + ItemYear = item.ProductionYear, + ItemType = item.GetType().Name, + Type = LinkedChildType.Manual, + ItemIndexNumber = item.IndexNumber + }; + } } public enum LinkedChildType diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 0d2be9f740..5e6bd97079 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Progress; +using System.Runtime.Serialization; +using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -58,6 +59,15 @@ namespace MediaBrowser.Controller.Entities.Movies return config.BlockUnratedItems.Contains(UnratedItem.Movie); } + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + public override IEnumerable GetChildren(User user, bool includeLinkedChildren) { var children = base.GetChildren(user, includeLinkedChildren); diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index cf39cda899..3977d869c2 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -29,6 +29,15 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + /// /// We want to group into our Series /// diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 9c2ed27bb8..27ca8b18da 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -39,6 +39,15 @@ namespace MediaBrowser.Controller.Entities.TV DisplaySpecialsWithSeasons = true; } + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + public bool DisplaySpecialsWithSeasons { get; set; } public List LocalTrailerIds { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index f8ca56fa88..34ca85d1d7 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,7 +1,6 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Entities; -using MoreLinq; using System; using System.Collections.Generic; using System.Linq; diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e20387eba3..5ea535f4d5 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,20 +1,87 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.Playlists { public class Playlist : Folder { - public List ItemIds { get; set; } + public string OwnerUserId { get; set; } - public Playlist() + public override IEnumerable GetChildren(User user, bool includeLinkedChildren) { - ItemIds = new List(); + return GetPlayableItems(user); } - public override IEnumerable GetChildren(User user, bool includeLinkedChildren) + public override IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) + { + return GetPlayableItems(user); + } + + public IEnumerable GetManageableItems() + { + return GetLinkedChildren(); + } + + private IEnumerable GetPlayableItems(User user) + { + return GetPlaylistItems(MediaType, base.GetChildren(user, true), user); + } + + public static IEnumerable GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, User user) + { + return inputItems.SelectMany(i => + { + var folder = i as Folder; + + if (folder != null) + { + var items = folder.GetRecursiveChildren(user, true) + .Where(m => !m.IsFolder && string.Equals(m.MediaType, playlistMediaType, StringComparison.OrdinalIgnoreCase)); + + if (!folder.IsPreSorted) + { + items = LibraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending); + } + + return items; + } + + return new[] { i }; + }); + } + + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + + public string PlaylistMediaType { get; set; } + + public override string MediaType + { + get + { + return PlaylistMediaType; + } + } + + public void SetMediaType(string value) + { + PlaylistMediaType = value; + } + + public override bool IsVisible(User user) { - return base.GetChildren(user, includeLinkedChildren); + return base.IsVisible(user) && string.Equals(user.Id.ToString("N"), OwnerUserId); } } } diff --git a/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs b/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs index a62cbe12e5..1766ba75ca 100644 --- a/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs +++ b/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs @@ -8,6 +8,10 @@ namespace MediaBrowser.Controller.Playlists public List ItemIdList { get; set; } + public string MediaType { get; set; } + + public string UserId { get; set; } + public PlaylistCreationOptions() { ItemIdList = new List(); diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 3cb90d360e..7e6895ec5f 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -1283,6 +1283,78 @@ namespace MediaBrowser.Controller.Providers return new[] { personInfo }; } + protected LinkedChild GetLinkedChild(XmlReader reader) + { + reader.MoveToContent(); + + var linkedItem = new LinkedChild + { + Type = LinkedChildType.Manual + }; + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Name": + { + linkedItem.ItemName = reader.ReadElementContentAsString(); + break; + } + + case "Type": + { + linkedItem.ItemType = reader.ReadElementContentAsString(); + break; + } + + case "Year": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + { + linkedItem.ItemYear = rval; + } + } + + break; + } + + case "IndexNumber": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + { + linkedItem.ItemIndexNumber = rval; + } + } + + break; + } + + default: + reader.Skip(); + break; + } + } + } + + return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; + } + + /// /// Used to split names of comma or pipe delimeted genres and people /// diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index aced2009d1..4eb6baeed2 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -462,7 +462,7 @@ namespace MediaBrowser.Dlna.ContentDirectory items = FilterUnsupportedContent(items); - if (folder is Series || folder is Season || folder is BoxSet) + if (folder.IsPreSorted) { return items; } diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 2cc7e989b4..b103d9f5a0 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -83,6 +83,7 @@ + diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index 51a4684d70..85a72cf281 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -71,59 +71,5 @@ namespace MediaBrowser.LocalMetadata.Parsers item.LinkedChildren = list; } - - private LinkedChild GetLinkedChild(XmlReader reader) - { - reader.MoveToContent(); - - var linkedItem = new LinkedChild - { - Type = LinkedChildType.Manual - }; - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "Name": - { - linkedItem.ItemName = reader.ReadElementContentAsString(); - break; - } - - case "Type": - { - linkedItem.ItemType = reader.ReadElementContentAsString(); - break; - } - - case "Year": - { - var val = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(val)) - { - int rval; - - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) - { - linkedItem.ItemYear = rval; - } - } - - break; - } - - default: - reader.Skip(); - break; - } - } - } - - return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; - } } } diff --git a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs index 6dd65b69c5..c38a33c406 100644 --- a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs @@ -1,12 +1,13 @@ -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; namespace MediaBrowser.LocalMetadata.Savers { @@ -37,7 +38,8 @@ namespace MediaBrowser.LocalMetadata.Savers { if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) && !(item is Season) && - !(item is GameSystem)) + !(item is GameSystem) && + !(item is Playlist)) { return updateType >= ItemUpdateType.MetadataDownload; } diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs new file mode 100644 index 0000000000..cdb3a2500f --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -0,0 +1,68 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class PlaylistXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// + /// Determines whether [is enabled for] [the specified item]. + /// + /// The item. + /// Type of the update. + /// true if [is enabled for] [the specified item]; otherwise, false. + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is BoxSet && updateType >= ItemUpdateType.MetadataDownload; + } + + /// + /// Saves the specified item. + /// + /// The item. + /// The cancellation token. + /// Task. + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append(""); + + XmlSaverHelpers.AddCommonNodes((BoxSet)item, builder); + + builder.Append(""); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); + } + + /// + /// Gets the save path. + /// + /// The item. + /// System.String. + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "playlist.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs index 1a2c341da1..491592989c 100644 --- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; namespace MediaBrowser.LocalMetadata.Savers @@ -109,7 +110,8 @@ namespace MediaBrowser.LocalMetadata.Savers "VoteCount", "Website", "Zap2ItId", - "CollectionItems" + "CollectionItems", + "PlaylistItems" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); @@ -631,10 +633,16 @@ namespace MediaBrowser.LocalMetadata.Savers builder.Append(""); } - var folder = item as BoxSet; - if (folder != null) + var boxset = item as BoxSet; + if (boxset != null) { - AddCollectionItems(folder, builder); + AddLinkedChildren(boxset, builder, "CollectionItems", "CollectionItem"); + } + + var playlist = item as Playlist; + if (playlist != null) + { + AddLinkedChildren(playlist, builder, "PlaylistItems", "PlaylistItem"); } } @@ -693,7 +701,7 @@ namespace MediaBrowser.LocalMetadata.Savers } } - public static void AddCollectionItems(Folder item, StringBuilder builder) + public static void AddLinkedChildren(Folder item, StringBuilder builder, string pluralNodeName, string singularNodeName) { var items = item.LinkedChildren .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName)) @@ -704,10 +712,10 @@ namespace MediaBrowser.LocalMetadata.Savers return; } - builder.Append(""); + builder.Append("<" + pluralNodeName + ">"); foreach (var link in items) { - builder.Append(""); + builder.Append("<" + singularNodeName + ">"); builder.Append("" + SecurityElement.Escape(link.ItemName) + ""); builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); @@ -717,9 +725,14 @@ namespace MediaBrowser.LocalMetadata.Savers builder.Append("" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + ""); } - builder.Append(""); + if (link.ItemIndexNumber.HasValue) + { + builder.Append("" + SecurityElement.Escape(link.ItemIndexNumber.Value.ToString(UsCulture)) + ""); + } + + builder.Append(""); } - builder.Append(""); + builder.Append(""); } } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index d138ddf9bc..9581b57407 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -594,7 +594,19 @@ namespace MediaBrowser.Model.Dto /// /// The parent thumb image tag. public string ParentThumbImageTag { get; set; } - + + /// + /// Gets or sets the parent primary image item identifier. + /// + /// The parent primary image item identifier. + public string ParentPrimaryImageItemId { get; set; } + + /// + /// Gets or sets the parent primary image tag. + /// + /// The parent primary image tag. + public string ParentPrimaryImageTag { get; set; } + /// /// Gets or sets the chapters. /// diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index af1bd94271..567092caec 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -239,7 +239,7 @@ namespace MediaBrowser.Server.Implementations.Channels throw new ApplicationException("Unexpected response type encountered: " + response.ContentType); } - File.Move(response.TempFilePath, destination); + File.Copy(response.TempFilePath, destination, true); await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 6a87453ab6..fe4b16645c 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -162,13 +162,7 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentException("Item already exists in collection"); } - list.Add(new LinkedChild - { - ItemName = item.Name, - ItemYear = item.ProductionYear, - ItemType = item.GetType().Name, - Type = LinkedChildType.Manual - }); + list.Add(LinkedChild.Create(item)); var supportsGrouping = item as ISupportsBoxSetGrouping; diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index aaa02c7209..b02c52874c 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Server.Implementations.Collections public ManualCollectionsFolder() { Name = "Collections"; + DisplayMediaType = "CollectionFolder"; } public override bool IsVisible(User user) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index afcfde556b..e3a386841b 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Drawing; @@ -179,6 +180,11 @@ namespace MediaBrowser.Server.Implementations.Dto } } + if (item is Playlist) + { + AttachLinkedChildImages(dto, (Folder)item, user); + } + return dto; } @@ -819,7 +825,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.DisplayOrder = hasDisplayOrder.DisplayOrder; } - var collectionFolder = item as CollectionFolder; + var collectionFolder = item as ICollectionFolder; if (collectionFolder != null) { dto.CollectionType = collectionFolder.CollectionType; @@ -1211,6 +1217,45 @@ namespace MediaBrowser.Server.Implementations.Dto } } + private void AttachLinkedChildImages(BaseItemDto dto, Folder folder, User user) + { + List linkedChildren = null; + + if (dto.BackdropImageTags.Count == 0) + { + if (linkedChildren == null) + { + linkedChildren = user == null + ? folder.GetRecursiveChildren().ToList() + : folder.GetRecursiveChildren(user, true).ToList(); + } + var parentWithBackdrop = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Backdrop).Any()); + + if (parentWithBackdrop != null) + { + dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop); + dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop); + } + } + + if (!dto.ImageTags.ContainsKey(ImageType.Primary)) + { + if (linkedChildren == null) + { + linkedChildren = user == null + ? folder.GetRecursiveChildren().ToList() + : folder.GetRecursiveChildren(user, true).ToList(); + } + var parentWithImage = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Primary).Any()); + + if (parentWithImage != null) + { + dto.ParentPrimaryImageItemId = GetDtoId(parentWithImage); + dto.ParentPrimaryImageTag = GetImageCacheTag(parentWithImage, ImageType.Primary); + } + } + } + private string GetMappedPath(IHasMetadata item) { var path = item.Path; diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 4e182ea88f..1c4ccb1414 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public bool IsEnabled { - get { return !GetTvOptions().IsEnabled; } + get { return GetTvOptions().IsEnabled; } } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs new file mode 100644 index 0000000000..7eff53ce1f --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -0,0 +1,38 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using System; +using System.IO; + +namespace MediaBrowser.Server.Implementations.Library.Resolvers +{ + public class PlaylistResolver : FolderResolver + { + /// + /// Resolves the specified args. + /// + /// The args. + /// BoxSet. + protected override Playlist Resolve(ItemResolveArgs args) + { + // It's a boxset if all of the following conditions are met: + // Is a Directory + // Contains [playlist] in the path + if (args.IsDirectory) + { + var filename = Path.GetFileName(args.Path); + + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1) + { + return new Playlist { Path = args.Path }; + } + } + + return null; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 3a5b91abd9..3c1748ef1b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -323,6 +323,13 @@ "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", "ButtonNew": "New", - "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", - "HeaderVideoError": "Video Error" + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error", + "ButtonAddToPlaylist": "Add to playlist", + "HeaderAddToPlaylist": "Add to Playlist", + "LabelName": "Name:", + "ButtonSubmit": "Submit", + "LabelSelectPlaylist": "Playlist:", + "OptionNewPlaylist": "New playlist...", + "MessageAddedToPlaylistSuccess": "Ok" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index caf8860fc5..4716209481 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -808,6 +808,8 @@ "TabNextUp": "Next Up", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the New button to start creating Collections.", + "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", + "MessageNoPlaylistItemsAvailable": "This playlist is currently empty.", "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client", "ButtonDismiss": "Dismiss", "MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.", @@ -915,5 +917,7 @@ "OptionProtocolHls": "Http Live Streaming", "LabelContext": "Context:", "OptionContextStreaming": "Streaming", - "OptionContextStatic": "Sync" + "OptionContextStatic": "Sync", + "ButtonAddToPlaylist": "Add to playlist", + "TabPlaylists": "Playlists" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8bfbc08550..c60835ee6a 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -166,6 +166,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 3b46191b1d..a87edde7ba 100644 --- a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -1,5 +1,7 @@ -using MediaBrowser.Common.Configuration; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Playlists; using System.IO; using System.Linq; @@ -14,8 +16,15 @@ namespace MediaBrowser.Server.Implementations.Playlists public override bool IsVisible(User user) { - return GetChildren(user, true).Any() && - base.IsVisible(user); + return base.IsVisible(user) && GetRecursiveChildren(user, false) + .OfType() + .Any(i => string.Equals(i.OwnerUserId, user.Id.ToString("N"))); + } + + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) + { + return RecursiveChildren + .OfType(); } public override bool IsHidden @@ -48,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.Playlists public BasePluginFolder GetFolder() { - var path = Path.Combine(_appPaths.DataPath, "playlists"); + var path = Path.Combine(_appPaths.CachePath, "playlists"); Directory.CreateDirectory(path); diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs index 92f01305cb..79b673283d 100644 --- a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs +++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,8 +1,10 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -41,9 +43,6 @@ namespace MediaBrowser.Server.Implementations.Playlists { var name = options.Name; - // Need to use the [boxset] suffix - // If internet metadata is not found, or if xml saving is off there will be no collection.xml - // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [playlist]"; var parentFolder = GetPlaylistsFolder(null); @@ -53,7 +52,55 @@ namespace MediaBrowser.Server.Implementations.Playlists throw new ArgumentException(); } + if (string.IsNullOrWhiteSpace(options.MediaType)) + { + foreach (var itemId in options.ItemIdList) + { + var item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + throw new ArgumentException("No item exists with the supplied Id"); + } + + if (!string.IsNullOrWhiteSpace(item.MediaType)) + { + options.MediaType = item.MediaType; + } + else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) + { + options.MediaType = MediaType.Audio; + } + else if (item is Genre) + { + options.MediaType = MediaType.Video; + } + else + { + var folder = item as Folder; + if (folder != null) + { + options.MediaType = folder.GetRecursiveChildren() + .Where(i => !i.IsFolder) + .Select(i => i.MediaType) + .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + } + } + + if (!string.IsNullOrWhiteSpace(options.MediaType)) + { + break; + } + } + } + + if (string.IsNullOrWhiteSpace(options.MediaType)) + { + throw new ArgumentException("A playlist media type is required."); + } + var path = Path.Combine(parentFolder.Path, folderName); + path = GetTargetPath(path); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); @@ -61,24 +108,27 @@ namespace MediaBrowser.Server.Implementations.Playlists { Directory.CreateDirectory(path); - var collection = new Playlist + var playlist = new Playlist { Name = name, Parent = parentFolder, - Path = path + Path = path, + OwnerUserId = options.UserId }; - await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); + playlist.SetMediaType(options.MediaType); + + await parentFolder.AddChild(playlist, CancellationToken.None).ConfigureAwait(false); - await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) + await playlist.RefreshMetadata(new MetadataRefreshOptions { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); if (options.ItemIdList.Count > 0) { - await AddToPlaylist(collection.Id.ToString("N"), options.ItemIdList); + await AddToPlaylist(playlist.Id.ToString("N"), options.ItemIdList); } - return collection; + return playlist; } finally { @@ -87,11 +137,28 @@ namespace MediaBrowser.Server.Implementations.Playlists } } + private string GetTargetPath(string path) + { + while (Directory.Exists(path)) + { + path += "1"; + } + + return path; + } + + private IEnumerable GetPlaylistItems(IEnumerable itemIds, string playlistMediaType, User user) + { + var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null); + + return Playlist.GetPlaylistItems(playlistMediaType, items, user); + } + public async Task AddToPlaylist(string playlistId, IEnumerable itemIds) { - var collection = _libraryManager.GetItemById(playlistId) as Playlist; + var playlist = _libraryManager.GetItemById(playlistId) as Playlist; - if (collection == null) + if (playlist == null) { throw new ArgumentException("No Playlist exists with the supplied Id"); } @@ -110,17 +177,17 @@ namespace MediaBrowser.Server.Implementations.Playlists itemList.Add(item); - list.Add(new LinkedChild - { - Type = LinkedChildType.Manual, - ItemId = item.Id - }); + list.Add(LinkedChild.Create(item)); } - collection.LinkedChildren.AddRange(list); + playlist.LinkedChildren.AddRange(list); + + await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await playlist.RefreshMetadata(new MetadataRefreshOptions{ + + ForceSave = true - await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + }, CancellationToken.None).ConfigureAwait(false); } public Task RemoveFromPlaylist(string playlistId, IEnumerable indeces) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 9235beacf5..c058193612 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -528,6 +528,7 @@ namespace MediaBrowser.WebDashboard.Api "chromecast.js", "backdrops.js", "sync.js", + "playlistmanager.js", "mediaplayer.js", "mediaplayer-video.js", @@ -621,6 +622,9 @@ namespace MediaBrowser.WebDashboard.Api "notificationsetting.js", "notificationsettings.js", "playlist.js", + "playlists.js", + "playlistedit.js", + "plugincatalogpage.js", "pluginspage.js", "remotecontrol.js", @@ -676,7 +680,6 @@ namespace MediaBrowser.WebDashboard.Api "librarymenu.css", "librarybrowser.css", "detailtable.css", - "posteritem.css", "card.css", "tileitem.css", "metadataeditor.css", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 4bc05e0c16..c0fe0261b9 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -347,6 +347,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -527,9 +533,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -665,6 +668,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest -- cgit v1.2.3