mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-15 23:58:57 +00:00
Merge branch 'master' into fix-hwa-video-rotation
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Jellyfin.Extensions;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -13,6 +14,8 @@ namespace Emby.Server.Implementations.Data
|
||||
public abstract class BaseSqliteRepository : IDisposable
|
||||
{
|
||||
private bool _disposed = false;
|
||||
private SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
|
||||
private SqliteConnection _writeConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
|
||||
@@ -28,17 +31,6 @@ namespace Emby.Server.Implementations.Data
|
||||
/// </summary>
|
||||
protected string DbFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of write connections to create.
|
||||
/// </summary>
|
||||
/// <value>Path to the DB file.</value>
|
||||
protected int WriteConnectionsCount { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of read connections to create.
|
||||
/// </summary>
|
||||
protected int ReadConnectionsCount { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
@@ -98,9 +90,55 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
protected SqliteConnection GetConnection()
|
||||
protected ManagedConnection GetConnection(bool readOnly = false)
|
||||
{
|
||||
var connection = new SqliteConnection($"Filename={DbFilePath}");
|
||||
if (!readOnly)
|
||||
{
|
||||
_writeLock.Wait();
|
||||
if (_writeConnection is not null)
|
||||
{
|
||||
return new ManagedConnection(_writeConnection, _writeLock);
|
||||
}
|
||||
|
||||
var writeConnection = new SqliteConnection($"Filename={DbFilePath};Pooling=False");
|
||||
writeConnection.Open();
|
||||
|
||||
if (CacheSize.HasValue)
|
||||
{
|
||||
writeConnection.Execute("PRAGMA cache_size=" + CacheSize.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(LockingMode))
|
||||
{
|
||||
writeConnection.Execute("PRAGMA locking_mode=" + LockingMode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(JournalMode))
|
||||
{
|
||||
writeConnection.Execute("PRAGMA journal_mode=" + JournalMode);
|
||||
}
|
||||
|
||||
if (JournalSizeLimit.HasValue)
|
||||
{
|
||||
writeConnection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value);
|
||||
}
|
||||
|
||||
if (Synchronous.HasValue)
|
||||
{
|
||||
writeConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
|
||||
}
|
||||
|
||||
if (PageSize.HasValue)
|
||||
{
|
||||
writeConnection.Execute("PRAGMA page_size=" + PageSize.Value);
|
||||
}
|
||||
|
||||
writeConnection.Execute("PRAGMA temp_store=" + (int)TempStore);
|
||||
|
||||
return new ManagedConnection(_writeConnection = writeConnection, _writeLock);
|
||||
}
|
||||
|
||||
var connection = new SqliteConnection($"Filename={DbFilePath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
|
||||
if (CacheSize.HasValue)
|
||||
@@ -135,17 +173,17 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
connection.Execute("PRAGMA temp_store=" + (int)TempStore);
|
||||
|
||||
return connection;
|
||||
return new ManagedConnection(connection, null);
|
||||
}
|
||||
|
||||
public SqliteCommand PrepareStatement(SqliteConnection connection, string sql)
|
||||
public SqliteCommand PrepareStatement(ManagedConnection connection, string sql)
|
||||
{
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = sql;
|
||||
return command;
|
||||
}
|
||||
|
||||
protected bool TableExists(SqliteConnection connection, string name)
|
||||
protected bool TableExists(ManagedConnection connection, string name)
|
||||
{
|
||||
using var statement = PrepareStatement(connection, "select DISTINCT tbl_name from sqlite_master");
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
@@ -159,7 +197,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return false;
|
||||
}
|
||||
|
||||
protected List<string> GetColumnNames(SqliteConnection connection, string table)
|
||||
protected List<string> GetColumnNames(ManagedConnection connection, string table)
|
||||
{
|
||||
var columnNames = new List<string>();
|
||||
|
||||
@@ -174,7 +212,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
protected void AddColumn(SqliteConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
|
||||
protected void AddColumn(ManagedConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
|
||||
{
|
||||
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -186,10 +224,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
protected void CheckDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed.");
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -210,6 +245,24 @@ namespace Emby.Server.Implementations.Data
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
_writeLock.Wait();
|
||||
try
|
||||
{
|
||||
_writeConnection.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writeLock.Release();
|
||||
}
|
||||
|
||||
_writeLock.Dispose();
|
||||
}
|
||||
|
||||
_writeConnection = null;
|
||||
_writeLock = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
62
Emby.Server.Implementations/Data/ManagedConnection.cs
Normal file
62
Emby.Server.Implementations/Data/ManagedConnection.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace Emby.Server.Implementations.Data;
|
||||
|
||||
public sealed class ManagedConnection : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim? _writeLock;
|
||||
|
||||
private SqliteConnection _db;
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
public ManagedConnection(SqliteConnection db, SemaphoreSlim? writeLock)
|
||||
{
|
||||
_db = db;
|
||||
_writeLock = writeLock;
|
||||
}
|
||||
|
||||
public SqliteTransaction BeginTransaction()
|
||||
=> _db.BeginTransaction();
|
||||
|
||||
public SqliteCommand CreateCommand()
|
||||
=> _db.CreateCommand();
|
||||
|
||||
public void Execute(string commandText)
|
||||
=> _db.Execute(commandText);
|
||||
|
||||
public SqliteCommand PrepareStatement(string sql)
|
||||
=> _db.PrepareStatement(sql);
|
||||
|
||||
public IEnumerable<SqliteDataReader> Query(string commandText)
|
||||
=> _db.Query(commandText);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_writeLock is null)
|
||||
{
|
||||
// Read connections are managed with an internal pool
|
||||
_db.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write lock is managed by BaseSqliteRepository
|
||||
// Don't dispose here
|
||||
_writeLock.Release();
|
||||
}
|
||||
|
||||
_db = null!;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,8 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private const string SaveItemCommandText =
|
||||
@"replace into TypedBaseItems
|
||||
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
|
||||
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
|
||||
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,NormalizationGain,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
|
||||
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@NormalizationGain,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
@@ -111,6 +111,7 @@ namespace Emby.Server.Implementations.Data
|
||||
"DateLastMediaAdded",
|
||||
"Album",
|
||||
"LUFS",
|
||||
"NormalizationGain",
|
||||
"CriticRating",
|
||||
"IsVirtualItem",
|
||||
"SeriesName",
|
||||
@@ -327,7 +328,6 @@ namespace Emby.Server.Implementations.Data
|
||||
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
|
||||
|
||||
CacheSize = configuration.GetSqliteCacheSize();
|
||||
ReadConnectionsCount = Environment.ProcessorCount * 2;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -479,6 +479,7 @@ namespace Emby.Server.Implementations.Data
|
||||
AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
|
||||
AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames);
|
||||
AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames);
|
||||
AddColumn(connection, "TypedBaseItems", "NormalizationGain", "Float", existingColumnNames);
|
||||
AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
|
||||
AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
|
||||
AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
|
||||
@@ -602,7 +603,7 @@ namespace Emby.Server.Implementations.Data
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
private void SaveItemsInTransaction(SqliteConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
|
||||
private void SaveItemsInTransaction(ManagedConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
|
||||
{
|
||||
using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText))
|
||||
using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId"))
|
||||
@@ -889,6 +890,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
saveItemStatement.TryBind("@Album", item.Album);
|
||||
saveItemStatement.TryBind("@LUFS", item.LUFS);
|
||||
saveItemStatement.TryBind("@NormalizationGain", item.NormalizationGain);
|
||||
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
|
||||
|
||||
if (item is IHasSeries hasSeriesName)
|
||||
@@ -1047,9 +1049,10 @@ namespace Emby.Server.Implementations.Data
|
||||
foreach (var part in value.SpanSplit('|'))
|
||||
{
|
||||
var providerDelimiterIndex = part.IndexOf('=');
|
||||
if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
|
||||
// Don't let empty values through
|
||||
if (providerDelimiterIndex != -1 && part.Length != providerDelimiterIndex + 1)
|
||||
{
|
||||
item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString());
|
||||
item.SetProviderId(part[..providerDelimiterIndex].ToString(), part[(providerDelimiterIndex + 1)..].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1261,7 +1264,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
|
||||
{
|
||||
statement.TryBind("@guid", id);
|
||||
@@ -1298,16 +1301,15 @@ namespace Emby.Server.Implementations.Data
|
||||
&& type != typeof(Book)
|
||||
&& type != typeof(LiveTvProgram)
|
||||
&& type != typeof(AudioBook)
|
||||
&& type != typeof(Audio)
|
||||
&& type != typeof(MusicAlbum);
|
||||
}
|
||||
|
||||
private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query)
|
||||
{
|
||||
return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query));
|
||||
return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query), false);
|
||||
}
|
||||
|
||||
private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
|
||||
private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields, bool skipDeserialization)
|
||||
{
|
||||
var typeString = reader.GetString(0);
|
||||
|
||||
@@ -1320,7 +1322,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
BaseItem item = null;
|
||||
|
||||
if (TypeRequiresDeserialization(type))
|
||||
if (TypeRequiresDeserialization(type) && !skipDeserialization)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1675,6 +1677,11 @@ namespace Emby.Server.Implementations.Data
|
||||
item.LUFS = lUFS;
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var normalizationGain))
|
||||
{
|
||||
item.NormalizationGain = normalizationGain;
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var criticRating))
|
||||
{
|
||||
item.CriticRating = criticRating;
|
||||
@@ -1883,7 +1890,7 @@ namespace Emby.Server.Implementations.Data
|
||||
CheckDisposed();
|
||||
|
||||
var chapters = new List<ChapterInfo>();
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
|
||||
{
|
||||
statement.TryBind("@ItemId", item.Id);
|
||||
@@ -1902,7 +1909,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex"))
|
||||
{
|
||||
statement.TryBind("@ItemId", item.Id);
|
||||
@@ -1976,7 +1983,7 @@ namespace Emby.Server.Implementations.Data
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, SqliteConnection db)
|
||||
private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, ManagedConnection db)
|
||||
{
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
@@ -2318,14 +2325,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
columns.Add(builder.ToString());
|
||||
|
||||
var oldLen = query.ExcludeItemIds.Length;
|
||||
var newLen = oldLen + item.ExtraIds.Length + 1;
|
||||
var excludeIds = new Guid[newLen];
|
||||
query.ExcludeItemIds.CopyTo(excludeIds, 0);
|
||||
excludeIds[oldLen] = item.Id;
|
||||
item.ExtraIds.CopyTo(excludeIds, oldLen + 1);
|
||||
|
||||
query.ExcludeItemIds = excludeIds;
|
||||
query.ExcludeItemIds = [.. query.ExcludeItemIds, item.Id, .. item.ExtraIds];
|
||||
query.ExcludeProviderIds = item.ProviderIds;
|
||||
}
|
||||
|
||||
@@ -2472,7 +2472,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var commandText = commandTextBuilder.ToString();
|
||||
|
||||
using (new QueryTimeLogger(Logger, commandText))
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, commandText))
|
||||
{
|
||||
if (EnableJoinUserData(query))
|
||||
@@ -2540,7 +2540,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var commandText = commandTextBuilder.ToString();
|
||||
var items = new List<BaseItem>();
|
||||
using (new QueryTimeLogger(Logger, commandText))
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, commandText))
|
||||
{
|
||||
if (EnableJoinUserData(query))
|
||||
@@ -2564,7 +2564,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
|
||||
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, query.SkipDeserialization);
|
||||
if (item is not null)
|
||||
{
|
||||
items.Add(item);
|
||||
@@ -2748,7 +2748,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
var list = new List<BaseItem>();
|
||||
var result = new QueryResult<BaseItem>();
|
||||
using var connection = GetConnection();
|
||||
using var connection = GetConnection(true);
|
||||
using var transaction = connection.BeginTransaction();
|
||||
if (!isReturningZeroItems)
|
||||
{
|
||||
@@ -2776,7 +2776,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
|
||||
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, false);
|
||||
if (item is not null)
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -2833,10 +2833,7 @@ namespace Emby.Server.Implementations.Data
|
||||
prepend.Add((ItemSortBy.Random, SortOrder.Ascending));
|
||||
}
|
||||
|
||||
var arr = new (ItemSortBy, SortOrder)[prepend.Count + orderBy.Count];
|
||||
prepend.CopyTo(arr, 0);
|
||||
orderBy.CopyTo(arr, prepend.Count);
|
||||
orderBy = query.OrderBy = arr;
|
||||
orderBy = query.OrderBy = [.. prepend, .. orderBy];
|
||||
}
|
||||
else if (orderBy.Count == 0)
|
||||
{
|
||||
@@ -2933,7 +2930,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var commandText = commandTextBuilder.ToString();
|
||||
var list = new List<Guid>();
|
||||
using (new QueryTimeLogger(Logger, commandText))
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, commandText))
|
||||
{
|
||||
if (EnableJoinUserData(query))
|
||||
@@ -4197,7 +4194,19 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
int index = 0;
|
||||
string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++));
|
||||
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)");
|
||||
// Episodes do not store inherit tags from their parents in the database, and the tag may be still required by the client.
|
||||
// In addtion to the tags for the episodes themselves, we need to manually query its parent (the season)'s tags as well.
|
||||
if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
|
||||
{
|
||||
whereClauses.Add($"""
|
||||
((select CleanValue from ItemValues where ItemId=Guid and Type=6 and CleanValue in ({includedTags})) is not null
|
||||
OR (select CleanValue from ItemValues where ItemId=ParentId and Type=6 and CleanValue in ({includedTags})) is not null)
|
||||
""");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4470,7 +4479,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
private void ExecuteWithSingleParam(SqliteConnection db, string query, Guid value)
|
||||
private void ExecuteWithSingleParam(ManagedConnection db, string query, Guid value)
|
||||
{
|
||||
using (var statement = PrepareStatement(db, query))
|
||||
{
|
||||
@@ -4503,7 +4512,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
|
||||
var list = new List<string>();
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, commandText.ToString()))
|
||||
{
|
||||
// Run this again to bind the params
|
||||
@@ -4541,7 +4550,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
|
||||
var list = new List<PersonInfo>();
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, commandText.ToString()))
|
||||
{
|
||||
// Run this again to bind the params
|
||||
@@ -4626,7 +4635,7 @@ AND Type = @InternalPersonType)");
|
||||
return whereClauses;
|
||||
}
|
||||
|
||||
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement)
|
||||
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, ManagedConnection db, SqliteCommand deleteAncestorsStatement)
|
||||
{
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
@@ -4781,7 +4790,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
var list = new List<string>();
|
||||
using (new QueryTimeLogger(Logger, commandText))
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, commandText))
|
||||
{
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
@@ -4981,8 +4990,8 @@ AND Type = @InternalPersonType)");
|
||||
var list = new List<(BaseItem, ItemCounts)>();
|
||||
var result = new QueryResult<(BaseItem, ItemCounts)>();
|
||||
using (new QueryTimeLogger(Logger, commandText))
|
||||
using (var connection = GetConnection())
|
||||
using (var transaction = connection.BeginTransaction(deferred: true))
|
||||
using (var connection = GetConnection(true))
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
if (!isReturningZeroItems)
|
||||
{
|
||||
@@ -5014,7 +5023,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
|
||||
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, false);
|
||||
if (item is not null)
|
||||
{
|
||||
var countStartColumn = columns.Count - 1;
|
||||
@@ -5137,12 +5146,12 @@ AND Type = @InternalPersonType)");
|
||||
list.AddRange(inheritedTags.Select(i => (6, i)));
|
||||
|
||||
// Remove all invalid values.
|
||||
list.RemoveAll(i => string.IsNullOrEmpty(i.Item2));
|
||||
list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db)
|
||||
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, ManagedConnection db)
|
||||
{
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
@@ -5161,7 +5170,7 @@ AND Type = @InternalPersonType)");
|
||||
InsertItemValues(itemId, values, db);
|
||||
}
|
||||
|
||||
private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, SqliteConnection db)
|
||||
private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, ManagedConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
@@ -5195,12 +5204,6 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
var itemValue = currentValueInfo.Value;
|
||||
|
||||
// Don't save if invalid
|
||||
if (string.IsNullOrWhiteSpace(itemValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
statement.TryBind("@Type" + index, currentValueInfo.MagicNumber);
|
||||
statement.TryBind("@Value" + index, itemValue);
|
||||
statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue));
|
||||
@@ -5221,24 +5224,25 @@ AND Type = @InternalPersonType)");
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(people);
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
using var connection = GetConnection();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
// First delete chapters
|
||||
// Delete all existing people first
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "delete from People where ItemId=@ItemId";
|
||||
command.TryBind("@ItemId", itemId);
|
||||
command.ExecuteNonQuery();
|
||||
|
||||
InsertPeople(itemId, people, connection);
|
||||
if (people is not null)
|
||||
{
|
||||
InsertPeople(itemId, people, connection);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
private void InsertPeople(Guid id, List<PersonInfo> people, SqliteConnection db)
|
||||
private void InsertPeople(Guid id, List<PersonInfo> people, ManagedConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
@@ -5334,7 +5338,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
cmdText += " order by StreamIndex ASC";
|
||||
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
var list = new List<MediaStream>();
|
||||
|
||||
@@ -5387,7 +5391,7 @@ AND Type = @InternalPersonType)");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, SqliteConnection db)
|
||||
private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, ManagedConnection db)
|
||||
{
|
||||
const int Limit = 10;
|
||||
var startIndex = 0;
|
||||
@@ -5700,13 +5704,17 @@ AND Type = @InternalPersonType)");
|
||||
item.Rotation = rotation;
|
||||
}
|
||||
|
||||
if (item.Type == MediaStreamType.Subtitle)
|
||||
if (item.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
|
||||
{
|
||||
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||
item.LocalizedDefault = _localization.GetLocalizedString("Default");
|
||||
item.LocalizedForced = _localization.GetLocalizedString("Forced");
|
||||
item.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
|
||||
|
||||
if (item.Type is MediaStreamType.Subtitle)
|
||||
{
|
||||
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||
item.LocalizedForced = _localization.GetLocalizedString("Forced");
|
||||
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -5728,7 +5736,7 @@ AND Type = @InternalPersonType)");
|
||||
cmdText += " order by AttachmentIndex ASC";
|
||||
|
||||
var list = new List<MediaAttachment>();
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = PrepareStatement(connection, cmdText))
|
||||
{
|
||||
statement.TryBind("@ItemId", query.ItemId);
|
||||
@@ -5778,7 +5786,7 @@ AND Type = @InternalPersonType)");
|
||||
private void InsertMediaAttachments(
|
||||
Guid id,
|
||||
IReadOnlyList<MediaAttachment> attachments,
|
||||
SqliteConnection db,
|
||||
ManagedConnection db,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
const int InsertAtOnce = 10;
|
||||
|
||||
@@ -58,7 +58,8 @@ namespace Emby.Server.Implementations.Data
|
||||
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
|
||||
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
|
||||
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
|
||||
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"));
|
||||
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)",
|
||||
"create index if not exists UserDatasIndex5 on UserDatas (key, userId, lastPlayedDate)"));
|
||||
|
||||
if (!userDataTableExists)
|
||||
{
|
||||
@@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportUserIds(SqliteConnection db, IEnumerable<User> users)
|
||||
private void ImportUserIds(ManagedConnection db, IEnumerable<User> users)
|
||||
{
|
||||
var userIdsWithUserData = GetAllUserIdsWithUserData(db);
|
||||
|
||||
@@ -106,7 +107,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private List<Guid> GetAllUserIdsWithUserData(SqliteConnection db)
|
||||
private List<Guid> GetAllUserIdsWithUserData(ManagedConnection db)
|
||||
{
|
||||
var list = new List<Guid>();
|
||||
|
||||
@@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveUserData(SqliteConnection db, long internalUserId, string key, UserItemData userData)
|
||||
private static void SaveUserData(ManagedConnection db, long internalUserId, string key, UserItemData userData)
|
||||
{
|
||||
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
|
||||
{
|
||||
@@ -266,7 +267,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
|
||||
using (var connection = GetConnection())
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user