mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-25 23:18:54 +02:00
commit
6cdbb25b9d
|
@ -198,6 +198,12 @@ namespace Emby.Common.Implementations.Networking
|
||||||
return Dns.GetHostAddressesAsync(hostName);
|
return Dns.GetHostAddressesAsync(hostName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly List<NetworkInterfaceType> _validNetworkInterfaceTypes = new List<NetworkInterfaceType>
|
||||||
|
{
|
||||||
|
NetworkInterfaceType.Ethernet,
|
||||||
|
NetworkInterfaceType.Wireless80211
|
||||||
|
};
|
||||||
|
|
||||||
private List<IPAddress> GetIPsDefault()
|
private List<IPAddress> GetIPsDefault()
|
||||||
{
|
{
|
||||||
NetworkInterface[] interfaces;
|
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);
|
Logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
|
||||||
|
|
||||||
var properties = network.GetIPProperties();
|
var ipProperties = network.GetIPProperties();
|
||||||
|
|
||||||
return properties.UnicastAddresses
|
// 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 ipProperties.UnicastAddresses
|
||||||
.Where(i => i.IsDnsEligible)
|
.Where(i => i.IsDnsEligible)
|
||||||
.Select(i => i.Address)
|
.Select(i => i.Address)
|
||||||
.Where(i => i.AddressFamily == AddressFamily.InterNetwork)
|
.Where(i => i.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
|
|
@ -387,6 +387,7 @@ namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_currentTask = null;
|
_currentTask = null;
|
||||||
|
GC.Collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,8 @@ namespace Emby.Dlna.Didl
|
||||||
streamInfo.TargetVideoStreamCount,
|
streamInfo.TargetVideoStreamCount,
|
||||||
streamInfo.TargetAudioStreamCount,
|
streamInfo.TargetAudioStreamCount,
|
||||||
streamInfo.TargetVideoCodecTag,
|
streamInfo.TargetVideoCodecTag,
|
||||||
streamInfo.IsTargetAVC);
|
streamInfo.IsTargetAVC,
|
||||||
|
streamInfo.AllAudioCodecs);
|
||||||
|
|
||||||
foreach (var contentFeature in contentFeatureList)
|
foreach (var contentFeature in contentFeatureList)
|
||||||
{
|
{
|
||||||
|
@ -347,7 +348,8 @@ namespace Emby.Dlna.Didl
|
||||||
streamInfo.TargetVideoStreamCount,
|
streamInfo.TargetVideoStreamCount,
|
||||||
streamInfo.TargetAudioStreamCount,
|
streamInfo.TargetAudioStreamCount,
|
||||||
streamInfo.TargetVideoCodecTag,
|
streamInfo.TargetVideoCodecTag,
|
||||||
streamInfo.IsTargetAVC);
|
streamInfo.IsTargetAVC,
|
||||||
|
streamInfo.AllAudioCodecs);
|
||||||
|
|
||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
|
|
|
@ -541,7 +541,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
streamInfo.TargetVideoStreamCount,
|
streamInfo.TargetVideoStreamCount,
|
||||||
streamInfo.TargetAudioStreamCount,
|
streamInfo.TargetAudioStreamCount,
|
||||||
streamInfo.TargetVideoCodecTag,
|
streamInfo.TargetVideoCodecTag,
|
||||||
streamInfo.IsTargetAVC);
|
streamInfo.IsTargetAVC,
|
||||||
|
streamInfo.AllAudioCodecs);
|
||||||
|
|
||||||
return list.FirstOrDefault();
|
return list.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
|
@ -489,6 +489,7 @@ namespace Emby.Server.Core
|
||||||
{
|
{
|
||||||
var migrations = new List<IVersionMigration>
|
var migrations = new List<IVersionMigration>
|
||||||
{
|
{
|
||||||
|
new LibraryScanMigration(ServerConfigurationManager, TaskManager)
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var task in migrations)
|
foreach (var task in migrations)
|
||||||
|
|
|
@ -84,9 +84,6 @@ namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
var list = new List<ActivityLogEntry>();
|
|
||||||
var result = new QueryResult<ActivityLogEntry>();
|
|
||||||
|
|
||||||
var commandText = BaseActivitySelectText;
|
var commandText = BaseActivitySelectText;
|
||||||
var whereClauses = new List<string>();
|
var whereClauses = new List<string>();
|
||||||
|
|
||||||
|
@ -127,9 +124,12 @@ namespace Emby.Server.Implementations.Activity
|
||||||
statementTexts.Add(commandText);
|
statementTexts.Add(commandText);
|
||||||
statementTexts.Add("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging);
|
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])
|
using (var statement = statements[0])
|
||||||
{
|
{
|
||||||
|
@ -153,10 +153,11 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
||||||
}
|
}
|
||||||
}, ReadTransactionMode);
|
|
||||||
|
|
||||||
result.Items = list.ToArray();
|
result.Items = list.ToArray();
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
}, ReadTransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
protected TransactionMode TransactionMode
|
protected TransactionMode TransactionMode
|
||||||
{
|
{
|
||||||
get { return TransactionMode.Immediate; }
|
get { return TransactionMode.Deferred; }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TransactionMode ReadTransactionMode
|
protected TransactionMode ReadTransactionMode
|
||||||
|
@ -43,6 +44,8 @@ namespace Emby.Server.Implementations.Data
|
||||||
//CheckOk(rc);
|
//CheckOk(rc);
|
||||||
|
|
||||||
rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD, 1);
|
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);
|
//CheckOk(rc);
|
||||||
|
|
||||||
rc = raw.sqlite3_enable_shared_cache(1);
|
rc = raw.sqlite3_enable_shared_cache(1);
|
||||||
|
@ -53,84 +56,125 @@ namespace Emby.Server.Implementations.Data
|
||||||
private static bool _versionLogged;
|
private static bool _versionLogged;
|
||||||
|
|
||||||
private string _defaultWal;
|
private string _defaultWal;
|
||||||
|
protected ManagedConnection _connection;
|
||||||
|
|
||||||
protected SQLiteDatabaseConnection CreateConnection(bool isReadOnly = false)
|
protected virtual bool EnableSingleConnection
|
||||||
{
|
{
|
||||||
if (!_versionLogged)
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ManagedConnection CreateConnection(bool isReadOnly = false)
|
||||||
|
{
|
||||||
|
if (_connection != null)
|
||||||
{
|
{
|
||||||
_versionLogged = true;
|
return _connection;
|
||||||
Logger.Info("Sqlite version: " + SQLite3.Version);
|
|
||||||
Logger.Info("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionFlags connectionFlags;
|
lock (WriteLock)
|
||||||
|
|
||||||
if (isReadOnly)
|
|
||||||
{
|
{
|
||||||
//Logger.Info("Opening read connection");
|
if (!_versionLogged)
|
||||||
//connectionFlags = ConnectionFlags.ReadOnly;
|
|
||||||
connectionFlags = ConnectionFlags.Create;
|
|
||||||
connectionFlags |= ConnectionFlags.ReadWrite;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Logger.Info("Opening write connection");
|
|
||||||
connectionFlags = ConnectionFlags.Create;
|
|
||||||
connectionFlags |= ConnectionFlags.ReadWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionFlags |= ConnectionFlags.SharedCached;
|
|
||||||
connectionFlags |= ConnectionFlags.NoMutex;
|
|
||||||
|
|
||||||
var db = SQLite3.Open(DbFilePath, connectionFlags, null);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_defaultWal))
|
|
||||||
{
|
|
||||||
_defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First();
|
|
||||||
|
|
||||||
Logger.Info("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal);
|
|
||||||
}
|
|
||||||
|
|
||||||
var queries = new List<string>
|
|
||||||
{
|
|
||||||
//"PRAGMA cache size=-10000"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (EnableTempStoreMemory)
|
|
||||||
{
|
|
||||||
queries.Add("PRAGMA temp_store = memory");
|
|
||||||
}
|
|
||||||
|
|
||||||
//var cacheSize = CacheSize;
|
|
||||||
//if (cacheSize.HasValue)
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
////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))
|
|
||||||
{
|
|
||||||
queries.Add("PRAGMA journal_mode=WAL");
|
|
||||||
|
|
||||||
using (WriteLock.Write())
|
|
||||||
{
|
{
|
||||||
db.ExecuteAll(string.Join(";", queries.ToArray()));
|
_versionLogged = true;
|
||||||
|
Logger.Info("Sqlite version: " + SQLite3.Version);
|
||||||
|
Logger.Info("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else*/
|
|
||||||
if (queries.Count > 0)
|
|
||||||
{
|
|
||||||
db.ExecuteAll(string.Join(";", queries.ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return db;
|
ConnectionFlags connectionFlags;
|
||||||
|
|
||||||
|
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 (EnableSingleConnection)
|
||||||
|
{
|
||||||
|
connectionFlags |= ConnectionFlags.PrivateCache;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connectionFlags |= ConnectionFlags.SharedCached;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionFlags |= ConnectionFlags.NoMutex;
|
||||||
|
|
||||||
|
var db = SQLite3.Open(DbFilePath, connectionFlags, null);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_defaultWal))
|
||||||
|
{
|
||||||
|
_defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First();
|
||||||
|
|
||||||
|
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"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (CacheSize.HasValue)
|
||||||
|
{
|
||||||
|
queries.Add("PRAGMA cache_size=-" + CacheSize.Value.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EnableTempStoreMemory)
|
||||||
|
{
|
||||||
|
queries.Add("PRAGMA temp_store = memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
//var cacheSize = CacheSize;
|
||||||
|
//if (cacheSize.HasValue)
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
////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))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IStatement PrepareStatement(ManagedConnection connection, string sql)
|
||||||
|
{
|
||||||
|
return connection.PrepareStatement(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IStatement PrepareStatementSafe(ManagedConnection connection, string sql)
|
||||||
|
{
|
||||||
|
return connection.PrepareStatement(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IStatement PrepareStatement(IDatabaseConnection connection, string sql)
|
public IStatement PrepareStatement(IDatabaseConnection connection, string sql)
|
||||||
|
@ -143,22 +187,23 @@ namespace Emby.Server.Implementations.Data
|
||||||
return connection.PrepareStatement(sql);
|
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>
|
var queries = new List<string>
|
||||||
{
|
{
|
||||||
"PRAGMA journal_mode=WAL",
|
"PRAGMA journal_mode=WAL",
|
||||||
"PRAGMA page_size=4096",
|
"PRAGMA page_size=4096",
|
||||||
|
"PRAGMA synchronous=Normal"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (EnableTempStoreMemory)
|
if (EnableTempStoreMemory)
|
||||||
|
@ -171,6 +216,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
db.ExecuteAll(string.Join(";", queries.ToArray()));
|
db.ExecuteAll(string.Join(";", queries.ToArray()));
|
||||||
|
Logger.Info("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool EnableTempStoreMemory
|
protected virtual bool EnableTempStoreMemory
|
||||||
|
@ -238,6 +284,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (WriteLock.Write())
|
using (WriteLock.Write())
|
||||||
{
|
{
|
||||||
|
if (_connection != null)
|
||||||
|
{
|
||||||
|
_connection.Close();
|
||||||
|
_connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
CloseConnection();
|
CloseConnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +384,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
//{
|
//{
|
||||||
// return new DummyToken();
|
// return new DummyToken();
|
||||||
//}
|
//}
|
||||||
return new ReadLockToken(obj);
|
return new WriteLockToken(obj);
|
||||||
}
|
}
|
||||||
public static IDisposable Write(this ReaderWriterLockSlim obj)
|
public static IDisposable Write(this ReaderWriterLockSlim obj)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
var commandText = string.Format("attach @path as {0};", alias);
|
||||||
|
|
||||||
|
|
|
@ -105,13 +105,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var cacheSize = _config.Configuration.SqliteCacheSize;
|
return 20000;
|
||||||
if (cacheSize <= 0)
|
|
||||||
{
|
|
||||||
cacheSize = Math.Min(Environment.ProcessorCount * 50000, 100000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0 - cacheSize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,6 +322,8 @@ namespace Emby.Server.Implementations.Data
|
||||||
"drop table if exists Images",
|
"drop table if exists Images",
|
||||||
"drop index if exists idx_Images",
|
"drop index if exists idx_Images",
|
||||||
"drop index if exists idx_TypeSeriesPresentationUniqueKey",
|
"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_PathTypedBaseItems on TypedBaseItems(Path)",
|
||||||
"create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
|
"create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
|
||||||
|
@ -343,8 +339,9 @@ namespace Emby.Server.Implementations.Data
|
||||||
// series
|
// series
|
||||||
"create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)",
|
"create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)",
|
||||||
|
|
||||||
// series next up
|
// series counts
|
||||||
"create index if not exists idx_SeriesPresentationUniqueKey on TypedBaseItems(SeriesPresentationUniqueKey)",
|
// seriesdateplayed sort order
|
||||||
|
"create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)",
|
||||||
|
|
||||||
// live tv programs
|
// live tv programs
|
||||||
"create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
|
"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);
|
//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)
|
private void OnShrinkMemoryTimerCallback(object state)
|
||||||
|
@ -698,12 +695,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
var requiresReset = false;
|
var requiresReset = false;
|
||||||
|
|
||||||
var statements = PrepareAll(db, string.Join(";", new string[]
|
var statements = PrepareAllSafe(db, new string[]
|
||||||
{
|
{
|
||||||
GetSaveItemCommandText(),
|
GetSaveItemCommandText(),
|
||||||
"delete from AncestorIds where ItemId=@ItemId",
|
"delete from AncestorIds where ItemId=@ItemId",
|
||||||
"insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values (@ItemId, @AncestorId, @AncestorIdText)"
|
"insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values (@ItemId, @AncestorId, @AncestorIdText)"
|
||||||
})).ToList();
|
}).ToList();
|
||||||
|
|
||||||
using (var saveItemStatement = statements[0])
|
using (var saveItemStatement = statements[0])
|
||||||
{
|
{
|
||||||
|
@ -1264,9 +1261,10 @@ namespace Emby.Server.Implementations.Data
|
||||||
return GetItem(row);
|
return GetItem(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader)
|
private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader)
|
||||||
|
@ -2079,12 +2077,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
throw new ArgumentNullException("id");
|
throw new ArgumentNullException("id");
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<ChapterInfo>();
|
|
||||||
|
|
||||||
using (WriteLock.Read())
|
using (WriteLock.Read())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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"))
|
using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", id);
|
statement.TryBind("@ItemId", id);
|
||||||
|
@ -2094,10 +2092,10 @@ namespace Emby.Server.Implementations.Data
|
||||||
list.Add(GetChapter(row));
|
list.Add(GetChapter(row));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2240,7 +2238,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (query.SimilarTo != null && query.User != null)
|
if (query.SimilarTo != null && query.User != null)
|
||||||
{
|
{
|
||||||
return true;
|
//return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortingFields = query.SortBy.ToList();
|
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) < 10 Then 2 Else 0 End )");
|
||||||
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 2 Else 0 End )");
|
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 2 Else 0 End )");
|
||||||
|
|
||||||
//// genres
|
//// genres, tags
|
||||||
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)");
|
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)");
|
||||||
|
|
||||||
//// tags
|
//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=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)");
|
|
||||||
|
|
||||||
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)");
|
//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);
|
//commandText += GetGroupBy(query);
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
using (WriteLock.Read())
|
using (WriteLock.Read())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
|
@ -2493,14 +2484,13 @@ namespace Emby.Server.Implementations.Data
|
||||||
// Running this again will bind the params
|
// Running this again will bind the params
|
||||||
GetWhereClauses(query, statement);
|
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)
|
public List<BaseItem> GetItemList(InternalItemsQuery query)
|
||||||
|
@ -2516,8 +2506,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
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
|
// Hack for right now since we currently don't support filtering out these duplicates within a query
|
||||||
if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
|
if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
|
||||||
{
|
{
|
||||||
|
@ -2558,53 +2546,59 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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))
|
||||||
}
|
|
||||||
|
|
||||||
BindSimilarParams(query, statement);
|
|
||||||
|
|
||||||
// Running this again will bind the params
|
|
||||||
GetWhereClauses(query, statement);
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
var item = GetItem(row, query);
|
|
||||||
if (item != null)
|
|
||||||
{
|
{
|
||||||
list.Add(item);
|
statement.TryBind("@UserId", query.User.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
BindSimilarParams(query, statement);
|
||||||
|
|
||||||
|
// Running this again will bind the params
|
||||||
|
GetWhereClauses(query, statement);
|
||||||
|
|
||||||
|
foreach (var row in statement.ExecuteQuery())
|
||||||
|
{
|
||||||
|
var item = GetItem(row, query);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if (newList.Count >= limit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = newList;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogQueryTime("GetItemList", commandText, now);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
|
||||||
|
}, ReadTransactionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
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>();
|
|
||||||
|
|
||||||
foreach (var item in list)
|
|
||||||
{
|
|
||||||
AddItem(newList, item);
|
|
||||||
|
|
||||||
if (newList.Count >= limit)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list = newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddItem(List<BaseItem> items, BaseItem newItem)
|
private void AddItem(List<BaseItem> items, BaseItem newItem)
|
||||||
|
@ -2642,7 +2636,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
var slowThreshold = 1000;
|
var slowThreshold = 1000;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
slowThreshold = 50;
|
slowThreshold = 2;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (elapsed >= slowThreshold)
|
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 isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
|
||||||
|
|
||||||
var statementTexts = new List<string>();
|
var statementTexts = new List<string>();
|
||||||
|
@ -2753,9 +2746,10 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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();
|
.ToList();
|
||||||
|
|
||||||
if (!isReturningZeroItems)
|
if (!isReturningZeroItems)
|
||||||
|
@ -2801,12 +2795,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogQueryTime("GetItems", commandText, now);
|
||||||
|
|
||||||
|
result.Items = list.ToArray();
|
||||||
|
return result;
|
||||||
|
|
||||||
}, ReadTransactionMode);
|
}, ReadTransactionMode);
|
||||||
|
|
||||||
LogQueryTime("GetItems", commandText, now);
|
|
||||||
|
|
||||||
result.Items = list.ToArray();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2967,12 +2961,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<Guid>();
|
|
||||||
|
|
||||||
using (WriteLock.Read())
|
using (WriteLock.Read())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
|
var list = new List<Guid>();
|
||||||
|
|
||||||
using (var statement = PrepareStatementSafe(connection, commandText))
|
using (var statement = PrepareStatementSafe(connection, commandText))
|
||||||
{
|
{
|
||||||
if (EnableJoinUserData(query))
|
if (EnableJoinUserData(query))
|
||||||
|
@ -2990,11 +2984,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
list.Add(row[0].ReadGuid());
|
list.Add(row[0].ReadGuid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogQueryTime("GetItemList", commandText, now);
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogQueryTime("GetItemList", commandText, now);
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3158,11 +3152,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
var result = new QueryResult<Guid>();
|
return connection.RunInTransaction(db =>
|
||||||
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
{
|
||||||
var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray()))
|
var result = new QueryResult<Guid>();
|
||||||
|
|
||||||
|
var statements = PrepareAllSafe(db, statementTexts)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (!isReturningZeroItems)
|
if (!isReturningZeroItems)
|
||||||
|
@ -3204,12 +3198,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogQueryTime("GetItemIds", commandText, now);
|
||||||
|
|
||||||
|
result.Items = list.ToArray();
|
||||||
|
return result;
|
||||||
|
|
||||||
}, ReadTransactionMode);
|
}, ReadTransactionMode);
|
||||||
|
|
||||||
LogQueryTime("GetItemIds", commandText, now);
|
|
||||||
|
|
||||||
result.Items = list.ToArray();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4658,26 +4652,23 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
commandText += " order by ListOrder";
|
commandText += " order by ListOrder";
|
||||||
|
|
||||||
var list = new List<string>();
|
|
||||||
using (WriteLock.Read())
|
using (WriteLock.Read())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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())
|
foreach (var row in statement.ExecuteQuery())
|
||||||
{
|
{
|
||||||
list.Add(row.GetString(0));
|
list.Add(row.GetString(0));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, ReadTransactionMode);
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4701,29 +4692,26 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
commandText += " order by ListOrder";
|
commandText += " order by ListOrder";
|
||||||
|
|
||||||
var list = new List<PersonInfo>();
|
|
||||||
|
|
||||||
using (WriteLock.Read())
|
using (WriteLock.Read())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
var list = new List<PersonInfo>();
|
||||||
{
|
|
||||||
using (var statement = PrepareStatementSafe(db, commandText))
|
|
||||||
{
|
|
||||||
// Run this again to bind the params
|
|
||||||
GetPeopleWhereClauses(query, statement);
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
using (var statement = PrepareStatementSafe(connection, commandText))
|
||||||
{
|
{
|
||||||
list.Add(GetPerson(row));
|
// Run this again to bind the params
|
||||||
}
|
GetPeopleWhereClauses(query, statement);
|
||||||
|
|
||||||
|
foreach (var row in statement.ExecuteQuery())
|
||||||
|
{
|
||||||
|
list.Add(GetPerson(row));
|
||||||
}
|
}
|
||||||
}, ReadTransactionMode);
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, IStatement statement)
|
private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, IStatement statement)
|
||||||
|
@ -4904,8 +4892,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
|
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
|
||||||
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")");
|
("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;
|
var commandText = "Select Value From ItemValues where " + typeClause;
|
||||||
|
|
||||||
if (withItemTypes.Count > 0)
|
if (withItemTypes.Count > 0)
|
||||||
|
@ -4925,24 +4911,24 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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)
|
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 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>();
|
var statementTexts = new List<string>();
|
||||||
if (!isReturningZeroItems)
|
if (!isReturningZeroItems)
|
||||||
{
|
{
|
||||||
|
@ -5107,9 +5090,13 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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)
|
if (!isReturningZeroItems)
|
||||||
{
|
{
|
||||||
|
@ -5172,17 +5159,18 @@ namespace Emby.Server.Implementations.Data
|
||||||
LogQueryTime("GetItemValues", commandText, now);
|
LogQueryTime("GetItemValues", commandText, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.TotalRecordCount == 0)
|
||||||
|
{
|
||||||
|
result.TotalRecordCount = list.Count;
|
||||||
|
}
|
||||||
|
result.Items = list.ToArray();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
}, ReadTransactionMode);
|
}, 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)
|
private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, List<string> typesToCount)
|
||||||
|
@ -5395,8 +5383,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
throw new ArgumentNullException("query");
|
throw new ArgumentNullException("query");
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<MediaStream>();
|
|
||||||
|
|
||||||
var cmdText = "select " + string.Join(",", _mediaStreamSaveColumns) + " from mediastreams where";
|
var cmdText = "select " + string.Join(",", _mediaStreamSaveColumns) + " from mediastreams where";
|
||||||
|
|
||||||
cmdText += " ItemId=@ItemId";
|
cmdText += " ItemId=@ItemId";
|
||||||
|
@ -5417,32 +5403,31 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
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());
|
||||||
|
|
||||||
|
if (query.Type.HasValue)
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue());
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
list.Add(GetMediaStream(row));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, ReadTransactionMode);
|
|
||||||
|
if (query.Index.HasValue)
|
||||||
|
{
|
||||||
|
statement.TryBind("@StreamIndex", query.Index.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var row in statement.ExecuteQuery())
|
||||||
|
{
|
||||||
|
list.Add(GetMediaStream(row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)
|
public async Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -42,8 +42,10 @@ namespace Emby.Server.Implementations.Data
|
||||||
/// Opens the connection to the database
|
/// Opens the connection to the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public void Initialize(ReaderWriterLockSlim writeLock)
|
public void Initialize(ReaderWriterLockSlim writeLock, ManagedConnection managedConnection)
|
||||||
{
|
{
|
||||||
|
_connection = managedConnection;
|
||||||
|
|
||||||
WriteLock.Dispose();
|
WriteLock.Dispose();
|
||||||
WriteLock = writeLock;
|
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))
|
if (!_fileSystem.FileExists(_importFile))
|
||||||
{
|
{
|
||||||
|
@ -117,7 +119,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}, TransactionMode);
|
}, TransactionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ImportUserData(IDatabaseConnection connection, string file)
|
private void ImportUserData(ManagedConnection connection, string file)
|
||||||
{
|
{
|
||||||
SqliteExtensions.Attach(connection, file, "UserDataBackup");
|
SqliteExtensions.Attach(connection, file, "UserDataBackup");
|
||||||
|
|
||||||
|
@ -300,24 +302,18 @@ namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
UserItemData result = null;
|
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId"))
|
||||||
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
{
|
||||||
using (var statement = db.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId"))
|
statement.TryBind("@UserId", userId.ToGuidParamValue());
|
||||||
|
statement.TryBind("@Key", key);
|
||||||
|
|
||||||
|
foreach (var row in statement.ExecuteQuery())
|
||||||
{
|
{
|
||||||
statement.TryBind("@UserId", userId.ToGuidParamValue());
|
return ReadRow(row);
|
||||||
statement.TryBind("@Key", key);
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
result = ReadRow(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, ReadTransactionMode);
|
}
|
||||||
|
|
||||||
return result;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -459,12 +459,21 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
if (dtoOptions.EnableUserData)
|
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)
|
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))
|
if (fields.Contains(ItemFields.CumulativeRunTimeTicks))
|
||||||
|
@ -1151,28 +1160,29 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
dto.Artists = hasArtist.Artists;
|
dto.Artists = hasArtist.Artists;
|
||||||
|
|
||||||
var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
||||||
{
|
//{
|
||||||
EnableTotalRecordCount = false,
|
// EnableTotalRecordCount = false,
|
||||||
ItemIds = new[] { item.Id.ToString("N") }
|
// ItemIds = new[] { item.Id.ToString("N") }
|
||||||
});
|
//});
|
||||||
|
|
||||||
dto.ArtistItems = artistItems.Items
|
//dto.ArtistItems = artistItems.Items
|
||||||
.Select(i =>
|
// .Select(i =>
|
||||||
{
|
// {
|
||||||
var artist = i.Item1;
|
// var artist = i.Item1;
|
||||||
return new NameIdPair
|
// return new NameIdPair
|
||||||
{
|
// {
|
||||||
Name = artist.Name,
|
// Name = artist.Name,
|
||||||
Id = artist.Id.ToString("N")
|
// Id = artist.Id.ToString("N")
|
||||||
};
|
// };
|
||||||
})
|
// })
|
||||||
.ToList();
|
// .ToList();
|
||||||
|
|
||||||
// Include artists that are not in the database yet, e.g., just added via metadata editor
|
// 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
|
dto.ArtistItems.AddRange(hasArtist.Artists
|
||||||
.Except(foundArtists, new DistinctNameComparer())
|
//.Except(foundArtists, new DistinctNameComparer())
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
// This should not be necessary but we're seeing some cases of it
|
// 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();
|
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||||
|
|
||||||
var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
||||||
{
|
//{
|
||||||
EnableTotalRecordCount = false,
|
// EnableTotalRecordCount = false,
|
||||||
ItemIds = new[] { item.Id.ToString("N") }
|
// ItemIds = new[] { item.Id.ToString("N") }
|
||||||
});
|
//});
|
||||||
|
|
||||||
dto.AlbumArtists = artistItems.Items
|
//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 =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
var artist = i.Item1;
|
// This should not be necessary but we're seeing some cases of it
|
||||||
return new NameIdPair
|
if (string.IsNullOrWhiteSpace(i))
|
||||||
{
|
{
|
||||||
Name = artist.Name,
|
return null;
|
||||||
Id = artist.Id.ToString("N")
|
}
|
||||||
};
|
|
||||||
})
|
var artist = _libraryManager.GetArtist(i);
|
||||||
.ToList();
|
if (artist != null)
|
||||||
|
{
|
||||||
|
return new NameIdPair
|
||||||
|
{
|
||||||
|
Name = artist.Name,
|
||||||
|
Id = artist.Id.ToString("N")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}).Where(i => i != null));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add video info
|
// Add video info
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
<Compile Include="Connect\ConnectManager.cs" />
|
<Compile Include="Connect\ConnectManager.cs" />
|
||||||
<Compile Include="Connect\Responses.cs" />
|
<Compile Include="Connect\Responses.cs" />
|
||||||
<Compile Include="Connect\Validator.cs" />
|
<Compile Include="Connect\Validator.cs" />
|
||||||
|
<Compile Include="Data\ManagedConnection.cs" />
|
||||||
<Compile Include="Data\SqliteDisplayPreferencesRepository.cs" />
|
<Compile Include="Data\SqliteDisplayPreferencesRepository.cs" />
|
||||||
<Compile Include="Data\SqliteFileOrganizationRepository.cs" />
|
<Compile Include="Data\SqliteFileOrganizationRepository.cs" />
|
||||||
<Compile Include="Data\SqliteItemRepository.cs" />
|
<Compile Include="Data\SqliteItemRepository.cs" />
|
||||||
|
@ -180,6 +181,7 @@
|
||||||
<Compile Include="Localization\LocalizationManager.cs" />
|
<Compile Include="Localization\LocalizationManager.cs" />
|
||||||
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
||||||
<Compile Include="Migrations\IVersionMigration.cs" />
|
<Compile Include="Migrations\IVersionMigration.cs" />
|
||||||
|
<Compile Include="Migrations\LibraryScanMigration.cs" />
|
||||||
<Compile Include="Migrations\UpdateLevelMigration.cs" />
|
<Compile Include="Migrations\UpdateLevelMigration.cs" />
|
||||||
<Compile Include="News\NewsEntryPoint.cs" />
|
<Compile Include="News\NewsEntryPoint.cs" />
|
||||||
<Compile Include="News\NewsService.cs" />
|
<Compile Include="News\NewsService.cs" />
|
||||||
|
|
|
@ -519,6 +519,12 @@ namespace Emby.Server.Implementations.FileOrganization
|
||||||
|
|
||||||
private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result)
|
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);
|
_libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath);
|
||||||
|
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(result.TargetPath));
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(result.TargetPath));
|
||||||
|
|
|
@ -76,7 +76,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||||
|
|
||||||
private void ProcessContext(HttpListenerContext context)
|
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)
|
private Task InitTask(HttpListenerContext context)
|
||||||
|
|
|
@ -817,7 +817,31 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
return _userRootFolder;
|
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)
|
public BaseItem FindByPath(string path, bool? isFolder)
|
||||||
{
|
{
|
||||||
// If this returns multiple items it could be tricky figuring out which one is correct.
|
// 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
|
// 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[] { };
|
query.AncestorIds = new string[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1489,7 +1513,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
// Optimize by querying against top level views
|
// 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
|
else
|
||||||
{
|
{
|
||||||
|
@ -1515,11 +1539,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
}, CancellationToken.None).Result.ToList();
|
}, 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;
|
var view = item as UserView;
|
||||||
|
|
||||||
|
@ -1527,7 +1551,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
if (string.Equals(view.ViewType, CollectionType.LiveTv))
|
if (string.Equals(view.ViewType, CollectionType.LiveTv))
|
||||||
{
|
{
|
||||||
return new[] { view };
|
return new[] { view.Id };
|
||||||
}
|
}
|
||||||
if (string.Equals(view.ViewType, CollectionType.Channels))
|
if (string.Equals(view.ViewType, CollectionType.Channels))
|
||||||
{
|
{
|
||||||
|
@ -1537,7 +1561,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).Result;
|
||||||
|
|
||||||
return channelResult.Items;
|
return channelResult.Items.Select(i => i.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate view into folders
|
// Translate view into folders
|
||||||
|
@ -1546,18 +1570,18 @@ namespace Emby.Server.Implementations.Library
|
||||||
var displayParent = GetItemById(view.DisplayParentId);
|
var displayParent = GetItemById(view.DisplayParentId);
|
||||||
if (displayParent != null)
|
if (displayParent != null)
|
||||||
{
|
{
|
||||||
return GetTopParentsForQuery(displayParent, user);
|
return GetTopParentIdsForQuery(displayParent, user);
|
||||||
}
|
}
|
||||||
return new BaseItem[] { };
|
return new Guid[] { };
|
||||||
}
|
}
|
||||||
if (view.ParentId != Guid.Empty)
|
if (view.ParentId != Guid.Empty)
|
||||||
{
|
{
|
||||||
var displayParent = GetItemById(view.ParentId);
|
var displayParent = GetItemById(view.ParentId);
|
||||||
if (displayParent != null)
|
if (displayParent != null)
|
||||||
{
|
{
|
||||||
return GetTopParentsForQuery(displayParent, user);
|
return GetTopParentIdsForQuery(displayParent, user);
|
||||||
}
|
}
|
||||||
return new BaseItem[] { };
|
return new Guid[] { };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle grouping
|
// Handle grouping
|
||||||
|
@ -1568,23 +1592,23 @@ namespace Emby.Server.Implementations.Library
|
||||||
.OfType<CollectionFolder>()
|
.OfType<CollectionFolder>()
|
||||||
.Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase))
|
.Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase))
|
||||||
.Where(i => user.IsFolderGrouped(i.Id))
|
.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;
|
var collectionFolder = item as CollectionFolder;
|
||||||
if (collectionFolder != null)
|
if (collectionFolder != null)
|
||||||
{
|
{
|
||||||
return collectionFolder.GetPhysicalParents();
|
return collectionFolder.PhysicalFolderIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topParent = item.GetTopParent();
|
var topParent = item.GetTopParent();
|
||||||
if (topParent != null)
|
if (topParent != null)
|
||||||
{
|
{
|
||||||
return new[] { topParent };
|
return new[] { topParent.Id };
|
||||||
}
|
}
|
||||||
return new BaseItem[] { };
|
return new Guid[] { };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -12,6 +12,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
|
@ -186,16 +187,16 @@ namespace Emby.Server.Implementations.Library
|
||||||
var userData = GetUserData(user.Id, item);
|
var userData = GetUserData(user.Id, item);
|
||||||
var dto = GetUserItemDataDto(userData);
|
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;
|
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 userData = GetUserData(user.Id, item);
|
||||||
var dto = GetUserItemDataDto(userData);
|
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;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -328,15 +328,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
await UpdateTimersForSeriesTimer(epgData, timer, true).ConfigureAwait(false);
|
await UpdateTimersForSeriesTimer(epgData, timer, true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshTimers(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
|
{
|
||||||
var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
|
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))
|
if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id))
|
||||||
{
|
{
|
||||||
OnTimerOutOfDate(timer);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo)
|
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.SeasonNumber = programInfo.SeasonNumber;
|
||||||
timerInfo.EpisodeNumber = programInfo.EpisodeNumber;
|
timerInfo.EpisodeNumber = programInfo.EpisodeNumber;
|
||||||
timerInfo.IsMovie = programInfo.IsMovie;
|
timerInfo.IsMovie = programInfo.IsMovie;
|
||||||
|
@ -54,6 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
timerInfo.HomePageUrl = programInfo.HomePageUrl;
|
timerInfo.HomePageUrl = programInfo.HomePageUrl;
|
||||||
timerInfo.CommunityRating = programInfo.CommunityRating;
|
timerInfo.CommunityRating = programInfo.CommunityRating;
|
||||||
|
timerInfo.Overview = programInfo.Overview;
|
||||||
timerInfo.ShortOverview = programInfo.ShortOverview;
|
timerInfo.ShortOverview = programInfo.ShortOverview;
|
||||||
timerInfo.OfficialRating = programInfo.OfficialRating;
|
timerInfo.OfficialRating = programInfo.OfficialRating;
|
||||||
timerInfo.IsRepeat = programInfo.IsRepeat;
|
timerInfo.IsRepeat = programInfo.IsRepeat;
|
||||||
|
|
|
@ -1231,6 +1231,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
if (coreService != null)
|
if (coreService != null)
|
||||||
{
|
{
|
||||||
await coreService.RefreshSeriesTimers(cancellationToken, new Progress<double>()).ConfigureAwait(false);
|
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
|
// Load these now which will prefetch metadata
|
||||||
|
|
|
@ -65,9 +65,9 @@ namespace Emby.Server.Implementations.Notifications
|
||||||
|
|
||||||
var whereClause = " where " + string.Join(" And ", clauses.ToArray());
|
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();
|
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();
|
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"))
|
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();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
using (var connection = CreateConnection())
|
lock (WriteLock)
|
||||||
{
|
{
|
||||||
lock (WriteLock)
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(conn =>
|
connection.RunInTransaction(conn =>
|
||||||
{
|
{
|
||||||
|
@ -286,9 +286,9 @@ namespace Emby.Server.Implementations.Notifications
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
using (var connection = CreateConnection())
|
using (WriteLock.Write())
|
||||||
{
|
{
|
||||||
lock (WriteLock)
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(conn =>
|
connection.RunInTransaction(conn =>
|
||||||
{
|
{
|
||||||
|
@ -308,9 +308,9 @@ namespace Emby.Server.Implementations.Notifications
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
using (var connection = CreateConnection())
|
using (WriteLock.Write())
|
||||||
{
|
{
|
||||||
lock (WriteLock)
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(conn =>
|
connection.RunInTransaction(conn =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -206,15 +206,15 @@ namespace Emby.Server.Implementations.Security
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
var result = new QueryResult<AuthenticationInfo>();
|
return connection.RunInTransaction(db =>
|
||||||
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
{
|
||||||
|
var result = new QueryResult<AuthenticationInfo>();
|
||||||
|
|
||||||
var statementTexts = new List<string>();
|
var statementTexts = new List<string>();
|
||||||
statementTexts.Add(commandText);
|
statementTexts.Add(commandText);
|
||||||
statementTexts.Add("select count (Id) from AccessTokens" + whereTextWithoutPaging);
|
statementTexts.Add("select count (Id) from AccessTokens" + whereTextWithoutPaging);
|
||||||
|
|
||||||
var statements = PrepareAllSafe(db, string.Join(";", statementTexts.ToArray()))
|
var statements = PrepareAllSafe(db, statementTexts)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
using (var statement = statements[0])
|
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();
|
}, ReadTransactionMode);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.TV
|
||||||
|
|
||||||
// If viewing all next up for all series, remove first episodes
|
// If viewing all next up for all series, remove first episodes
|
||||||
// But if that returns empty, keep those first episodes (avoid completely empty view)
|
// 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;
|
var isFirstItemAFirstEpisode = true;
|
||||||
|
|
||||||
return allNextUp
|
return allNextUp
|
||||||
|
|
|
@ -121,7 +121,9 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
var options = new DtoOptions();
|
var options = new DtoOptions();
|
||||||
|
|
||||||
options.DeviceId = authContext.GetAuthorizationInfo(Request).DeviceId;
|
var authInfo = authContext.GetAuthorizationInfo(Request);
|
||||||
|
|
||||||
|
options.DeviceId = authInfo.DeviceId;
|
||||||
|
|
||||||
var hasFields = request as IHasItemFields;
|
var hasFields = request as IHasItemFields;
|
||||||
if (hasFields != null)
|
if (hasFields != null)
|
||||||
|
@ -129,6 +131,34 @@ namespace MediaBrowser.Api
|
||||||
options.Fields = hasFields.GetItemFields().ToList();
|
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;
|
var hasDtoOptions = request as IHasDtoOptions;
|
||||||
if (hasDtoOptions != null)
|
if (hasDtoOptions != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,8 +14,6 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Controller.IO;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Services;
|
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")]
|
[Route("/Items/RemoteSearch/Image", "GET", Summary = "Gets a remote image")]
|
||||||
public class GetRemoteSearchImage
|
public class GetRemoteSearchImage
|
||||||
{
|
{
|
||||||
|
@ -147,6 +151,13 @@ namespace MediaBrowser.Api
|
||||||
return ToOptimizedResult(result);
|
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)
|
public async Task<object> Post(GetMovieRemoteSearchResults request)
|
||||||
{
|
{
|
||||||
var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false);
|
var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
|
@ -2358,7 +2358,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
state.TargetVideoStreamCount,
|
state.TargetVideoStreamCount,
|
||||||
state.TargetAudioStreamCount,
|
state.TargetAudioStreamCount,
|
||||||
state.TargetVideoCodecTag,
|
state.TargetVideoCodecTag,
|
||||||
state.IsTargetAVC);
|
state.IsTargetAVC,
|
||||||
|
state.AllAudioCodecs);
|
||||||
|
|
||||||
if (mediaProfile != null)
|
if (mediaProfile != null)
|
||||||
{
|
{
|
||||||
|
@ -2580,7 +2581,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
state.TargetVideoStreamCount,
|
state.TargetVideoStreamCount,
|
||||||
state.TargetAudioStreamCount,
|
state.TargetAudioStreamCount,
|
||||||
state.TargetVideoCodecTag,
|
state.TargetVideoCodecTag,
|
||||||
state.IsTargetAVC
|
state.IsTargetAVC,
|
||||||
|
state.AllAudioCodecs
|
||||||
|
|
||||||
).FirstOrDefault() ?? string.Empty;
|
).FirstOrDefault() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
|
@ -244,6 +245,17 @@ namespace MediaBrowser.Api.Playback
|
||||||
public int? OutputAudioBitrate;
|
public int? OutputAudioBitrate;
|
||||||
public int? OutputVideoBitrate;
|
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
|
public string ActualOutputVideoCodec
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -30,6 +30,7 @@ using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
|
@ -2191,7 +2192,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return path;
|
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)
|
if (RunTimeTicks.HasValue)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
public CollectionFolder()
|
public CollectionFolder()
|
||||||
{
|
{
|
||||||
PhysicalLocationsList = new List<string>();
|
PhysicalLocationsList = new List<string>();
|
||||||
|
PhysicalFolderIds = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
|
@ -153,6 +154,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> PhysicalLocationsList { get; set; }
|
public List<string> PhysicalLocationsList { get; set; }
|
||||||
|
public List<Guid> PhysicalFolderIds { get; set; }
|
||||||
|
|
||||||
protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
|
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;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +200,31 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return changed;
|
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)
|
internal override bool IsValidFromResolver(BaseItem newItem)
|
||||||
{
|
{
|
||||||
var newCollectionFolder = newItem as CollectionFolder;
|
var newCollectionFolder = newItem as CollectionFolder;
|
||||||
|
@ -260,26 +299,6 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return Task.FromResult(true);
|
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>
|
/// <summary>
|
||||||
/// Our children are actually just references to the ones in the physical root...
|
/// Our children are actually just references to the ones in the physical root...
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -292,11 +311,16 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
private IEnumerable<BaseItem> GetActualChildren()
|
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
|
var rootChildren = LibraryManager.RootFolder.Children
|
||||||
.OfType<Folder>()
|
.OfType<Folder>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
|
@ -1222,7 +1222,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
/// Refreshes the linked children.
|
/// Refreshes the linked children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
/// <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 currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
|
||||||
var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).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)
|
if (!SupportsUserDataFromChildren)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var recursiveItemCount = GetRecursiveChildCount(user);
|
|
||||||
|
|
||||||
if (itemDto != null)
|
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,
|
Recursive = true,
|
||||||
IsFolder = false,
|
IsFolder = false,
|
||||||
|
@ -1435,21 +1436,24 @@ namespace MediaBrowser.Controller.Entities
|
||||||
Limit = 0,
|
Limit = 0,
|
||||||
IsPlayed = false
|
IsPlayed = false
|
||||||
|
|
||||||
}).ConfigureAwait(false) : new QueryResult<BaseItem>();
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
double unplayedCount = unplayedQueryResult.TotalRecordCount;
|
double unplayedCount = unplayedQueryResult.TotalRecordCount;
|
||||||
|
|
||||||
var unplayedPercentage = (unplayedCount / recursiveItemCount) * 100;
|
|
||||||
dto.PlayedPercentage = 100 - unplayedPercentage;
|
|
||||||
dto.Played = dto.PlayedPercentage.Value >= 100;
|
|
||||||
dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
|
dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
|
||||||
}
|
|
||||||
|
|
||||||
if (itemDto != null)
|
if (itemDto != null && itemDto.RecursiveItemCount.HasValue)
|
||||||
{
|
|
||||||
if (this is Season || this is MusicAlbum)
|
|
||||||
{
|
{
|
||||||
itemDto.ChildCount = recursiveItemCount;
|
if (itemDto.RecursiveItemCount.Value > 0)
|
||||||
|
{
|
||||||
|
var unplayedPercentage = (unplayedCount/itemDto.RecursiveItemCount.Value)*100;
|
||||||
|
dto.PlayedPercentage = 100 - unplayedPercentage;
|
||||||
|
dto.Played = dto.PlayedPercentage.Value >= 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dto.Played = (dto.UnplayedItemCount ?? 0) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
|
@ -14,10 +15,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fills the user data dto values.
|
/// Fills the user data dto values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto">The dto.</param>
|
Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, List<ItemFields> fields);
|
||||||
/// <param name="userData">The user data.</param>
|
|
||||||
/// <param name="user">The user.</param>
|
|
||||||
Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user);
|
|
||||||
|
|
||||||
bool EnableRememberingTrackSelections { get; }
|
bool EnableRememberingTrackSelections { get; }
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <returns>BaseItem.</returns>
|
/// <returns>BaseItem.</returns>
|
||||||
BaseItem FindByPath(string path, bool? isFolder);
|
BaseItem FindByPath(string path, bool? isFolder);
|
||||||
|
|
||||||
|
Guid? FindIdByPath(string path, bool? isFolder);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the artist.
|
/// Gets the artist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -5,6 +5,7 @@ using MediaBrowser.Model.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Library
|
namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
|
@ -37,12 +38,9 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the user data dto.
|
/// Gets the user data dto.
|
||||||
/// </summary>
|
/// </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, User user);
|
||||||
|
|
||||||
Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user);
|
Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all user data for the given user
|
/// Get all user data for the given user
|
||||||
|
|
|
@ -11,6 +11,7 @@ using MediaBrowser.Model.Net;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
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()
|
private void DisposeIsoMount()
|
||||||
{
|
{
|
||||||
if (IsoMount != null)
|
if (IsoMount != null)
|
||||||
|
|
|
@ -846,7 +846,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
state.TargetVideoStreamCount,
|
state.TargetVideoStreamCount,
|
||||||
state.TargetAudioStreamCount,
|
state.TargetAudioStreamCount,
|
||||||
state.TargetVideoCodecTag,
|
state.TargetVideoCodecTag,
|
||||||
state.IsTargetAVC);
|
state.IsTargetAVC,
|
||||||
|
state.AllAudioCodecs);
|
||||||
|
|
||||||
if (mediaProfile != null)
|
if (mediaProfile != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -191,7 +191,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public int SharingExpirationDays { get; set; }
|
public int SharingExpirationDays { get; set; }
|
||||||
|
|
||||||
public int SchemaVersion { get; set; }
|
public int SchemaVersion { get; set; }
|
||||||
public int SqliteCacheSize { get; set; }
|
|
||||||
|
|
||||||
public bool EnableAnonymousUsageReporting { get; set; }
|
public bool EnableAnonymousUsageReporting { get; set; }
|
||||||
public bool EnableStandaloneMusicKeys { get; set; }
|
public bool EnableStandaloneMusicKeys { get; set; }
|
||||||
|
@ -202,6 +201,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public bool DisplayCollectionsView { get; set; }
|
public bool DisplayCollectionsView { get; set; }
|
||||||
public string[] LocalNetworkAddresses { get; set; }
|
public string[] LocalNetworkAddresses { get; set; }
|
||||||
public string[] CodecsUsed { get; set; }
|
public string[] CodecsUsed { get; set; }
|
||||||
|
public string[] Migrations { get; set; }
|
||||||
public bool EnableChannelView { get; set; }
|
public bool EnableChannelView { get; set; }
|
||||||
public bool EnableExternalContentInSuggestions { get; set; }
|
public bool EnableExternalContentInSuggestions { get; set; }
|
||||||
public bool EnableSimpleArtistDetection { get; set; }
|
public bool EnableSimpleArtistDetection { get; set; }
|
||||||
|
@ -214,7 +214,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
{
|
{
|
||||||
LocalNetworkAddresses = new string[] { };
|
LocalNetworkAddresses = new string[] { };
|
||||||
CodecsUsed = new string[] { };
|
CodecsUsed = new string[] { };
|
||||||
SqliteCacheSize = 0;
|
Migrations = new string[] { };
|
||||||
ImageExtractionTimeoutMs = 0;
|
ImageExtractionTimeoutMs = 0;
|
||||||
|
|
||||||
EnableLocalizedGuids = true;
|
EnableLocalizedGuids = true;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
|
@ -22,12 +24,15 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? numVideoStreams,
|
int? numVideoStreams,
|
||||||
int? numAudioStreams,
|
int? numAudioStreams,
|
||||||
string videoCodecTag,
|
string videoCodecTag,
|
||||||
bool? isAvc)
|
bool? isAvc,
|
||||||
|
List<string> allAudioCodecs )
|
||||||
{
|
{
|
||||||
switch (condition.Property)
|
switch (condition.Property)
|
||||||
{
|
{
|
||||||
case ProfileConditionValue.IsAnamorphic:
|
case ProfileConditionValue.IsAnamorphic:
|
||||||
return IsConditionSatisfied(condition, isAnamorphic);
|
return IsConditionSatisfied(condition, isAnamorphic);
|
||||||
|
case ProfileConditionValue.HasAudioCodec:
|
||||||
|
return IsHasAudioCodecConditionSatisfied(condition, allAudioCodecs);
|
||||||
case ProfileConditionValue.IsAvc:
|
case ProfileConditionValue.IsAvc:
|
||||||
return IsConditionSatisfied(condition, isAvc);
|
return IsConditionSatisfied(condition, isAvc);
|
||||||
case ProfileConditionValue.VideoFramerate:
|
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)
|
private bool IsConditionSatisfied(ProfileCondition condition, bool? currentValue)
|
||||||
{
|
{
|
||||||
if (!currentValue.HasValue)
|
if (!currentValue.HasValue)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Extensions;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
|
@ -27,5 +28,12 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ContainsContainer(string container)
|
||||||
|
{
|
||||||
|
List<string> containers = GetContainers();
|
||||||
|
|
||||||
|
return containers.Count == 0 || ListHelper.ContainsIgnoreCase(containers, container ?? string.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? numVideoStreams,
|
int? numVideoStreams,
|
||||||
int? numAudioStreams,
|
int? numAudioStreams,
|
||||||
string videoCodecTag,
|
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
|
// 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);
|
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
|
||||||
|
@ -161,7 +162,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
numVideoStreams,
|
numVideoStreams,
|
||||||
numAudioStreams,
|
numAudioStreams,
|
||||||
videoCodecTag,
|
videoCodecTag,
|
||||||
isAvc);
|
isAvc,
|
||||||
|
allAudioCodecs);
|
||||||
|
|
||||||
List<string> orgPnValues = new List<string>();
|
List<string> orgPnValues = new List<string>();
|
||||||
|
|
||||||
|
|
|
@ -297,7 +297,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? numVideoStreams,
|
int? numVideoStreams,
|
||||||
int? numAudioStreams,
|
int? numAudioStreams,
|
||||||
string videoCodecTag,
|
string videoCodecTag,
|
||||||
bool? isAvc)
|
bool? isAvc,
|
||||||
|
List<string> allAudioCodecs)
|
||||||
{
|
{
|
||||||
container = StringHelper.TrimStart(container ?? string.Empty, '.');
|
container = StringHelper.TrimStart(container ?? string.Empty, '.');
|
||||||
|
|
||||||
|
@ -331,7 +332,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
var anyOff = false;
|
var anyOff = false;
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
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;
|
anyOff = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
NumVideoStreams = 17,
|
NumVideoStreams = 17,
|
||||||
IsSecondaryAudio = 18,
|
IsSecondaryAudio = 18,
|
||||||
VideoCodecTag = 19,
|
VideoCodecTag = 19,
|
||||||
IsAvc = 20
|
IsAvc = 20,
|
||||||
|
HasAudioCodec = 21
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ using MediaBrowser.Model.Session;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
|
@ -409,6 +410,9 @@ namespace MediaBrowser.Model.Dlna
|
||||||
audioStreamIndex = audioStream.Index;
|
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;
|
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
|
// 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)
|
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
||||||
{
|
{
|
||||||
// See if it can be direct played
|
// 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)
|
if (directPlay != null)
|
||||||
{
|
{
|
||||||
|
@ -552,7 +556,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
||||||
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
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);
|
LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
|
@ -653,7 +657,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
MediaStream videoStream,
|
MediaStream videoStream,
|
||||||
MediaStream audioStream,
|
MediaStream audioStream,
|
||||||
bool isEligibleForDirectPlay,
|
bool isEligibleForDirectPlay,
|
||||||
bool isEligibleForDirectStream)
|
bool isEligibleForDirectStream,
|
||||||
|
List<MediaStream> allMediaStreams)
|
||||||
{
|
{
|
||||||
DeviceProfile profile = options.Profile;
|
DeviceProfile profile = options.Profile;
|
||||||
|
|
||||||
|
@ -701,7 +706,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
foreach (ContainerProfile i in profile.ContainerProfiles)
|
foreach (ContainerProfile i in profile.ContainerProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type == DlnaProfileType.Video &&
|
if (i.Type == DlnaProfileType.Video &&
|
||||||
ListHelper.ContainsIgnoreCase(i.GetContainers(), container))
|
i.ContainsContainer(container))
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
{
|
{
|
||||||
|
@ -734,10 +739,12 @@ namespace MediaBrowser.Model.Dlna
|
||||||
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
|
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
|
||||||
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
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
|
// Check container conditions
|
||||||
foreach (ProfileCondition i in 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);
|
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
|
||||||
|
|
||||||
|
@ -764,7 +771,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
bool applyConditions = true;
|
bool applyConditions = true;
|
||||||
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
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);
|
LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
|
||||||
applyConditions = false;
|
applyConditions = false;
|
||||||
|
@ -784,7 +791,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
foreach (ProfileCondition i in 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, "VideoCodecProfile", i, mediaSource);
|
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
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>
|
/// <summary>
|
||||||
/// Returns the video stream that will be used
|
/// Returns the video stream that will be used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -171,6 +171,8 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PrimaryImageAspectRatio,
|
PrimaryImageAspectRatio,
|
||||||
|
|
||||||
|
RecursiveItemCount,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The revenue
|
/// The revenue
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -307,9 +307,7 @@
|
||||||
<None Include="LiveTv\TunerHosts\SatIp\ini\satellite\3594.ini" />
|
<None Include="LiveTv\TunerHosts\SatIp\ini\satellite\3594.ini" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup />
|
||||||
<Folder Include="Persistence\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- 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.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -291,7 +291,7 @@ namespace Rssdp.Infrastructure
|
||||||
if (devices != null)
|
if (devices != null)
|
||||||
{
|
{
|
||||||
var deviceList = devices.ToList();
|
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)
|
foreach (var device in deviceList)
|
||||||
{
|
{
|
||||||
|
@ -300,7 +300,7 @@ namespace Rssdp.Infrastructure
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WriteTrace(String.Format("Sending 0 search responses."));
|
//WriteTrace(String.Format("Sending 0 search responses."));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,7 @@ namespace Rssdp.Infrastructure
|
||||||
|
|
||||||
//DisposeRebroadcastTimer();
|
//DisposeRebroadcastTimer();
|
||||||
|
|
||||||
WriteTrace("Begin Sending Alive Notifications For All Devices");
|
//WriteTrace("Begin Sending Alive Notifications For All Devices");
|
||||||
|
|
||||||
_LastNotificationTime = DateTime.Now;
|
_LastNotificationTime = DateTime.Now;
|
||||||
|
|
||||||
|
@ -430,7 +430,7 @@ namespace Rssdp.Infrastructure
|
||||||
SendAliveNotifications(device, true);
|
SendAliveNotifications(device, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteTrace("Completed Sending Alive Notifications For All Devices");
|
//WriteTrace("Completed Sending Alive Notifications For All Devices");
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException ex)
|
catch (ObjectDisposedException ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,5 +37,10 @@
|
||||||
<ProjectReference Include="..\..\ServiceStack\ServiceStack.csproj" />
|
<ProjectReference Include="..\..\ServiceStack\ServiceStack.csproj" />
|
||||||
<ProjectReference Include="..\..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj" />
|
<ProjectReference Include="..\..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\..\SharedVersion.cs">
|
||||||
|
<Link>Properties\SharedVersion.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.0-*",
|
"version": "3.1.0-*",
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"emitEntryPoint": true
|
"emitEntryPoint": true
|
||||||
},
|
},
|
||||||
|
@ -33,8 +33,26 @@
|
||||||
"win10-arm64": {},
|
"win10-arm64": {},
|
||||||
"osx.10.10-x64": {},
|
"osx.10.10-x64": {},
|
||||||
"osx.10.11-x64": {},
|
"osx.10.11-x64": {},
|
||||||
"osx.10.12-x64": {},
|
"osx.10.12-x64": ,
|
||||||
"ubuntu.14.04-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": {
|
"frameworks": {
|
||||||
|
|
Loading…
Reference in a new issue