瀏覽代碼

move book support into the core

Luke Pulverenti 8 年之前
父節點
當前提交
1aff48b93b
共有 25 個文件被更改,包括 567 次插入217 次删除
  1. 1 0
      Emby.Server.Core/ApplicationHost.cs
  2. 26 18
      Emby.Server.Implementations/Activity/ActivityRepository.cs
  3. 1 1
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  4. 162 144
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  5. 15 9
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  6. 1 0
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  7. 6 0
      Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
  8. 77 0
      Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
  9. 1 1
      Emby.Server.Implementations/Library/UserDataManager.cs
  10. 28 16
      Emby.Server.Implementations/Security/AuthenticationRepository.cs
  11. 48 22
      Emby.Server.Implementations/TV/TVSeriesManager.cs
  12. 2 1
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  13. 11 1
      MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs
  14. 64 0
      MediaBrowser.Controller/Entities/AudioBook.cs
  15. 9 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  16. 1 0
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  17. 2 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  18. 9 0
      MediaBrowser.Controller/Entities/Video.cs
  19. 9 0
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  20. 9 0
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  21. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  22. 41 0
      MediaBrowser.Providers/Books/AudioBookMetadataService.cs
  23. 41 0
      MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs
  24. 2 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  25. 0 3
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 1 - 0
Emby.Server.Core/ApplicationHost.cs

@@ -424,6 +424,7 @@ namespace Emby.Server.Core
             ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
             ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
             ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
+            ServiceStack.Text.JsConfig<AudioBook>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
             ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
             ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
             ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };

+ 26 - 18
Emby.Server.Implementations/Activity/ActivityRepository.cs

@@ -84,6 +84,9 @@ namespace Emby.Server.Implementations.Activity
             {
                 using (var connection = CreateConnection(true))
                 {
+                    var list = new List<ActivityLogEntry>();
+                    int totalRecordCount = 0;
+
                     var commandText = BaseActivitySelectText;
                     var whereClauses = new List<string>();
 
@@ -120,32 +123,37 @@ namespace Emby.Server.Implementations.Activity
                         commandText += " LIMIT " + limit.Value.ToString(_usCulture);
                     }
 
-                    var list = new List<ActivityLogEntry>();
+                    var statementTexts = new List<string>();
+                    statementTexts.Add(commandText);
+                    statementTexts.Add("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging);
 
-                    using (var statement = connection.PrepareStatement(commandText))
+                    connection.RunInTransaction(db =>
                     {
-                        if (minDate.HasValue)
-                        {
-                            statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
-                        }
+                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())).ToList();
 
-                        foreach (var row in statement.ExecuteQuery())
+                        using (var statement = statements[0])
                         {
-                            list.Add(GetEntry(row));
+                            if (minDate.HasValue)
+                            {
+                                statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
+                            }
+
+                            foreach (var row in statement.ExecuteQuery())
+                            {
+                                list.Add(GetEntry(row));
+                            }
                         }
-                    }
-
-                    int totalRecordCount;
 
-                    using (var statement = connection.PrepareStatement("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging))
-                    {
-                        if (minDate.HasValue)
+                        using (var statement = statements[1])
                         {
-                            statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
-                        }
+                            if (minDate.HasValue)
+                            {
+                                statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
+                            }
 
-                        totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
-                    }
+                            totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+                        }
+                    }, ReadTransactionMode);
 
                     return new QueryResult<ActivityLogEntry>()
                     {

+ 1 - 1
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);

+ 162 - 144
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);
+                                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())
-                            {
-                                var item = GetItem(row, query);
-                                if (item != null)
+                                foreach (var row in statement.ExecuteQuery())
                                 {
-                                    list.Add(item);
+                                    var item = GetItem(row, query);
+                                    if (item != null)
+                                    {
+                                        list.Add(item);
+                                    }
                                 }
                             }
-                        }
-                    }
 
-                    if (query.EnableTotalRecordCount)
-                    {
-                        using (var statement = statements[statements.Count - 1])
-                        {
-                            if (EnableJoinUserData(query))
+                            if (query.EnableTotalRecordCount)
                             {
-                                statement.TryBind("@UserId", query.User.Id);
-                            }
+                                using (var statement = statements[statements.Count - 1])
+                                {
+                                    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);
 
-                            totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+                                    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);
+                                BindSimilarParams(query, statement);
 
-                            // Running this again will bind the params
-                            GetWhereClauses(query, statement);
+                                // Running this again will bind the params
+                                GetWhereClauses(query, statement);
 
-                            totalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+                                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<string>();
-
             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);
-                            }
+                                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);
+                                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)
+                                foreach (var row in statement.ExecuteQuery())
                                 {
-                                    var countStartColumn = columns.Count - 1;
+                                    var item = GetItem(row);
+                                    if (item != null)
+                                    {
+                                        var countStartColumn = columns.Count - 1;
 
-                                    list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount)));
+                                        list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount)));
+                                    }
                                 }
-                            }
 
-                            LogQueryTime("GetItemValues", commandText, now);
+                                LogQueryTime("GetItemValues", commandText, now);
+                            }
                         }
-                    }
 
-                    if (query.EnableTotalRecordCount)
-                    {
-                        commandText = "select count (distinct PresentationUniqueKey)" + GetFromText();
+                        if (query.EnableTotalRecordCount)
+                        {
+                            commandText = "select count (distinct PresentationUniqueKey)" + GetFromText();
 
-                        commandText += GetJoinUserDataText(query);
-                        commandText += whereText;
+                            commandText += GetJoinUserDataText(query);
+                            commandText += whereText;
 
-                        using (var statement = statements[statements.Count - 1])
-                        {
-                            statement.TryBind("@SelectType", returnType);
-                            if (EnableJoinUserData(query))
+                            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);
+                                if (typeSubQuery != null)
+                                {
+                                    GetWhereClauses(typeSubQuery, null, "itemTypes");
+                                }
+                                BindSimilarParams(query, statement);
+                                GetWhereClauses(innerQuery, statement);
+                                GetWhereClauses(outerQuery, statement);
 
-                            count = statement.ExecuteQuery().SelectScalarInt().First();
+                                count = statement.ExecuteQuery().SelectScalarInt().First();
 
-                            LogQueryTime("GetItemValues", commandText, now);
+                                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);
                 }
             }
 

+ 15 - 9
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<string> keys)

+ 1 - 0
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -119,6 +119,7 @@
     <Compile Include="Library\Resolvers\Audio\MusicAlbumResolver.cs" />
     <Compile Include="Library\Resolvers\Audio\MusicArtistResolver.cs" />
     <Compile Include="Library\Resolvers\BaseVideoResolver.cs" />
+    <Compile Include="Library\Resolvers\Books\BookResolver.cs" />
     <Compile Include="Library\Resolvers\FolderResolver.cs" />
     <Compile Include="Library\Resolvers\ItemResolver.cs" />
     <Compile Include="Library\Resolvers\Movies\BoxSetResolver.cs" />

+ 6 - 0
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();
+                    }
                 }
             }
 

+ 77 - 0
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
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    public class BookResolver : MediaBrowser.Controller.Resolvers.ItemResolver<Book>
+    {
+        private readonly string[] _validExtensions = {".pdf", ".epub", ".mobi", ".cbr", ".cbz"};
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="args"></param>
+        /// <returns></returns>
+        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;
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="args"></param>
+        /// <returns></returns>
+        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
+                       };
+        }
+    }
+}

+ 1 - 1
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;
             }

+ 28 - 16
Emby.Server.Implementations/Security/AuthenticationRepository.cs

@@ -201,35 +201,47 @@ namespace Emby.Server.Implementations.Security
             }
 
             var list = new List<AuthenticationInfo>();
+            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<string>();
+                        statementTexts.Add(commandText);
+                        statementTexts.Add("select count (Id) from AccessTokens" + whereTextWithoutPaging);
 
-                        foreach (var row in statement.ExecuteQuery())
-                        {
-                            list.Add(Get(row));
-                        }
+                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray()))
+                            .ToList();
 
-                        using (var totalCountStatement = connection.PrepareStatement("select count (Id) from AccessTokens" + whereTextWithoutPaging))
+                        using (var statement = statements[0])
                         {
-                            BindAuthenticationQueryParams(query, totalCountStatement);
+                            BindAuthenticationQueryParams(query, statement);
 
-                            var count = totalCountStatement.ExecuteQuery()
-                                .SelectScalarInt()
-                                .First();
+                            foreach (var row in statement.ExecuteQuery())
+                            {
+                                list.Add(Get(row));
+                            }
 
-                            return new QueryResult<AuthenticationInfo>()
+                            using (var totalCountStatement = statements[1])
                             {
-                                Items = list.ToArray(),
-                                TotalRecordCount = count
-                            };
+                                BindAuthenticationQueryParams(query, totalCountStatement);
+
+                                totalRecordCount = totalCountStatement.ExecuteQuery()
+                                    .SelectScalarInt()
+                                    .First();
+                            }
                         }
-                    }
+
+                    }, ReadTransactionMode);
+
+                    return new QueryResult<AuthenticationInfo>()
+                    {
+                        Items = list.ToArray(),
+                        TotalRecordCount = totalRecordCount
+                    };
                 }
             }
         }

+ 48 - 22
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<ItemFields>
+                    {
+
+                    }
+                }
 
             }).Cast<Series>();
 
@@ -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<ItemFields>
+                    {
+                        
+                    },
+                    EnableImages = false
+                }
 
             }, parentsFolders.Cast<BaseItem>().ToList()).Cast<Series>();
 
@@ -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
         /// <summary>
         /// Gets the next up.
         /// </summary>
-        /// <param name="series">The series.</param>
-        /// <param name="user">The user.</param>
         /// <returns>Task{Episode}.</returns>
-        private Tuple<DateTime, Func<Episode>> GetNextUp(Series series, User user)
+        private Tuple<DateTime, Func<Episode>> 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<ItemFields>
+                    {
+
+                    },
+                    EnableImages = false
+                }
 
             }).FirstOrDefault();
 

+ 2 - 1
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;

+ 11 - 1
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;
+            }
+        }
     }
 }

+ 64 - 0
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;
+        }
+    }
+}

+ 9 - 0
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)

+ 1 - 0
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...
         /// </summary>
         /// <value>The linked children.</value>
+        [IgnoreDataMember]
         public override List<LinkedChild> LinkedChildren
         {
             get { return GetLinkedChildrenInternal(); }

+ 2 - 1
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()

+ 9 - 0
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
         {

+ 9 - 0
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -46,6 +46,15 @@ namespace MediaBrowser.Controller.LiveTv
             set { }
         }
 
+        [IgnoreDataMember]
+        public override bool SupportsPositionTicksResume
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         /// <summary>
         /// Gets a value indicating whether this instance is owned item.
         /// </summary>

+ 9 - 0
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
         {

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -96,6 +96,7 @@
     <Compile Include="Drawing\ImageStream.cs" />
     <Compile Include="Dto\DtoOptions.cs" />
     <Compile Include="Dto\IDtoService.cs" />
+    <Compile Include="Entities\AudioBook.cs" />
     <Compile Include="Entities\Audio\AudioPodcast.cs" />
     <Compile Include="Entities\Audio\IHasAlbumArtist.cs" />
     <Compile Include="Entities\Audio\IHasMusicGenres.cs" />

+ 41 - 0
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<AudioBook, SongInfo>
+    {
+        protected override void MergeData(MetadataResult<AudioBook> source, MetadataResult<AudioBook> target, List<MetadataFields> 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)
+        {
+        }
+    }
+}

+ 41 - 0
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<AudioPodcast, SongInfo>
+    {
+        protected override void MergeData(MetadataResult<AudioPodcast> source, MetadataResult<AudioPodcast> target, List<MetadataFields> 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)
+        {
+        }
+    }
+}

+ 2 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -46,6 +46,8 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="Books\AudioBookMetadataService.cs" />
+    <Compile Include="Books\AudioPodcastMetadataService.cs" />
     <Compile Include="Books\BookMetadataService.cs" />
     <Compile Include="BoxSets\BoxSetMetadataService.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />

+ 0 - 3
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -1400,9 +1400,6 @@
     <Content Include="dashboard-ui\css\images\clients\dlna.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\about.html">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\scripts\mediaplayer.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>