diff options
| author | Luke <luke.pulverenti@gmail.com> | 2016-08-19 14:57:01 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-08-19 14:57:01 -0400 |
| commit | 0780889c51819c9f1c3b01f5bcdae9806c219c10 (patch) | |
| tree | e91e8542e997c25992fd24e0b40fd7a765c45d62 /MediaBrowser.Controller | |
| parent | ac8c4ccc3ce53a4eafbd0493e9fe333d95b28730 (diff) | |
| parent | 003f61c8914706c6ea1d211644dabf5a5fbd759e (diff) | |
Merge pull request #2080 from MediaBrowser/beta
Beta
Diffstat (limited to 'MediaBrowser.Controller')
34 files changed, 870 insertions, 275 deletions
diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index d627cc67ae..e69b649488 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -19,12 +19,16 @@ namespace MediaBrowser.Controller.Dto public bool EnableImages { get; set; } public bool AddProgramRecordingInfo { get; set; } public string DeviceId { get; set; } + public bool EnableUserData { get; set; } + public bool AddCurrentProgram { get; set; } public DtoOptions() { Fields = new List<ItemFields>(); ImageTypeLimit = int.MaxValue; EnableImages = true; + EnableUserData = true; + AddCurrentProgram = true; Fields = Enum.GetNames(typeof (ItemFields)) .Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true)) diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 7b42310101..efc4502481 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Providers; @@ -67,6 +69,31 @@ namespace MediaBrowser.Controller.Entities return CreateResolveArgs(directoryService, true).FileSystemChildren; } + private List<Guid> _childrenIds = null; + private readonly object _childIdsLock = new object(); + protected override IEnumerable<BaseItem> LoadChildren() + { + lock (_childIdsLock) + { + if (_childrenIds == null || _childrenIds.Count == 0) + { + var list = base.LoadChildren().ToList(); + _childrenIds = list.Select(i => i.Id).ToList(); + return list; + } + + return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); + } + } + + private void ClearCache() + { + lock (_childIdsLock) + { + _childrenIds = null; + } + } + private bool _requiresRefresh; public override bool RequiresRefresh() { @@ -89,6 +116,8 @@ namespace MediaBrowser.Controller.Entities public override bool BeforeMetadataRefresh() { + ClearCache(); + var changed = base.BeforeMetadataRefresh() || _requiresRefresh; _requiresRefresh = false; return changed; @@ -96,9 +125,11 @@ namespace MediaBrowser.Controller.Entities private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) { + ClearCache(); + var path = ContainingFolderPath; - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths , directoryService) + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { FileInfo = FileSystem.GetDirectoryInfo(path), Path = path, @@ -135,7 +166,22 @@ namespace MediaBrowser.Controller.Entities return args; } - + + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) + { + return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); + } + + protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) + { + ClearCache(); + + await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) + .ConfigureAwait(false); + + ClearCache(); + } + /// <summary> /// Adds the virtual child. /// </summary> @@ -152,15 +198,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Get the children of this folder from the actual file system - /// </summary> - /// <returns>IEnumerable{BaseItem}.</returns> - protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) - { - return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); - } - - /// <summary> /// Finds the virtual child. /// </summary> /// <param name="id">The id.</param> diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 1897511af7..1af55a389f 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -5,9 +5,11 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; namespace MediaBrowser.Controller.Entities.Audio @@ -47,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Audio } [IgnoreDataMember] - public override bool EnableForceSaveOnDateModifiedChange + public override bool EnableRefreshOnDateModifiedChange { get { return true; } } @@ -266,6 +268,11 @@ namespace MediaBrowser.Controller.Entities.Audio Size = i.Size }; + if (info.Protocol == MediaProtocol.File) + { + info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"); + } + if (string.IsNullOrEmpty(info.Container)) { if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual) diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 6790a1bcf1..81d1deaa23 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -169,13 +169,9 @@ namespace MediaBrowser.Controller.Entities.Audio list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics()); return list; } - - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get - { - return "Artist-" + (Name ?? string.Empty).RemoveDiacritics(); - } + return "Artist-" + (Name ?? string.Empty).RemoveDiacritics(); } protected override bool GetBlockUnratedValue(UserPolicy config) { @@ -274,5 +270,54 @@ namespace MediaBrowser.Controller.Entities.Audio return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.ArtistsPath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + if (IsAccessedByName) + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + if (IsAccessedByName) + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 798bc79fbb..bd991d9f47 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -18,13 +18,9 @@ namespace MediaBrowser.Controller.Entities.Audio list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } - - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get - { - return GetUserDataKeys()[0]; - } + return GetUserDataKeys()[0]; } [IgnoreDataMember] @@ -96,5 +92,48 @@ namespace MediaBrowser.Controller.Entities.Audio return LibraryManager.GetItemList(query); } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.MusicGenrePath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 8d00f38beb..cbbb9a89a3 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -455,7 +455,7 @@ namespace MediaBrowser.Controller.Entities public DateTime DateLastRefreshed { get; set; } [IgnoreDataMember] - public virtual bool EnableForceSaveOnDateModifiedChange + public virtual bool EnableRefreshOnDateModifiedChange { get { return false; } } @@ -951,7 +951,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) ); - return LibraryManager.ResolvePaths(files, directoryService, null) + return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) .OfType<Audio.Audio>() .Select(audio => { @@ -981,7 +981,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => directoryService.GetFiles(i.FullName)); - return LibraryManager.ResolvePaths(files, directoryService, null) + return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) .OfType<Video>() .Select(item => { @@ -1194,10 +1194,17 @@ namespace MediaBrowser.Controller.Entities get { return null; } } + public virtual string CreatePresentationUniqueKey() + { + return Id.ToString("N"); + } + [IgnoreDataMember] - public virtual string PresentationUniqueKey + public string PresentationUniqueKey { get; set; } + + public string GetPresentationUniqueKey() { - get { return Id.ToString("N"); } + return PresentationUniqueKey ?? CreatePresentationUniqueKey(); } public virtual bool RequiresRefresh() @@ -2206,6 +2213,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public virtual bool StopRefreshIfLocalMetadataFound + { + get + { + return true; + } + } + public virtual IEnumerable<Guid> GetIdsForAncestorQuery() { return new[] { Id }; diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 59ab954378..56f9fa784a 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public override bool EnableForceSaveOnDateModifiedChange + public override bool EnableRefreshOnDateModifiedChange { get { return true; } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 8bf9919f25..e120f2e23d 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -3,11 +3,15 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; using MoreLinq; namespace MediaBrowser.Controller.Entities @@ -18,6 +22,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class CollectionFolder : Folder, ICollectionFolder { + public static IXmlSerializer XmlSerializer { get; set; } + public CollectionFolder() { PhysicalLocationsList = new List<string>(); @@ -39,6 +45,71 @@ namespace MediaBrowser.Controller.Entities public string CollectionType { get; set; } + private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>(); + public LibraryOptions GetLibraryOptions() + { + lock (LibraryOptions) + { + LibraryOptions options; + if (!LibraryOptions.TryGetValue(Path, out options)) + { + options = LoadLibraryOptions(); + LibraryOptions[Path] = options; + } + + return options; + } + } + + private LibraryOptions LoadLibraryOptions() + { + try + { + var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(Path)) as LibraryOptions; + + if (result == null) + { + return new LibraryOptions(); + } + + return result; + } + catch (FileNotFoundException) + { + return new LibraryOptions(); + } + catch (DirectoryNotFoundException) + { + return new LibraryOptions(); + } + catch (Exception ex) + { + Logger.ErrorException("Error loading library options", ex); + + return new LibraryOptions(); + } + } + + private static string GetLibraryOptionsPath(string path) + { + return System.IO.Path.Combine(path, "options.xml"); + } + + public void UpdateLibraryOptions(LibraryOptions options) + { + SaveLibraryOptions(Path, options); + } + + public static void SaveLibraryOptions(string path, LibraryOptions options) + { + lock (LibraryOptions) + { + LibraryOptions[path] = options; + + XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path)); + } + } + /// <summary> /// Allow different display preferences for each collection folder /// </summary> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index c1728ce389..b5c76c0ebc 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Channels; @@ -273,6 +274,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> protected virtual IEnumerable<BaseItem> LoadChildren() { + //Logger.Debug("Loading children from {0} {1} {2}", GetType().Name, Id, Path); //just load our children from the repo - the library will be validated and maintained in other processes return GetCachedChildren(); } @@ -643,8 +645,9 @@ namespace MediaBrowser.Controller.Entities protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { var collectionType = LibraryManager.GetContentType(this); + var libraryOptions = LibraryManager.GetLibraryOptions(this); - return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, collectionType); + return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, libraryOptions, collectionType); } /// <summary> @@ -699,7 +702,7 @@ namespace MediaBrowser.Controller.Entities items = GetRecursiveChildren(user, query); } - return PostFilterAndSort(items, query); + return PostFilterAndSort(items, query, true, true); } if (!(this is UserRootFolder) && !(this is AggregateFolder)) @@ -900,7 +903,15 @@ namespace MediaBrowser.Controller.Entities if (query.ItemIds.Length > 0) { var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); - return Task.FromResult(PostFilterAndSort(specificItems, query)); + + if (query.SortBy.Length == 0) + { + var ids = query.ItemIds.ToList(); + + // Try to preserve order + specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToList(); + } + return Task.FromResult(PostFilterAndSort(specificItems, query, true, true)); } return GetItemsInternal(query); @@ -956,12 +967,12 @@ namespace MediaBrowser.Controller.Entities : GetChildren(user, true).Where(filter); } - return PostFilterAndSort(items, query); + return PostFilterAndSort(items, query, true, true); } - protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query) + protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool collapseBoxSetItems, bool enableSorting) { - return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager); + return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager, collapseBoxSetItems, enableSorting); } public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) @@ -1425,7 +1436,7 @@ namespace MediaBrowser.Controller.Entities itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount; } - double recursiveItemCount = allItemsQueryResult.TotalRecordCount; + var recursiveItemCount = allItemsQueryResult.TotalRecordCount; double unplayedCount = unplayedQueryResult.TotalRecordCount; if (recursiveItemCount > 0) @@ -1435,6 +1446,14 @@ namespace MediaBrowser.Controller.Entities dto.Played = dto.PlayedPercentage.Value >= 100; dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount; } + + if (itemDto != null) + { + if (this is Season || this is MusicAlbum) + { + itemDto.ChildCount = recursiveItemCount; + } + } } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index 54386a1795..24910498f5 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public override bool EnableForceSaveOnDateModifiedChange + public override bool EnableRefreshOnDateModifiedChange { get { return true; } } diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 45e766c0f0..6448828fb3 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -16,12 +16,9 @@ namespace MediaBrowser.Controller.Entities return list; } - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get - { - return GetUserDataKeys()[0]; - } + return GetUserDataKeys()[0]; } /// <summary> @@ -87,5 +84,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GameGenrePath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index cc5aebb2a4..1736ba8c76 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -19,13 +19,9 @@ namespace MediaBrowser.Controller.Entities list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } - - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get - { - return GetUserDataKeys()[0]; - } + return GetUserDataKeys()[0]; } /// <summary> @@ -91,5 +87,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GenrePath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index 378c4a390b..26f425ff0b 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The date last refreshed.</value> DateTime DateLastRefreshed { get; set; } - + /// <summary> /// This is called before any metadata refresh and returns true or false indicating if changes were made /// </summary> @@ -52,6 +52,12 @@ namespace MediaBrowser.Controller.Entities bool RequiresRefresh(); - bool EnableForceSaveOnDateModifiedChange { get; } + bool EnableRefreshOnDateModifiedChange { get; } + + string PresentationUniqueKey { get; set; } + + string GetPresentationUniqueKey(); + string CreatePresentationUniqueKey(); + bool StopRefreshIfLocalMetadataFound { get; } } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 69cab5ec53..deea631127 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -37,6 +37,7 @@ namespace MediaBrowser.Controller.Entities public string[] Genres { get; set; } public string[] Keywords { get; set; } + public bool? IsSpecialSeason { get; set; } public bool? IsMissing { get; set; } public bool? IsUnaired { get; set; } public bool? IsVirtualUnaired { get; set; } @@ -50,6 +51,7 @@ namespace MediaBrowser.Controller.Entities public string PresentationUniqueKey { get; set; } public string Path { get; set; } + public string PathNotStartsWith { get; set; } public string Name { get; set; } public string SlugName { get; set; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 4effc162e4..6d5278f1fe 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -62,6 +62,26 @@ namespace MediaBrowser.Controller.Entities.Movies return UnratedItem.Movie; } + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) + { + if (IsLegacyBoxSet) + { + return base.GetNonCachedChildren(directoryService); + } + return new List<BaseItem>(); + } + + protected override IEnumerable<BaseItem> LoadChildren() + { + if (IsLegacyBoxSet) + { + return base.LoadChildren(); + } + + // Save a trip to the database + return new List<BaseItem>(); + } + [IgnoreDataMember] public override bool IsPreSorted { @@ -76,7 +96,21 @@ namespace MediaBrowser.Controller.Entities.Movies { get { - return true; + if (IsLegacyBoxSet) + { + return true; + } + + return false; + } + } + + [IgnoreDataMember] + private bool IsLegacyBoxSet + { + get + { + return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path); } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index c7a833c58f..f0270497c0 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -179,5 +179,15 @@ namespace MediaBrowser.Controller.Entities.Movies return list; } + + [IgnoreDataMember] + public override bool StopRefreshIfLocalMetadataFound + { + get + { + // Need people id's from internet metadata + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 8ef0d70bf9..f21bc0a71e 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -26,13 +26,9 @@ namespace MediaBrowser.Controller.Entities list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } - - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get - { - return GetUserDataKeys()[0]; - } + return GetUserDataKeys()[0]; } public PersonLookupInfo GetLookupInfo() @@ -126,6 +122,64 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validFilename = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + string subFolderPrefix = null; + + foreach (char c in validFilename) + { + if (char.IsLetterOrDigit(c)) + { + subFolderPrefix = c.ToString(); + break; + } + } + + var path = ConfigurationManager.ApplicationPaths.PeoplePath; + + return string.IsNullOrEmpty(subFolderPrefix) ? + System.IO.Path.Combine(path, validFilename) : + System.IO.Path.Combine(path, subFolderPrefix, validFilename); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 804ea04a59..965616eb53 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public override bool EnableForceSaveOnDateModifiedChange + public override bool EnableRefreshOnDateModifiedChange { get { return true; } } diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 762798b553..04b09b7442 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -18,13 +18,9 @@ namespace MediaBrowser.Controller.Entities list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } - - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get - { - return GetUserDataKeys()[0]; - } + return GetUserDataKeys()[0]; } /// <summary> @@ -89,5 +85,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.StudioPath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index f6ca19005b..842b2fd602 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -85,7 +85,11 @@ namespace MediaBrowser.Controller.Entities.TV public override int GetChildCount(User user) { - return GetChildren(user, true).Count(); + Logger.Debug("Season {0} getting child cound", (Path ?? Name)); + var result = GetChildren(user, true).Count(); + Logger.Debug("Season {0} child cound: ", result); + + return result; } /// <summary> @@ -114,22 +118,18 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get + if (IndexNumber.HasValue) { - if (IndexNumber.HasValue) + var series = Series; + if (series != null) { - var series = Series; - if (series != null) - { - return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000"); - } + return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000"); } - - return base.PresentationUniqueKey; } + + return base.CreatePresentationUniqueKey(); } /// <summary> @@ -141,24 +141,6 @@ namespace MediaBrowser.Controller.Entities.TV return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name; } - [IgnoreDataMember] - public bool IsMissingSeason - { - get { return (IsVirtualItem) && !IsUnaired; } - } - - [IgnoreDataMember] - public bool IsVirtualUnaired - { - get { return (IsVirtualItem) && IsUnaired; } - } - - [IgnoreDataMember] - public bool IsSpecialSeason - { - get { return (IndexNumber ?? -1) == 0; } - } - protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) { if (query.User == null) @@ -170,10 +152,15 @@ namespace MediaBrowser.Controller.Entities.TV Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); + var id = Guid.NewGuid().ToString("N"); + + Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id); var items = GetEpisodes(user).Where(filter); - var result = PostFilterAndSort(items, query); + Logger.Debug("Season.GetItemsInternal entering PostFilterAndSort. Request id: " + id); + var result = PostFilterAndSort(items, query, false, false); + Logger.Debug("Season.GetItemsInternal complete. Request id: " + id); return Task.FromResult(result); } @@ -184,19 +171,17 @@ namespace MediaBrowser.Controller.Entities.TV /// <returns>IEnumerable{Episode}.</returns> public IEnumerable<Episode> GetEpisodes(User user) { - var config = user.Configuration; - - return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); + return GetEpisodes(Series, user); } - public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) + public IEnumerable<Episode> GetEpisodes(Series series, User user) { - return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null); + return GetEpisodes(series, user, null); } - public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes) + public IEnumerable<Episode> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes) { - return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes); + return series.GetSeasonEpisodes(user, this, allSeriesEpisodes); } public IEnumerable<Episode> GetEpisodes() diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index ad35b3d369..4915cfedc7 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -96,19 +96,29 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get + var userdatakeys = GetUserDataKeys(); + + if (userdatakeys.Count > 1) { - var userdatakeys = GetUserDataKeys(); + return AddLibrariesToPresentationUniqueKey(userdatakeys[0]); + } + return base.CreatePresentationUniqueKey(); + } - if (userdatakeys.Count > 1) - { - return userdatakeys[0]; - } - return base.PresentationUniqueKey; + private string AddLibrariesToPresentationUniqueKey(string key) + { + var folders = LibraryManager.GetCollectionFolders(this) + .Select(i => i.Id.ToString("N")) + .ToArray(); + + if (folders.Length == 0) + { + return key; } + + return key + "-" + string.Join("-", folders); } private static string GetUniqueSeriesKey(BaseItem series) @@ -117,7 +127,7 @@ namespace MediaBrowser.Controller.Entities.TV { return series.Id.ToString("N"); } - return series.PresentationUniqueKey; + return series.GetPresentationUniqueKey(); } public override int GetChildCount(User user) @@ -197,7 +207,30 @@ namespace MediaBrowser.Controller.Entities.TV { var config = user.Configuration; - return GetSeasons(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); + var seriesKey = GetUniqueSeriesKey(this); + + Logger.Debug("GetSeasons SeriesKey: {0}", seriesKey); + var query = new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = seriesKey, + IncludeItemTypes = new[] {typeof (Season).Name}, + SortBy = new[] {ItemSortBy.SortName} + }; + + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) + { + query.IsVirtualItem = false; + } + else if (!config.DisplayMissingEpisodes) + { + query.IsMissing = false; + } + else if (!config.DisplayUnairedEpisodes) + { + query.IsVirtualUnaired = false; + } + + return LibraryManager.GetItemList(query).Cast<Season>(); } protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) @@ -227,55 +260,43 @@ namespace MediaBrowser.Controller.Entities.TV Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); var items = GetSeasons(user).Where(filter); - var result = PostFilterAndSort(items, query); + var result = PostFilterAndSort(items, query, false, true); return Task.FromResult(result); } - public IEnumerable<Season> GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired) + public IEnumerable<Episode> GetEpisodes(User user) { - IEnumerable<Season> seasons; + var seriesKey = GetUniqueSeriesKey(this); + Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey); - seasons = LibraryManager.GetItemList(new InternalItemsQuery(user) + var query = new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), - IncludeItemTypes = new[] { typeof(Season).Name }, - SortBy = new[] { ItemSortBy.SortName } - - }).Cast<Season>(); - - if (!includeMissingSeasons) + AncestorWithPresentationUniqueKey = seriesKey, + IncludeItemTypes = new[] {typeof (Episode).Name, typeof (Season).Name}, + SortBy = new[] {ItemSortBy.SortName} + }; + var config = user.Configuration; + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) { - seasons = seasons.Where(i => !(i.IsMissingSeason)); + query.IsVirtualItem = false; } - if (!includeVirtualUnaired) + else if (!config.DisplayMissingEpisodes) { - seasons = seasons.Where(i => !i.IsVirtualUnaired); + query.IsMissing = false; } - - return seasons; - } - - public IEnumerable<Episode> GetEpisodes(User user) - { - var config = user.Configuration; - - return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); - } - - public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired) - { - var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user) + else if (!config.DisplayUnairedEpisodes) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), - IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }, - SortBy = new[] { ItemSortBy.SortName } + query.IsVirtualUnaired = false; + } - }).ToList(); + var allItems = LibraryManager.GetItemList(query).ToList(); + + Logger.Debug("GetEpisodes return {0} items from database", allItems.Count); var allSeriesEpisodes = allItems.OfType<Episode>().ToList(); var allEpisodes = allItems.OfType<Season>() - .SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes)) + .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes)) .Reverse() .ToList(); @@ -352,78 +373,68 @@ namespace MediaBrowser.Controller.Entities.TV progress.Report(100); } - public IEnumerable<Episode> GetEpisodes(User user, Season season) - { - var config = user.Configuration; - - return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes); - } - private IEnumerable<Episode> GetAllEpisodes(User user) { - return LibraryManager.GetItemList(new InternalItemsQuery(user) + Logger.Debug("Series.GetAllEpisodes entering GetItemList"); + + var result = LibraryManager.GetItemList(new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName } - }).Cast<Episode>(); - } + }).Cast<Episode>().ToList(); - public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) - { - IEnumerable<Episode> episodes = GetAllEpisodes(user); + Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count); - return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes); + return result; } - public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes) + public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason) { - if (allSeriesEpisodes == null) + var seriesKey = GetUniqueSeriesKey(this); + Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey); + + var query = new InternalItemsQuery(user) { - return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes); + AncestorWithPresentationUniqueKey = seriesKey, + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.SortName } + }; + var config = user.Configuration; + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) + { + query.IsVirtualItem = false; } - - var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons); - - if (!includeMissingEpisodes) + else if (!config.DisplayMissingEpisodes) { - episodes = episodes.Where(i => !i.IsMissingEpisode); + query.IsMissing = false; } - if (!includeVirtualUnairedEpisodes) + else if (!config.DisplayUnairedEpisodes) { - episodes = episodes.Where(i => !i.IsVirtualUnaired); + query.IsVirtualUnaired = false; } - var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; + var allItems = LibraryManager.GetItemList(query).OfType<Episode>(); - return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) - .Cast<Episode>(); + return GetSeasonEpisodes(user, parentSeason, allItems); } - /// <summary> - /// Filters the episodes by season. - /// </summary> - public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) + public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes) { - if (!includeSpecials || seasonNumber < 1) + if (allSeriesEpisodes == null) { - return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); + Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null"); + return GetSeasonEpisodes(user, parentSeason); } - return episodes.Where(i => - { - var episode = i; - - if (episode != null) - { - var currentSeasonNumber = episode.AiredSeasonNumber; + Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason"); + var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons); - return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber; - } + var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; - return false; - }); + return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) + .Cast<Episode>(); } /// <summary> @@ -454,6 +465,32 @@ namespace MediaBrowser.Controller.Entities.TV }); } + /// <summary> + /// Filters the episodes by season. + /// </summary> + public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) + { + if (!includeSpecials || seasonNumber < 1) + { + return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); + } + + return episodes.Where(i => + { + var episode = i; + + if (episode != null) + { + var currentSeasonNumber = episode.AiredSeasonNumber; + + return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber; + } + + return false; + }); + } + + protected override bool GetBlockUnratedValue(UserPolicy config) { return config.BlockUnratedItems.Contains(UnratedItem.Series); @@ -509,5 +546,15 @@ namespace MediaBrowser.Controller.Entities.TV return list; } + + [IgnoreDataMember] + public override bool StopRefreshIfLocalMetadataFound + { + get + { + // Need people id's from internet metadata + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 6604be977d..306ce35ece 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -124,5 +124,15 @@ namespace MediaBrowser.Controller.Entities return list; } + + [IgnoreDataMember] + public override bool StopRefreshIfLocalMetadataFound + { + get + { + // Need people id's from internet metadata + return false; + } + } } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 4549d0d1df..aff1f5e286 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -16,6 +16,31 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class UserRootFolder : Folder { + private List<Guid> _childrenIds = null; + private readonly object _childIdsLock = new object(); + protected override IEnumerable<BaseItem> LoadChildren() + { + lock (_childIdsLock) + { + if (_childrenIds == null) + { + var list = base.LoadChildren().ToList(); + _childrenIds = list.Select(i => i.Id).ToList(); + return list; + } + + return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); + } + } + + private void ClearCache() + { + lock (_childIdsLock) + { + _childrenIds = null; + } + } + protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query) { if (query.Recursive) @@ -33,7 +58,7 @@ namespace MediaBrowser.Controller.Entities var user = query.User; Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager); - return PostFilterAndSort(result.Where(filter), query); + return PostFilterAndSort(result.Where(filter), query, true, true); } public override int GetChildCount(User user) @@ -69,6 +94,8 @@ namespace MediaBrowser.Controller.Entities public override bool BeforeMetadataRefresh() { + ClearCache(); + var hasChanges = base.BeforeMetadataRefresh(); if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase)) @@ -80,11 +107,22 @@ namespace MediaBrowser.Controller.Entities return hasChanges; } + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) + { + ClearCache(); + + return base.GetNonCachedChildren(directoryService); + } + protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { + ClearCache(); + await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) .ConfigureAwait(false); + ClearCache(); + // Not the best way to handle this, but it solves an issue // CollectionFolders aren't always getting saved after changes // This means that grabbing the item by Id may end up returning the old one diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 11ed269317..9f3acc3fc3 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -424,7 +424,7 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new string[] { }; - return PostFilterAndSort(items, parent, null, query); + return PostFilterAndSort(items, parent, null, query, false, true); } private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query) @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.Entities { items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager)); - return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config); + return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config, true, true); } public static bool FilterItem(BaseItem item, InternalItemsQuery query) @@ -791,9 +791,11 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, BaseItem queryParent, int? totalRecordLimit, - InternalItemsQuery query) + InternalItemsQuery query, + bool collapseBoxSetItems, + bool enableSorting) { - return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config); + return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config, collapseBoxSetItems, enableSorting); } public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, @@ -801,7 +803,9 @@ namespace MediaBrowser.Controller.Entities int? totalRecordLimit, InternalItemsQuery query, ILibraryManager libraryManager, - IServerConfigurationManager configurationManager) + IServerConfigurationManager configurationManager, + bool collapseBoxSetItems, + bool enableSorting) { var user = query.User; @@ -810,7 +814,10 @@ namespace MediaBrowser.Controller.Entities query.IsVirtualUnaired, query.IsUnaired); - items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager); + if (collapseBoxSetItems) + { + items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager); + } // This must be the last filter if (!string.IsNullOrEmpty(query.AdjacentTo)) @@ -818,7 +825,7 @@ namespace MediaBrowser.Controller.Entities items = FilterForAdjacency(items, query.AdjacentTo); } - return Sort(items, totalRecordLimit, query, libraryManager); + return SortAndPage(items, totalRecordLimit, query, libraryManager, enableSorting); } public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items, @@ -1093,8 +1100,6 @@ namespace MediaBrowser.Controller.Entities bool? isVirtualUnaired, bool? isUnaired) { - items = FilterVirtualSeasons(items, isMissing, isVirtualUnaired, isUnaired); - if (isMissing.HasValue) { var val = isMissing.Value; @@ -1140,65 +1145,14 @@ namespace MediaBrowser.Controller.Entities return items; } - private static IEnumerable<BaseItem> FilterVirtualSeasons( - IEnumerable<BaseItem> items, - bool? isMissing, - bool? isVirtualUnaired, - bool? isUnaired) - { - if (isMissing.HasValue) - { - var val = isMissing.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return (e.IsMissingSeason) == val; - } - return true; - }); - } - - if (isUnaired.HasValue) - { - var val = isUnaired.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsUnaired == val; - } - return true; - }); - } - - if (isVirtualUnaired.HasValue) - { - var val = isVirtualUnaired.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsVirtualUnaired == val; - } - return true; - }); - } - - return items; - } - - public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items, + public static QueryResult<BaseItem> SortAndPage(IEnumerable<BaseItem> items, int? totalRecordLimit, InternalItemsQuery query, - ILibraryManager libraryManager) + ILibraryManager libraryManager, bool enableSorting) { var user = query.User; - items = items.DistinctBy(i => i.PresentationUniqueKey, StringComparer.OrdinalIgnoreCase); + items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase); if (query.SortBy.Length > 0) { diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index eba1e466a2..8809f155c3 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -12,6 +12,7 @@ using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; namespace MediaBrowser.Controller.Entities @@ -44,24 +45,23 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] - public override string PresentationUniqueKey + public override string CreatePresentationUniqueKey() { - get + if (!string.IsNullOrWhiteSpace(PrimaryVersionId)) { - if (!string.IsNullOrWhiteSpace(PrimaryVersionId)) - { - return PrimaryVersionId; - } - - return base.PresentationUniqueKey; + return PrimaryVersionId; } + + return base.CreatePresentationUniqueKey(); } [IgnoreDataMember] - public override bool EnableForceSaveOnDateModifiedChange + public override bool EnableRefreshOnDateModifiedChange { - get { return true; } + get + { + return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso; + } } public int? TotalBitrate { get; set; } @@ -612,6 +612,11 @@ namespace MediaBrowser.Controller.Entities SupportsDirectStream = i.VideoType == VideoType.VideoFile }; + if (info.Protocol == MediaProtocol.File) + { + info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"); + } + if (i.IsShortcut) { info.Path = i.ShortcutPath; diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index db896f1fc7..4197ea93e5 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -112,5 +112,48 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public static string GetPath(string name, bool normalizeName = true) + { + // Trim the period at the end because windows will have a hard time with that + var validName = normalizeName ? + FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : + name; + + return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.YearPath, validName); + } + + private string GetRebasedPath() + { + return GetPath(System.IO.Path.GetFileName(Path), false); + } + + public override bool RequiresRefresh() + { + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); + return true; + } + return base.RequiresRefresh(); + } + + /// <summary> + /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// </summary> + public override bool BeforeMetadataRefresh() + { + var hasChanges = base.BeforeMetadataRefresh(); + + var newPath = GetRebasedPath(); + if (!string.Equals(Path, newPath, StringComparison.Ordinal)) + { + Path = newPath; + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index c07934d0b1..c89a60a6f8 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -106,5 +106,7 @@ namespace MediaBrowser.Controller /// </summary> /// <value>The internal metadata path.</value> string InternalMetadataPath { get; } + + string ArtistsPath { get; } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index ff7f2fe678..0862e3eaf9 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -11,6 +11,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Library @@ -32,15 +34,11 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Resolves a set of files into a list of BaseItem /// </summary> - /// <param name="files">The files.</param> - /// <param name="directoryService">The directory service.</param> - /// <param name="parent">The parent.</param> - /// <param name="collectionType">Type of the collection.</param> - /// <returns>List{``0}.</returns> IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, - Folder parent, string - collectionType = null); + Folder parent, + LibraryOptions libraryOptions, + string collectionType = null); /// <summary> /// Gets the root folder. @@ -397,6 +395,9 @@ namespace MediaBrowser.Controller.Library /// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns> bool IsAudioFile(string path); + bool IsAudioFile(string path, LibraryOptions libraryOptions); + bool IsVideoFile(string path, LibraryOptions libraryOptions); + /// <summary> /// Gets the season number from path. /// </summary> @@ -453,6 +454,8 @@ namespace MediaBrowser.Controller.Library /// <returns>IEnumerable<Folder>.</returns> IEnumerable<Folder> GetCollectionFolders(BaseItem item); + LibraryOptions GetLibraryOptions(BaseItem item); + /// <summary> /// Gets the people. /// </summary> @@ -551,7 +554,7 @@ namespace MediaBrowser.Controller.Library /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> bool IgnoreFile(FileSystemMetadata file, BaseItem parent); - void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary); + void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary); void RemoveVirtualFolder(string name, bool refreshLibrary); void AddMediaPath(string virtualFolderName, string path); void RemoveMediaPath(string virtualFolderName, string path); diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index ea3199b318..ec0ac325bc 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using CommonIO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Library { @@ -51,6 +53,13 @@ namespace MediaBrowser.Controller.Library } } + public LibraryOptions LibraryOptions { get; set; } + + public LibraryOptions GetLibraryOptions() + { + return LibraryOptions ?? (LibraryOptions = (Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent))); + } + /// <summary> /// Gets or sets the file system dictionary. /// </summary> diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 0462117cb5..e7eaa1dc0b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -236,6 +236,7 @@ <Compile Include="Net\IAuthorizationContext.cs" /> <Compile Include="Net\IAuthService.cs" /> <Compile Include="Net\IHasAuthorization.cs" /> + <Compile Include="Net\IAsyncStreamSource.cs" /> <Compile Include="Net\IHasResultFactory.cs" /> <Compile Include="Net\IHasSession.cs" /> <Compile Include="Net\IHttpResultFactory.cs" /> diff --git a/MediaBrowser.Controller/Net/IAsyncStreamSource.cs b/MediaBrowser.Controller/Net/IAsyncStreamSource.cs new file mode 100644 index 0000000000..0f41f60a55 --- /dev/null +++ b/MediaBrowser.Controller/Net/IAsyncStreamSource.cs @@ -0,0 +1,18 @@ +using ServiceStack.Web; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Net +{ + /// <summary> + /// Interface IAsyncStreamSource + /// Enables asynchronous writing to http resonse streams + /// </summary> + public interface IAsyncStreamSource + { + /// <summary> + /// Asynchronously write to the response stream. + /// </summary> + Task WriteToAsync(Stream responseStream); + } +} diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index 49d4614d81..8fdb1ce37e 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Net /// <returns>System.Object.</returns> object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null); - object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null); + object GetAsyncStreamWriter(IAsyncStreamSource streamSource); /// <summary> /// Gets the optimized result. diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 437f0e157a..87937869d5 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -170,6 +170,12 @@ namespace MediaBrowser.Controller.Persistence QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); + + List<string> GetGameGenreNames(); + List<string> GetMusicGenreNames(); + List<string> GetStudioNames(); + List<string> GetGenreNames(); + List<string> GetAllArtistNames(); } } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 5ffe3d5daf..654b97d7d4 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; +using MediaBrowser.Controller.Providers; namespace MediaBrowser.Controller.Playlists { @@ -58,11 +59,22 @@ namespace MediaBrowser.Controller.Playlists return true; } + protected override IEnumerable<BaseItem> LoadChildren() + { + // Save a trip to the database + return new List<BaseItem>(); + } + public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) { return GetPlayableItems(user).Result; } + protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) + { + return new List<BaseItem>(); + } + public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query) { var items = GetPlayableItems(user).Result; |
