Browse Source

Merge pull request #2340 from MediaBrowser/dev

Dev
Luke 8 years ago
parent
commit
6cdbb25b9d
48 changed files with 736 additions and 466 deletions
  1. 21 2
      Emby.Common.Implementations/Networking/NetworkManager.cs
  2. 1 0
      Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  3. 4 2
      Emby.Dlna/Didl/DidlBuilder.cs
  4. 2 1
      Emby.Dlna/PlayTo/PlayToController.cs
  5. 1 0
      Emby.Server.Core/ApplicationHost.cs
  6. 9 8
      Emby.Server.Implementations/Activity/ActivityRepository.cs
  7. 115 63
      Emby.Server.Implementations/Data/BaseSqliteRepository.cs
  8. 1 1
      Emby.Server.Implementations/Data/SqliteExtensions.cs
  9. 147 162
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  10. 13 17
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  11. 70 35
      Emby.Server.Implementations/Dto/DtoService.cs
  12. 2 0
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  13. 6 0
      Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  14. 2 1
      Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
  15. 41 17
      Emby.Server.Implementations/Library/LibraryManager.cs
  16. 4 3
      Emby.Server.Implementations/Library/UserDataManager.cs
  17. 21 1
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  18. 6 0
      Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
  19. 1 0
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  20. 10 10
      Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
  21. 7 7
      Emby.Server.Implementations/Security/AuthenticationRepository.cs
  22. 1 1
      Emby.Server.Implementations/TV/TVSeriesManager.cs
  23. 31 1
      MediaBrowser.Api/BaseApiService.cs
  24. 13 2
      MediaBrowser.Api/ItemLookupService.cs
  25. 4 2
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  26. 12 0
      MediaBrowser.Api/Playback/StreamState.cs
  27. 2 1
      MediaBrowser.Controller/Entities/BaseItem.cs
  28. 46 22
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  29. 20 16
      MediaBrowser.Controller/Entities/Folder.cs
  30. 2 4
      MediaBrowser.Controller/Entities/IHasUserData.cs
  31. 2 0
      MediaBrowser.Controller/Library/ILibraryManager.cs
  32. 2 4
      MediaBrowser.Controller/Library/IUserDataManager.cs
  33. 12 0
      MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
  34. 2 1
      MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
  35. 2 2
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  36. 25 1
      MediaBrowser.Model/Dlna/ConditionProcessor.cs
  37. 8 0
      MediaBrowser.Model/Dlna/ContainerProfile.cs
  38. 4 2
      MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
  39. 3 2
      MediaBrowser.Model/Dlna/DeviceProfile.cs
  40. 2 1
      MediaBrowser.Model/Dlna/ProfileConditionValue.cs
  41. 14 7
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  42. 12 0
      MediaBrowser.Model/Dlna/StreamInfo.cs
  43. 2 0
      MediaBrowser.Model/Querying/ItemFields.cs
  44. 1 3
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  45. 0 57
      MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs
  46. 4 4
      RSSDP/SsdpDevicePublisherBase.cs
  47. 5 0
      src/Emby.Server/Emby.Server.xproj
  48. 21 3
      src/Emby.Server/project.json

+ 21 - 2
Emby.Common.Implementations/Networking/NetworkManager.cs

@@ -198,6 +198,12 @@ namespace Emby.Common.Implementations.Networking
             return Dns.GetHostAddressesAsync(hostName);
         }
 
+        private readonly List<NetworkInterfaceType> _validNetworkInterfaceTypes = new List<NetworkInterfaceType>
+        {
+            NetworkInterfaceType.Ethernet,
+            NetworkInterfaceType.Wireless80211
+        };
+
         private List<IPAddress> GetIPsDefault()
         {
             NetworkInterface[] interfaces;
@@ -223,9 +229,22 @@ namespace Emby.Common.Implementations.Networking
                 {
                     Logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
 
-                    var properties = network.GetIPProperties();
+                    var ipProperties = network.GetIPProperties();
+
+                    // Try to exclude virtual adapters
+                    // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
+                    var addr = ipProperties.GatewayAddresses.FirstOrDefault();
+                    if (addr == null|| string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
+                    {
+                        return new List<IPAddress>();
+                    }
+
+                    //if (!_validNetworkInterfaceTypes.Contains(network.NetworkInterfaceType))
+                    //{
+                    //    return new List<IPAddress>();
+                    //}
 
-                    return properties.UnicastAddresses
+                    return ipProperties.UnicastAddresses
                         .Where(i => i.IsDnsEligible)
                         .Select(i => i.Address)
                         .Where(i => i.AddressFamily == AddressFamily.InterNetwork)

+ 1 - 0
Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -387,6 +387,7 @@ namespace Emby.Common.Implementations.ScheduledTasks
             finally
             {
                 _currentTask = null;
+                GC.Collect();
             }
         }
 

+ 4 - 2
Emby.Dlna/Didl/DidlBuilder.cs

@@ -207,7 +207,8 @@ namespace Emby.Dlna.Didl
                 streamInfo.TargetVideoStreamCount,
                 streamInfo.TargetAudioStreamCount,
                 streamInfo.TargetVideoCodecTag,
-                streamInfo.IsTargetAVC);
+                streamInfo.IsTargetAVC,
+                streamInfo.AllAudioCodecs);
 
             foreach (var contentFeature in contentFeatureList)
             {
@@ -347,7 +348,8 @@ namespace Emby.Dlna.Didl
                 streamInfo.TargetVideoStreamCount,
                 streamInfo.TargetAudioStreamCount,
                 streamInfo.TargetVideoCodecTag,
-                streamInfo.IsTargetAVC);
+                streamInfo.IsTargetAVC,
+                streamInfo.AllAudioCodecs);
 
             var filename = url.Substring(0, url.IndexOf('?'));
 

+ 2 - 1
Emby.Dlna/PlayTo/PlayToController.cs

@@ -541,7 +541,8 @@ namespace Emby.Dlna.PlayTo
                     streamInfo.TargetVideoStreamCount,
                     streamInfo.TargetAudioStreamCount,
                     streamInfo.TargetVideoCodecTag,
-                    streamInfo.IsTargetAVC);
+                    streamInfo.IsTargetAVC,
+                    streamInfo.AllAudioCodecs);
 
                 return list.FirstOrDefault();
             }

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

@@ -489,6 +489,7 @@ namespace Emby.Server.Core
         {
             var migrations = new List<IVersionMigration>
             {
+                new LibraryScanMigration(ServerConfigurationManager, TaskManager)
             };
 
             foreach (var task in migrations)

+ 9 - 8
Emby.Server.Implementations/Activity/ActivityRepository.cs

@@ -84,9 +84,6 @@ namespace Emby.Server.Implementations.Activity
             {
                 using (var connection = CreateConnection(true))
                 {
-                    var list = new List<ActivityLogEntry>();
-                    var result = new QueryResult<ActivityLogEntry>();
-
                     var commandText = BaseActivitySelectText;
                     var whereClauses = new List<string>();
 
@@ -127,9 +124,12 @@ namespace Emby.Server.Implementations.Activity
                     statementTexts.Add(commandText);
                     statementTexts.Add("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging);
 
-                    connection.RunInTransaction(db =>
+                    return connection.RunInTransaction(db =>
                     {
-                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())).ToList();
+                        var list = new List<ActivityLogEntry>();
+                        var result = new QueryResult<ActivityLogEntry>();
+
+                        var statements = PrepareAllSafe(db, statementTexts).ToList();
 
                         using (var statement = statements[0])
                         {
@@ -153,10 +153,11 @@ namespace Emby.Server.Implementations.Activity
 
                             result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
                         }
-                    }, ReadTransactionMode);
 
-                    result.Items = list.ToArray();
-                    return result;
+                        result.Items = list.ToArray();
+                        return result;
+
+                    }, ReadTransactionMode);
                 }
             }
         }

+ 115 - 63
Emby.Server.Implementations/Data/BaseSqliteRepository.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Logging;
@@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Data
 
         protected TransactionMode TransactionMode
         {
-            get { return TransactionMode.Immediate; }
+            get { return TransactionMode.Deferred; }
         }
 
         protected TransactionMode ReadTransactionMode
@@ -43,6 +44,8 @@ namespace Emby.Server.Implementations.Data
             //CheckOk(rc);
 
             rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD, 1);
+            //rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SINGLETHREAD, 1);
+            //rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SERIALIZED, 1);
             //CheckOk(rc);
 
             rc = raw.sqlite3_enable_shared_cache(1);
@@ -53,84 +56,125 @@ namespace Emby.Server.Implementations.Data
         private static bool _versionLogged;
 
         private string _defaultWal;
+        protected ManagedConnection _connection;
 
-        protected SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false)
+        protected virtual bool EnableSingleConnection
         {
-            if (!_versionLogged)
-            {
-                _versionLogged = true;
-                Logger.Info("Sqlite version: " + SQLite3.Version);
-                Logger.Info("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray()));
-            }
-
-            ConnectionFlags connectionFlags;
+            get { return true; }
+        }
 
-            if (isReadOnly)
+        protected ManagedConnection CreateConnection(bool isReadOnly = false)
+        {
+            if (_connection != null)
             {
-                //Logger.Info("Opening read connection");
-                //connectionFlags = ConnectionFlags.ReadOnly;
-                connectionFlags = ConnectionFlags.Create;
-                connectionFlags |= ConnectionFlags.ReadWrite;
+                return _connection;
             }
-            else
+
+            lock (WriteLock)
             {
-                //Logger.Info("Opening write connection");
-                connectionFlags = ConnectionFlags.Create;
-                connectionFlags |= ConnectionFlags.ReadWrite;
-            }
+                if (!_versionLogged)
+                {
+                    _versionLogged = true;
+                    Logger.Info("Sqlite version: " + SQLite3.Version);
+                    Logger.Info("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray()));
+                }
 
-            connectionFlags |= ConnectionFlags.SharedCached;
-            connectionFlags |= ConnectionFlags.NoMutex;
+                ConnectionFlags connectionFlags;
 
-            var db = SQLite3.Open(DbFilePath, connectionFlags, null);
+                if (isReadOnly)
+                {
+                    //Logger.Info("Opening read connection");
+                    //connectionFlags = ConnectionFlags.ReadOnly;
+                    connectionFlags = ConnectionFlags.Create;
+                    connectionFlags |= ConnectionFlags.ReadWrite;
+                }
+                else
+                {
+                    //Logger.Info("Opening write connection");
+                    connectionFlags = ConnectionFlags.Create;
+                    connectionFlags |= ConnectionFlags.ReadWrite;
+                }
 
-            if (string.IsNullOrWhiteSpace(_defaultWal))
-            {
-                _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First();
+                if (EnableSingleConnection)
+                {
+                    connectionFlags |= ConnectionFlags.PrivateCache;
+                }
+                else
+                {
+                    connectionFlags |= ConnectionFlags.SharedCached;
+                }
 
-                Logger.Info("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal);
-            }
+                connectionFlags |= ConnectionFlags.NoMutex;
 
-            var queries = new List<string>
-            {
-                //"PRAGMA cache size=-10000"
-            };
+                var db = SQLite3.Open(DbFilePath, connectionFlags, null);
 
-            if (EnableTempStoreMemory)
-            {
-                queries.Add("PRAGMA temp_store = memory");
-            }
+                if (string.IsNullOrWhiteSpace(_defaultWal))
+                {
+                    _defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First();
 
-            //var cacheSize = CacheSize;
-            //if (cacheSize.HasValue)
-            //{
+                    Logger.Info("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal);
+                }
 
-            //}
+                var queries = new List<string>
+                {
+                    //"PRAGMA cache size=-10000"
+                    //"PRAGMA read_uncommitted = true",
+                    "PRAGMA synchronous=Normal"
+                };
 
-            ////foreach (var query in queries)
-            ////{
-            ////    db.Execute(query);
-            ////}
+                if (CacheSize.HasValue)
+                {
+                    queries.Add("PRAGMA cache_size=-" + CacheSize.Value.ToString(CultureInfo.InvariantCulture));
+                }
 
-            //Logger.Info("synchronous: " + db.Query("PRAGMA synchronous").SelectScalarString().First());
-            //Logger.Info("temp_store: " + db.Query("PRAGMA temp_store").SelectScalarString().First());
+                if (EnableTempStoreMemory)
+                {
+                    queries.Add("PRAGMA temp_store = memory");
+                }
 
-            /*if (!string.Equals(_defaultWal, "wal", StringComparison.OrdinalIgnoreCase))
-            {
-                queries.Add("PRAGMA journal_mode=WAL");
+                //var cacheSize = CacheSize;
+                //if (cacheSize.HasValue)
+                //{
 
-                using (WriteLock.Write())
+                //}
+
+                ////foreach (var query in queries)
+                ////{
+                ////    db.Execute(query);
+                ////}
+
+                //Logger.Info("synchronous: " + db.Query("PRAGMA synchronous").SelectScalarString().First());
+                //Logger.Info("temp_store: " + db.Query("PRAGMA temp_store").SelectScalarString().First());
+
+                /*if (!string.Equals(_defaultWal, "wal", StringComparison.OrdinalIgnoreCase))
                 {
-                    db.ExecuteAll(string.Join(";", queries.ToArray()));
+                    queries.Add("PRAGMA journal_mode=WAL");
+
+                    using (WriteLock.Write())
+                    {
+                        db.ExecuteAll(string.Join(";", queries.ToArray()));
+                    }
                 }
+                else*/
+                foreach (var query in queries)
+                {
+                    db.Execute(query);
+                }
+
+                _connection = new ManagedConnection(db, false);
+
+                return _connection;
             }
-            else*/
-            if (queries.Count > 0)
-            {
-                db.ExecuteAll(string.Join(";", queries.ToArray()));
-            }
+        }
+
+        public IStatement PrepareStatement(ManagedConnection connection, string sql)
+        {
+            return connection.PrepareStatement(sql);
+        }
 
-            return db;
+        public IStatement PrepareStatementSafe(ManagedConnection connection, string sql)
+        {
+            return connection.PrepareStatement(sql);
         }
 
         public IStatement PrepareStatement(IDatabaseConnection connection, string sql)
@@ -143,22 +187,23 @@ namespace Emby.Server.Implementations.Data
             return connection.PrepareStatement(sql);
         }
 
-        public List<IStatement> PrepareAll(IDatabaseConnection connection, string sql)
+        public List<IStatement> PrepareAll(IDatabaseConnection connection, IEnumerable<string> sql)
         {
-            return connection.PrepareAll(sql).ToList();
+            return PrepareAllSafe(connection, sql);
         }
 
-        public List<IStatement> PrepareAllSafe(IDatabaseConnection connection, string sql)
+        public List<IStatement> PrepareAllSafe(IDatabaseConnection connection, IEnumerable<string> sql)
         {
-            return connection.PrepareAll(sql).ToList();
+            return sql.Select(connection.PrepareStatement).ToList();
         }
 
-        protected void RunDefaultInitialization(IDatabaseConnection db)
+        protected void RunDefaultInitialization(ManagedConnection db)
         {
             var queries = new List<string>
             {
                 "PRAGMA journal_mode=WAL",
                 "PRAGMA page_size=4096",
+                "PRAGMA synchronous=Normal"
             };
 
             if (EnableTempStoreMemory)
@@ -171,6 +216,7 @@ namespace Emby.Server.Implementations.Data
             }
 
             db.ExecuteAll(string.Join(";", queries.ToArray()));
+            Logger.Info("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
         }
 
         protected virtual bool EnableTempStoreMemory
@@ -238,6 +284,12 @@ namespace Emby.Server.Implementations.Data
                     {
                         using (WriteLock.Write())
                         {
+                            if (_connection != null)
+                            {
+                                _connection.Close();
+                                _connection = null;
+                            }
+
                             CloseConnection();
                         }
                     }
@@ -332,7 +384,7 @@ namespace Emby.Server.Implementations.Data
             //{
             //    return new DummyToken();
             //}
-            return new ReadLockToken(obj);
+            return new WriteLockToken(obj);
         }
         public static IDisposable Write(this ReaderWriterLockSlim obj)
         {

+ 1 - 1
Emby.Server.Implementations/Data/SqliteExtensions.cs

@@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Data
             }
         }
 
-        public static void Attach(IDatabaseConnection db, string path, string alias)
+        public static void Attach(ManagedConnection db, string path, string alias)
         {
             var commandText = string.Format("attach @path as {0};", alias);
 

+ 147 - 162
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -105,13 +105,7 @@ namespace Emby.Server.Implementations.Data
         {
             get
             {
-                var cacheSize = _config.Configuration.SqliteCacheSize;
-                if (cacheSize <= 0)
-                {
-                    cacheSize = Math.Min(Environment.ProcessorCount * 50000, 100000);
-                }
-
-                return 0 - cacheSize;
+                return 20000;
             }
         }
 
@@ -328,6 +322,8 @@ namespace Emby.Server.Implementations.Data
                 "drop table if exists Images",
                 "drop index if exists idx_Images",
                 "drop index if exists idx_TypeSeriesPresentationUniqueKey",
+                "drop index if exists idx_SeriesPresentationUniqueKey",
+                "drop index if exists idx_TypeSeriesPresentationUniqueKey2",
 
                 "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)",
                 "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
@@ -343,8 +339,9 @@ namespace Emby.Server.Implementations.Data
                 // series
                 "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)",
 
-                // series next up
-                "create index if not exists idx_SeriesPresentationUniqueKey on TypedBaseItems(SeriesPresentationUniqueKey)",
+                // series counts
+                // seriesdateplayed sort order
+                "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)",
 
                 // live tv programs
                 "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
@@ -373,9 +370,9 @@ namespace Emby.Server.Implementations.Data
                 //await Vacuum(_connection).ConfigureAwait(false);
             }
 
-            userDataRepo.Initialize(WriteLock);
+            userDataRepo.Initialize(WriteLock, _connection);
 
-            _shrinkMemoryTimer = _timerFactory.Create(OnShrinkMemoryTimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(30));
+            _shrinkMemoryTimer = _timerFactory.Create(OnShrinkMemoryTimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(15));
         }
 
         private void OnShrinkMemoryTimerCallback(object state)
@@ -698,12 +695,12 @@ namespace Emby.Server.Implementations.Data
         {
             var requiresReset = false;
 
-            var statements = PrepareAll(db, string.Join(";", new string[]
+            var statements = PrepareAllSafe(db, new string[]
             {
                 GetSaveItemCommandText(),
                 "delete from AncestorIds where ItemId=@ItemId",
                 "insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values (@ItemId, @AncestorId, @AncestorIdText)"
-            })).ToList();
+            }).ToList();
 
             using (var saveItemStatement = statements[0])
             {
@@ -1264,9 +1261,10 @@ namespace Emby.Server.Implementations.Data
                             return GetItem(row);
                         }
                     }
+
+                    return null;
                 }
             }
-            return null;
         }
 
         private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader)
@@ -2079,12 +2077,12 @@ namespace Emby.Server.Implementations.Data
                 throw new ArgumentNullException("id");
             }
 
-            var list = new List<ChapterInfo>();
-
             using (WriteLock.Read())
             {
                 using (var connection = CreateConnection(true))
                 {
+                    var list = new List<ChapterInfo>();
+
                     using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
                     {
                         statement.TryBind("@ItemId", id);
@@ -2094,10 +2092,10 @@ namespace Emby.Server.Implementations.Data
                             list.Add(GetChapter(row));
                         }
                     }
+
+                    return list;
                 }
             }
-
-            return list;
         }
 
         /// <summary>
@@ -2240,7 +2238,7 @@ namespace Emby.Server.Implementations.Data
 
             if (query.SimilarTo != null && query.User != null)
             {
-                return true;
+                //return true;
             }
 
             var sortingFields = query.SortBy.ToList();
@@ -2369,15 +2367,10 @@ namespace Emby.Server.Implementations.Data
                 builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 2 Else 0 End )");
                 builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 2 Else 0 End )");
 
-                //// genres
-                builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=2 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=2)) * 10)");
-
-                //// tags
-                builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=4 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=4)) * 10)");
-
-                builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=5 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=5)) * 10)");
+                //// genres, tags
+                builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type in (2,3,4,5) and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and Type in (2,3,4,5))) * 10)");
 
-                builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=3 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=3)) * 3)");
+                //builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=3 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=3)) * 3)");
 
                 //builder.Append("+ ((Select count(Name) from People where ItemId=Guid and Name in (select Name from People where ItemId=@SimilarItemId)) * 3)");
 
@@ -2475,8 +2468,6 @@ namespace Emby.Server.Implementations.Data
 
             //commandText += GetGroupBy(query);
 
-            int count = 0;
-
             using (WriteLock.Read())
             {
                 using (var connection = CreateConnection(true))
@@ -2493,14 +2484,13 @@ namespace Emby.Server.Implementations.Data
                         // Running this again will bind the params
                         GetWhereClauses(query, statement);
 
-                        count = statement.ExecuteQuery().SelectScalarInt().First();
+                        var count = statement.ExecuteQuery().SelectScalarInt().First();
+                        LogQueryTime("GetCount", commandText, now);
+                        return count;
                     }
                 }
 
-                LogQueryTime("GetCount", commandText, now);
             }
-
-            return count;
         }
 
         public List<BaseItem> GetItemList(InternalItemsQuery query)
@@ -2516,8 +2506,6 @@ namespace Emby.Server.Implementations.Data
 
             var now = DateTime.UtcNow;
 
-            var list = new List<BaseItem>();
-
             // Hack for right now since we currently don't support filtering out these duplicates within a query
             if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
             {
@@ -2558,53 +2546,59 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    using (var statement = PrepareStatementSafe(connection, commandText))
+                    return connection.RunInTransaction(db =>
                     {
-                        if (EnableJoinUserData(query))
+                        var list = new List<BaseItem>();
+
+                        using (var statement = PrepareStatementSafe(db, commandText))
                         {
-                            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);
+                                }
                             }
                         }
-                    }
-                }
 
-                LogQueryTime("GetItemList", commandText, now);
-            }
+                        // Hack for right now since we currently don't support filtering out these duplicates within a query
+                        if (query.EnableGroupByMetadataKey)
+                        {
+                            var limit = query.Limit ?? int.MaxValue;
+                            limit -= 4;
+                            var newList = new List<BaseItem>();
 
-            // Hack for right now since we currently don't support filtering out these duplicates within a query
-            if (query.EnableGroupByMetadataKey)
-            {
-                var limit = query.Limit ?? int.MaxValue;
-                limit -= 4;
-                var newList = new List<BaseItem>();
+                            foreach (var item in list)
+                            {
+                                AddItem(newList, item);
 
-                foreach (var item in list)
-                {
-                    AddItem(newList, item);
+                                if (newList.Count >= limit)
+                                {
+                                    break;
+                                }
+                            }
 
-                    if (newList.Count >= limit)
-                    {
-                        break;
-                    }
-                }
+                            list = newList;
+                        }
 
-                list = newList;
-            }
+                        LogQueryTime("GetItemList", commandText, now);
 
-            return list;
+                        return list;
+
+                    }, ReadTransactionMode);
+                }
+            }
         }
 
         private void AddItem(List<BaseItem> items, BaseItem newItem)
@@ -2642,7 +2636,7 @@ namespace Emby.Server.Implementations.Data
             var slowThreshold = 1000;
 
 #if DEBUG
-            slowThreshold = 50;
+            slowThreshold = 2;
 #endif
 
             if (elapsed >= slowThreshold)
@@ -2723,7 +2717,6 @@ namespace Emby.Server.Implementations.Data
                 }
             }
 
-            var result = new QueryResult<BaseItem>();
             var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
 
             var statementTexts = new List<string>();
@@ -2753,9 +2746,10 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    connection.RunInTransaction(db =>
+                    return connection.RunInTransaction(db =>
                     {
-                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray()))
+                        var result = new QueryResult<BaseItem>();
+                        var statements = PrepareAllSafe(db, statementTexts)
                             .ToList();
 
                         if (!isReturningZeroItems)
@@ -2801,12 +2795,12 @@ namespace Emby.Server.Implementations.Data
                             }
                         }
 
-                    }, ReadTransactionMode);
+                        LogQueryTime("GetItems", commandText, now);
 
-                    LogQueryTime("GetItems", commandText, now);
+                        result.Items = list.ToArray();
+                        return result;
 
-                    result.Items = list.ToArray();
-                    return result;
+                    }, ReadTransactionMode);
                 }
             }
         }
@@ -2967,12 +2961,12 @@ namespace Emby.Server.Implementations.Data
                 }
             }
 
-            var list = new List<Guid>();
-
             using (WriteLock.Read())
             {
                 using (var connection = CreateConnection(true))
                 {
+                    var list = new List<Guid>();
+
                     using (var statement = PrepareStatementSafe(connection, commandText))
                     {
                         if (EnableJoinUserData(query))
@@ -2990,11 +2984,11 @@ namespace Emby.Server.Implementations.Data
                             list.Add(row[0].ReadGuid());
                         }
                     }
-                }
 
-                LogQueryTime("GetItemList", commandText, now);
+                    LogQueryTime("GetItemList", commandText, now);
 
-                return list;
+                    return list;
+                }
             }
         }
 
@@ -3158,11 +3152,11 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    var result = new QueryResult<Guid>();
-
-                    connection.RunInTransaction(db =>
+                    return connection.RunInTransaction(db =>
                     {
-                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray()))
+                        var result = new QueryResult<Guid>();
+
+                        var statements = PrepareAllSafe(db, statementTexts)
                             .ToList();
 
                         if (!isReturningZeroItems)
@@ -3204,12 +3198,12 @@ namespace Emby.Server.Implementations.Data
                             }
                         }
 
-                    }, ReadTransactionMode);
+                        LogQueryTime("GetItemIds", commandText, now);
 
-                    LogQueryTime("GetItemIds", commandText, now);
+                        result.Items = list.ToArray();
+                        return result;
 
-                    result.Items = list.ToArray();
-                    return result;
+                    }, ReadTransactionMode);
                 }
             }
         }
@@ -4658,26 +4652,23 @@ namespace Emby.Server.Implementations.Data
 
             commandText += " order by ListOrder";
 
-            var list = new List<string>();
             using (WriteLock.Read())
             {
                 using (var connection = CreateConnection(true))
                 {
-                    connection.RunInTransaction(db =>
+                    var list = new List<string>();
+                    using (var statement = PrepareStatementSafe(connection, commandText))
                     {
-                        using (var statement = PrepareStatementSafe(db, commandText))
-                        {
-                            // Run this again to bind the params
-                            GetPeopleWhereClauses(query, statement);
+                        // Run this again to bind the params
+                        GetPeopleWhereClauses(query, statement);
 
-                            foreach (var row in statement.ExecuteQuery())
-                            {
-                                list.Add(row.GetString(0));
-                            }
+                        foreach (var row in statement.ExecuteQuery())
+                        {
+                            list.Add(row.GetString(0));
                         }
-                    }, ReadTransactionMode);
+                    }
+                    return list;
                 }
-                return list;
             }
         }
 
@@ -4701,29 +4692,26 @@ namespace Emby.Server.Implementations.Data
 
             commandText += " order by ListOrder";
 
-            var list = new List<PersonInfo>();
-
             using (WriteLock.Read())
             {
                 using (var connection = CreateConnection(true))
                 {
-                    connection.RunInTransaction(db =>
+                    var list = new List<PersonInfo>();
+
+                    using (var statement = PrepareStatementSafe(connection, commandText))
                     {
-                        using (var statement = PrepareStatementSafe(db, commandText))
-                        {
-                            // Run this again to bind the params
-                            GetPeopleWhereClauses(query, statement);
+                        // Run this again to bind the params
+                        GetPeopleWhereClauses(query, statement);
 
-                            foreach (var row in statement.ExecuteQuery())
-                            {
-                                list.Add(GetPerson(row));
-                            }
+                        foreach (var row in statement.ExecuteQuery())
+                        {
+                            list.Add(GetPerson(row));
                         }
-                    }, ReadTransactionMode);
+                    }
+
+                    return list;
                 }
             }
-
-            return list;
         }
 
         private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, IStatement statement)
@@ -4904,8 +4892,6 @@ namespace Emby.Server.Implementations.Data
                 ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
                 ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")");
 
-            var list = new List<string>();
-
             var commandText = "Select Value From ItemValues where " + typeClause;
 
             if (withItemTypes.Count > 0)
@@ -4925,24 +4911,24 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    connection.RunInTransaction(db =>
+                    var list = new List<string>();
+
+                    using (var statement = PrepareStatementSafe(connection, commandText))
                     {
-                        using (var statement = PrepareStatementSafe(db, commandText))
+                        foreach (var row in statement.ExecuteQuery())
                         {
-                            foreach (var row in statement.ExecuteQuery())
+                            if (!row.IsDBNull(0))
                             {
-                                if (!row.IsDBNull(0))
-                                {
-                                    list.Add(row.GetString(0));
-                                }
+                                list.Add(row.GetString(0));
                             }
                         }
-                    }, ReadTransactionMode);
+                    }
+
+                    LogQueryTime("GetItemValueNames", commandText, now);
+
+                    return list;
                 }
             }
-            LogQueryTime("GetItemValueNames", commandText, now);
-
-            return list;
         }
 
         private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
@@ -5086,9 +5072,6 @@ namespace Emby.Server.Implementations.Data
 
             var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
 
-            var list = new List<Tuple<BaseItem, ItemCounts>>();
-            var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
-
             var statementTexts = new List<string>();
             if (!isReturningZeroItems)
             {
@@ -5107,9 +5090,13 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    connection.RunInTransaction(db =>
+                    return connection.RunInTransaction(db =>
                     {
-                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray())).ToList();
+                        var list = new List<Tuple<BaseItem, ItemCounts>>();
+                        var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
+
+                        var statements = PrepareAllSafe(db, statementTexts)
+                            .ToList();
 
                         if (!isReturningZeroItems)
                         {
@@ -5172,17 +5159,18 @@ namespace Emby.Server.Implementations.Data
                                 LogQueryTime("GetItemValues", commandText, now);
                             }
                         }
+
+                        if (result.TotalRecordCount == 0)
+                        {
+                            result.TotalRecordCount = list.Count;
+                        }
+                        result.Items = list.ToArray();
+
+                        return result;
+
                     }, ReadTransactionMode);
                 }
             }
-
-            if (result.TotalRecordCount == 0)
-            {
-                result.TotalRecordCount = list.Count;
-            }
-            result.Items = list.ToArray();
-
-            return result;
         }
 
         private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, List<string> typesToCount)
@@ -5395,8 +5383,6 @@ namespace Emby.Server.Implementations.Data
                 throw new ArgumentNullException("query");
             }
 
-            var list = new List<MediaStream>();
-
             var cmdText = "select " + string.Join(",", _mediaStreamSaveColumns) + " from mediastreams where";
 
             cmdText += " ItemId=@ItemId";
@@ -5417,32 +5403,31 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    connection.RunInTransaction(db =>
+                    var list = new List<MediaStream>();
+
+                    using (var statement = PrepareStatementSafe(connection, cmdText))
                     {
-                        using (var statement = PrepareStatementSafe(db, cmdText))
-                        {
-                            statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue());
+                        statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue());
 
-                            if (query.Type.HasValue)
-                            {
-                                statement.TryBind("@StreamType", query.Type.Value.ToString());
-                            }
+                        if (query.Type.HasValue)
+                        {
+                            statement.TryBind("@StreamType", query.Type.Value.ToString());
+                        }
 
-                            if (query.Index.HasValue)
-                            {
-                                statement.TryBind("@StreamIndex", query.Index.Value);
-                            }
+                        if (query.Index.HasValue)
+                        {
+                            statement.TryBind("@StreamIndex", query.Index.Value);
+                        }
 
-                            foreach (var row in statement.ExecuteQuery())
-                            {
-                                list.Add(GetMediaStream(row));
-                            }
+                        foreach (var row in statement.ExecuteQuery())
+                        {
+                            list.Add(GetMediaStream(row));
                         }
-                    }, ReadTransactionMode);
+                    }
+
+                    return list;
                 }
             }
-
-            return list;
         }
 
         public async Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)

+ 13 - 17
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -42,8 +42,10 @@ namespace Emby.Server.Implementations.Data
         /// Opens the connection to the database
         /// </summary>
         /// <returns>Task.</returns>
-        public void Initialize(ReaderWriterLockSlim writeLock)
+        public void Initialize(ReaderWriterLockSlim writeLock, ManagedConnection managedConnection)
         {
+            _connection = managedConnection;
+
             WriteLock.Dispose();
             WriteLock = writeLock;
 
@@ -90,7 +92,7 @@ namespace Emby.Server.Implementations.Data
             }
         }
 
-        private void ImportUserDataIfNeeded(IDatabaseConnection connection)
+        private void ImportUserDataIfNeeded(ManagedConnection connection)
         {
             if (!_fileSystem.FileExists(_importFile))
             {
@@ -117,7 +119,7 @@ namespace Emby.Server.Implementations.Data
             }, TransactionMode);
         }
 
-        private void ImportUserData(IDatabaseConnection connection, string file)
+        private void ImportUserData(ManagedConnection connection, string file)
         {
             SqliteExtensions.Attach(connection, file, "UserDataBackup");
 
@@ -300,24 +302,18 @@ namespace Emby.Server.Implementations.Data
             {
                 using (var connection = CreateConnection(true))
                 {
-                    UserItemData result = null;
-
-                    connection.RunInTransaction(db =>
+                    using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId"))
                     {
-                        using (var statement = db.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);
+
+                        foreach (var row in statement.ExecuteQuery())
                         {
-                            statement.TryBind("@UserId", userId.ToGuidParamValue());
-                            statement.TryBind("@Key", key);
-
-                            foreach (var row in statement.ExecuteQuery())
-                            {
-                                result = ReadRow(row);
-                                break;
-                            }
+                            return ReadRow(row);
                         }
-                    }, ReadTransactionMode);
+                    }
 
-                    return result;
+                    return null;
                 }
             }
         }

+ 70 - 35
Emby.Server.Implementations/Dto/DtoService.cs

@@ -459,12 +459,21 @@ namespace Emby.Server.Implementations.Dto
 
                 if (dtoOptions.EnableUserData)
                 {
-                    dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false);
+                    dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user, dtoOptions.Fields).ConfigureAwait(false);
                 }
 
                 if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
                 {
-                    dto.ChildCount = GetChildCount(folder, user);
+                    // For these types we can try to optimize and assume these values will be equal
+                    if (item is MusicAlbum || item is Season)
+                    {
+                        dto.ChildCount = dto.RecursiveItemCount;
+                    }
+
+                    if (dtoOptions.Fields.Contains(ItemFields.ChildCount))
+                    {
+                        dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user);
+                    }
                 }
 
                 if (fields.Contains(ItemFields.CumulativeRunTimeTicks))
@@ -1151,28 +1160,29 @@ namespace Emby.Server.Implementations.Dto
             {
                 dto.Artists = hasArtist.Artists;
 
-                var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
-                {
-                    EnableTotalRecordCount = false,
-                    ItemIds = new[] { item.Id.ToString("N") }
-                });
-
-                dto.ArtistItems = artistItems.Items
-                    .Select(i =>
-                    {
-                        var artist = i.Item1;
-                        return new NameIdPair
-                        {
-                            Name = artist.Name,
-                            Id = artist.Id.ToString("N")
-                        };
-                    })
-                    .ToList();
+                //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
+                //{
+                //    EnableTotalRecordCount = false,
+                //    ItemIds = new[] { item.Id.ToString("N") }
+                //});
+
+                //dto.ArtistItems = artistItems.Items
+                //    .Select(i =>
+                //    {
+                //        var artist = i.Item1;
+                //        return new NameIdPair
+                //        {
+                //            Name = artist.Name,
+                //            Id = artist.Id.ToString("N")
+                //        };
+                //    })
+                //    .ToList();
 
                 // Include artists that are not in the database yet, e.g., just added via metadata editor
-                var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
+                //var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
+                dto.ArtistItems = new List<NameIdPair>();
                 dto.ArtistItems.AddRange(hasArtist.Artists
-                    .Except(foundArtists, new DistinctNameComparer())
+                    //.Except(foundArtists, new DistinctNameComparer())
                     .Select(i =>
                     {
                         // This should not be necessary but we're seeing some cases of it
@@ -1201,23 +1211,48 @@ namespace Emby.Server.Implementations.Dto
             {
                 dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 
-                var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
-                {
-                    EnableTotalRecordCount = false,
-                    ItemIds = new[] { item.Id.ToString("N") }
-                });
-
-                dto.AlbumArtists = artistItems.Items
+                //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
+                //{
+                //    EnableTotalRecordCount = false,
+                //    ItemIds = new[] { item.Id.ToString("N") }
+                //});
+
+                //dto.AlbumArtists = artistItems.Items
+                //    .Select(i =>
+                //    {
+                //        var artist = i.Item1;
+                //        return new NameIdPair
+                //        {
+                //            Name = artist.Name,
+                //            Id = artist.Id.ToString("N")
+                //        };
+                //    })
+                //    .ToList();
+
+                dto.AlbumArtists = new List<NameIdPair>();
+                dto.AlbumArtists.AddRange(hasAlbumArtist.AlbumArtists
+                    //.Except(foundArtists, new DistinctNameComparer())
                     .Select(i =>
                     {
-                        var artist = i.Item1;
-                        return new NameIdPair
+                        // This should not be necessary but we're seeing some cases of it
+                        if (string.IsNullOrWhiteSpace(i))
                         {
-                            Name = artist.Name,
-                            Id = artist.Id.ToString("N")
-                        };
-                    })
-                    .ToList();
+                            return null;
+                        }
+
+                        var artist = _libraryManager.GetArtist(i);
+                        if (artist != null)
+                        {
+                            return new NameIdPair
+                            {
+                                Name = artist.Name,
+                                Id = artist.Id.ToString("N")
+                            };
+                        }
+
+                        return null;
+
+                    }).Where(i => i != null));
             }
 
             // Add video info

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

@@ -51,6 +51,7 @@
     <Compile Include="Connect\ConnectManager.cs" />
     <Compile Include="Connect\Responses.cs" />
     <Compile Include="Connect\Validator.cs" />
+    <Compile Include="Data\ManagedConnection.cs" />
     <Compile Include="Data\SqliteDisplayPreferencesRepository.cs" />
     <Compile Include="Data\SqliteFileOrganizationRepository.cs" />
     <Compile Include="Data\SqliteItemRepository.cs" />
@@ -180,6 +181,7 @@
     <Compile Include="Localization\LocalizationManager.cs" />
     <Compile Include="MediaEncoder\EncodingManager.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
+    <Compile Include="Migrations\LibraryScanMigration.cs" />
     <Compile Include="Migrations\UpdateLevelMigration.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />
     <Compile Include="News\NewsService.cs" />

+ 6 - 0
Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -519,6 +519,12 @@ namespace Emby.Server.Implementations.FileOrganization
 
         private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result)
         {
+            // We should probably handle this earlier so that we never even make it this far
+            if (string.Equals(result.OriginalPath, result.TargetPath, StringComparison.OrdinalIgnoreCase))
+            {
+                return;
+            }
+
             _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath);
 
             _fileSystem.CreateDirectory(Path.GetDirectoryName(result.TargetPath));

+ 2 - 1
Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs

@@ -76,7 +76,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
 
         private void ProcessContext(HttpListenerContext context)
         {
-            Task.Factory.StartNew(() => InitTask(context));
+            //Task.Factory.StartNew(() => InitTask(context), TaskCreationOptions.DenyChildAttach | TaskCreationOptions.PreferFairness);
+            Task.Run(() => InitTask(context));
         }
 
         private Task InitTask(HttpListenerContext context)

+ 41 - 17
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -817,7 +817,31 @@ namespace Emby.Server.Implementations.Library
 
             return _userRootFolder;
         }
-        
+
+        public Guid? FindIdByPath(string path, bool? isFolder)
+        {
+            // If this returns multiple items it could be tricky figuring out which one is correct. 
+            // In most cases, the newest one will be and the others obsolete but not yet cleaned up
+
+            var query = new InternalItemsQuery
+            {
+                Path = path,
+                IsFolder = isFolder,
+                SortBy = new[] { ItemSortBy.DateCreated },
+                SortOrder = SortOrder.Descending,
+                Limit = 1
+            };
+
+            var id = GetItemIds(query);
+
+            if (id.Count == 0)
+            {
+                return null;
+            }
+
+            return id[0];
+        }
+
         public BaseItem FindByPath(string path, bool? isFolder)
         {
             // If this returns multiple items it could be tricky figuring out which one is correct. 
@@ -1430,7 +1454,7 @@ namespace Emby.Server.Implementations.Library
             }))
             {
                 // Optimize by querying against top level views
-                query.TopParentIds = parents.SelectMany(i => GetTopParentsForQuery(i, query.User)).Select(i => i.Id.ToString("N")).ToArray();
+                query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).Select(i => i.ToString("N")).ToArray();
                 query.AncestorIds = new string[] { };
             }
         }
@@ -1489,7 +1513,7 @@ namespace Emby.Server.Implementations.Library
             }))
             {
                 // Optimize by querying against top level views
-                query.TopParentIds = parents.SelectMany(i => GetTopParentsForQuery(i, query.User)).Select(i => i.Id.ToString("N")).ToArray();
+                query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).Select(i => i.ToString("N")).ToArray();
             }
             else
             {
@@ -1515,11 +1539,11 @@ namespace Emby.Server.Implementations.Library
 
                 }, CancellationToken.None).Result.ToList();
 
-                query.TopParentIds = userViews.SelectMany(i => GetTopParentsForQuery(i, user)).Select(i => i.Id.ToString("N")).ToArray();
+                query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).Select(i => i.ToString("N")).ToArray();
             }
         }
 
-        private IEnumerable<BaseItem> GetTopParentsForQuery(BaseItem item, User user)
+        private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User user)
         {
             var view = item as UserView;
 
@@ -1527,7 +1551,7 @@ namespace Emby.Server.Implementations.Library
             {
                 if (string.Equals(view.ViewType, CollectionType.LiveTv))
                 {
-                    return new[] { view };
+                    return new[] { view.Id };
                 }
                 if (string.Equals(view.ViewType, CollectionType.Channels))
                 {
@@ -1537,7 +1561,7 @@ namespace Emby.Server.Implementations.Library
 
                     }, CancellationToken.None).Result;
 
-                    return channelResult.Items;
+                    return channelResult.Items.Select(i =>  i.Id);
                 }
 
                 // Translate view into folders
@@ -1546,18 +1570,18 @@ namespace Emby.Server.Implementations.Library
                     var displayParent = GetItemById(view.DisplayParentId);
                     if (displayParent != null)
                     {
-                        return GetTopParentsForQuery(displayParent, user);
+                        return GetTopParentIdsForQuery(displayParent, user);
                     }
-                    return new BaseItem[] { };
+                    return new Guid[] { };
                 }
                 if (view.ParentId != Guid.Empty)
                 {
                     var displayParent = GetItemById(view.ParentId);
                     if (displayParent != null)
                     {
-                        return GetTopParentsForQuery(displayParent, user);
+                        return GetTopParentIdsForQuery(displayParent, user);
                     }
-                    return new BaseItem[] { };
+                    return new Guid[] { };
                 }
 
                 // Handle grouping
@@ -1568,23 +1592,23 @@ namespace Emby.Server.Implementations.Library
                         .OfType<CollectionFolder>()
                         .Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase))
                         .Where(i => user.IsFolderGrouped(i.Id))
-                        .SelectMany(i => GetTopParentsForQuery(i, user));
+                        .SelectMany(i => GetTopParentIdsForQuery(i, user));
                 }
-                return new BaseItem[] { };
+                return new Guid[] { };
             }
 
             var collectionFolder = item as CollectionFolder;
             if (collectionFolder != null)
             {
-                return collectionFolder.GetPhysicalParents();
+                return collectionFolder.PhysicalFolderIds;
             }
-
+            
             var topParent = item.GetTopParent();
             if (topParent != null)
             {
-                return new[] { topParent };
+                return new[] { topParent.Id };
             }
-            return new BaseItem[] { };
+            return new Guid[] { };
         }
 
         /// <summary>

+ 4 - 3
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -12,6 +12,7 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Querying;
 
 namespace Emby.Server.Implementations.Library
 {
@@ -186,16 +187,16 @@ namespace Emby.Server.Implementations.Library
             var userData = GetUserData(user.Id, item);
             var dto = GetUserItemDataDto(userData);
 
-            await item.FillUserDataDtoValues(dto, userData, null, user).ConfigureAwait(false);
+            await item.FillUserDataDtoValues(dto, userData, null, user, new List<ItemFields>()).ConfigureAwait(false);
             return dto;
         }
 
-        public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user)
+        public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields)
         {
             var userData = GetUserData(user.Id, item);
             var dto = GetUserItemDataDto(userData);
 
-            await item.FillUserDataDtoValues(dto, userData, itemDto, user).ConfigureAwait(false);
+            await item.FillUserDataDtoValues(dto, userData, itemDto, user, fields).ConfigureAwait(false);
             return dto;
         }
 

+ 21 - 1
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -328,15 +328,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 }
                 await UpdateTimersForSeriesTimer(epgData, timer, true).ConfigureAwait(false);
             }
+        }
 
+        public async Task RefreshTimers(CancellationToken cancellationToken, IProgress<double> progress)
+        {
             var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
 
-            foreach (var timer in timers.ToList())
+            foreach (var timer in timers)
             {
                 if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id))
                 {
                     OnTimerOutOfDate(timer);
+                    continue;
+                }
+
+                if (string.IsNullOrWhiteSpace(timer.ProgramId) || string.IsNullOrWhiteSpace(timer.ChannelId))
+                {
+                    continue;
+                }
+
+                var epg = GetEpgDataForChannel(timer.ChannelId);
+                var program = epg.FirstOrDefault(i => string.Equals(i.Id, timer.ProgramId, StringComparison.OrdinalIgnoreCase));
+                if (program == null)
+                {
+                    OnTimerOutOfDate(timer);
+                    continue;
                 }
+
+                RecordingHelper.CopyProgramInfoToTimerInfo(program, timer);
+                _timerProvider.Update(timer);
             }
         }
 

+ 6 - 0
Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs

@@ -41,6 +41,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo)
         {
+            timerInfo.Name = programInfo.Name;
+            timerInfo.StartDate = programInfo.StartDate;
+            timerInfo.EndDate = programInfo.EndDate;
+            timerInfo.ChannelId = programInfo.ChannelId;
+
             timerInfo.SeasonNumber = programInfo.SeasonNumber;
             timerInfo.EpisodeNumber = programInfo.EpisodeNumber;
             timerInfo.IsMovie = programInfo.IsMovie;
@@ -54,6 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             timerInfo.HomePageUrl = programInfo.HomePageUrl;
             timerInfo.CommunityRating = programInfo.CommunityRating;
+            timerInfo.Overview = programInfo.Overview;
             timerInfo.ShortOverview = programInfo.ShortOverview;
             timerInfo.OfficialRating = programInfo.OfficialRating;
             timerInfo.IsRepeat = programInfo.IsRepeat;

+ 1 - 0
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1231,6 +1231,7 @@ namespace Emby.Server.Implementations.LiveTv
             if (coreService != null)
             {
                 await coreService.RefreshSeriesTimers(cancellationToken, new Progress<double>()).ConfigureAwait(false);
+                await coreService.RefreshTimers(cancellationToken, new Progress<double>()).ConfigureAwait(false);
             }
 
             // Load these now which will prefetch metadata

+ 10 - 10
Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs

@@ -65,9 +65,9 @@ namespace Emby.Server.Implementations.Notifications
 
             var whereClause = " where " + string.Join(" And ", clauses.ToArray());
 
-            using (var connection = CreateConnection(true))
+            using (WriteLock.Read())
             {
-                lock (WriteLock)
+                using (var connection = CreateConnection(true))
                 {
                     result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray()).SelectScalarInt().First();
 
@@ -106,9 +106,9 @@ namespace Emby.Server.Implementations.Notifications
         {
             var result = new NotificationsSummary();
 
-            using (var connection = CreateConnection(true))
+            using (WriteLock.Read())
             {
-                lock (WriteLock)
+                using (var connection = CreateConnection(true))
                 {
                     using (var statement = connection.PrepareStatement("select Level from Notifications where UserId=@UserId and IsRead=@IsRead"))
                     {
@@ -223,9 +223,9 @@ namespace Emby.Server.Implementations.Notifications
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            using (var connection = CreateConnection())
+            lock (WriteLock)
             {
-                lock (WriteLock)
+                using (var connection = CreateConnection())
                 {
                     connection.RunInTransaction(conn =>
                     {
@@ -286,9 +286,9 @@ namespace Emby.Server.Implementations.Notifications
         {
             cancellationToken.ThrowIfCancellationRequested();
 
-            using (var connection = CreateConnection())
+            using (WriteLock.Write())
             {
-                lock (WriteLock)
+                using (var connection = CreateConnection())
                 {
                     connection.RunInTransaction(conn =>
                     {
@@ -308,9 +308,9 @@ namespace Emby.Server.Implementations.Notifications
         {
             cancellationToken.ThrowIfCancellationRequested();
 
-            using (var connection = CreateConnection())
+            using (WriteLock.Write())
             {
-                lock (WriteLock)
+                using (var connection = CreateConnection())
                 {
                     connection.RunInTransaction(conn =>
                     {

+ 7 - 7
Emby.Server.Implementations/Security/AuthenticationRepository.cs

@@ -206,15 +206,15 @@ namespace Emby.Server.Implementations.Security
             {
                 using (var connection = CreateConnection(true))
                 {
-                    var result = new QueryResult<AuthenticationInfo>();
-
-                    connection.RunInTransaction(db =>
+                    return connection.RunInTransaction(db =>
                     {
+                        var result = new QueryResult<AuthenticationInfo>();
+
                         var statementTexts = new List<string>();
                         statementTexts.Add(commandText);
                         statementTexts.Add("select count (Id) from AccessTokens" + whereTextWithoutPaging);
 
-                        var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray()))
+                        var statements = PrepareAllSafe(db, statementTexts)
                             .ToList();
 
                         using (var statement = statements[0])
@@ -236,10 +236,10 @@ namespace Emby.Server.Implementations.Security
                             }
                         }
 
-                    }, ReadTransactionMode);
+                        result.Items = list.ToArray();
+                        return result;
 
-                    result.Items = list.ToArray();
-                    return result;
+                    }, ReadTransactionMode);
                 }
             }
         }

+ 1 - 1
Emby.Server.Implementations/TV/TVSeriesManager.cs

@@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.TV
 
             // If viewing all next up for all series, remove first episodes
             // But if that returns empty, keep those first episodes (avoid completely empty view)
-            var alwaysEnableFirstEpisode = string.IsNullOrWhiteSpace(request.SeriesId);
+            var alwaysEnableFirstEpisode = !string.IsNullOrWhiteSpace(request.SeriesId);
             var isFirstItemAFirstEpisode = true;
 
             return allNextUp

+ 31 - 1
MediaBrowser.Api/BaseApiService.cs

@@ -121,7 +121,9 @@ namespace MediaBrowser.Api
         {
             var options = new DtoOptions();
 
-            options.DeviceId = authContext.GetAuthorizationInfo(Request).DeviceId;
+            var authInfo = authContext.GetAuthorizationInfo(Request);
+
+            options.DeviceId = authInfo.DeviceId;
 
             var hasFields = request as IHasItemFields;
             if (hasFields != null)
@@ -129,6 +131,34 @@ namespace MediaBrowser.Api
                 options.Fields = hasFields.GetItemFields().ToList();
             }
 
+            var client = authInfo.Client ?? string.Empty;
+            if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
+                client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
+                client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
+                client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                options.Fields.Add(Model.Querying.ItemFields.RecursiveItemCount);
+            }
+
+            if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
+               client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
+               client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
+               client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 ||
+               client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 ||
+               client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                options.Fields.Add(Model.Querying.ItemFields.ChildCount);
+            }
+
+            if (client.IndexOf("web", StringComparison.OrdinalIgnoreCase) == -1 &&
+                client.IndexOf("mobile", StringComparison.OrdinalIgnoreCase) == -1 &&
+                client.IndexOf("ios", StringComparison.OrdinalIgnoreCase) == -1 &&
+                client.IndexOf("android", StringComparison.OrdinalIgnoreCase) == -1 &&
+                client.IndexOf("theater", StringComparison.OrdinalIgnoreCase) == -1)
+            {
+                options.Fields.Add(Model.Querying.ItemFields.ChildCount);
+            }
+
             var hasDtoOptions = request as IHasDtoOptions;
             if (hasDtoOptions != null)
             {

+ 13 - 2
MediaBrowser.Api/ItemLookupService.cs

@@ -14,8 +14,6 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Services;
@@ -88,6 +86,12 @@ namespace MediaBrowser.Api
     {
     }
 
+    [Route("/Items/RemoteSearch/Book", "POST")]
+    [Authenticated]
+    public class GetBookRemoteSearchResults : RemoteSearchQuery<BookInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
     [Route("/Items/RemoteSearch/Image", "GET", Summary = "Gets a remote image")]
     public class GetRemoteSearchImage
     {
@@ -147,6 +151,13 @@ namespace MediaBrowser.Api
             return ToOptimizedResult(result);
         }
 
+        public async Task<object> Post(GetBookRemoteSearchResults request)
+        {
+            var result = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(request, CancellationToken.None).ConfigureAwait(false);
+
+            return ToOptimizedResult(result);
+        }
+
         public async Task<object> Post(GetMovieRemoteSearchResults request)
         {
             var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false);

+ 4 - 2
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -2358,7 +2358,8 @@ namespace MediaBrowser.Api.Playback
                 state.TargetVideoStreamCount,
                 state.TargetAudioStreamCount,
                 state.TargetVideoCodecTag,
-                state.IsTargetAVC);
+                state.IsTargetAVC,
+                state.AllAudioCodecs);
 
             if (mediaProfile != null)
             {
@@ -2580,7 +2581,8 @@ namespace MediaBrowser.Api.Playback
                     state.TargetVideoStreamCount,
                     state.TargetAudioStreamCount,
                     state.TargetVideoCodecTag,
-                    state.IsTargetAVC
+                    state.IsTargetAVC,
+                    state.AllAudioCodecs
 
                     ).FirstOrDefault() ?? string.Empty;
             }

+ 12 - 0
MediaBrowser.Api/Playback/StreamState.cs

@@ -11,6 +11,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Linq;
 using System.Threading;
 
 namespace MediaBrowser.Api.Playback
@@ -244,6 +245,17 @@ namespace MediaBrowser.Api.Playback
         public int? OutputAudioBitrate;
         public int? OutputVideoBitrate;
 
+        public List<string> AllAudioCodecs
+        {
+            get
+            {
+                return MediaSource.MediaStreams.Where(i => i.Type == MediaStreamType.Audio)
+                        .Select(i => i.Codec)
+                        .Where(i => !string.IsNullOrWhiteSpace(i))
+                        .ToList();
+            }
+        }
+
         public string ActualOutputVideoCodec
         {
             get

+ 2 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -30,6 +30,7 @@ using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Serialization;
 
 namespace MediaBrowser.Controller.Entities
@@ -2191,7 +2192,7 @@ namespace MediaBrowser.Controller.Entities
             return path;
         }
 
-        public virtual Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
+        public virtual Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, List<ItemFields> itemFields)
         {
             if (RunTimeTicks.HasValue)
             {

+ 46 - 22
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -27,6 +27,7 @@ namespace MediaBrowser.Controller.Entities
         public CollectionFolder()
         {
             PhysicalLocationsList = new List<string>();
+            PhysicalFolderIds = new List<Guid>();
         }
 
         [IgnoreDataMember]
@@ -153,6 +154,7 @@ namespace MediaBrowser.Controller.Entities
         }
 
         public List<string> PhysicalLocationsList { get; set; }
+        public List<Guid> PhysicalFolderIds { get; set; }
 
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         {
@@ -176,6 +178,18 @@ namespace MediaBrowser.Controller.Entities
                 }
             }
 
+            if (!changed)
+            {
+                var folderIds = PhysicalFolderIds.ToList();
+
+                var newFolderIds = GetPhysicalFolders(false).Select(i => i.Id).ToList();
+
+                if (!folderIds.SequenceEqual(newFolderIds))
+                {
+                    changed = true;
+                }
+            }
+
             return changed;
         }
 
@@ -186,6 +200,31 @@ namespace MediaBrowser.Controller.Entities
             return changed;
         }
 
+        protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
+        {
+            var physicalFolders = GetPhysicalFolders(false)
+                .ToList();
+
+            var linkedChildren = physicalFolders
+                .SelectMany(c => c.LinkedChildren)
+                .ToList();
+
+            var changed = !linkedChildren.SequenceEqual(LinkedChildren, new LinkedChildComparer());
+
+            LinkedChildren = linkedChildren;
+
+            var folderIds = PhysicalFolderIds.ToList();
+            var newFolderIds = physicalFolders.Select(i => i.Id).ToList();
+
+            if (!folderIds.SequenceEqual(newFolderIds))
+            {
+                changed = true;
+                PhysicalFolderIds = newFolderIds.ToList();
+            }
+
+            return changed;
+        }
+
         internal override bool IsValidFromResolver(BaseItem newItem)
         {
             var newCollectionFolder = newItem as CollectionFolder;
@@ -260,26 +299,6 @@ namespace MediaBrowser.Controller.Entities
             return Task.FromResult(true);
         }
 
-        /// <summary>
-        /// 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(); }
-            set
-            {
-                base.LinkedChildren = value;
-            }
-        }
-        private List<LinkedChild> GetLinkedChildrenInternal()
-        {
-            return GetPhysicalParents()
-                .SelectMany(c => c.LinkedChildren)
-                .ToList();
-        }
-
         /// <summary>
         /// Our children are actually just references to the ones in the physical root...
         /// </summary>
@@ -292,11 +311,16 @@ namespace MediaBrowser.Controller.Entities
 
         private IEnumerable<BaseItem> GetActualChildren()
         {
-            return GetPhysicalParents().SelectMany(c => c.Children);
+            return GetPhysicalFolders(true).SelectMany(c => c.Children);
         }
 
-        public IEnumerable<Folder> GetPhysicalParents()
+        private IEnumerable<Folder> GetPhysicalFolders(bool enableCache)
         {
+            if (enableCache)
+            {
+                return PhysicalFolderIds.Select(i => LibraryManager.GetItemById(i)).OfType<Folder>();
+            }
+
             var rootChildren = LibraryManager.RootFolder.Children
                 .OfType<Folder>()
                 .ToList();

+ 20 - 16
MediaBrowser.Controller/Entities/Folder.cs

@@ -1222,7 +1222,7 @@ namespace MediaBrowser.Controller.Entities
         /// Refreshes the linked children.
         /// </summary>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
+        protected virtual bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
         {
             var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
             var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
@@ -1410,23 +1410,24 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        public override async Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
+        public override async Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, List<ItemFields> itemFields)
         {
             if (!SupportsUserDataFromChildren)
             {
                 return;
             }
 
-            var recursiveItemCount = GetRecursiveChildCount(user);
-
             if (itemDto != null)
             {
-                itemDto.RecursiveItemCount = recursiveItemCount;
+                if (itemFields.Contains(ItemFields.RecursiveItemCount))
+                {
+                    itemDto.RecursiveItemCount = GetRecursiveChildCount(user);
+                }
             }
 
-            if (recursiveItemCount > 0 && SupportsPlayedStatus)
+            if (SupportsPlayedStatus)
             {
-                var unplayedQueryResult = recursiveItemCount > 0 ? await GetItems(new InternalItemsQuery(user)
+                var unplayedQueryResult = await GetItems(new InternalItemsQuery(user)
                 {
                     Recursive = true,
                     IsFolder = false,
@@ -1435,21 +1436,24 @@ namespace MediaBrowser.Controller.Entities
                     Limit = 0,
                     IsPlayed = false
 
-                }).ConfigureAwait(false) : new QueryResult<BaseItem>();
+                }).ConfigureAwait(false);
 
                 double unplayedCount = unplayedQueryResult.TotalRecordCount;
 
-                var unplayedPercentage = (unplayedCount / recursiveItemCount) * 100;
-                dto.PlayedPercentage = 100 - unplayedPercentage;
-                dto.Played = dto.PlayedPercentage.Value >= 100;
                 dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
-            }
 
-            if (itemDto != null)
-            {
-                if (this is Season || this is MusicAlbum)
+                if (itemDto != null && itemDto.RecursiveItemCount.HasValue)
+                {
+                    if (itemDto.RecursiveItemCount.Value > 0)
+                    {
+                        var unplayedPercentage = (unplayedCount/itemDto.RecursiveItemCount.Value)*100;
+                        dto.PlayedPercentage = 100 - unplayedPercentage;
+                        dto.Played = dto.PlayedPercentage.Value >= 100;
+                    }
+                }
+                else
                 {
-                    itemDto.ChildCount = recursiveItemCount;
+                    dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
                 }
             }
         }

+ 2 - 4
MediaBrowser.Controller/Entities/IHasUserData.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -14,10 +15,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// Fills the user data dto values.
         /// </summary>
-        /// <param name="dto">The dto.</param>
-        /// <param name="userData">The user data.</param>
-        /// <param name="user">The user.</param>
-        Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user);
+        Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, List<ItemFields> fields);
 
         bool EnableRememberingTrackSelections { get; }
 

+ 2 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -62,6 +62,8 @@ namespace MediaBrowser.Controller.Library
         /// <returns>BaseItem.</returns>
         BaseItem FindByPath(string path, bool? isFolder);
 
+        Guid? FindIdByPath(string path, bool? isFolder);
+
         /// <summary>
         /// Gets the artist.
         /// </summary>

+ 2 - 4
MediaBrowser.Controller/Library/IUserDataManager.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Model.Entities;
 using System;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Controller.Library
 {
@@ -37,12 +38,9 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// Gets the user data dto.
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="user">The user.</param>
-        /// <returns>UserItemDataDto.</returns>
         Task<UserItemDataDto> GetUserDataDto(IHasUserData item, User user);
 
-        Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user);
+        Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields);
 
         /// <summary>
         /// Get all user data for the given user

+ 12 - 0
MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Model.Net;
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -117,6 +118,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
         }
 
+        public List<string> AllAudioCodecs
+        {
+            get
+            {
+                return MediaSource.MediaStreams.Where(i => i.Type == MediaStreamType.Audio)
+                        .Select(i => i.Codec)
+                        .Where(i => !string.IsNullOrWhiteSpace(i))
+                        .ToList();
+            }
+        }
+
         private void DisposeIsoMount()
         {
             if (IsoMount != null)

+ 2 - 1
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -846,7 +846,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 state.TargetVideoStreamCount,
                 state.TargetAudioStreamCount,
                 state.TargetVideoCodecTag,
-                state.IsTargetAVC);
+                state.IsTargetAVC,
+                state.AllAudioCodecs);
 
             if (mediaProfile != null)
             {

+ 2 - 2
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -191,7 +191,6 @@ namespace MediaBrowser.Model.Configuration
         public int SharingExpirationDays { get; set; }
 
         public int SchemaVersion { get; set; }
-        public int SqliteCacheSize { get; set; }
 
         public bool EnableAnonymousUsageReporting { get; set; }
         public bool EnableStandaloneMusicKeys { get; set; }
@@ -202,6 +201,7 @@ namespace MediaBrowser.Model.Configuration
         public bool DisplayCollectionsView { get; set; }
         public string[] LocalNetworkAddresses { get; set; }
         public string[] CodecsUsed { get; set; }
+        public string[] Migrations { get; set; }
         public bool EnableChannelView { get; set; }
         public bool EnableExternalContentInSuggestions { get; set; }
         public bool EnableSimpleArtistDetection { get; set; }
@@ -214,7 +214,7 @@ namespace MediaBrowser.Model.Configuration
         {
             LocalNetworkAddresses = new string[] { };
             CodecsUsed = new string[] { };
-            SqliteCacheSize = 0;
+            Migrations = new string[] { };
             ImageExtractionTimeoutMs = 0;
 
             EnableLocalizedGuids = true;

+ 25 - 1
MediaBrowser.Model/Dlna/ConditionProcessor.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.MediaInfo;
 using System;
+using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -22,12 +24,15 @@ namespace MediaBrowser.Model.Dlna
             int? numVideoStreams,
             int? numAudioStreams,
             string videoCodecTag,
-            bool? isAvc)
+            bool? isAvc,
+            List<string> allAudioCodecs )
         {
             switch (condition.Property)
             {
                 case ProfileConditionValue.IsAnamorphic:
                     return IsConditionSatisfied(condition, isAnamorphic);
+                case ProfileConditionValue.HasAudioCodec:
+                    return IsHasAudioCodecConditionSatisfied(condition, allAudioCodecs);
                 case ProfileConditionValue.IsAvc:
                     return IsConditionSatisfied(condition, isAvc);
                 case ProfileConditionValue.VideoFramerate:
@@ -162,6 +167,25 @@ namespace MediaBrowser.Model.Dlna
             }
         }
 
+        private bool IsHasAudioCodecConditionSatisfied(ProfileCondition condition, List<string> allAudioCodecs)
+        {
+            if (allAudioCodecs.Count == 0)
+            {
+                // If the value is unknown, it satisfies if not marked as required
+                return !condition.IsRequired;
+            }
+
+            switch (condition.Condition)
+            {
+                case ProfileConditionType.Equals:
+                    return allAudioCodecs.Contains(condition.Value, StringComparer.Ordinal);
+                case ProfileConditionType.NotEquals:
+                    return !allAudioCodecs.Contains(condition.Value, StringComparer.Ordinal);
+                default:
+                    throw new InvalidOperationException("Unexpected ProfileConditionType");
+            }
+        }
+
         private bool IsConditionSatisfied(ProfileCondition condition, bool? currentValue)
         {
             if (!currentValue.HasValue)

+ 8 - 0
MediaBrowser.Model/Dlna/ContainerProfile.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using System.Xml.Serialization;
 using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -27,5 +28,12 @@ namespace MediaBrowser.Model.Dlna
             }
             return list;
         }
+
+        public bool ContainsContainer(string container)
+        {
+            List<string> containers = GetContainers();
+
+            return containers.Count == 0 || ListHelper.ContainsIgnoreCase(containers, container ?? string.Empty);
+        }
     }
 }

+ 4 - 2
MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs

@@ -119,7 +119,8 @@ namespace MediaBrowser.Model.Dlna
             int? numVideoStreams,
             int? numAudioStreams,
             string videoCodecTag,
-            bool? isAvc)
+            bool? isAvc,
+            List<string> allAudioCodecs)
         {
             // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
             string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
@@ -161,7 +162,8 @@ namespace MediaBrowser.Model.Dlna
                 numVideoStreams,
                 numAudioStreams,
                 videoCodecTag,
-                isAvc);
+                isAvc,
+                allAudioCodecs);
 
             List<string> orgPnValues = new List<string>();
 

+ 3 - 2
MediaBrowser.Model/Dlna/DeviceProfile.cs

@@ -297,7 +297,8 @@ namespace MediaBrowser.Model.Dlna
             int? numVideoStreams,
             int? numAudioStreams,
             string videoCodecTag,
-            bool? isAvc)
+            bool? isAvc,
+            List<string> allAudioCodecs)
         {
             container = StringHelper.TrimStart(container ?? string.Empty, '.');
 
@@ -331,7 +332,7 @@ namespace MediaBrowser.Model.Dlna
                 var anyOff = false;
                 foreach (ProfileCondition c in i.Conditions)
                 {
-                    if (!conditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+                    if (!conditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc, allAudioCodecs))
                     {
                         anyOff = true;
                         break;

+ 2 - 1
MediaBrowser.Model/Dlna/ProfileConditionValue.cs

@@ -21,6 +21,7 @@
         NumVideoStreams = 17,
         IsSecondaryAudio = 18,
         VideoCodecTag = 19,
-        IsAvc = 20
+        IsAvc = 20,
+        HasAudioCodec = 21
     }
 }

+ 14 - 7
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -7,6 +7,7 @@ using MediaBrowser.Model.Session;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -409,6 +410,9 @@ namespace MediaBrowser.Model.Dlna
                 audioStreamIndex = audioStream.Index;
             }
 
+            var allMediaStreams = item.MediaStreams;
+            var allAudioCodecs = allMediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+
             MediaStream videoStream = item.VideoStream;
 
             // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
@@ -424,7 +428,7 @@ namespace MediaBrowser.Model.Dlna
             if (isEligibleForDirectPlay || isEligibleForDirectStream)
             {
                 // See if it can be direct played
-                PlayMethod? directPlay = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectPlay, isEligibleForDirectStream);
+                PlayMethod? directPlay = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectPlay, isEligibleForDirectStream, allMediaStreams);
 
                 if (directPlay != null)
                 {
@@ -552,7 +556,7 @@ namespace MediaBrowser.Model.Dlna
                             int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
                             int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
 
-                            if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+                            if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc, allAudioCodecs))
                             {
                                 LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
                                 applyConditions = false;
@@ -653,7 +657,8 @@ namespace MediaBrowser.Model.Dlna
             MediaStream videoStream,
             MediaStream audioStream,
             bool isEligibleForDirectPlay,
-            bool isEligibleForDirectStream)
+            bool isEligibleForDirectStream,
+            List<MediaStream> allMediaStreams)
         {
             DeviceProfile profile = options.Profile;
 
@@ -701,7 +706,7 @@ namespace MediaBrowser.Model.Dlna
             foreach (ContainerProfile i in profile.ContainerProfiles)
             {
                 if (i.Type == DlnaProfileType.Video &&
-                    ListHelper.ContainsIgnoreCase(i.GetContainers(), container))
+                    i.ContainsContainer(container))
                 {
                     foreach (ProfileCondition c in i.Conditions)
                     {
@@ -734,10 +739,12 @@ namespace MediaBrowser.Model.Dlna
             int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
             int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
 
+            var allAudioCodecs = allMediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+
             // Check container conditions
             foreach (ProfileCondition i in conditions)
             {
-                if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+                if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc, allAudioCodecs))
                 {
                     LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
 
@@ -764,7 +771,7 @@ namespace MediaBrowser.Model.Dlna
                     bool applyConditions = true;
                     foreach (ProfileCondition applyCondition in i.ApplyConditions)
                     {
-                        if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+                        if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc, allAudioCodecs))
                         {
                             LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
                             applyConditions = false;
@@ -784,7 +791,7 @@ namespace MediaBrowser.Model.Dlna
 
             foreach (ProfileCondition i in conditions)
             {
-                if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+                if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc, allAudioCodecs))
                 {
                     LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
 

+ 12 - 0
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Session;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -412,6 +413,17 @@ namespace MediaBrowser.Model.Dlna
             }
         }
 
+        public List<string> AllAudioCodecs
+        {
+            get
+            {
+                return MediaSource.MediaStreams.Where(i => i.Type == MediaStreamType.Audio)
+                        .Select(i => i.Codec)
+                        .Where(i => !string.IsNullOrWhiteSpace(i))
+                        .ToList();
+            }
+        }
+
         /// <summary>
         /// Returns the video stream that will be used
         /// </summary>

+ 2 - 0
MediaBrowser.Model/Querying/ItemFields.cs

@@ -171,6 +171,8 @@
         /// </summary>
         PrimaryImageAspectRatio,
 
+        RecursiveItemCount,
+
         /// <summary>
         /// The revenue
         /// </summary>

+ 1 - 3
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -307,9 +307,7 @@
     <None Include="LiveTv\TunerHosts\SatIp\ini\satellite\3594.ini" />
     <None Include="packages.config" />
   </ItemGroup>
-  <ItemGroup>
-    <Folder Include="Persistence\" />
-  </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 0 - 57
MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs

@@ -1,57 +0,0 @@
-using System;
-using System.Data;
-using System.Data.SQLite;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
-
-namespace Emby.Server.Core.Data
-{
-    /// <summary>
-    /// Class SQLiteExtensions
-    /// </summary>
-    public static class SqliteExtensions
-    {
-        /// <summary>
-        /// Connects to db.
-        /// </summary>
-        public static async Task<IDbConnection> ConnectToDb(string dbPath, 
-            bool isReadOnly, 
-            bool enablePooling, 
-            int? cacheSize, 
-            ILogger logger)
-        {
-            if (string.IsNullOrEmpty(dbPath))
-            {
-                throw new ArgumentNullException("dbPath");
-            }
-
-            SQLiteConnection.SetMemoryStatus(false);
-
-            var connectionstr = new SQLiteConnectionStringBuilder
-            {
-                PageSize = 4096,
-                CacheSize = cacheSize ?? 2000,
-                SyncMode = SynchronizationModes.Normal,
-                DataSource = dbPath,
-                JournalMode = SQLiteJournalModeEnum.Wal,
-
-                // This is causing crashing under linux
-                Pooling = enablePooling && Environment.OSVersion.Platform == PlatformID.Win32NT,
-                ReadOnly = isReadOnly
-            };
-
-            var connectionString = connectionstr.ConnectionString;
-
-            if (!enablePooling)
-            {
-                logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, connectionString);
-            }
-
-            var connection = new SQLiteConnection(connectionString);
-
-            await connection.OpenAsync().ConfigureAwait(false);
-
-            return connection;
-        }
-    }
-}

+ 4 - 4
RSSDP/SsdpDevicePublisherBase.cs

@@ -291,7 +291,7 @@ namespace Rssdp.Infrastructure
                 if (devices != null)
                 {
                     var deviceList = devices.ToList();
-                    WriteTrace(String.Format("Sending {0} search responses", deviceList.Count));
+                    //WriteTrace(String.Format("Sending {0} search responses", deviceList.Count));
 
                     foreach (var device in deviceList)
                     {
@@ -300,7 +300,7 @@ namespace Rssdp.Infrastructure
                 }
                 else
                 {
-                    WriteTrace(String.Format("Sending 0 search responses."));
+                    //WriteTrace(String.Format("Sending 0 search responses."));
                 }
             });
         }
@@ -413,7 +413,7 @@ namespace Rssdp.Infrastructure
 
                 //DisposeRebroadcastTimer();
 
-                WriteTrace("Begin Sending Alive Notifications For All Devices");
+                //WriteTrace("Begin Sending Alive Notifications For All Devices");
 
                 _LastNotificationTime = DateTime.Now;
 
@@ -430,7 +430,7 @@ namespace Rssdp.Infrastructure
                     SendAliveNotifications(device, true);
                 }
 
-                WriteTrace("Completed Sending Alive Notifications For All Devices");
+                //WriteTrace("Completed Sending Alive Notifications For All Devices");
             }
             catch (ObjectDisposedException ex)
             {

+ 5 - 0
src/Emby.Server/Emby.Server.xproj

@@ -37,5 +37,10 @@
     <ProjectReference Include="..\..\ServiceStack\ServiceStack.csproj" />
     <ProjectReference Include="..\..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj" />
   </ItemGroup>
+  <ItemGroup>
+    <Compile Include="..\..\SharedVersion.cs">
+      <Link>Properties\SharedVersion.cs</Link>
+    </Compile>
+  </ItemGroup>
   <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
 </Project>

+ 21 - 3
src/Emby.Server/project.json

@@ -1,5 +1,5 @@
 {
-  "version": "1.0.0-*",
+  "version": "3.1.0-*",
   "buildOptions": {
     "emitEntryPoint": true
   },
@@ -33,8 +33,26 @@
     "win10-arm64": {},
     "osx.10.10-x64": {},
     "osx.10.11-x64": {},
-    "osx.10.12-x64": {},
-    "ubuntu.14.04-x64": {}
+    "osx.10.12-x64": ,
+    "rhel.7.0-x64": {},
+    "rhel.7.1-x64": {},
+    "rhel.7.2-x64": {},
+    "ubuntu.14.04-x64": {},
+    "ubuntu.14.10-x64": {},
+    "ubuntu.15.04-x64": {},
+    "ubuntu.15.10-x64": {},
+    "ubuntu.16.04-x64": {},
+    "ubuntu.16.10-x64": {},
+    "centos.7-x64": {},
+    "debian.8-x64": {},
+    "fedora.23-x64": {},
+    "fedora.24-x64": {},
+    "opensuse.13.2-x64": {},
+    "opensuse.42.1-x64": {},
+    "ol.7-x64": {},
+    "ol.7.0-x64": {},
+    "ol.7.1-x64": {},
+    "ol.7.2-x64": {}
   },
 
   "frameworks": {