From 4ddde2cdc2ad3a951120853825df9f3588bc36e4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 1 May 2016 18:11:24 -0400 Subject: introduce presentation unique key --- .../Persistence/SqliteItemRepository.cs | 168 ++++++++++++++++----- 1 file changed, 133 insertions(+), 35 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs') diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index a44d66c73..6afdb8d9f 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.LiveTv; @@ -55,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.Persistence /// /// The _app paths /// - private readonly IApplicationPaths _appPaths; + private readonly IServerConfigurationManager _config; /// /// The _save item command @@ -81,35 +82,31 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedTagsCommand; - private const int LatestSchemaVersion = 65; + public const int LatestSchemaVersion = 66; /// /// Initializes a new instance of the class. /// - /// The app paths. - /// The json serializer. - /// The log manager. - /// /// appPaths /// or /// jsonSerializer /// - public SqliteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) + public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager) : base(logManager) { - if (appPaths == null) + if (config == null) { - throw new ArgumentNullException("appPaths"); + throw new ArgumentNullException("config"); } if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } - _appPaths = appPaths; + _config = config; _jsonSerializer = jsonSerializer; - _criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews"); + _criticReviewsPath = Path.Combine(_config.ApplicationPaths.DataPath, "critic-reviews"); } private const string ChaptersTableName = "Chapters2"; @@ -118,11 +115,11 @@ namespace MediaBrowser.Server.Implementations.Persistence /// Opens the connection to the database /// /// Task. - public async Task Initialize() + public async Task Initialize(IDbConnector dbConnector) { - var dbFile = Path.Combine(_appPaths.DataPath, "library.db"); + var dbFile = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); - _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false); + _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false); var createMediaStreamsTableCommand = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, PRIMARY KEY (ItemId, StreamIndex))"; @@ -228,18 +225,27 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "TypedBaseItems", "DateModifiedDuringLastRefresh", "DATETIME"); _connection.AddColumn(Logger, "TypedBaseItems", "InheritedTags", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "CleanName", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "PresentationUniqueKey", "Text"); + + string[] postQueries = + { + "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", + "create index if not exists idx_Type on TypedBaseItems(Type)" + }; + + _connection.RunQueries(postQueries, Logger); PrepareStatements(); new MediaStreamColumns(_connection, Logger).AddColumns(); - var chapterDbFile = Path.Combine(_appPaths.DataPath, "chapters.db"); + var chapterDbFile = Path.Combine(_config.ApplicationPaths.DataPath, "chapters.db"); if (File.Exists(chapterDbFile)) { MigrateChapters(chapterDbFile); } - var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db"); + var mediaStreamsDbFile = Path.Combine(_config.ApplicationPaths.DataPath, "mediainfo.db"); if (File.Exists(mediaStreamsDbFile)) { MigrateMediaStreams(mediaStreamsDbFile); @@ -252,7 +258,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var backupFile = file + ".bak"; File.Copy(file, backupFile, true); - SqliteExtensions.Attach(_connection, backupFile, "MediaInfoOld"); + DataExtensions.Attach(_connection, backupFile, "MediaInfoOld"); var columns = string.Join(",", _mediaStreamSaveColumns); @@ -278,7 +284,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { var backupFile = file + ".bak"; File.Copy(file, backupFile, true); - SqliteExtensions.Attach(_connection, backupFile, "ChaptersOld"); + DataExtensions.Attach(_connection, backupFile, "ChaptersOld"); string[] queries = { "REPLACE INTO "+ChaptersTableName+"(ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath) SELECT ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath FROM ChaptersOld.Chapters;" @@ -469,7 +475,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "CriticRatingSummary", "DateModifiedDuringLastRefresh", "InheritedTags", - "CleanName" + "CleanName", + "PresentationUniqueKey" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -803,6 +810,7 @@ namespace MediaBrowser.Server.Implementations.Persistence { _saveItemCommand.GetParameter(index++).Value = item.Name.RemoveDiacritics(); } + _saveItemCommand.GetParameter(index++).Value = item.PresentationUniqueKey; _saveItemCommand.Transaction = transaction; @@ -1463,6 +1471,8 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + var now = DateTime.UtcNow; + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems"; @@ -1475,6 +1485,11 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += whereText; + if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += " Group by PresentationUniqueKey"; + } + cmd.CommandText += GetOrderByText(query); if (query.Limit.HasValue) @@ -1482,10 +1497,12 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture); } - //Logger.Debug(cmd.CommandText); - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) { + Logger.Debug("GetItemList query time: {0}ms. Query: {1}", + Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds), + cmd.CommandText); + while (reader.Read()) { var item = GetItem(reader); @@ -1507,6 +1524,8 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + var now = DateTime.UtcNow; + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems"; @@ -1525,6 +1544,11 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += whereText; + if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += " Group by PresentationUniqueKey"; + } + cmd.CommandText += GetOrderByText(query); if (query.Limit.HasValue) @@ -1532,15 +1556,24 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture); } - cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging; - - //Logger.Debug(cmd.CommandText); + if (_config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += "; select count (distinct PresentationUniqueKey) from TypedBaseItems" + whereTextWithoutPaging; + } + else + { + cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging; + } var list = new List(); var count = 0; using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { + Logger.Debug("GetItems query time: {0}ms. Query: {1}", + Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds), + cmd.CommandText); + while (reader.Read()) { var item = GetItem(reader); @@ -1608,6 +1641,8 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + var now = DateTime.UtcNow; + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select guid from TypedBaseItems"; @@ -1620,6 +1655,11 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += whereText; + if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += " Group by PresentationUniqueKey"; + } + cmd.CommandText += GetOrderByText(query); if (query.Limit.HasValue) @@ -1629,10 +1669,12 @@ namespace MediaBrowser.Server.Implementations.Persistence var list = new List(); - //Logger.Debug(cmd.CommandText); - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) { + Logger.Debug("GetItemIdsList query time: {0}ms. Query: {1}", + Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds), + cmd.CommandText); + while (reader.Read()) { list.Add(reader.GetGuid(0)); @@ -1662,7 +1704,7 @@ namespace MediaBrowser.Server.Implementations.Persistence string.Empty : " where " + string.Join(" AND ", whereClauses.ToArray()); - whereClauses = GetWhereClauses(query, cmd, true); + whereClauses = GetWhereClauses(query, cmd, true, false); var whereText = whereClauses.Count == 0 ? string.Empty : @@ -1670,6 +1712,11 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += whereText; + if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += " Group by PresentationUniqueKey"; + } + cmd.CommandText += GetOrderByText(query); if (query.Limit.HasValue) @@ -1721,6 +1768,8 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + var now = DateTime.UtcNow; + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select guid from TypedBaseItems"; @@ -1739,6 +1788,11 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += whereText; + if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += " Group by PresentationUniqueKey"; + } + cmd.CommandText += GetOrderByText(query); if (query.Limit.HasValue) @@ -1746,15 +1800,24 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture); } - cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging; + if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + cmd.CommandText += "; select count (distinct PresentationUniqueKey) from TypedBaseItems" + whereTextWithoutPaging; + } + else + { + cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging; + } var list = new List(); var count = 0; - //Logger.Debug(cmd.CommandText); - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { + Logger.Debug("GetItemIds query time: {0}ms. Query: {1}", + Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds), + cmd.CommandText); + while (reader.Read()) { list.Add(reader.GetGuid(0)); @@ -1774,7 +1837,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } } - private List GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging) + private List GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging, bool enablePresentationUniqueKey = true) { var whereClauses = new List(); @@ -1998,7 +2061,14 @@ namespace MediaBrowser.Server.Implementations.Persistence if (!string.IsNullOrWhiteSpace(query.NameContains)) { - whereClauses.Add("CleanName like @NameContains"); + if (_config.Configuration.SchemaVersion >= 66) + { + whereClauses.Add("CleanName like @NameContains"); + } + else + { + whereClauses.Add("Name like @NameContains"); + } cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains + "%"; } @@ -2134,7 +2204,8 @@ namespace MediaBrowser.Server.Implementations.Persistence whereClauses.Add("MediaType in (" + val + ")"); } - var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0; + //var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0; + var enableItemsByName = query.IncludeItemsByName ?? false; if (query.TopParentIds.Length == 1) { @@ -2197,7 +2268,7 @@ namespace MediaBrowser.Server.Implementations.Persistence excludeTagIndex = 0; foreach (var excludeTag in query.ExcludeInheritedTags) { - whereClauses.Add("(InheritedTags is null OR InheritedTags not like @excludeInheritedTag" + excludeTagIndex +")"); + whereClauses.Add("(InheritedTags is null OR InheritedTags not like @excludeInheritedTag" + excludeTagIndex + ")"); cmd.Parameters.Add(cmd, "@excludeInheritedTag" + excludeTagIndex, DbType.String).Value = "%" + excludeTag + "%"; excludeTagIndex++; } @@ -2210,6 +2281,11 @@ namespace MediaBrowser.Server.Implementations.Persistence string.Empty : " where " + string.Join(" AND ", whereClauses.ToArray()); + if (enablePresentationUniqueKey && EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66) + { + pagingWhereText += " Group by PresentationUniqueKey"; + } + var orderBy = GetOrderByText(query); whereClauses.Add(string.Format("guid NOT IN (SELECT guid FROM TypedBaseItems {0}" + orderBy + " LIMIT {1})", @@ -2221,6 +2297,28 @@ namespace MediaBrowser.Server.Implementations.Persistence return whereClauses; } + private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) + { + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new[] { + typeof(Episode).Name, + typeof(Video).Name , + typeof(Movie).Name , + typeof(MusicVideo).Name , + typeof(Series).Name }; + + if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase))) + { + return true; + } + + return false; + } + private static readonly Type[] KnownTypes = { typeof(LiveTvProgram), @@ -2299,7 +2397,7 @@ namespace MediaBrowser.Server.Implementations.Persistence try { transaction = _connection.BeginTransaction(); - + foreach (var item in newValues) { _updateInheritedTagsCommand.GetParameter(0).Value = item.Item1; -- cgit v1.2.3 From 311dd50eb7ec777a055755c3791b115fa2c0d762 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 1 May 2016 21:46:08 -0400 Subject: support more filters at db level --- MediaBrowser.Controller/Entities/Folder.cs | 29 ---------------------- .../Persistence/SqliteItemRepository.cs | 24 ++++++++++++++++++ 2 files changed, 24 insertions(+), 29 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs') diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 41f0c02bd..fca2c6e43 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -839,11 +839,6 @@ namespace MediaBrowser.Controller.Entities Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName"); return true; } - if (query.SortBy.Contains(ItemSortBy.StartDate, StringComparer.OrdinalIgnoreCase)) - { - Logger.Debug("Query requires post-filtering due to ItemSortBy.StartDate"); - return true; - } if (query.SortBy.Contains(ItemSortBy.Studio, StringComparer.OrdinalIgnoreCase)) { Logger.Debug("Query requires post-filtering due to ItemSortBy.Studio"); @@ -1059,30 +1054,6 @@ namespace MediaBrowser.Controller.Entities return true; } - if (!string.IsNullOrWhiteSpace(query.NameContains)) - { - Logger.Debug("Query requires post-filtering due to NameContains"); - return true; - } - - if (!string.IsNullOrWhiteSpace(query.NameLessThan)) - { - Logger.Debug("Query requires post-filtering due to NameLessThan"); - return true; - } - - if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) - { - Logger.Debug("Query requires post-filtering due to NameStartsWith"); - return true; - } - - if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) - { - Logger.Debug("Query requires post-filtering due to NameStartsWithOrGreater"); - return true; - } - if (query.AirDays.Length > 0) { Logger.Debug("Query requires post-filtering due to AirDays"); diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 6afdb8d9f..87b3bffe4 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -2071,6 +2071,30 @@ namespace MediaBrowser.Server.Implementations.Persistence } cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains + "%"; } + if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) + { + if (_config.Configuration.SchemaVersion >= 66) + { + whereClauses.Add("CleanName like @NameStartsWith"); + } + else + { + whereClauses.Add("Name like @NameStartsWith"); + } + cmd.Parameters.Add(cmd, "@NameStartsWith", DbType.String).Value = query.NameStartsWith + "%"; + } + if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) + { + whereClauses.Add("SortName >= @NameStartsWithOrGreater"); + // lowercase this because SortName is stored as lowercase + cmd.Parameters.Add(cmd, "@NameStartsWithOrGreater", DbType.String).Value = query.NameStartsWithOrGreater.ToLower(); + } + if (!string.IsNullOrWhiteSpace(query.NameLessThan)) + { + whereClauses.Add("SortName < @NameLessThan"); + // lowercase this because SortName is stored as lowercase + cmd.Parameters.Add(cmd, "@NameLessThan", DbType.String).Value = query.NameLessThan.ToLower(); + } if (query.Genres.Length > 0) { -- cgit v1.2.3 From cd02373e554df232d88063f41b8aee391f3e7667 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 2 May 2016 01:32:04 -0400 Subject: support pooling series and seasons --- .../Entities/InternalItemsQuery.cs | 1 + MediaBrowser.Controller/Entities/TV/Episode.cs | 33 ++++----- MediaBrowser.Controller/Entities/TV/Season.cs | 26 +++++-- MediaBrowser.Controller/Entities/TV/Series.cs | 63 ++++++++++++----- .../Persistence/SqliteItemRepository.cs | 16 ++++- .../Sorting/AiredEpisodeOrderComparer.cs | 10 +-- .../TV/TVSeriesManager.cs | 82 +++++++++++++--------- 7 files changed, 153 insertions(+), 78 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs') diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index f3b4f4053..b148251ff 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Controller.Entities public string NameLessThan { get; set; } public string NameContains { get; set; } + public string PresentationUniqueKey { get; set; } public string Path { get; set; } public string Person { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 3ad6170a5..605b838cd 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -58,25 +58,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? PhysicalSeasonNumber; - } - } - - [IgnoreDataMember] - public int? PhysicalSeasonNumber - { - get - { - var value = ParentIndexNumber; - - if (value.HasValue) - { - return value; - } - - var season = Season; - - return season != null ? season.IndexNumber : null; + return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber; } } @@ -316,6 +298,19 @@ namespace MediaBrowser.Controller.Entities.TV Logger.ErrorException("Error in FillMissingEpisodeNumbersFromPath. Episode: {0}", ex, Path ?? Name ?? Id.ToString()); } + if (!ParentIndexNumber.HasValue) + { + var season = Season; + if (season != null) + { + if (season.ParentIndexNumber.HasValue) + { + ParentIndexNumber = season.ParentIndexNumber; + hasChanges = true; + } + } + } + return hasChanges; } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index ebd22c6c4..1f443071c 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -92,6 +92,24 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override string PresentationUniqueKey + { + get + { + if (IndexNumber.HasValue) + { + var series = Series; + if (series != null) + { + return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000"); + } + } + + return base.PresentationUniqueKey; + } + } + /// /// Creates the name of the sort. /// @@ -169,16 +187,16 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) { - var episodes = GetRecursiveChildren(user) - .OfType(); - var series = Series; if (IndexNumber.HasValue && series != null) { - return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes); + return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes); } + var episodes = GetRecursiveChildren(user) + .OfType(); + if (series != null && series.ContainsEpisodesWithoutSeasonFolders) { var seasonNumber = IndexNumber; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index c062b5fba..abca6a643 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public override string PresentationUniqueKey { - get { return GetUserDataKey(); } + get { return GetUserDataKeys().First(); } } /// @@ -191,8 +191,28 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired) { - var seasons = base.GetChildren(user, true) - .OfType(); + var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user) + { + PresentationUniqueKey = PresentationUniqueKey, + IncludeItemTypes = new[] { typeof(Series).Name } + }); + + IEnumerable seasons; + + if (seriesIds.Count > 1) + { + seasons = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(), + IncludeItemTypes = new[] { typeof(Season).Name }, + SortBy = new[] { ItemSortBy.SortName } + + }).OfType(); + } + else + { + seasons = base.GetChildren(user, true).OfType(); + } if (!includeMissingSeasons && !includeVirtualUnaired) { @@ -210,9 +230,7 @@ namespace MediaBrowser.Controller.Entities.TV } } - return LibraryManager - .Sort(seasons, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending) - .Cast(); + return seasons; } public IEnumerable GetEpisodes(User user) @@ -321,18 +339,31 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) { - return GetEpisodes(user, seasonNumber, includeMissingEpisodes, includeVirtualUnairedEpisodes, - new List()); - } + var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user) + { + PresentationUniqueKey = PresentationUniqueKey, + IncludeItemTypes = new[] { typeof(Series).Name } + }); - internal IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable additionalEpisodes) - { - var episodes = GetRecursiveChildren(user, i => i is Episode) - .Cast(); + IEnumerable episodes; - episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons); + if (seriesIds.Count > 1) + { + episodes = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(), + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.SortName } - episodes = episodes.Concat(additionalEpisodes).Distinct(); + }).OfType(); + } + else + { + episodes = GetRecursiveChildren(user, i => i is Episode) + .Cast(); + } + + episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons); if (!includeMissingEpisodes) { @@ -360,7 +391,7 @@ namespace MediaBrowser.Controller.Entities.TV { if (!includeSpecials || seasonNumber < 1) { - return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber); + return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); } return episodes.Where(i => diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 87b3bffe4..6959de8e6 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedTagsCommand; - public const int LatestSchemaVersion = 66; + public const int LatestSchemaVersion = 68; /// /// Initializes a new instance of the class. @@ -1936,6 +1936,12 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.Parameters.Add(cmd, "@Path", DbType.String).Value = query.Path; } + if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) + { + whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey"); + cmd.Parameters.Add(cmd, "@PresentationUniqueKey", DbType.String).Value = query.PresentationUniqueKey; + } + if (query.MinCommunityRating.HasValue) { whereClauses.Add("CommunityRating>=@MinCommunityRating"); @@ -2323,6 +2329,11 @@ namespace MediaBrowser.Server.Implementations.Persistence private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) { + if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) + { + return false; + } + if (query.IncludeItemTypes.Length == 0) { return true; @@ -2333,7 +2344,8 @@ namespace MediaBrowser.Server.Implementations.Persistence typeof(Video).Name , typeof(Movie).Name , typeof(MusicVideo).Name , - typeof(Series).Name }; + typeof(Series).Name , + typeof(Season).Name }; if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase))) { diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 70cf805cf..91abbe34c 100644 --- a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -49,8 +49,8 @@ namespace MediaBrowser.Server.Implementations.Sorting private int Compare(Episode x, Episode y) { - var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0; - var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0; + var isXSpecial = (x.ParentIndexNumber ?? -1) == 0; + var isYSpecial = (y.ParentIndexNumber ?? -1) == 0; if (isXSpecial && isYSpecial) { @@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.Sorting { // http://thetvdb.com/wiki/index.php?title=Special_Episodes - var xSeason = x.PhysicalSeasonNumber ?? -1; + var xSeason = x.ParentIndexNumber ?? -1; var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; if (xSeason != ySeason) @@ -142,8 +142,8 @@ namespace MediaBrowser.Server.Implementations.Sorting private int CompareEpisodes(Episode x, Episode y) { - var xValue = (x.PhysicalSeasonNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); - var yValue = (y.PhysicalSeasonNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); + var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); + var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); return xValue.CompareTo(yValue); } diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs index dc880d84b..ec91dc1b7 100644 --- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs +++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs @@ -36,10 +36,25 @@ namespace MediaBrowser.Server.Implementations.TV ? new string[] { } : new[] { request.ParentId }; + string presentationUniqueKey = null; + int? limit = null; + if (!string.IsNullOrWhiteSpace(request.SeriesId)) + { + var series = _libraryManager.GetItemById(request.SeriesId); + + if (series != null) + { + presentationUniqueKey = series.PresentationUniqueKey; + limit = 1; + } + } + var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Series).Name }, - SortOrder = SortOrder.Ascending + SortOrder = SortOrder.Ascending, + PresentationUniqueKey = presentationUniqueKey, + Limit = limit }, parentIds).Cast(); @@ -58,10 +73,25 @@ namespace MediaBrowser.Server.Implementations.TV throw new ArgumentException("User not found"); } + string presentationUniqueKey = null; + int? limit = null; + if (!string.IsNullOrWhiteSpace(request.SeriesId)) + { + var series = _libraryManager.GetItemById(request.SeriesId); + + if (series != null) + { + presentationUniqueKey = series.PresentationUniqueKey; + limit = 1; + } + } + var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Series).Name }, - SortOrder = SortOrder.Ascending + SortOrder = SortOrder.Ascending, + PresentationUniqueKey = presentationUniqueKey, + Limit = limit }, parentsFolders.Select(i => i.Id.ToString("N"))).Cast(); @@ -76,30 +106,30 @@ namespace MediaBrowser.Server.Implementations.TV // Avoid implicitly captured closure var currentUser = user; - return FilterSeries(request, series) + return series .AsParallel() .Select(i => GetNextUp(i, currentUser)) // Include if an episode was found, and either the series is not unwatched or the specific series was requested .Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId))) - .OrderByDescending(i => - { - var episode = i.Item1; + //.OrderByDescending(i => + //{ + // var episode = i.Item1; - var seriesUserData = _userDataManager.GetUserData(user, episode.Series); + // var seriesUserData = _userDataManager.GetUserData(user, episode.Series); - if (seriesUserData.IsFavorite) - { - return 2; - } + // if (seriesUserData.IsFavorite) + // { + // return 2; + // } - if (seriesUserData.Likes.HasValue) - { - return seriesUserData.Likes.Value ? 1 : -1; - } + // if (seriesUserData.Likes.HasValue) + // { + // return seriesUserData.Likes.Value ? 1 : -1; + // } - return 0; - }) - .ThenByDescending(i => i.Item2) + // return 0; + //}) + .OrderByDescending(i => i.Item2) .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) .Select(i => i.Item1); } @@ -142,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.TV } else { - if (!episode.IsVirtualUnaired && (!episode.IsMissingEpisode || includeMissing)) + if (!episode.IsVirtualUnaired && (includeMissing || !episode.IsMissingEpisode)) { nextUp = episode; } @@ -154,24 +184,12 @@ namespace MediaBrowser.Server.Implementations.TV return new Tuple(nextUp, lastWatchedDate, false); } - var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (!i.IsMissingEpisode || includeMissing) && !i.IsPlayed(user)); + var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (includeMissing || !i.IsMissingEpisode) && !i.IsPlayed(user)); // Return the first episode return new Tuple(firstEpisode, DateTime.MinValue, true); } - private IEnumerable FilterSeries(NextUpQuery request, IEnumerable items) - { - if (!string.IsNullOrWhiteSpace(request.SeriesId)) - { - var id = new Guid(request.SeriesId); - - items = items.Where(i => i.Id == id); - } - - return items; - } - private QueryResult GetResult(IEnumerable items, int? totalRecordLimit, NextUpQuery query) { var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); -- cgit v1.2.3