diff options
| author | Tim Hobbs <jesus.tesh@gmail.com> | 2014-03-17 15:47:22 -0700 |
|---|---|---|
| committer | Tim Hobbs <jesus.tesh@gmail.com> | 2014-03-17 15:47:22 -0700 |
| commit | cf43180a2dcab023ba6a48f37920615d7e87c599 (patch) | |
| tree | 1b94ff05caf34974161595823898b8b32e1f6d24 | |
| parent | 7a0963129126679aad8b64cc8a36474edaca7170 (diff) | |
| parent | 78acab691693d6adfac67bcf3f0617e336f801a6 (diff) | |
Merge remote-tracking branch 'upstream/master'
48 files changed, 1416 insertions, 332 deletions
diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index b8ca70ba5..df35c93a0 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -277,7 +277,7 @@ namespace MediaBrowser.Api SeekPositionTicks = request.SeekPositionTicks }; - var task = _sessionManager.SendPlaystateCommand(request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -296,7 +296,7 @@ namespace MediaBrowser.Api ItemType = request.ItemType }; - var task = _sessionManager.SendBrowseCommand(request.Id, command, CancellationToken.None); + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -307,7 +307,7 @@ namespace MediaBrowser.Api /// <param name="request">The request.</param> public void Post(SendSystemCommand request) { - var task = _sessionManager.SendSystemCommand(request.Id, request.Command, CancellationToken.None); + var task = _sessionManager.SendSystemCommand(GetSession().Id, request.Id, request.Command, CancellationToken.None); Task.WaitAll(task); } @@ -325,7 +325,7 @@ namespace MediaBrowser.Api Text = request.Text }; - var task = _sessionManager.SendMessageCommand(request.Id, command, CancellationToken.None); + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -344,7 +344,7 @@ namespace MediaBrowser.Api StartPositionTicks = request.StartPositionTicks }; - var task = _sessionManager.SendPlayCommand(request.Id, command, CancellationToken.None); + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); Task.WaitAll(task); } @@ -367,5 +367,14 @@ namespace MediaBrowser.Api .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .ToList(); } + + private SessionInfo GetSession() + { + var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); + + return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && + string.Equals(i.Client, auth.Client) && + string.Equals(i.ApplicationVersion, auth.Version)); + } } }
\ No newline at end of file diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index b040d3dd8..44c224989 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -238,6 +238,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public bool? HasOfficialRating { get; set; } + + [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool CollapseBoxSetItems { get; set; } } /// <summary> @@ -315,6 +318,11 @@ namespace MediaBrowser.Api.UserLibrary items = items.AsEnumerable(); + if (request.CollapseBoxSetItems && AllowBoxSetCollapsing(request)) + { + items = CollapseItemsWithinBoxSets(items, user); + } + items = ApplySortOrder(request, items, user, _libraryManager); // This must be the last filter @@ -1218,6 +1226,41 @@ namespace MediaBrowser.Api.UserLibrary return false; } + private IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user) + { + var itemsToCollapse = new List<ISupportsBoxSetGrouping>(); + var boxsets = new List<BaseItem>(); + + var list = items.ToList(); + + foreach (var item in list.OfType<ISupportsBoxSetGrouping>()) + { + var currentBoxSets = item.BoxSetIdList + .Select(i => _libraryManager.GetItemById(i)) + .Where(i => i != null && i.IsVisible(user)) + .ToList(); + + if (currentBoxSets.Count > 0) + { + itemsToCollapse.Add(item); + boxsets.AddRange(currentBoxSets); + } + } + + return list.Except(itemsToCollapse.Cast<BaseItem>()).Concat(boxsets).Distinct(); + } + + private bool AllowBoxSetCollapsing(GetItems request) + { + // Only allow when using default sort order + if (!string.IsNullOrEmpty(request.SortBy) && !string.Equals(request.SortBy, "SortName", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + internal static IEnumerable<BaseItem> FilterForAdjacency(IEnumerable<BaseItem> items, string adjacentToId) { var list = items.ToList(); diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index fb58e58b7..ba3b0912b 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; @@ -22,14 +25,62 @@ 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", "GET")] + [Api(Description = "Gets alternate versions of a video.")] + public class GetAlternateVersions : IReturn<ItemsResult> + { + [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { 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", "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; } + + [ApiMember(Name = "IsAlternateEncoding", Description = "Filter by versions that are considered alternate encodings of the original.", IsRequired = true, DataType = "bool", ParameterType = "path", Verb = "GET")] + public bool? IsAlternateEncoding { 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; @@ -48,7 +99,7 @@ namespace MediaBrowser.Api var item = string.IsNullOrEmpty(request.Id) ? (request.UserId.HasValue ? user.RootFolder - : (Folder)_libraryManager.RootFolder) + : _libraryManager.RootFolder) : _dtoService.GetItemByDtoId(request.Id, request.UserId); // Get everything @@ -58,8 +109,7 @@ namespace MediaBrowser.Api var video = (Video)item; - var items = video.AdditionalPartIds.Select(_libraryManager.GetItemById) - .OrderBy(i => i.SortName) + var items = video.GetAdditionalParts() .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video)) .ToArray(); @@ -71,5 +121,91 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } + + public object Get(GetAlternateVersions request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var item = string.IsNullOrEmpty(request.Id) + ? (request.UserId.HasValue + ? user.RootFolder + : _libraryManager.RootFolder) + : _dtoService.GetItemByDtoId(request.Id, request.UserId); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + var video = (Video)item; + + var items = video.GetAlternateVersions() + .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video)) + .ToArray(); + + var result = new ItemsResult + { + Items = items, + TotalRecordCount = items.Length + }; + + 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/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index e147e0905..74ae42095 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -14,9 +14,12 @@ namespace MediaBrowser.Controller.Collections public Dictionary<string, string> ProviderIds { get; set; } + public List<Guid> ItemIdList { get; set; } + public CollectionCreationOptions() { ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + ItemIdList = new List<Guid>(); } } } diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index d7bc178ad..af65bbaca 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,4 +1,5 @@ -using System; +using MediaBrowser.Controller.Entities.Movies; +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Collections /// </summary> /// <param name="options">The options.</param> /// <returns>Task.</returns> - Task CreateCollection(CollectionCreationOptions options); + Task<BoxSet> CreateCollection(CollectionCreationOptions options); /// <summary> /// Adds to collection. diff --git a/MediaBrowser.Controller/Dlna/DeviceIdentification.cs b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs new file mode 100644 index 000000000..a3a615516 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Dlna +{ + public class DeviceIdentification + { + /// <summary> + /// Gets or sets the name of the friendly. + /// </summary> + /// <value>The name of the friendly.</value> + public string FriendlyName { get; set; } + /// <summary> + /// Gets or sets the model number. + /// </summary> + /// <value>The model number.</value> + public string ModelNumber { get; set; } + /// <summary> + /// Gets or sets the serial number. + /// </summary> + /// <value>The serial number.</value> + public string SerialNumber { get; set; } + /// <summary> + /// Gets or sets the name of the model. + /// </summary> + /// <value>The name of the model.</value> + public string ModelName { get; set; } + /// <summary> + /// Gets or sets the manufacturer. + /// </summary> + /// <value> + /// The manufacturer. + /// </value> + public string Manufacturer { get; set; } + /// <summary> + /// Gets or sets the manufacturer URL. + /// </summary> + /// <value>The manufacturer URL.</value> + public string ManufacturerUrl { get; set; } + /// <summary> + /// Gets or sets the headers. + /// </summary> + /// <value>The headers.</value> + public List<HttpHeaderInfo> Headers { get; set; } + + public DeviceIdentification() + { + Headers = new List<HttpHeaderInfo>(); + } + } + + public class HttpHeaderInfo + { + public string Name { get; set; } + public string Value { get; set; } + } +} diff --git a/MediaBrowser.Controller/Dlna/DlnaProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs index 33f95b794..119cfffd7 100644 --- a/MediaBrowser.Controller/Dlna/DlnaProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Controller.Dlna { - public class DlnaProfile + public class DeviceProfile { /// <summary> /// Gets or sets the name. @@ -16,24 +16,6 @@ namespace MediaBrowser.Controller.Dlna public string ClientType { get; set; } /// <summary> - /// Gets or sets the name of the friendly. - /// </summary> - /// <value>The name of the friendly.</value> - public string FriendlyName { get; set; } - - /// <summary> - /// Gets or sets the model number. - /// </summary> - /// <value>The model number.</value> - public string ModelNumber { get; set; } - - /// <summary> - /// Gets or sets the name of the model. - /// </summary> - /// <value>The name of the model.</value> - public string ModelName { get; set; } - - /// <summary> /// Gets or sets the transcoding profiles. /// </summary> /// <value>The transcoding profiles.</value> @@ -45,7 +27,13 @@ namespace MediaBrowser.Controller.Dlna /// <value>The direct play profiles.</value> public DirectPlayProfile[] DirectPlayProfiles { get; set; } - public DlnaProfile() + /// <summary> + /// Gets or sets the identification. + /// </summary> + /// <value>The identification.</value> + public DeviceIdentification Identification { get; set; } + + public DeviceProfile() { DirectPlayProfiles = new DirectPlayProfile[] { }; TranscodingProfiles = new TranscodingProfile[] { }; diff --git a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs index f1922dd32..8c35b52a8 100644 --- a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs @@ -1,25 +1,97 @@ - +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Xml.Serialization; + namespace MediaBrowser.Controller.Dlna { public class DirectPlayProfile { - public string[] Containers { get; set; } - public string[] AudioCodecs { get; set; } - public string[] VideoCodecs { get; set; } + public string Container { get; set; } + public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + + [IgnoreDataMember] + [XmlIgnore] + public string[] Containers + { + get + { + return (Container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + set + { + Container = value == null ? null : string.Join(",", value); + } + } + + [IgnoreDataMember] + [XmlIgnore] + public string[] AudioCodecs + { + get + { + return (AudioCodec ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + set + { + AudioCodec = value == null ? null : string.Join(",", value); + } + } + + [IgnoreDataMember] + [XmlIgnore] + public string[] VideoCodecs + { + get + { + return (VideoCodec ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + set + { + VideoCodec = value == null ? null : string.Join(",", value); + } + } + public string MimeType { get; set; } public DlnaProfileType Type { get; set; } + public List<ProfileCondition> Conditions { get; set; } + public DirectPlayProfile() { - Containers = new string[] { }; - AudioCodecs = new string[] { }; - VideoCodecs = new string[] { }; + Conditions = new List<ProfileCondition>(); } } + public class ProfileCondition + { + public ProfileConditionType Condition { get; set; } + public ProfileConditionValue Value { get; set; } + } + public enum DlnaProfileType { Audio = 0, Video = 1 } + + public enum ProfileConditionType + { + Equals = 0, + NotEquals = 1, + LessThanEqual = 2, + GreaterThanEqual = 3 + } + + public enum ProfileConditionValue + { + AudioChannels, + AudioBitrate, + Filesize, + VideoWidth, + VideoHeight, + VideoBitrate, + VideoFramerate + } } diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index 017dbc874..6de17e551 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -8,21 +8,19 @@ namespace MediaBrowser.Controller.Dlna /// Gets the dlna profiles. /// </summary> /// <returns>IEnumerable{DlnaProfile}.</returns> - IEnumerable<DlnaProfile> GetProfiles(); + IEnumerable<DeviceProfile> GetProfiles(); /// <summary> /// Gets the default profile. /// </summary> /// <returns>DlnaProfile.</returns> - DlnaProfile GetDefaultProfile(); + DeviceProfile GetDefaultProfile(); /// <summary> /// Gets the profile. /// </summary> - /// <param name="friendlyName">Name of the friendly.</param> - /// <param name="modelName">Name of the model.</param> - /// <param name="modelNumber">The model number.</param> - /// <returns>DlnaProfile.</returns> - DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber); + /// <param name="deviceInfo">The device information.</param> + /// <returns>DeviceProfile.</returns> + DeviceProfile GetProfile(DeviceIdentification deviceInfo); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e0c792307..be64d20c3 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -955,6 +955,83 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Gets the linked child. + /// </summary> + /// <param name="info">The info.</param> + /// <returns>BaseItem.</returns> + protected BaseItem GetLinkedChild(LinkedChild info) + { + // First get using the cached Id + if (info.ItemId.HasValue) + { + if (info.ItemId.Value == Guid.Empty) + { + return null; + } + + var itemById = LibraryManager.GetItemById(info.ItemId.Value); + + if (itemById != null) + { + return itemById; + } + } + + var item = FindLinkedChild(info); + + // If still null, log + if (item == null) + { + // Don't keep searching over and over + info.ItemId = Guid.Empty; + } + else + { + // Cache the id for next time + info.ItemId = item.Id; + } + + return item; + } + + private BaseItem FindLinkedChild(LinkedChild info) + { + if (!string.IsNullOrEmpty(info.Path)) + { + var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); + + if (itemByPath == null) + { + Logger.Warn("Unable to find linked item at path {0}", info.Path); + } + + return itemByPath; + } + + if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) + { + return LibraryManager.RootFolder.RecursiveChildren.FirstOrDefault(i => + { + if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) + { + if (info.ItemYear.HasValue) + { + return info.ItemYear.Value == (i.ProductionYear ?? -1); + } + return true; + } + } + + return false; + }); + } + + return null; + } + + /// <summary> /// Adds a person to the item /// </summary> /// <param name="person">The person.</param> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ee371680e..45daaba0b 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -354,20 +354,45 @@ namespace MediaBrowser.Controller.Entities private bool IsValidFromResolver(BaseItem current, BaseItem newItem) { - var currentAsPlaceHolder = current as ISupportsPlaceHolders; + var currentAsVideo = current as Video; - if (currentAsPlaceHolder != null) + if (currentAsVideo != null) { - var newHasPlaceHolder = newItem as ISupportsPlaceHolders; + var newAsVideo = newItem as Video; - if (newHasPlaceHolder != null) + if (newAsVideo != null) { - if (currentAsPlaceHolder.IsPlaceHolder != newHasPlaceHolder.IsPlaceHolder) + if (currentAsVideo.IsPlaceHolder != newAsVideo.IsPlaceHolder) + { + return false; + } + if (currentAsVideo.IsMultiPart != newAsVideo.IsMultiPart) + { + return false; + } + if (currentAsVideo.HasLocalAlternateVersions != newAsVideo.HasLocalAlternateVersions) { return false; } } } + else + { + var currentAsPlaceHolder = current as ISupportsPlaceHolders; + + if (currentAsPlaceHolder != null) + { + var newHasPlaceHolder = newItem as ISupportsPlaceHolders; + + if (newHasPlaceHolder != null) + { + if (currentAsPlaceHolder.IsPlaceHolder != newHasPlaceHolder.IsPlaceHolder) + { + return false; + } + } + } + } return current.IsInMixedFolder == newItem.IsInMixedFolder; } @@ -898,83 +923,6 @@ namespace MediaBrowser.Controller.Entities .Where(i => i != null); } - /// <summary> - /// Gets the linked child. - /// </summary> - /// <param name="info">The info.</param> - /// <returns>BaseItem.</returns> - private BaseItem GetLinkedChild(LinkedChild info) - { - // First get using the cached Id - if (info.ItemId.HasValue) - { - if (info.ItemId.Value == Guid.Empty) - { - return null; - } - - var itemById = LibraryManager.GetItemById(info.ItemId.Value); - - if (itemById != null) - { - return itemById; - } - } - - var item = FindLinkedChild(info); - - // If still null, log - if (item == null) - { - // Don't keep searching over and over - info.ItemId = Guid.Empty; - } - else - { - // Cache the id for next time - info.ItemId = item.Id; - } - - return item; - } - - private BaseItem FindLinkedChild(LinkedChild info) - { - if (!string.IsNullOrEmpty(info.Path)) - { - var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); - - if (itemByPath == null) - { - Logger.Warn("Unable to find linked item at path {0}", info.Path); - } - - return itemByPath; - } - - if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) - { - return LibraryManager.RootFolder.RecursiveChildren.FirstOrDefault(i => - { - if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) - { - if (info.ItemYear.HasValue) - { - return info.ItemYear.Value == (i.ProductionYear ?? -1); - } - return true; - } - } - - return false; - }); - } - - return null; - } - protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { var changesFound = false; diff --git a/MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs b/MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs new file mode 100644 index 000000000..0fd463155 --- /dev/null +++ b/MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Entities +{ + /// <summary> + /// Marker interface to denote a class that supports being hidden underneath it's boxset. + /// Just about anything can be placed into a boxset, + /// but movies should also only appear underneath and not outside separately (subject to configuration). + /// </summary> + public interface ISupportsBoxSetGrouping + { + /// <summary> + /// Gets or sets the box set identifier list. + /// </summary> + /// <value>The box set identifier list.</value> + List<Guid> BoxSetIdList { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 9858dd5a9..f53b67610 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,11 +1,11 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// <summary> /// Class Movie /// </summary> - public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo> + public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping { public List<Guid> SpecialFeatureIds { get; set; } @@ -24,6 +24,12 @@ namespace MediaBrowser.Controller.Entities.Movies public List<Guid> ThemeVideoIds { get; set; } /// <summary> + /// This is just a cache to enable quick access by Id + /// </summary> + [IgnoreDataMember] + public List<Guid> BoxSetIdList { get; set; } + + /// <summary> /// Gets or sets the preferred metadata country code. /// </summary> /// <value>The preferred metadata country code.</value> @@ -39,6 +45,7 @@ namespace MediaBrowser.Controller.Entities.Movies LocalTrailerIds = new List<Guid>(); ThemeSongIds = new List<Guid>(); ThemeVideoIds = new List<Guid>(); + BoxSetIdList = new List<Guid>(); Taglines = new List<string>(); Keywords = new List<string>(); } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 10034d7e5..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; @@ -19,15 +20,64 @@ namespace MediaBrowser.Controller.Entities public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio, IHasTags, ISupportsPlaceHolders { public bool IsMultiPart { get; set; } + public bool HasLocalAlternateVersions { get; set; } + public Guid? PrimaryVersionId { get; set; } public List<Guid> AdditionalPartIds { get; set; } + public List<Guid> LocalAlternateVersionIds { get; set; } public Video() { PlayableStreamFileNames = new List<string>(); AdditionalPartIds = new List<Guid>(); + LocalAlternateVersionIds = new List<Guid>(); Tags = new List<string>(); SubtitleFiles = new List<string>(); + LinkedAlternateVersions = new List<LinkedChild>(); + } + + [IgnoreDataMember] + public int AlternateVersionCount + { + get + { + return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count; + } + } + + public List<LinkedChild> LinkedAlternateVersions { get; set; } + + /// <summary> + /// Gets the linked children. + /// </summary> + /// <returns>IEnumerable{BaseItem}.</returns> + public IEnumerable<BaseItem> GetAlternateVersions() + { + var filesWithinSameDirectory = LocalAlternateVersionIds + .Select(i => LibraryManager.GetItemById(i)) + .Where(i => i != null) + .OfType<Video>(); + + var linkedVersions = LinkedAlternateVersions + .Select(GetLinkedChild) + .Where(i => i != null) + .OfType<Video>(); + + return filesWithinSameDirectory.Concat(linkedVersions) + .OrderBy(i => i.SortName); + } + + /// <summary> + /// Gets the additional parts. + /// </summary> + /// <returns>IEnumerable{Video}.</returns> + public IEnumerable<Video> GetAdditionalParts() + { + return AdditionalPartIds + .Select(i => LibraryManager.GetItemById(i)) + .Where(i => i != null) + .OfType<Video>() + .OrderBy(i => i.SortName); } /// <summary> @@ -43,13 +93,13 @@ namespace MediaBrowser.Controller.Entities public bool HasSubtitles { get; set; } public bool IsPlaceHolder { get; set; } - + /// <summary> /// Gets or sets the tags. /// </summary> /// <value>The tags.</value> public List<string> Tags { get; set; } - + /// <summary> /// Gets or sets the video bit rate. /// </summary> @@ -167,22 +217,50 @@ namespace MediaBrowser.Controller.Entities { var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - // Must have a parent to have additional parts + // Must have a parent to have additional parts or alternate versions // In other words, it must be part of the Parent/Child tree // The additional parts won't have additional parts themselves - if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null) + if (LocationType == LocationType.FileSystem && Parent != null) { - var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + if (IsMultiPart) + { + var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); - if (additionalPartsChanged) + if (additionalPartsChanged) + { + hasChanges = true; + } + } + else { - hasChanges = true; + RefreshLinkedAlternateVersions(); + + var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + + if (additionalPartsChanged) + { + hasChanges = true; + } } } return hasChanges; } + private bool RefreshLinkedAlternateVersions() + { + foreach (var child in LinkedAlternateVersions) + { + // Reset the cached value + if (child.ItemId.HasValue && child.ItemId.Value == Guid.Empty) + { + child.ItemId = null; + } + } + + return false; + } + /// <summary> /// Refreshes the additional parts. /// </summary> @@ -223,7 +301,7 @@ namespace MediaBrowser.Controller.Entities { if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { - return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name); + return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFolder(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name); } return false; @@ -258,6 +336,123 @@ namespace MediaBrowser.Controller.Entities }).OrderBy(i => i.Path).ToList(); } + private async Task<bool> RefreshAlternateVersionsWithinSameDirectory(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + { + var newItems = HasLocalAlternateVersions ? + LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList() : + new List<Video>(); + + var newItemIds = newItems.Select(i => i.Id).ToList(); + + var itemsChanged = !LocalAlternateVersionIds.SequenceEqual(newItemIds); + + var tasks = newItems.Select(i => RefreshAlternateVersion(options, i, cancellationToken)); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + 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> + /// <returns>IEnumerable{Video}.</returns> + private IEnumerable<Video> LoadAlternateVersionsWithinSameDirectory(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) + { + IEnumerable<FileSystemInfo> files; + + var path = Path; + var currentFilename = System.IO.Path.GetFileNameWithoutExtension(path) ?? string.Empty; + + // Only support this for video files. For folder rips, they'll have to use the linking feature + if (VideoType == VideoType.VideoFile || VideoType == VideoType.Iso) + { + files = fileSystemChildren.Where(i => + { + if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + return false; + } + + return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && + EntityResolutionHelper.IsVideoFile(i.FullName) && + i.Name.StartsWith(currentFilename, StringComparison.OrdinalIgnoreCase); + }); + } + else + { + files = new List<FileSystemInfo>(); + } + + return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video => + { + // Try to retrieve it from the db. If we don't find it, use the resolved version + var dbItem = LibraryManager.GetItemById(video.Id) as Video; + + if (dbItem != null) + { + video = dbItem; + } + + video.PrimaryVersionId = Id; + + return video; + + // Sort them so that the list can be easily compared for changes + }).OrderBy(i => i.Path).ToList(); + } + public override IEnumerable<string> GetDeletePaths() { if (!IsInMixedFolder) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 5554ced37..4b2ca497b 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -25,5 +25,11 @@ namespace MediaBrowser.Controller /// </summary> /// <value><c>true</c> if [supports automatic run at startup]; otherwise, <c>false</c>.</value> bool SupportsAutoRunAtStartup { get; } + + /// <summary> + /// Gets the HTTP server port. + /// </summary> + /// <value>The HTTP server port.</value> + int HttpServerPort { get; } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 21a501b08..2dc444ea9 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -73,9 +73,10 @@ <Compile Include="Channels\IChannelManager.cs" /> <Compile Include="Collections\CollectionCreationOptions.cs" /> <Compile Include="Collections\ICollectionManager.cs" /> + <Compile Include="Dlna\DeviceIdentification.cs" /> <Compile Include="Dlna\DirectPlayProfile.cs" /> <Compile Include="Dlna\IDlnaManager.cs" /> - <Compile Include="Dlna\DlnaProfile.cs" /> + <Compile Include="Dlna\DeviceProfile.cs" /> <Compile Include="Dlna\TranscodingProfile.cs" /> <Compile Include="Drawing\IImageProcessor.cs" /> <Compile Include="Drawing\ImageFormat.cs" /> @@ -114,6 +115,7 @@ <Compile Include="Entities\ILibraryItem.cs" /> <Compile Include="Entities\ImageSourceInfo.cs" /> <Compile Include="Entities\IMetadataContainer.cs" /> + <Compile Include="Entities\ISupportsBoxSetGrouping.cs" /> <Compile Include="Entities\ISupportsPlaceHolders.cs" /> <Compile Include="Entities\ItemImageInfo.cs" /> <Compile Include="Entities\LinkedChild.cs" /> diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs index 0f93e8e8a..9c757503c 100644 --- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs +++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs @@ -71,7 +71,21 @@ namespace MediaBrowser.Controller.Resolvers throw new ArgumentNullException("path"); } - return MultiFileRegex.Match(path).Success || MultiFolderRegex.Match(path).Success; + path = Path.GetFileName(path); + + return MultiFileRegex.Match(path).Success; + } + + public static bool IsMultiPartFolder(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + path = Path.GetFileName(path); + + return MultiFolderRegex.Match(path).Success; } /// <summary> diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index ee29671c0..6ca15585a 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -86,47 +86,52 @@ namespace MediaBrowser.Controller.Session /// <summary> /// Sends the system command. /// </summary> + /// <param name="controllingSessionId">The controlling session identifier.</param> /// <param name="sessionId">The session id.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken); + Task SendSystemCommand(Guid controllingSessionId, Guid sessionId, SystemCommand command, CancellationToken cancellationToken); /// <summary> /// Sends the message command. /// </summary> + /// <param name="controllingSessionId">The controlling session identifier.</param> /// <param name="sessionId">The session id.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken); + Task SendMessageCommand(Guid controllingSessionId, Guid sessionId, MessageCommand command, CancellationToken cancellationToken); /// <summary> /// Sends the play command. /// </summary> + /// <param name="controllingSessionId">The controlling session identifier.</param> /// <param name="sessionId">The session id.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken); + Task SendPlayCommand(Guid controllingSessionId, Guid sessionId, PlayRequest command, CancellationToken cancellationToken); /// <summary> /// Sends the browse command. /// </summary> + /// <param name="controllingSessionId">The controlling session identifier.</param> /// <param name="sessionId">The session id.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken); + Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken); /// <summary> /// Sends the playstate command. /// </summary> + /// <param name="controllingSessionId">The controlling session identifier.</param> /// <param name="sessionId">The session id.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken); + Task SendPlaystateCommand(Guid controllingSessionId, Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken); /// <summary> /// Sends the restart required message. diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 1c9cba2be..b0c1e1749 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -1,29 +1,51 @@ -using MediaBrowser.Controller.Dlna; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Serialization; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace MediaBrowser.Dlna { public class DlnaManager : IDlnaManager { - public IEnumerable<DlnaProfile> GetProfiles() + private IApplicationPaths _appPaths; + private readonly IXmlSerializer _xmlSerializer; + private readonly IFileSystem _fileSystem; + + public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem) { - var list = new List<DlnaProfile>(); + _xmlSerializer = xmlSerializer; + _fileSystem = fileSystem; + + //GetProfiles(); + } - list.Add(new DlnaProfile + public IEnumerable<DeviceProfile> GetProfiles() + { + var list = new List<DeviceProfile>(); + + #region Samsung + + list.Add(new DeviceProfile { Name = "Samsung TV (B Series)", ClientType = "DLNA", - FriendlyName = "^TV$", - ModelNumber = @"1\.0", - ModelName = "Samsung DTV DMR", + + Identification = new DeviceIdentification + { + FriendlyName = "^TV$", + ModelNumber = @"1\.0", + ModelName = "Samsung DTV DMR" + }, TranscodingProfiles = new[] { new TranscodingProfile { Container = "mp3", - Type = DlnaProfileType.Audio + Type = DlnaProfileType.Audio, }, new TranscodingProfile { @@ -37,7 +59,7 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"mp3"}, - Type = DlnaProfileType.Audio + Type = DlnaProfileType.Audio, }, new DirectPlayProfile { @@ -57,14 +79,20 @@ namespace MediaBrowser.Dlna Type = DlnaProfileType.Video } } + + }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Samsung TV (E/F-series)", ClientType = "DLNA", - FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung", - ModelNumber = @"(1\.0)|(AllShare1\.0)", + + Identification = new DeviceIdentification + { + FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung|(^\[TV\]Samsung [A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)", + ModelNumber = @"(1\.0)|(AllShare1\.0)" + }, TranscodingProfiles = new[] { @@ -107,12 +135,17 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Samsung TV (C/D-series)", ClientType = "DLNA", - FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung", - ModelNumber = @"(1\.0)|(AllShare1\.0)", + + Identification = new DeviceIdentification + { + FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung", + ModelNumber = @"(1\.0)|(AllShare1\.0)" + }, + TranscodingProfiles = new[] { new TranscodingProfile @@ -154,11 +187,20 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + #endregion + + #region Xbox + + list.Add(new DeviceProfile { Name = "Xbox 360", ClientType = "DLNA", - ModelName = "Xbox 360", + + Identification = new DeviceIdentification + { + ModelName = "Xbox 360" + }, + TranscodingProfiles = new[] { new TranscodingProfile @@ -183,18 +225,23 @@ namespace MediaBrowser.Dlna new DirectPlayProfile { Containers = new[]{"avi"}, - MimeType = "x-msvideo", + MimeType = "avi", Type = DlnaProfileType.Video } } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "Xbox One", - ModelName = "Xbox One", ClientType = "DLNA", - FriendlyName = "Xbox-SystemOS", + + Identification = new DeviceIdentification + { + ModelName = "Xbox One", + FriendlyName = "Xbox-SystemOS" + }, + TranscodingProfiles = new[] { new TranscodingProfile @@ -225,11 +272,159 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + #endregion + + #region Sony + + list.Add(new DeviceProfile { Name = "Sony Bravia (2012)", ClientType = "DLNA", - FriendlyName = @"BRAVIA KDL-\d{2}[A-Z]X\d5(\d|G).*", + + Identification = new DeviceIdentification + { + FriendlyName = @"BRAVIA KDL-\d{2}[A-Z]X\d5(\d|G).*" + }, + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video + } + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video, + MimeType = "avi" + } + } + }); + + list.Add(new DeviceProfile + { + Name = "Sony Bravia (2013)", + ClientType = "DLNA", + + Identification = new DeviceIdentification + { + FriendlyName = @"BRAVIA (KDL-\d{2}W[689]\d{2}A.*)|(KD-\d{2}X9\d{3}A.*)" + }, + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video, + MimeType = "mpeg" + } + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"wma"}, + Type = DlnaProfileType.Audio, + MimeType = "x-ms-wma" + }, + new DirectPlayProfile + { + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video, + MimeType = "avi" + }, + new DirectPlayProfile + { + Containers = new[]{"mp4"}, + Type = DlnaProfileType.Video, + MimeType = "mp4" + } + } + }); + + #endregion + + #region Panasonic + + list.Add(new DeviceProfile + { + //Panasonic Viera (2011|2012) Without AVI Support + Name = "Panasonic Viera E/S/ST/VT (2011)", + ClientType = "DLNA", + + Identification = new DeviceIdentification + { + FriendlyName = @"(VIERA (E|S)T?(3|5)0?.*)|(VIERA VT30.*)", + Manufacturer = "Panasonic" + }, + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + Type = DlnaProfileType.Audio + }, + new TranscodingProfile + { + Container = "ts", + Type = DlnaProfileType.Video + } + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video + } + } + }); + + list.Add(new DeviceProfile + { + //Panasonic Viera (2011|2012) With AVI Support + Name = "Panasonic Viera G/GT/DT/UT/VT (2011/2012)", + ClientType = "DLNA", + + Identification = new DeviceIdentification + { + FriendlyName = @"(VIERA (G|D|U)T?(3|5)0?.*)|(VIERA VT50.*)", + Manufacturer = "Panasonic" + }, TranscodingProfiles = new[] { @@ -254,19 +449,66 @@ namespace MediaBrowser.Dlna }, new DirectPlayProfile { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video + }, + new DirectPlayProfile + { + Containers = new[]{"avi"}, + Type = DlnaProfileType.Video , + MimeType="divx" + } + } + }); + + #endregion + + //WDTV does not need any transcoding of the formats we support statically + list.Add(new DeviceProfile + { + Name = "Philips (2010-)", + ClientType = "DLNA", + + Identification = new DeviceIdentification + { + FriendlyName = ".*PHILIPS.*", + ModelName = "WD TV HD Live" + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3", "wma"}, + Type = DlnaProfileType.Audio + }, + + new DirectPlayProfile + { Containers = new[]{"avi"}, Type = DlnaProfileType.Video, MimeType = "avi" + }, + + new DirectPlayProfile + { + Containers = new[]{"mkv"}, + Type = DlnaProfileType.Video, + MimeType = "x-matroska" } } }); //WDTV does not need any transcoding of the formats we support statically - list.Add(new DlnaProfile + list.Add(new DeviceProfile { Name = "WDTV Live", ClientType = "DLNA", - ModelName = "WD TV HD Live", + + Identification = new DeviceIdentification + { + ModelName = "WD TV HD Live" + }, DirectPlayProfiles = new[] { @@ -284,12 +526,16 @@ namespace MediaBrowser.Dlna } }); - list.Add(new DlnaProfile + list.Add(new DeviceProfile { //Linksys DMA2100us does not need any transcoding of the formats we support statically Name = "Linksys DMA2100", ClientType = "DLNA", - ModelName = "DMA2100us", + + Identification = new DeviceIdentification + { + ModelName = "DMA2100us" + }, DirectPlayProfiles = new[] { @@ -307,12 +553,38 @@ namespace MediaBrowser.Dlna } }); + list.Add(new DeviceProfile + { + Name = "Denon AVR", + ClientType = "DLNA", + + Identification = new DeviceIdentification + { + FriendlyName = @"Denon:\[AVR:.*", + Manufacturer = "Denon" + }, + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Containers = new[]{"mp3", "flac", "m4a", "wma"}, + Type = DlnaProfileType.Audio + }, + } + }); + + foreach (var item in list) + { + //_xmlSerializer.SerializeToFile(item, "d:\\" + _fileSystem.GetValidFilename(item.Name)); + } + return list; } - public DlnaProfile GetDefaultProfile() + public DeviceProfile GetDefaultProfile() { - return new DlnaProfile + return new DeviceProfile { TranscodingProfiles = new[] { @@ -345,32 +617,51 @@ namespace MediaBrowser.Dlna }; } - public DlnaProfile GetProfile(string friendlyName, string modelName, string modelNumber) + public DeviceProfile GetProfile(DeviceIdentification deviceInfo) + { + return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ?? + GetDefaultProfile(); + } + + private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - foreach (var profile in GetProfiles()) + if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) { - if (!string.IsNullOrEmpty(profile.FriendlyName)) - { - if (!Regex.IsMatch(friendlyName, profile.FriendlyName)) - continue; - } + if (!Regex.IsMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) + return false; + } - if (!string.IsNullOrEmpty(profile.ModelNumber)) - { - if (!Regex.IsMatch(modelNumber, profile.ModelNumber)) - continue; - } + if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) + { + if (!Regex.IsMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) + return false; + } - if (!string.IsNullOrEmpty(profile.ModelName)) - { - if (!Regex.IsMatch(modelName, profile.ModelName)) - continue; - } + if (!string.IsNullOrEmpty(profileInfo.ModelName)) + { + if (!Regex.IsMatch(deviceInfo.ModelName, profileInfo.ModelName)) + return false; + } - return profile; + if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) + { + if (!Regex.IsMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) + return false; + } + + if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) + { + if (!Regex.IsMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) + return false; + } + if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) + { + if (!Regex.IsMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) + return false; } - return GetDefaultProfile(); + + return true; } } }
\ No newline at end of file diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 4120c1a7f..2e092a939 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -364,7 +364,7 @@ namespace MediaBrowser.Dlna.PlayTo var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0)) + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false); diff --git a/MediaBrowser.Dlna/PlayTo/DeviceInfo.cs b/MediaBrowser.Dlna/PlayTo/DeviceInfo.cs index f952b725e..4b40daf70 100644 --- a/MediaBrowser.Dlna/PlayTo/DeviceInfo.cs +++ b/MediaBrowser.Dlna/PlayTo/DeviceInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using MediaBrowser.Controller.Dlna; namespace MediaBrowser.Dlna.PlayTo { @@ -62,5 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo return _services; } } + + public DeviceIdentification ToDeviceIdentification() + { + return new DeviceIdentification + { + Manufacturer = Manufacturer, + ModelName = ModelName, + ModelNumber = ModelNumber, + FriendlyName = Name, + ManufacturerUrl = ManufacturerUrl + }; + } } } diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 5836a1639..4180154a4 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -13,7 +14,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Timers; -using Timer = System.Timers.Timer; namespace MediaBrowser.Dlna.PlayTo { @@ -28,8 +28,12 @@ namespace MediaBrowser.Dlna.PlayTo private readonly INetworkManager _networkManager; private readonly ILogger _logger; private readonly IDlnaManager _dlnaManager; + private readonly IUserManager _userManager; + private readonly IServerApplicationHost _appHost; private bool _playbackStarted = false; + private int UpdateTimerIntervalMs = 1000; + public bool SupportsMediaRemoteControl { get { return true; } @@ -46,7 +50,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, IServerApplicationHost appHost) { _session = session; _itemRepository = itemRepository; @@ -54,6 +58,8 @@ namespace MediaBrowser.Dlna.PlayTo _libraryManager = libraryManager; _networkManager = networkManager; _dlnaManager = dlnaManager; + _userManager = userManager; + _appHost = appHost; _logger = logger; } @@ -64,14 +70,12 @@ namespace MediaBrowser.Dlna.PlayTo _device.CurrentIdChanged += Device_CurrentIdChanged; _device.Start(); - _updateTimer = new Timer(1000); - _updateTimer.Elapsed += updateTimer_Elapsed; - _updateTimer.Start(); + _updateTimer = new System.Threading.Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs); } #region Device EventHandlers & Update Timer - Timer _updateTimer; + System.Threading.Timer _updateTimer; async void Device_PlaybackChanged(object sender, TransportStateEventArgs e) { @@ -122,27 +126,23 @@ namespace MediaBrowser.Dlna.PlayTo /// <summary> /// Handles the Elapsed event of the updateTimer control. /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="ElapsedEventArgs"/> instance containing the event data.</param> - async void updateTimer_Elapsed(object sender, ElapsedEventArgs e) + /// <param name="state">The state.</param> + private async void updateTimer_Elapsed(object state) { if (_disposed) return; - ((Timer)sender).Stop(); - - - if (!IsSessionActive) + if (IsSessionActive) { - //Session is inactive, mark it for Disposal and don't start the elapsed timer. - await _sessionManager.ReportSessionEnded(this._session.Id); - return; + await ReportProgress().ConfigureAwait(false); } + else + { + _updateTimer.Change(Timeout.Infinite, Timeout.Infinite); - await ReportProgress().ConfigureAwait(false); - - if (!_disposed && IsSessionActive) - ((Timer)sender).Start(); + //Session is inactive, mark it for Disposal and don't start the elapsed timer. + await _sessionManager.ReportSessionEnded(_session.Id); + } } /// <summary> @@ -194,7 +194,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 +227,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) @@ -376,7 +385,7 @@ namespace MediaBrowser.Dlna.PlayTo "http", _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost", - "8096" + _appHost.HttpServerPort ); } @@ -386,7 +395,7 @@ namespace MediaBrowser.Dlna.PlayTo var deviceInfo = _device.Properties; - var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.Name, deviceInfo.ModelName, deviceInfo.ModelNumber)); + var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification())); playlistItem.StartPositionTicks = startPostionTicks; if (playlistItem.IsAudio) @@ -482,10 +491,10 @@ namespace MediaBrowser.Dlna.PlayTo { if (!_disposed) { - _updateTimer.Stop(); _disposed = true; + _updateTimer.Dispose(); _device.Dispose(); - _logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed"); + _logger.Log(LogSeverity.Debug, "Controller disposed"); } } } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index d1fdc9626..d18ed595f 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; @@ -32,8 +33,9 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; private readonly IServerConfigurationManager _config; + private readonly IServerApplicationHost _appHost; - public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager) + public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost) { _locations = new ConcurrentDictionary<string, DateTime>(); _tokenSource = new CancellationTokenSource(); @@ -46,6 +48,7 @@ namespace MediaBrowser.Dlna.PlayTo _networkManager = networkManager; _userManager = userManager; _dlnaManager = dlnaManager; + _appHost = appHost; _config = config; } @@ -227,7 +230,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, _appHost); } controller.Init(device); @@ -243,8 +246,7 @@ namespace MediaBrowser.Dlna.PlayTo /// <returns>The TranscodeSettings for the device</returns> private void GetProfileSettings(DeviceInfo deviceProperties) { - var profile = _dlnaManager.GetProfile(deviceProperties.DisplayName, deviceProperties.ModelName, - deviceProperties.ModelNumber); + var profile = _dlnaManager.GetProfile(deviceProperties.ToDeviceIdentification()); if (!string.IsNullOrWhiteSpace(profile.Name)) { diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs index a7afeab3c..91b03bc23 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; @@ -22,8 +23,9 @@ namespace MediaBrowser.Dlna.PlayTo private readonly INetworkManager _networkManager; private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; + private readonly IServerApplicationHost _appHost; - public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager) + public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost) { _config = config; _sessionManager = sessionManager; @@ -33,6 +35,7 @@ namespace MediaBrowser.Dlna.PlayTo _networkManager = networkManager; _userManager = userManager; _dlnaManager = dlnaManager; + _appHost = appHost; _logger = logManager.GetLogger("PlayTo"); } @@ -69,7 +72,7 @@ namespace MediaBrowser.Dlna.PlayTo { try { - _manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager); + _manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager, _appHost); _manager.Start(); } catch (Exception ex) diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index cfb2c7d1c..4f776807e 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Dlna.PlayTo public long StartPositionTicks { get; set; } - public static PlaylistItem Create(BaseItem item, DlnaProfile profile) + public static PlaylistItem Create(BaseItem item, DeviceProfile profile) { var playlistItem = new PlaylistItem { @@ -92,7 +92,7 @@ namespace MediaBrowser.Dlna.PlayTo return true; } - private static bool IsSupported(DlnaProfile profile, TranscodingProfile transcodingProfile, string path) + private static bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, string path) { // Placeholder for future conditions return true; diff --git a/MediaBrowser.Model/Configuration/DlnaOptions.cs b/MediaBrowser.Model/Configuration/DlnaOptions.cs index 67145e664..b2503ebc7 100644 --- a/MediaBrowser.Model/Configuration/DlnaOptions.cs +++ b/MediaBrowser.Model/Configuration/DlnaOptions.cs @@ -5,5 +5,10 @@ namespace MediaBrowser.Model.Configuration { public bool EnablePlayTo { get; set; } public bool EnablePlayToDebugLogging { get; set; } + + public DlnaOptions() + { + EnablePlayTo = true; + } } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 9e57f045a..857332b06 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -494,6 +494,8 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The part count.</value> public int? PartCount { 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.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 74b54fe2c..dfd13d8c3 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -56,11 +56,16 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Gets the last result. /// </summary> - /// <param name="itemId">The item identifier.</param> + /// <param name="item">The item.</param> /// <returns>ProviderResult.</returns> - protected MetadataStatus GetLastResult(Guid itemId) + protected MetadataStatus GetLastResult(IHasMetadata item) { - return ProviderRepo.GetMetadataStatus(itemId) ?? new MetadataStatus { ItemId = itemId }; + if (item.DateLastSaved == default(DateTime)) + { + return new MetadataStatus { ItemId = item.Id }; + } + + return ProviderRepo.GetMetadataStatus(item.Id) ?? new MetadataStatus { ItemId = item.Id }; } public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) @@ -74,7 +79,7 @@ namespace MediaBrowser.Providers.Manager var config = ProviderManager.GetMetadataOptions(item); var updateType = ItemUpdateType.None; - var refreshResult = GetLastResult(item.Id); + var refreshResult = GetLastResult(item); refreshResult.LastErrorMessage = string.Empty; refreshResult.LastStatus = ProviderRefreshStatus.Success; diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 79cbdfa68..395e66529 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -187,7 +187,7 @@ <Compile Include="TV\EpisodeXmlProvider.cs" /> <Compile Include="TV\EpisodeXmlParser.cs" /> <Compile Include="TV\FanArtTvUpdatesPostScanTask.cs" /> - <Compile Include="TV\FanartSeasonProvider.cs" /> + <Compile Include="TV\FanArtSeasonProvider.cs" /> <Compile Include="TV\FanartSeriesProvider.cs" /> <Compile Include="TV\MissingEpisodeProvider.cs" /> <Compile Include="TV\MovieDbSeriesImageProvider.cs" /> diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index 9c5d70e3d..470bd7b3a 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -20,14 +20,14 @@ using System.Xml; namespace MediaBrowser.Providers.TV { - public class FanartSeasonProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor + public class FanArtSeasonProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor { private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; - public FanartSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) + public FanArtSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem) { _config = config; _httpClient = httpClient; diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 9a196cc47..c18856293 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.Collections _iLibraryMonitor = iLibraryMonitor; } - public async Task CreateCollection(CollectionCreationOptions options) + public async Task<BoxSet> CreateCollection(CollectionCreationOptions options) { var name = options.Name; @@ -64,6 +64,13 @@ namespace MediaBrowser.Server.Implementations.Collections await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) .ConfigureAwait(false); + + if (options.ItemIdList.Count > 0) + { + await AddToCollection(collection.Id, options.ItemIdList); + } + + return collection; } finally { @@ -104,6 +111,7 @@ namespace MediaBrowser.Server.Implementations.Collections } var list = new List<LinkedChild>(); + var currentLinkedChildren = collection.GetLinkedChildren().ToList(); foreach (var itemId in ids) { @@ -114,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"); } @@ -126,6 +134,18 @@ namespace MediaBrowser.Server.Implementations.Collections ItemType = item.GetType().Name, Type = LinkedChildType.Manual }); + + var supportsGrouping = item as ISupportsBoxSetGrouping; + + if (supportsGrouping != null) + { + var boxsetIdList = supportsGrouping.BoxSetIdList.ToList(); + if (!boxsetIdList.Contains(collectionId)) + { + boxsetIdList.Add(collectionId); + } + supportsGrouping.BoxSetIdList = boxsetIdList; + } } collection.LinkedChildren.AddRange(list); @@ -156,6 +176,16 @@ namespace MediaBrowser.Server.Implementations.Collections } list.Add(child); + + var childItem = _libraryManager.GetItemById(itemId); + var supportsGrouping = childItem as ISupportsBoxSetGrouping; + + if (supportsGrouping != null) + { + var boxsetIdList = supportsGrouping.BoxSetIdList.ToList(); + boxsetIdList.Remove(collectionId); + supportsGrouping.BoxSetIdList = boxsetIdList; + } } var shortcutFiles = Directory diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index fadf4c900..19d834c20 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1082,6 +1082,12 @@ namespace MediaBrowser.Server.Implementations.Dto dto.IsHD = video.IsHD; dto.PartCount = video.AdditionalPartIds.Count + 1; + 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/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index b2f0a2769..65bbb35ba 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -267,7 +267,7 @@ namespace MediaBrowser.Server.Implementations.IO } else { - Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); + Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary.", path); newWatcher.Dispose(); } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 16c0d1a27..b11457ec5 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using MediaBrowser.Model.Logging; namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { @@ -20,11 +21,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { private readonly IServerApplicationPaths _applicationPaths; private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; - public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager) + public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager, ILogger logger) { _applicationPaths = appPaths; _libraryManager = libraryManager; + _logger = logger; } /// <summary> @@ -76,29 +79,29 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); + return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false, false); } if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false, false); } if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); + return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, false); } - + if (string.IsNullOrEmpty(collectionType) || string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); + return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true, true); } return null; @@ -187,7 +190,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="directoryService">The directory service.</param> /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param> /// <returns>Movie.</returns> - private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems) + private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems, bool supportsAlternateVersions) where T : Video, new() { var movies = new List<T>(); @@ -218,7 +221,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies }; } - if (EntityResolutionHelper.IsMultiPartFile(filename)) + if (EntityResolutionHelper.IsMultiPartFolder(filename)) { multiDiscFolders.Add(child); } @@ -248,9 +251,27 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } } - if (movies.Count > 1 && supportMultiFileItems) + if (movies.Count > 1) { - return GetMultiFileMovie(movies); + if (supportMultiFileItems) + { + var result = GetMultiFileMovie(movies); + + if (result != null) + { + return result; + } + } + if (supportsAlternateVersions) + { + var result = GetMovieWithAlternateVersions(movies); + + if (result != null) + { + return result; + } + } + return null; } if (movies.Count == 1) @@ -356,12 +377,47 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies var firstMovie = sortedMovies[0]; // They must all be part of the sequence if we're going to consider it a multi-part movie - // Only support up to 8 (matches Plex), to help avoid incorrect detection - if (sortedMovies.All(i => EntityResolutionHelper.IsMultiPartFile(i.Path)) && sortedMovies.Count <= 8) + if (sortedMovies.All(i => EntityResolutionHelper.IsMultiPartFile(i.Path))) { - firstMovie.IsMultiPart = true; + // Only support up to 8 (matches Plex), to help avoid incorrect detection + if (sortedMovies.Count <= 8) + { + firstMovie.IsMultiPart = true; + + _logger.Info("Multi-part video found: " + firstMovie.Path); - return firstMovie; + return firstMovie; + } + } + + return null; + } + + private T GetMovieWithAlternateVersions<T>(IEnumerable<T> movies) + where T : Video, new() + { + var sortedMovies = movies.OrderBy(i => i.Path.Length).ToList(); + + // Cap this at five to help avoid incorrect matching + if (sortedMovies.Count > 5) + { + return null; + } + + var firstMovie = sortedMovies[0]; + + var filenamePrefix = Path.GetFileNameWithoutExtension(firstMovie.Path); + + if (!string.IsNullOrWhiteSpace(filenamePrefix)) + { + if (sortedMovies.Skip(1).All(i => Path.GetFileNameWithoutExtension(i.Path).StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase))) + { + firstMovie.HasLocalAlternateVersions = true; + + _logger.Info("Multi-version video found: " + firstMovie.Path); + + return firstMovie; + } } return null; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs new file mode 100644 index 000000000..f02c907c6 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/BoxSetPostScanTask.cs @@ -0,0 +1,50 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + public class BoxSetPostScanTask : ILibraryPostScanTask + { + private readonly ILibraryManager _libraryManager; + + public BoxSetPostScanTask(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var items = _libraryManager.RootFolder.RecursiveChildren.ToList(); + + var boxsets = items.OfType<BoxSet>().ToList(); + + var numComplete = 0; + + foreach (var boxset in boxsets) + { + foreach (var child in boxset.GetLinkedChildren().OfType<ISupportsBoxSetGrouping>()) + { + var boxsetIdList = child.BoxSetIdList.ToList(); + if (!boxsetIdList.Contains(boxset.Id)) + { + boxsetIdList.Add(boxset.Id); + } + child.BoxSetIdList = boxsetIdList; + } + + numComplete++; + double percent = numComplete; + percent /= boxsets.Count; + progress.Report(percent * 100); + } + + progress.Report(100); + return Task.FromResult(true); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index c44b60845..871565133 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -165,6 +165,7 @@ <Compile Include="Library\UserManager.cs" /> <Compile Include="Library\Validators\ArtistsPostScanTask.cs" /> <Compile Include="Library\Validators\ArtistsValidator.cs" /> + <Compile Include="Library\Validators\BoxSetPostScanTask.cs" /> <Compile Include="Library\Validators\CountHelpers.cs" /> <Compile Include="Library\Validators\GameGenresPostScanTask.cs" /> <Compile Include="Library\Validators\GameGenresValidator.cs" /> diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 71d95b97b..3acc5e892 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -622,42 +622,27 @@ namespace MediaBrowser.Server.Implementations.Session return session; } - /// <summary> - /// Sends the system command. - /// </summary> - /// <param name="sessionId">The session id.</param> - /// <param name="command">The command.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken) + public Task SendSystemCommand(Guid controllingSessionId, Guid sessionId, SystemCommand command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + return session.SessionController.SendSystemCommand(command, cancellationToken); } - /// <summary> - /// Sends the message command. - /// </summary> - /// <param name="sessionId">The session id.</param> - /// <param name="command">The command.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken) + public Task SendMessageCommand(Guid controllingSessionId, Guid sessionId, MessageCommand command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + return session.SessionController.SendMessageCommand(command, cancellationToken); } - /// <summary> - /// Sends the play command. - /// </summary> - /// <param name="sessionId">The session id.</param> - /// <param name="command">The command.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken) + public Task SendPlayCommand(Guid controllingSessionId, Guid sessionId, PlayRequest command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); @@ -690,31 +675,27 @@ namespace MediaBrowser.Server.Implementations.Session } } + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + if (controllingSession.UserId.HasValue) + { + command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + } + return session.SessionController.SendPlayCommand(command, cancellationToken); } - /// <summary> - /// Sends the browse command. - /// </summary> - /// <param name="sessionId">The session id.</param> - /// <param name="command">The command.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken) + public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + return session.SessionController.SendBrowseCommand(command, cancellationToken); } - /// <summary> - /// Sends the playstate command. - /// </summary> - /// <param name="sessionId">The session id.</param> - /// <param name="command">The command.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken) + public Task SendPlaystateCommand(Guid controllingSessionId, Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken) { var session = GetSessionForRemoteControl(sessionId); @@ -723,9 +704,28 @@ namespace MediaBrowser.Server.Implementations.Session throw new ArgumentException(string.Format("Session {0} is unable to seek.", session.Id)); } + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + if (controllingSession.UserId.HasValue) + { + command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + } + return session.SessionController.SendPlaystateCommand(command, cancellationToken); } + private void AssertCanControl(SessionInfo session, SessionInfo controllingSession) + { + if (session == null) + { + throw new ArgumentNullException("session"); + } + if (controllingSession == null) + { + throw new ArgumentNullException("controllingSession"); + } + } + /// <summary> /// Sends the restart required message. /// </summary> diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 32932fe0b..43aeb0ab0 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -492,7 +492,7 @@ namespace MediaBrowser.ServerApplication var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance<IAppThemeManager>(appThemeManager); - var dlnaManager = new DlnaManager(); + var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager); RegisterSingleInstance<IDlnaManager>(dlnaManager); var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); @@ -861,7 +861,7 @@ namespace MediaBrowser.ServerApplication ItemsByNamePath = ApplicationPaths.ItemsByNamePath, CachePath = ApplicationPaths.CachePath, MacAddress = GetMacAddress(), - HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber, + HttpServerPortNumber = HttpServerPort, OperatingSystem = Environment.OSVersion.ToString(), CanSelfRestart = CanSelfRestart, CanSelfUpdate = CanSelfUpdate, @@ -874,6 +874,11 @@ namespace MediaBrowser.ServerApplication }; } + public int HttpServerPort + { + get { return ServerConfigurationManager.Configuration.HttpServerPortNumber; } + } + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private string GetWanAddress() { diff --git a/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs b/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs index 1cd481faa..768e4ee5c 100644 --- a/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs +++ b/MediaBrowser.Tests/Resolvers/MovieResolverTests.cs @@ -9,6 +9,10 @@ namespace MediaBrowser.Tests.Resolvers [TestMethod] public void TestMultiPartFiles() { + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart.mkv")); + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart - 480p.mkv")); + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart - 720p.mkv")); + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"blah blah.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd1.mkv")); @@ -33,25 +37,25 @@ namespace MediaBrowser.Tests.Resolvers [TestMethod] public void TestMultiPartFolders() { - Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"blah blah")); + Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"blah blah")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disk1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - pt1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - part1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - dvd1")); // Add a space - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd 1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc 1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk 1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt 1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part 1")); - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd 1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd 1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc 1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disk 1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - pt 1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - part 1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - dvd 1")); // Not case sensitive - Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - Disc1")); + Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - Disc1")); } } } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index e5127a07a..2fc3caa2a 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -461,9 +461,11 @@ namespace MediaBrowser.WebDashboard.Api "extensions.js", "site.js", "librarybrowser.js", + "librarylist.js", "editorsidebar.js", "librarymenu.js", //"chromecast.js", + "contextmenu.js", "ratingdialog.js", "aboutpage.js", @@ -584,7 +586,7 @@ namespace MediaBrowser.WebDashboard.Api await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false); - + var assembly = GetType().Assembly; await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false); @@ -607,6 +609,7 @@ namespace MediaBrowser.WebDashboard.Api { "site.css", "chromecast.css", + "contextmenu.css", "mediaplayer.css", "librarybrowser.css", "detailtable.css", @@ -630,7 +633,7 @@ namespace MediaBrowser.WebDashboard.Api { await AppendResource(memoryStream, "css/" + file, newLineBytes).ConfigureAwait(false); } - + memoryStream.Position = 0; return memoryStream; } diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 90a8cedd3..15f160942 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -96,6 +96,9 @@ <Content Include="dashboard-ui\css\chromecast.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\css\contextmenu.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\css\icons.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -488,6 +491,9 @@ <Content Include="dashboard-ui\scripts\chromecast.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\contextmenu.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\dashboardinfo.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -506,6 +512,9 @@ <Content Include="dashboard-ui\scripts\autoorganizelog.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\librarylist.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\librarymenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -552,7 +561,7 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\mediaplayer-video.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\movieslatest.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 9ffd2c3e2..38422d545 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.340</version> + <version>3.0.341</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.340" /> + <dependency id="MediaBrowser.Common" version="3.0.341" /> <dependency id="NLog" version="2.1.0" /> <dependency id="SimpleInjector" version="2.4.1" /> <dependency id="sharpcompress" version="0.10.2" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 49d410c57..fd83505a1 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.340</version> + <version>3.0.341</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index acbce81ae..bef95570e 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.340</version> + <version>3.0.341</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.340" /> + <dependency id="MediaBrowser.Common" version="3.0.341" /> </dependencies> </metadata> <files> @@ -1,9 +1,9 @@ Media Browser ============ -Media Browser Server is a home media server built on top of other popular open source technologies such as **Service Stack**, **jQuery**, **jQuery mobile** and **Lucene .NET**. +Media Browser Server is a home media server built on top of other popular open source technologies such as **Service Stack**, **jQuery**, **jQuery mobile**, and **Mono**. -It features a REST-based api with built-in documention to facilitate client development. We also have full .net and javascript wrappers around the api. +It features a REST-based api with built-in documention to facilitate client development. We also have client libraries for our api to enable rapid development. We have several client apps released and in production: |
