diff options
| author | Luke <luke.pulverenti@gmail.com> | 2016-12-13 13:27:53 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-12-13 13:27:53 -0500 |
| commit | 6cdbb25b9d756b58806414455cc4c4d1bc555217 (patch) | |
| tree | b6bc4f10ad1c6f758215a719b2be932b5979e896 | |
| parent | b33dcdf7bd61a8096928e1b2b5e34af1c8373096 (diff) | |
| parent | ffad9c27e4844eeab235f88cb45739370d22a83a (diff) | |
Merge pull request #2340 from MediaBrowser/dev
Dev
48 files changed, 736 insertions, 466 deletions
diff --git a/Emby.Common.Implementations/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs index b9100f9db3..a4e6d47d40 100644 --- a/Emby.Common.Implementations/Networking/NetworkManager.cs +++ b/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) diff --git a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index f288c5c0f4..cbc7c7c2d8 100644 --- a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -387,6 +387,7 @@ namespace Emby.Common.Implementations.ScheduledTasks finally { _currentTask = null; + GC.Collect(); } } diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 3dcdaf2efc..e968509b5e 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/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('?')); diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 7dff8bda13..bfc29d0f5f 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/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(); } diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index a6d2d32c0b..3b3313169d 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/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) diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 7ac0e680ca..bf88358465 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/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); } } } diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 9e60a43aab..206422176b 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/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) { diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 5b25490872..d6ad0ba8ab 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/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); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 14b81022dd..5f2a314f9f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/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) diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 7afb5720e9..2e39b038ae 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/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; } } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2b2c3e000d..d0c473777d 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/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 diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index e478b9d817..df0301fc35 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/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" /> diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index cc2dcb6fd1..5bb21d02ac 100644 --- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/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)); diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 94cc383a7a..4606d0e316 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/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) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index ad91988e53..1ff61286f9 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/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> diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index f4a30fc00b..5a14edf135 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/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; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index f5bef058d2..84a255c7a9 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/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); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 881aaaf0d6..a5b19ff524 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/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; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index faf9687f40..5e12fc9b98 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/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 diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index 43e19da65a..f18278cb25 100644 --- a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/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 => { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 392db69353..a2d61873b8 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/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); } } } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 363fe55935..5e6de1d4db 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/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 diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 73a2bedb9a..7a8951396a 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/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) { diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 0ae1fbff41..b5c51bfef0 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/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); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 3718edba49..288553ade0 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/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; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 003599390e..9c337781cb 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/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 diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 9f45034665..2aa53d6515 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/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) { diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index ebc55ca8a0..c505aefb3f 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/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; @@ -263,26 +302,6 @@ namespace MediaBrowser.Controller.Entities /// <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> /// <value>The actual children.</value> [IgnoreDataMember] protected override IEnumerable<BaseItem> ActualChildren @@ -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(); diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 4705f03fa0..a84e9a5d23 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/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; } } } diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs index c21e170ae7..0b3b7dc8d9 100644 --- a/MediaBrowser.Controller/Entities/IHasUserData.cs +++ b/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; } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index d297fd0065..bf9a076264 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/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> diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 86c52c4c31..5940c7e292 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/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 diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 0c7ff1b760..145337a2a4 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/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) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index e197bdb6fd..70d02901ec 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/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) { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 35677813d8..493fe1bd24 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/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; diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 1fbb4ffa17..aa0b6f4319 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/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) diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index d9b75dfc23..35d7ada6b9 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/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); + } } } diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 4a16a27805..c6a5116b4d 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/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>(); diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 821531ed06..638f78dcbb 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/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; diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs index 7e2002f179..9d6321c848 100644 --- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs +++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs @@ -21,6 +21,7 @@ NumVideoStreams = 17, IsSecondaryAudio = 18, VideoCodecTag = 19, - IsAvc = 20 + IsAvc = 20, + HasAudioCodec = 21 } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 71f0fd541b..dee1605f36 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/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); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index e5dd0353e0..105edb131e 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/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> diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index bf1d4991cf..e36abf16ed 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -171,6 +171,8 @@ /// </summary> PrimaryImageAspectRatio, + RecursiveItemCount, + /// <summary> /// The revenue /// </summary> diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index eb5a5e26a3..e64a7d87c4 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/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. diff --git a/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs deleted file mode 100644 index 22aeb53dd7..0000000000 --- a/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs +++ /dev/null @@ -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; - } - } -} diff --git a/RSSDP/SsdpDevicePublisherBase.cs b/RSSDP/SsdpDevicePublisherBase.cs index 260c008960..8ab35d6616 100644 --- a/RSSDP/SsdpDevicePublisherBase.cs +++ b/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) { diff --git a/src/Emby.Server/Emby.Server.xproj b/src/Emby.Server/Emby.Server.xproj index 6a23809ced..2462c5179f 100644 --- a/src/Emby.Server/Emby.Server.xproj +++ b/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>
\ No newline at end of file diff --git a/src/Emby.Server/project.json b/src/Emby.Server/project.json index 5b64beae61..852c7d519d 100644 --- a/src/Emby.Server/project.json +++ b/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": { |
