aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
authorTim Hobbs <jesus.tesh@gmail.com>2014-03-17 15:47:22 -0700
committerTim Hobbs <jesus.tesh@gmail.com>2014-03-17 15:47:22 -0700
commitcf43180a2dcab023ba6a48f37920615d7e87c599 (patch)
tree1b94ff05caf34974161595823898b8b32e1f6d24 /MediaBrowser.Controller
parent7a0963129126679aad8b64cc8a36474edaca7170 (diff)
parent78acab691693d6adfac67bcf3f0617e336f801a6 (diff)
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Collections/CollectionCreationOptions.cs3
-rw-r--r--MediaBrowser.Controller/Collections/ICollectionManager.cs5
-rw-r--r--MediaBrowser.Controller/Dlna/DeviceIdentification.cs56
-rw-r--r--MediaBrowser.Controller/Dlna/DeviceProfile.cs (renamed from MediaBrowser.Controller/Dlna/DlnaProfile.cs)28
-rw-r--r--MediaBrowser.Controller/Dlna/DirectPlayProfile.cs86
-rw-r--r--MediaBrowser.Controller/Dlna/IDlnaManager.cs12
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs77
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs112
-rw-r--r--MediaBrowser.Controller/Entities/ISupportsBoxSetGrouping.cs19
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs13
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs213
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs6
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs16
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs15
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.