diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-03-16 00:23:58 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-03-16 00:23:58 -0400 |
| commit | b36aea4ff74052ae40c27db057f50f645659aa57 (patch) | |
| tree | f98c438363d961243fcf743c9a240a05429a2848 | |
| parent | 4e6d306d0021cda1e909da2647b803ea7d505d4a (diff) | |
#712 - Support grouping multiple versions of a movie
| -rw-r--r-- | MediaBrowser.Api/VideosService.cs | 95 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/Video.cs | 86 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/DlnaController.cs | 21 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.Model/Dto/BaseItemDto.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Model/Session/PlayRequest.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.Model/Session/PlaystateCommand.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.Providers/All/LocalImageProvider.cs | 15 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Collections/CollectionManager.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Dto/DtoService.cs | 7 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Session/SessionManager.cs | 10 |
12 files changed, 217 insertions, 39 deletions
diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index 31fda199e..af200895e 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -1,4 +1,7 @@ -using MediaBrowser.Controller.Dto; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Querying; @@ -37,14 +40,44 @@ namespace MediaBrowser.Api [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - + + [Route("/Videos/{Id}/AlternateVersions", "POST")] + [Api(Description = "Assigns videos as alternates of antoher.")] + public class PostAlternateVersions : IReturnVoid + { + [ApiMember(Name = "AlternateVersionIds", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string AlternateVersionIds { get; set; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Videos/{Id}/AlternateVersions", "DELETE")] + [Api(Description = "Assigns videos as alternates of antoher.")] + public class DeleteAlternateVersions : IReturnVoid + { + [ApiMember(Name = "AlternateVersionIds", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string AlternateVersionIds { get; set; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + public class VideosService : BaseApiService { private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; - public VideosService( ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService) + public VideosService(ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService) { _libraryManager = libraryManager; _userManager = userManager; @@ -115,5 +148,61 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } + + public void Post(PostAlternateVersions request) + { + var task = AddAlternateVersions(request); + + Task.WaitAll(task); + } + + public void Delete(DeleteAlternateVersions request) + { + var task = RemoveAlternateVersions(request); + + Task.WaitAll(task); + } + + private async Task AddAlternateVersions(PostAlternateVersions request) + { + var video = (Video)_dtoService.GetItemByDtoId(request.Id); + + var list = new List<LinkedChild>(); + var currentAlternateVersions = video.GetAlternateVersions().ToList(); + + foreach (var itemId in request.AlternateVersionIds.Split(',').Select(i => new Guid(i))) + { + var item = _libraryManager.GetItemById(itemId) as Video; + + if (item == null) + { + throw new ArgumentException("No item exists with the supplied Id"); + } + + if (currentAlternateVersions.Any(i => i.Id == itemId)) + { + throw new ArgumentException("Item already exists."); + } + + list.Add(new LinkedChild + { + Path = item.Path, + Type = LinkedChildType.Manual + }); + + item.PrimaryVersionId = video.Id; + } + + video.LinkedAlternateVersions.AddRange(list); + + await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + await video.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + } + + private async Task RemoveAlternateVersions(DeleteAlternateVersions request) + { + var video = (Video)_dtoService.GetItemByDtoId(request.Id); + } } } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index e30458dd8..18db21f38 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; @@ -20,26 +21,27 @@ namespace MediaBrowser.Controller.Entities { public bool IsMultiPart { get; set; } public bool HasLocalAlternateVersions { get; set; } + public Guid? PrimaryVersionId { get; set; } public List<Guid> AdditionalPartIds { get; set; } - public List<Guid> AlternateVersionIds { get; set; } + public List<Guid> LocalAlternateVersionIds { get; set; } public Video() { PlayableStreamFileNames = new List<string>(); AdditionalPartIds = new List<Guid>(); - AlternateVersionIds = new List<Guid>(); + LocalAlternateVersionIds = new List<Guid>(); Tags = new List<string>(); SubtitleFiles = new List<string>(); LinkedAlternateVersions = new List<LinkedChild>(); } [IgnoreDataMember] - public bool HasAlternateVersions + public int AlternateVersionCount { get { - return HasLocalAlternateVersions || LinkedAlternateVersions.Count > 0; + return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count; } } @@ -51,7 +53,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>IEnumerable{BaseItem}.</returns> public IEnumerable<BaseItem> GetAlternateVersions() { - var filesWithinSameDirectory = AlternateVersionIds + var filesWithinSameDirectory = LocalAlternateVersionIds .Select(i => LibraryManager.GetItemById(i)) .Where(i => i != null) .OfType<Video>(); @@ -233,14 +235,11 @@ namespace MediaBrowser.Controller.Entities { RefreshLinkedAlternateVersions(); - if (HasLocalAlternateVersions) - { - var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - if (additionalPartsChanged) - { - hasChanges = true; - } + if (additionalPartsChanged) + { + hasChanges = true; } } } @@ -339,21 +338,72 @@ namespace MediaBrowser.Controller.Entities private async Task<bool> RefreshAlternateVersionsWithinSameDirectory(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { - var newItems = LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList(); + var newItems = HasLocalAlternateVersions ? + LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList() : + new List<Video>(); var newItemIds = newItems.Select(i => i.Id).ToList(); - var itemsChanged = !AlternateVersionIds.SequenceEqual(newItemIds); + var itemsChanged = !LocalAlternateVersionIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken)); + var tasks = newItems.Select(i => RefreshAlternateVersion(options, i, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false); - AlternateVersionIds = newItemIds; + LocalAlternateVersionIds = newItemIds; return itemsChanged; } + private Task RefreshAlternateVersion(MetadataRefreshOptions options, Video video, CancellationToken cancellationToken) + { + var currentImagePath = video.GetImagePath(ImageType.Primary); + var ownerImagePath = this.GetImagePath(ImageType.Primary); + + var newOptions = new MetadataRefreshOptions + { + DirectoryService = options.DirectoryService, + ImageRefreshMode = options.ImageRefreshMode, + MetadataRefreshMode = options.MetadataRefreshMode, + ReplaceAllMetadata = options.ReplaceAllMetadata + }; + + if (!string.Equals(currentImagePath, ownerImagePath, StringComparison.OrdinalIgnoreCase)) + { + newOptions.ForceSave = true; + + if (string.IsNullOrWhiteSpace(ownerImagePath)) + { + video.ImageInfos.Clear(); + } + else + { + video.SetImagePath(ImageType.Primary, ownerImagePath); + } + } + + return video.RefreshMetadata(newOptions, cancellationToken); + } + + public override async Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) + { + await base.UpdateToRepository(updateReason, cancellationToken).ConfigureAwait(false); + + foreach (var item in LocalAlternateVersionIds.Select(i => LibraryManager.GetItemById(i))) + { + item.ImageInfos = ImageInfos; + item.Overview = Overview; + item.ProductionYear = ProductionYear; + item.PremiereDate = PremiereDate; + item.CommunityRating = CommunityRating; + item.OfficialRating = OfficialRating; + item.Genres = Genres; + item.ProviderIds = ProviderIds; + + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); + } + } + /// <summary> /// Loads the additional parts. /// </summary> @@ -395,7 +445,7 @@ namespace MediaBrowser.Controller.Entities video = dbItem; } - video.ImageInfos = ImageInfos; + video.PrimaryVersionId = Id; return video; diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 7885ee481..2d500db89 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -28,6 +28,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly INetworkManager _networkManager; private readonly ILogger _logger; private readonly IDlnaManager _dlnaManager; + private readonly IUserManager _userManager; private bool _playbackStarted = false; public bool SupportsMediaRemoteControl @@ -46,7 +47,7 @@ namespace MediaBrowser.Dlna.PlayTo } } - public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager) + public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager) { _session = session; _itemRepository = itemRepository; @@ -54,6 +55,7 @@ namespace MediaBrowser.Dlna.PlayTo _libraryManager = libraryManager; _networkManager = networkManager; _dlnaManager = dlnaManager; + _userManager = userManager; _logger = logger; } @@ -194,7 +196,7 @@ namespace MediaBrowser.Dlna.PlayTo #region SendCommands - public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) + public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) { _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); @@ -227,16 +229,25 @@ namespace MediaBrowser.Dlna.PlayTo if (command.PlayCommand == PlayCommand.PlayLast) { AddItemsToPlaylist(playlist); - return Task.FromResult(true); } if (command.PlayCommand == PlayCommand.PlayNext) { AddItemsToPlaylist(playlist); - return Task.FromResult(true); } _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count); - return PlayItems(playlist); + + if (!string.IsNullOrWhiteSpace(command.ControllingUserId)) + { + var userId = new Guid(command.ControllingUserId); + + var user = _userManager.GetUserById(userId); + + await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId, + _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false); + } + + await PlayItems(playlist).ConfigureAwait(false); } public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index cb30498e6..0eab41b1b 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -227,7 +227,7 @@ namespace MediaBrowser.Dlna.PlayTo if (controller == null) { - sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager); + sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager); } controller.Init(device); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index d571c233b..857332b06 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -494,7 +494,8 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The part count.</value> public int? PartCount { get; set; } - public bool? HasAlternateVersions { get; set; } + public int? AlternateVersionCount { get; set; } + public string PrimaryVersionId { get; set; } /// <summary> /// Determines whether the specified type is type. diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index 57f6c37f5..949274a5d 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -23,6 +23,12 @@ namespace MediaBrowser.Model.Session /// </summary> /// <value>The play command.</value> public PlayCommand PlayCommand { get; set; } + + /// <summary> + /// Gets or sets the controlling user identifier. + /// </summary> + /// <value>The controlling user identifier.</value> + public string ControllingUserId { get; set; } } /// <summary> diff --git a/MediaBrowser.Model/Session/PlaystateCommand.cs b/MediaBrowser.Model/Session/PlaystateCommand.cs index 918f4f70f..b0dec66d4 100644 --- a/MediaBrowser.Model/Session/PlaystateCommand.cs +++ b/MediaBrowser.Model/Session/PlaystateCommand.cs @@ -37,5 +37,11 @@ namespace MediaBrowser.Model.Session public PlaystateCommand Command { get; set; } public long? SeekPositionTicks { get; set; } + + /// <summary> + /// Gets or sets the controlling user identifier. + /// </summary> + /// <value>The controlling user identifier.</value> + public string ControllingUserId { get; set; } } }
\ No newline at end of file diff --git a/MediaBrowser.Providers/All/LocalImageProvider.cs b/MediaBrowser.Providers/All/LocalImageProvider.cs index 6ef5f6bf3..1d10fcfa3 100644 --- a/MediaBrowser.Providers/All/LocalImageProvider.cs +++ b/MediaBrowser.Providers/All/LocalImageProvider.cs @@ -94,14 +94,13 @@ namespace MediaBrowser.Providers.All public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService) { var files = paths.SelectMany(directoryService.GetFiles) - .Where(i => - { - var ext = i.Extension; - - return !string.IsNullOrEmpty(ext) && - BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - }) - .Cast<FileSystemInfo>() + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) .ToList(); var list = new List<LocalImageInfo>(); diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 60d631c1a..c18856293 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -111,6 +111,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List<LinkedChild>(); + var currentLinkedChildren = collection.GetLinkedChildren().ToList(); foreach (var itemId in ids) { @@ -121,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentException("No item exists with the supplied Id"); } - if (collection.LinkedChildren.Any(i => i.ItemId.HasValue && i.ItemId == itemId)) + if (currentLinkedChildren.Any(i => i.Id == itemId)) { throw new ArgumentException("Item already exists in collection"); } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 6f6a3f043..19d834c20 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1082,7 +1082,12 @@ namespace MediaBrowser.Server.Implementations.Dto dto.IsHD = video.IsHD; dto.PartCount = video.AdditionalPartIds.Count + 1; - dto.HasAlternateVersions = video.HasAlternateVersions; + dto.AlternateVersionCount = video.AlternateVersionCount; + + if (video.PrimaryVersionId.HasValue) + { + dto.PrimaryVersionId = video.PrimaryVersionId.Value.ToString("N"); + } if (fields.Contains(ItemFields.Chapters)) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index de10e669e..b11457ec5 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -410,7 +410,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrWhiteSpace(filenamePrefix)) { - if (sortedMovies.All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix, StringComparison.OrdinalIgnoreCase))) + if (sortedMovies.Skip(1).All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) { firstMovie.HasLocalAlternateVersions = true; diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 71d95b97b..cea70a8b4 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -690,6 +690,11 @@ namespace MediaBrowser.Server.Implementations.Session } } + if (session.UserId.HasValue) + { + command.ControllingUserId = session.UserId.Value.ToString("N"); + } + return session.SessionController.SendPlayCommand(command, cancellationToken); } @@ -723,6 +728,11 @@ namespace MediaBrowser.Server.Implementations.Session throw new ArgumentException(string.Format("Session {0} is unable to seek.", session.Id)); } + if (session.UserId.HasValue) + { + command.ControllingUserId = session.UserId.Value.ToString("N"); + } + return session.SessionController.SendPlaystateCommand(command, cancellationToken); } |
