From 1aff48b93b72fe7d418b4798f504bd0d145f44e8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 12 Dec 2016 00:49:19 -0500 Subject: [PATCH] move book support into the core --- Emby.Server.Core/ApplicationHost.cs | 1 + .../Activity/ActivityRepository.cs | 46 ++- .../Data/BaseSqliteRepository.cs | 2 +- .../Data/SqliteItemRepository.cs | 330 +++++++++--------- .../Data/SqliteUserDataRepository.cs | 24 +- .../Emby.Server.Implementations.csproj | 1 + .../Library/Resolvers/Audio/AudioResolver.cs | 6 + .../Library/Resolvers/Books/BookResolver.cs | 77 ++++ .../Library/UserDataManager.cs | 2 +- .../Security/AuthenticationRepository.cs | 48 ++- .../TV/TVSeriesManager.cs | 70 ++-- .../UserLibrary/UserLibraryService.cs | 3 +- .../Entities/Audio/AudioPodcast.cs | 12 +- MediaBrowser.Controller/Entities/AudioBook.cs | 64 ++++ MediaBrowser.Controller/Entities/BaseItem.cs | 9 + .../Entities/CollectionFolder.cs | 1 + .../Entities/TV/Episode.cs | 3 +- MediaBrowser.Controller/Entities/Video.cs | 9 + .../LiveTv/LiveTvAudioRecording.cs | 9 + .../LiveTv/LiveTvChannel.cs | 9 + .../MediaBrowser.Controller.csproj | 1 + .../Books/AudioBookMetadataService.cs | 41 +++ .../Books/AudioPodcastMetadataService.cs | 41 +++ .../MediaBrowser.Providers.csproj | 2 + .../MediaBrowser.WebDashboard.csproj | 3 - 25 files changed, 582 insertions(+), 232 deletions(-) create mode 100644 Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs create mode 100644 MediaBrowser.Controller/Entities/AudioBook.cs create mode 100644 MediaBrowser.Providers/Books/AudioBookMetadataService.cs create mode 100644 MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 2074b5ae47..a6d2d32c0b 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -424,6 +424,7 @@ namespace Emby.Server.Core ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; + ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" }; diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 17aef72687..fda8b949b5 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -84,6 +84,9 @@ namespace Emby.Server.Implementations.Activity { using (var connection = CreateConnection(true)) { + var list = new List(); + int totalRecordCount = 0; + var commandText = BaseActivitySelectText; var whereClauses = new List(); @@ -120,32 +123,37 @@ namespace Emby.Server.Implementations.Activity commandText += " LIMIT " + limit.Value.ToString(_usCulture); } - var list = new List(); + var statementTexts = new List(); + statementTexts.Add(commandText); + statementTexts.Add("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging); - using (var statement = connection.PrepareStatement(commandText)) + connection.RunInTransaction(db => { - if (minDate.HasValue) + var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())).ToList(); + + using (var statement = statements[0]) { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); + if (minDate.HasValue) + { + statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); + } + + foreach (var row in statement.ExecuteQuery()) + { + list.Add(GetEntry(row)); + } } - foreach (var row in statement.ExecuteQuery()) + using (var statement = statements[1]) { - list.Add(GetEntry(row)); + if (minDate.HasValue) + { + statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); + } + + totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } - } - - int totalRecordCount; - - using (var statement = connection.PrepareStatement("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging)) - { - if (minDate.HasValue) - { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } - - totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } + }, ReadTransactionMode); return new QueryResult() { diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 8f2bb2ceab..9e60a43aab 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Data connectionFlags |= ConnectionFlags.ReadWrite; } - //connectionFlags |= ConnectionFlags.SharedCached; + connectionFlags |= ConnectionFlags.SharedCached; connectionFlags |= ConnectionFlags.NoMutex; var db = SQLite3.Open(DbFilePath, connectionFlags, null); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index e3dd3f884c..768f5b5a06 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -123,17 +123,10 @@ namespace Emby.Server.Implementations.Data } } - private SQLiteDatabaseConnection _backgroundConnection; protected override void CloseConnection() { base.CloseConnection(); - if (_backgroundConnection != null) - { - _backgroundConnection.Dispose(); - _backgroundConnection = null; - } - if (_shrinkMemoryTimer != null) { _shrinkMemoryTimer.Dispose(); @@ -379,8 +372,6 @@ namespace Emby.Server.Implementations.Data userDataRepo.Initialize(WriteLock); - //_backgroundConnection = CreateConnection(true); - _shrinkMemoryTimer = _timerFactory.Create(OnShrinkMemoryTimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(30)); } @@ -1370,6 +1361,10 @@ namespace Emby.Server.Implementations.Data { return false; } + if (type == typeof(AudioBook)) + { + return false; + } if (type == typeof(MusicAlbum)) { return false; @@ -2691,51 +2686,55 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - var statements = PrepareAllSafe(connection, string.Join(";", statementTexts.ToArray())) - .ToList(); - - if (!isReturningZeroItems) + connection.RunInTransaction(db => { - using (var statement = statements[0]) + var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())) + .ToList(); + + if (!isReturningZeroItems) { - if (EnableJoinUserData(query)) + using (var statement = statements[0]) { - statement.TryBind("@UserId", query.User.Id); + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.Id); + } + + BindSimilarParams(query, statement); + + // Running this again will bind the params + GetWhereClauses(query, statement); + + foreach (var row in statement.ExecuteQuery()) + { + var item = GetItem(row, query); + if (item != null) + { + list.Add(item); + } + } } - BindSimilarParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) + if (query.EnableTotalRecordCount) { - var item = GetItem(row, query); - if (item != null) + using (var statement = statements[statements.Count - 1]) { - list.Add(item); + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.Id); + } + + BindSimilarParams(query, statement); + + // Running this again will bind the params + GetWhereClauses(query, statement); + + totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } } } - } - if (query.EnableTotalRecordCount) - { - using (var statement = statements[statements.Count - 1]) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.Id); - } - - BindSimilarParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } - } + }, ReadTransactionMode); LogQueryTime("GetItems", commandText, now); @@ -3095,49 +3094,53 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - var statements = PrepareAllSafe(connection, string.Join(";", statementTexts.ToArray())) - .ToList(); - var totalRecordCount = 0; - if (!isReturningZeroItems) + connection.RunInTransaction(db => { - using (var statement = statements[0]) + var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())) + .ToList(); + + if (!isReturningZeroItems) { - if (EnableJoinUserData(query)) + using (var statement = statements[0]) { - statement.TryBind("@UserId", query.User.Id); - } + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.Id); + } - BindSimilarParams(query, statement); + BindSimilarParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); - foreach (var row in statement.ExecuteQuery()) - { - list.Add(row[0].ReadGuid()); + foreach (var row in statement.ExecuteQuery()) + { + list.Add(row[0].ReadGuid()); + } } } - } - if (query.EnableTotalRecordCount) - { - using (var statement = statements[statements.Count - 1]) + if (query.EnableTotalRecordCount) { - if (EnableJoinUserData(query)) + using (var statement = statements[statements.Count - 1]) { - statement.TryBind("@UserId", query.User.Id); + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.Id); + } + + BindSimilarParams(query, statement); + + // Running this again will bind the params + GetWhereClauses(query, statement); + + totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } - - BindSimilarParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } - } + + }, ReadTransactionMode); LogQueryTime("GetItemIds", commandText, now); @@ -4426,6 +4429,7 @@ namespace Emby.Server.Implementations.Data typeof(Movie), typeof(Playlist), typeof(AudioPodcast), + typeof(AudioBook), typeof(Trailer), typeof(BoxSet), typeof(Episode), @@ -4594,21 +4598,23 @@ namespace Emby.Server.Implementations.Data commandText += " order by ListOrder"; var list = new List(); - using (WriteLock.Read()) { using (var connection = CreateConnection(true)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + connection.RunInTransaction(db => { - // Run this again to bind the params - GetPeopleWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) + using (var statement = PrepareStatementSafe(db, commandText)) { - list.Add(row.GetString(0)); + // Run this again to bind the params + GetPeopleWhereClauses(query, statement); + + foreach (var row in statement.ExecuteQuery()) + { + list.Add(row.GetString(0)); + } } - } + }, ReadTransactionMode); } return list; } @@ -4640,16 +4646,19 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + connection.RunInTransaction(db => { - // Run this again to bind the params - GetPeopleWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) + using (var statement = PrepareStatementSafe(db, commandText)) { - list.Add(GetPerson(row)); + // Run this again to bind the params + GetPeopleWhereClauses(query, statement); + + foreach (var row in statement.ExecuteQuery()) + { + list.Add(GetPerson(row)); + } } - } + }, ReadTransactionMode); } } @@ -4855,16 +4864,19 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - using (var statement = PrepareStatementSafe(connection, commandText)) + connection.RunInTransaction(db => { - foreach (var row in statement.ExecuteQuery()) + using (var statement = PrepareStatementSafe(db, commandText)) { - if (!row.IsDBNull(0)) + foreach (var row in statement.ExecuteQuery()) { - list.Add(row.GetString(0)); + if (!row.IsDBNull(0)) + { + list.Add(row.GetString(0)); + } } } - } + }, ReadTransactionMode); } } LogQueryTime("GetItemValueNames", commandText, now); @@ -5034,69 +5046,72 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - var statements = PrepareAllSafe(connection, string.Join(";", statementTexts.ToArray())).ToList(); - - if (!isReturningZeroItems) + connection.RunInTransaction(db => { - using (var statement = statements[0]) + var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())).ToList(); + + if (!isReturningZeroItems) { - statement.TryBind("@SelectType", returnType); - if (EnableJoinUserData(query)) + using (var statement = statements[0]) { - statement.TryBind("@UserId", query.User.Id); - } - - if (typeSubQuery != null) - { - GetWhereClauses(typeSubQuery, null, "itemTypes"); - } - BindSimilarParams(query, statement); - GetWhereClauses(innerQuery, statement); - GetWhereClauses(outerQuery, statement); - - foreach (var row in statement.ExecuteQuery()) - { - var item = GetItem(row); - if (item != null) + statement.TryBind("@SelectType", returnType); + if (EnableJoinUserData(query)) { - var countStartColumn = columns.Count - 1; - - list.Add(new Tuple(item, GetItemCounts(row, countStartColumn, typesToCount))); + statement.TryBind("@UserId", query.User.Id); } + + if (typeSubQuery != null) + { + GetWhereClauses(typeSubQuery, null, "itemTypes"); + } + BindSimilarParams(query, statement); + GetWhereClauses(innerQuery, statement); + GetWhereClauses(outerQuery, statement); + + foreach (var row in statement.ExecuteQuery()) + { + var item = GetItem(row); + if (item != null) + { + var countStartColumn = columns.Count - 1; + + list.Add(new Tuple(item, GetItemCounts(row, countStartColumn, typesToCount))); + } + } + + LogQueryTime("GetItemValues", commandText, now); } - - LogQueryTime("GetItemValues", commandText, now); } - } - if (query.EnableTotalRecordCount) - { - commandText = "select count (distinct PresentationUniqueKey)" + GetFromText(); - - commandText += GetJoinUserDataText(query); - commandText += whereText; - - using (var statement = statements[statements.Count - 1]) + if (query.EnableTotalRecordCount) { - statement.TryBind("@SelectType", returnType); - if (EnableJoinUserData(query)) + commandText = "select count (distinct PresentationUniqueKey)" + GetFromText(); + + commandText += GetJoinUserDataText(query); + commandText += whereText; + + using (var statement = statements[statements.Count - 1]) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@SelectType", returnType); + if (EnableJoinUserData(query)) + { + statement.TryBind("@UserId", query.User.Id); + } + + if (typeSubQuery != null) + { + GetWhereClauses(typeSubQuery, null, "itemTypes"); + } + BindSimilarParams(query, statement); + GetWhereClauses(innerQuery, statement); + GetWhereClauses(outerQuery, statement); + + count = statement.ExecuteQuery().SelectScalarInt().First(); + + LogQueryTime("GetItemValues", commandText, now); } - - if (typeSubQuery != null) - { - GetWhereClauses(typeSubQuery, null, "itemTypes"); - } - BindSimilarParams(query, statement); - GetWhereClauses(innerQuery, statement); - GetWhereClauses(outerQuery, statement); - - count = statement.ExecuteQuery().SelectScalarInt().First(); - - LogQueryTime("GetItemValues", commandText, now); } - } + }, ReadTransactionMode); } } @@ -5344,25 +5359,28 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - using (var statement = PrepareStatementSafe(connection, cmdText)) + connection.RunInTransaction(db => { - statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue()); - - if (query.Type.HasValue) + using (var statement = PrepareStatementSafe(db, cmdText)) { - statement.TryBind("@StreamType", query.Type.Value.ToString()); - } + statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue()); - if (query.Index.HasValue) - { - statement.TryBind("@StreamIndex", query.Index.Value); - } + if (query.Type.HasValue) + { + statement.TryBind("@StreamType", query.Type.Value.ToString()); + } - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetMediaStream(row)); + if (query.Index.HasValue) + { + statement.TryBind("@StreamIndex", query.Index.Value); + } + + foreach (var row in statement.ExecuteQuery()) + { + list.Add(GetMediaStream(row)); + } } - } + }, ReadTransactionMode); } } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index be59d71b33..7afb5720e9 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -300,20 +300,26 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId")) - { - statement.TryBind("@UserId", userId.ToGuidParamValue()); - statement.TryBind("@Key", key); + UserItemData result = null; - foreach (var row in statement.ExecuteQuery()) + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId")) { - return ReadRow(row); + statement.TryBind("@UserId", userId.ToGuidParamValue()); + statement.TryBind("@Key", key); + + foreach (var row in statement.ExecuteQuery()) + { + result = ReadRow(row); + break; + } } - } + }, ReadTransactionMode); + + return result; } } - - return null; } public UserItemData GetUserData(Guid userId, List keys) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 47f4e7ceda..e478b9d817 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -119,6 +119,7 @@ + diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index d8805355ad..2e3d81474a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; +using MediaBrowser.Controller.Entities; namespace Emby.Server.Implementations.Library.Resolvers.Audio { @@ -59,6 +60,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { return new MediaBrowser.Controller.Entities.Audio.Audio(); } + + if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase)) + { + return new AudioBook(); + } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs new file mode 100644 index 0000000000..4852c3c6ae --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; + +namespace Emby.Server.Implementations.Library.Resolvers.Books +{ + /// + /// + /// + public class BookResolver : MediaBrowser.Controller.Resolvers.ItemResolver + { + private readonly string[] _validExtensions = {".pdf", ".epub", ".mobi", ".cbr", ".cbz"}; + + /// + /// + /// + /// + /// + protected override Book Resolve(ItemResolveArgs args) + { + var collectionType = args.GetCollectionType(); + + // Only process items that are in a collection folder containing books + if (!string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase)) + return null; + + if (args.IsDirectory) + { + return GetBook(args); + } + + var extension = Path.GetExtension(args.Path); + + if (extension != null && _validExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + // It's a book + return new Book + { + Path = args.Path, + IsInMixedFolder = true + }; + } + + return null; + } + + /// + /// + /// + /// + /// + private Book GetBook(ItemResolveArgs args) + { + var bookFiles = args.FileSystemChildren.Where(f => + { + var fileExtension = Path.GetExtension(f.FullName) ?? + string.Empty; + + return _validExtensions.Contains(fileExtension, + StringComparer + .OrdinalIgnoreCase); + }).ToList(); + + // Don't return a Book if there is more (or less) than one document in the directory + if (bookFiles.Count != 1) + return null; + + return new Book + { + Path = bookFiles[0].FullName + }; + } + } +} diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index c8dde1287a..f4a30fc00b 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -274,7 +274,7 @@ namespace Emby.Server.Implementations.Library positionTicks = 0; data.Played = false; } - if (item is Audio) + if (!item.SupportsPositionTicksResume) { positionTicks = 0; } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index dbca4931bf..a136701daf 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -201,35 +201,47 @@ namespace Emby.Server.Implementations.Security } var list = new List(); + int totalRecordCount = 0; using (WriteLock.Read()) { using (var connection = CreateConnection(true)) { - using (var statement = connection.PrepareStatement(commandText)) + connection.RunInTransaction(db => { - BindAuthenticationQueryParams(query, statement); + var statementTexts = new List(); + statementTexts.Add(commandText); + statementTexts.Add("select count (Id) from AccessTokens" + whereTextWithoutPaging); - foreach (var row in statement.ExecuteQuery()) + var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())) + .ToList(); + + using (var statement = statements[0]) { - list.Add(Get(row)); - } + BindAuthenticationQueryParams(query, statement); - using (var totalCountStatement = connection.PrepareStatement("select count (Id) from AccessTokens" + whereTextWithoutPaging)) - { - BindAuthenticationQueryParams(query, totalCountStatement); - - var count = totalCountStatement.ExecuteQuery() - .SelectScalarInt() - .First(); - - return new QueryResult() + foreach (var row in statement.ExecuteQuery()) { - Items = list.ToArray(), - TotalRecordCount = count - }; + list.Add(Get(row)); + } + + using (var totalCountStatement = statements[1]) + { + BindAuthenticationQueryParams(query, totalCountStatement); + + totalRecordCount = totalCountStatement.ExecuteQuery() + .SelectScalarInt() + .First(); + } } - } + + }, ReadTransactionMode); + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = totalRecordCount + }; } } } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 4f876f6a39..88d2245253 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -62,7 +62,14 @@ namespace Emby.Server.Implementations.TV PresentationUniqueKey = presentationUniqueKey, Limit = limit, ParentId = parentIdGuid, - Recursive = true + Recursive = true, + DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions + { + Fields = new List + { + + } + } }).Cast(); @@ -104,7 +111,15 @@ namespace Emby.Server.Implementations.TV IncludeItemTypes = new[] { typeof(Series).Name }, SortOrder = SortOrder.Ascending, PresentationUniqueKey = presentationUniqueKey, - Limit = limit + Limit = limit, + DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions + { + Fields = new List + { + + }, + EnableImages = false + } }, parentsFolders.Cast().ToList()).Cast(); @@ -120,26 +135,32 @@ namespace Emby.Server.Implementations.TV var currentUser = user; var allNextUp = series - .Select(i => GetNextUp(i, currentUser)) + .Select(i => GetNextUp(GetUniqueSeriesKey(i), currentUser)) // Include if an episode was found, and either the series is not unwatched or the specific series was requested - .OrderByDescending(i => i.Item1) - .ToList(); + .OrderByDescending(i => i.Item1); // If viewing all next up for all series, remove first episodes - if (string.IsNullOrWhiteSpace(request.SeriesId)) - { - var withoutFirstEpisode = allNextUp - .Where(i => i.Item1 != DateTime.MinValue) - .ToList(); - - // But if that returns empty, keep those first episodes (avoid completely empty view) - if (withoutFirstEpisode.Count > 0) - { - allNextUp = withoutFirstEpisode; - } - } + // But if that returns empty, keep those first episodes (avoid completely empty view) + var alwaysEnableFirstEpisode = string.IsNullOrWhiteSpace(request.SeriesId); + var isFirstItemAFirstEpisode = true; return allNextUp + .Where(i => + { + if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue) + { + isFirstItemAFirstEpisode = false; + return true; + } + + if (isFirstItemAFirstEpisode) + { + return false; + } + + return true; + }) + .Take(request.Limit.HasValue ? (request.Limit.Value * 2) : int.MaxValue) .Select(i => i.Item2()) .Where(i => i != null) .Take(request.Limit ?? int.MaxValue); @@ -153,13 +174,10 @@ namespace Emby.Server.Implementations.TV /// /// Gets the next up. /// - /// The series. - /// The user. /// Task{Episode}. - private Tuple> GetNextUp(Series series, User user) + private Tuple> GetNextUp(string seriesKey, User user) { var enableSeriesPresentationKey = _config.Configuration.EnableSeriesPresentationUniqueKey; - var seriesKey = GetUniqueSeriesKey(series); var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -170,7 +188,15 @@ namespace Emby.Server.Implementations.TV SortOrder = SortOrder.Descending, IsPlayed = true, Limit = 1, - ParentIndexNumberNotEquals = 0 + ParentIndexNumberNotEquals = 0, + DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions + { + Fields = new List + { + + }, + EnableImages = false + } }).FirstOrDefault(); diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 4121cc2959..1ac98d1656 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Providers; @@ -324,7 +325,7 @@ namespace MediaBrowser.Api.UserLibrary var item = i.Item2[0]; var childCount = 0; - if (i.Item1 != null && i.Item2.Count > 1) + if (i.Item1 != null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum)) { item = i.Item1; childCount = i.Item2.Count; diff --git a/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs b/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs index 9072e10947..8c820d3677 100644 --- a/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs +++ b/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs @@ -1,6 +1,16 @@ -namespace MediaBrowser.Controller.Entities.Audio +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Controller.Entities.Audio { public class AudioPodcast : Audio { + [IgnoreDataMember] + public override bool SupportsPositionTicksResume + { + get + { + return true; + } + } } } diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs new file mode 100644 index 0000000000..efeb9b497d --- /dev/null +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -0,0 +1,64 @@ +using System; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Entities +{ + public class AudioBook : Audio.Audio, IHasSeries + { + [IgnoreDataMember] + public override bool SupportsPositionTicksResume + { + get + { + return true; + } + } + + [IgnoreDataMember] + public string SeriesPresentationUniqueKey { get; set; } + [IgnoreDataMember] + public string SeriesName { get; set; } + [IgnoreDataMember] + public Guid? SeriesId { get; set; } + [IgnoreDataMember] + public string SeriesSortName { get; set; } + + public string FindSeriesSortName() + { + return SeriesSortName; + } + public string FindSeriesName() + { + return SeriesName; + } + public string FindSeriesPresentationUniqueKey() + { + return SeriesPresentationUniqueKey; + } + + [IgnoreDataMember] + public override bool EnableRefreshOnDateModifiedChange + { + get { return true; } + } + + public Guid? FindSeriesId() + { + return SeriesId; + } + + public override bool CanDownload() + { + var locationType = LocationType; + return locationType != LocationType.Remote && + locationType != LocationType.Virtual; + } + + public override UnratedItem GetBlockUnratedType() + { + return UnratedItem.Book; + } + } +} diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b7ea7a92d8..9f45034665 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -142,6 +142,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public virtual bool SupportsPositionTicksResume + { + get + { + return false; + } + } + public bool DetectIsInMixedFolder() { if (SupportsIsInMixedFolderDetection) diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 68dd055f34..ebc55ca8a0 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -264,6 +264,7 @@ namespace MediaBrowser.Controller.Entities /// Our children are actually just references to the ones in the physical root... /// /// The linked children. + [IgnoreDataMember] public override List LinkedChildren { get { return GetLinkedChildrenInternal(); } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 737257898d..e6ebcb7fd9 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -193,9 +193,10 @@ namespace MediaBrowser.Controller.Entities.TV { return "Season " + ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture); } + return "Season Unknown"; } - return season == null ? SeasonName : season.Name; + return season.Name; } public string FindSeriesName() diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 2dd134334f..7ba59df4f3 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -44,6 +44,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public override bool SupportsPositionTicksResume + { + get + { + return true; + } + } + [IgnoreDataMember] protected override bool SupportsIsInMixedFolderDetection { diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs index 88e5b28026..e67fc57594 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -46,6 +46,15 @@ namespace MediaBrowser.Controller.LiveTv set { } } + [IgnoreDataMember] + public override bool SupportsPositionTicksResume + { + get + { + return true; + } + } + /// /// Gets a value indicating whether this instance is owned item. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index f568ae6ae6..d164b5e0d9 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -38,6 +38,15 @@ namespace MediaBrowser.Controller.LiveTv } } + [IgnoreDataMember] + public override bool SupportsPositionTicksResume + { + get + { + return false; + } + } + [IgnoreDataMember] public override SourceType SourceType { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 2f96088ab9..28229f8a7e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -96,6 +96,7 @@ + diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs new file mode 100644 index 0000000000..696619a8c1 --- /dev/null +++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.Providers.Books +{ + public class AudioBookMetadataService : MetadataService + { + protected override void MergeData(MetadataResult source, MetadataResult target, List lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + var sourceItem = source.Item; + var targetItem = target.Item; + + if (replaceData || targetItem.Artists.Count == 0) + { + targetItem.Artists = sourceItem.Artists.ToList(); + } + + if (replaceData || string.IsNullOrEmpty(targetItem.Album)) + { + targetItem.Album = sourceItem.Album; + } + } + + public AudioBookMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + { + } + } +} diff --git a/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs b/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs new file mode 100644 index 0000000000..86b2cf1b13 --- /dev/null +++ b/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.Providers.Books +{ + public class AudioPodcastMetadataService : MetadataService + { + protected override void MergeData(MetadataResult source, MetadataResult target, List lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + var sourceItem = source.Item; + var targetItem = target.Item; + + if (replaceData || targetItem.Artists.Count == 0) + { + targetItem.Artists = sourceItem.Artists.ToList(); + } + + if (replaceData || string.IsNullOrEmpty(targetItem.Album)) + { + targetItem.Album = sourceItem.Album; + } + } + + public AudioPodcastMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + { + } + } +} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index dcb21612fa..fe554545fa 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -46,6 +46,8 @@ Properties\SharedVersion.cs + + diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 3cf7e54c05..be5db5a0e0 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -1400,9 +1400,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest