From f0464dfa17d146f34849e45097085b891e51ebc8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 1 Aug 2014 22:34:45 -0400 Subject: [PATCH 1/3] 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 ++++++++++++++++++ .../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 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); From 2714127d2b663b735048da6d9def08efa38f2b5f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Aug 2014 22:16:37 -0400 Subject: [PATCH 2/3] fixes #791 - Support server-side playlists --- MediaBrowser.Api/PlaylistService.cs | 81 +++++++++++++- .../UserLibrary/BaseItemsRequest.cs | 6 +- MediaBrowser.Controller/Entities/BaseItem.cs | 24 +++- .../Entities/BasePluginFolder.cs | 5 - MediaBrowser.Controller/Entities/Folder.cs | 8 +- .../Entities/LinkedChild.cs | 13 +++ .../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 +++++++- .../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 ++++++++++++++---- .../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 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 From c5319bb4ae9606e07d62525a022e5a67f85a7d43 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 4 Aug 2014 23:41:56 -0400 Subject: [PATCH 3/3] update playlist xml saving --- .../DefaultTheme/DefaultThemeService.cs | 673 ------------------ MediaBrowser.Api/DefaultTheme/Models.cs | 83 --- MediaBrowser.Api/Library/LibraryService.cs | 5 + MediaBrowser.Api/MediaBrowser.Api.csproj | 2 - .../Serialization/JsonSerializer.cs | 20 - .../Serialization/XmlSerializer.cs | 15 - .../Configuration/ConfigurationHelper.cs | 28 +- MediaBrowser.Controller/Entities/BaseItem.cs | 7 - .../Entities/LinkedChild.cs | 8 +- MediaBrowser.Controller/Library/TVUtils.cs | 2 - .../Providers/BaseItemXmlParser.cs | 23 +- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 2 +- .../MediaBrowser.LocalMetadata.csproj | 2 + .../Parsers/BoxSetXmlParser.cs | 9 +- .../Parsers/PlaylistXmlParser.cs | 72 ++ .../Providers/BoxSetXmlProvider.cs | 6 +- .../Providers/PlaylistXmlProvider.cs | 31 + .../Providers/TrailerXmlProvider.cs | 8 +- .../Savers/PlaylistXmlSaver.cs | 6 +- .../Savers/XmlSaverHelpers.cs | 6 +- .../Serialization/IJsonSerializer.cs | 8 - .../Serialization/IXmlSerializer.cs | 7 - .../HttpServer/HttpListenerHost.cs | 2 +- .../Localization/JavaScript/ar.json | 15 +- .../Localization/JavaScript/ca.json | 15 +- .../Localization/JavaScript/cs.json | 15 +- .../Localization/JavaScript/da.json | 15 +- .../Localization/JavaScript/de.json | 15 +- .../Localization/JavaScript/el.json | 15 +- .../Localization/JavaScript/en_GB.json | 15 +- .../Localization/JavaScript/en_US.json | 15 +- .../Localization/JavaScript/es.json | 15 +- .../Localization/JavaScript/es_MX.json | 13 +- .../Localization/JavaScript/fr.json | 13 +- .../Localization/JavaScript/he.json | 15 +- .../Localization/JavaScript/it.json | 13 +- .../Localization/JavaScript/javascript.json | 7 +- .../Localization/JavaScript/kk.json | 15 +- .../Localization/JavaScript/ms.json | 15 +- .../Localization/JavaScript/nb.json | 15 +- .../Localization/JavaScript/nl.json | 13 +- .../Localization/JavaScript/pl.json | 15 +- .../Localization/JavaScript/pt_BR.json | 15 +- .../Localization/JavaScript/pt_PT.json | 15 +- .../Localization/JavaScript/ru.json | 19 +- .../Localization/JavaScript/sv.json | 13 +- .../Localization/JavaScript/tr.json | 19 +- .../Localization/JavaScript/vi.json | 15 +- .../Localization/JavaScript/zh_TW.json | 15 +- .../Localization/Server/ar.json | 48 +- .../Localization/Server/ca.json | 50 +- .../Localization/Server/cs.json | 48 +- .../Localization/Server/da.json | 48 +- .../Localization/Server/de.json | 48 +- .../Localization/Server/el.json | 48 +- .../Localization/Server/en_GB.json | 50 +- .../Localization/Server/en_US.json | 50 +- .../Localization/Server/es.json | 48 +- .../Localization/Server/es_MX.json | 48 +- .../Localization/Server/fr.json | 48 +- .../Localization/Server/he.json | 48 +- .../Localization/Server/it.json | 50 +- .../Localization/Server/kk.json | 52 +- .../Localization/Server/ko.json | 50 +- .../Localization/Server/ms.json | 50 +- .../Localization/Server/nb.json | 48 +- .../Localization/Server/nl.json | 50 +- .../Localization/Server/pl.json | 48 +- .../Localization/Server/pt_BR.json | 50 +- .../Localization/Server/pt_PT.json | 48 +- .../Localization/Server/ru.json | 58 +- .../Localization/Server/server.json | 47 +- .../Localization/Server/sv.json | 48 +- .../Localization/Server/tr.json | 66 +- .../Localization/Server/vi.json | 48 +- .../Localization/Server/zh_TW.json | 48 +- .../Persistence/SqliteExtensions.cs | 22 + 77 files changed, 1768 insertions(+), 1002 deletions(-) delete mode 100644 MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs delete mode 100644 MediaBrowser.Api/DefaultTheme/Models.cs create mode 100644 MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs create mode 100644 MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs deleted file mode 100644 index 21ba47bd4d..0000000000 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ /dev/null @@ -1,673 +0,0 @@ -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Api.DefaultTheme -{ - [Route("/MBT/DefaultTheme/Games", "GET")] - public class GetGamesView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "RecentlyPlayedGamesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int RecentlyPlayedGamesLimit { get; set; } - - public string ParentId { get; set; } - } - - [Route("/MBT/DefaultTheme/TV", "GET")] - public class GetTvView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ComedyGenre { get; set; } - - [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string RomanceGenre { get; set; } - - [ApiMember(Name = "TopCommunityRating", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double TopCommunityRating { get; set; } - - [ApiMember(Name = "NextUpEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int NextUpEpisodeLimit { get; set; } - - [ApiMember(Name = "ResumableEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int ResumableEpisodeLimit { get; set; } - - [ApiMember(Name = "LatestEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int LatestEpisodeLimit { get; set; } - - public string ParentId { get; set; } - } - - [Route("/MBT/DefaultTheme/Movies", "GET")] - public class GetMovieView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "FamilyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string FamilyGenre { get; set; } - - [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ComedyGenre { get; set; } - - [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string RomanceGenre { get; set; } - - [ApiMember(Name = "LatestMoviesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int LatestMoviesLimit { get; set; } - - [ApiMember(Name = "LatestTrailersLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int LatestTrailersLimit { get; set; } - - public string ParentId { get; set; } - } - - [Route("/MBT/DefaultTheme/Favorites", "GET")] - public class GetFavoritesView : IReturn - { - [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Authenticated] - public class DefaultThemeService : BaseApiService - { - private readonly IUserManager _userManager; - private readonly IDtoService _dtoService; - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IUserDataManager _userDataManager; - - private readonly IImageProcessor _imageProcessor; - private readonly IItemRepository _itemRepo; - - public DefaultThemeService(IUserManager userManager, IDtoService dtoService, ILogger logger, ILibraryManager libraryManager, IImageProcessor imageProcessor, IUserDataManager userDataManager, IItemRepository itemRepo) - { - _userManager = userManager; - _dtoService = dtoService; - _logger = logger; - _libraryManager = libraryManager; - _imageProcessor = imageProcessor; - _userDataManager = userDataManager; - _itemRepo = itemRepo; - } - - public object Get(GetFavoritesView request) - { - var user = _userManager.GetUserById(request.UserId); - - var allItems = user.RootFolder.GetRecursiveChildren(user) - .ToList(); - - var allFavoriteItems = allItems.Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite) - .ToList(); - - var itemsWithImages = allFavoriteItems.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)) - .ToList(); - - var itemsWithBackdrops = allFavoriteItems.Where(i => i.GetImages(ImageType.Backdrop).Any()) - .ToList(); - - var view = new FavoritesView(); - - var fields = new List(); - - view.BackdropItems = FilterItemsForBackdropDisplay(itemsWithBackdrops) - .Randomize("backdrop") - .Take(10) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - var spotlightItems = itemsWithBackdrops.Randomize("spotlight") - .Take(10) - .ToList(); - - view.SpotlightItems = spotlightItems - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - fields.Add(ItemFields.PrimaryImageAspectRatio); - - view.Albums = itemsWithImages - .OfType() - .Randomize() - .Take(4) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Books = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Episodes = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Games = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Movies = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Series = itemsWithImages - .OfType() - .Randomize() - .Take(6) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) - .ToList(); - - view.Songs = itemsWithImages - .OfType