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 /MediaBrowser.Controller | |
| parent | 7a0963129126679aad8b64cc8a36474edaca7170 (diff) | |
| parent | 78acab691693d6adfac67bcf3f0617e336f801a6 (diff) | |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'MediaBrowser.Controller')
15 files changed, 528 insertions, 137 deletions
diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index e147e09056..74ae420950 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 d7bc178ad3..af65bbacaf 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 0000000000..a3a6155160 --- /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 33f95b7944..119cfffd74 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 f1922dd323..8c35b52a81 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 017dbc8746..6de17e5511 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 e0c792307e..be64d20c33 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 ee371680ef..45daaba0b2 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 0000000000..0fd463155f --- /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 9858dd5a99..f53b676105 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 10034d7e5f..18db21f382 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 5554ced376..4b2ca497bb 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 21a501b08e..2dc444ea93 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 0f93e8e8a1..9c757503c0 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 ee29671c00..6ca15585ad 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. |
