You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jellyfin/Emby.Server.Implementations/Data/SqliteItemRepository.cs

5926 lines
223 KiB

#nullable disable
#pragma warning disable CS1591
8 years ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
8 years ago
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
8 years ago
using System.Text;
using System.Text.Json;
8 years ago
using System.Threading;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller;
8 years ago
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
8 years ago
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Extensions;
8 years ago
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
8 years ago
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
1 year ago
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
8 years ago
namespace Emby.Server.Implementations.Data
{
/// <summary>
/// Class SQLiteItemRepository.
8 years ago
/// </summary>
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
{
private const string FromText = " from TypedBaseItems A";
5 years ago
private const string ChaptersTableName = "Chapters2";
8 years ago
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)";
8 years ago
private readonly IServerConfigurationManager _config;
5 years ago
private readonly IServerApplicationHost _appHost;
private readonly ILocalizationManager _localization;
// TODO: Remove this dependency. GetImageCacheTag() is the only method used and it can be converted to a static helper method
private readonly IImageProcessor _imageProcessor;
private readonly TypeMapper _typeMapper;
private readonly JsonSerializerOptions _jsonOptions;
private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>();
private static readonly string[] _retrieveItemColumns =
{
"type",
"data",
"StartDate",
"EndDate",
"ChannelId",
"IsMovie",
"IsSeries",
"EpisodeTitle",
"IsRepeat",
"CommunityRating",
"CustomRating",
"IndexNumber",
"IsLocked",
"PreferredMetadataLanguage",
"PreferredMetadataCountryCode",
"Width",
"Height",
"DateLastRefreshed",
"Name",
"Path",
"PremiereDate",
"Overview",
"ParentIndexNumber",
"ProductionYear",
"OfficialRating",
"ForcedSortName",
"RunTimeTicks",
"Size",
"DateCreated",
"DateModified",
"guid",
"Genres",
"ParentId",
"Audio",
"ExternalServiceId",
"IsInMixedFolder",
"DateLastSaved",
"LockedFields",
"Studios",
"Tags",
"TrailerTypes",
"OriginalTitle",
"PrimaryVersionId",
"DateLastMediaAdded",
"Album",
"LUFS",
"CriticRating",
"IsVirtualItem",
"SeriesName",
"SeasonName",
"SeasonId",
"SeriesId",
"PresentationUniqueKey",
"InheritedParentalRatingValue",
"ExternalSeriesId",
"Tagline",
"ProviderIds",
"Images",
"ProductionLocations",
"ExtraIds",
"TotalBitrate",
"ExtraType",
"Artists",
"AlbumArtists",
"ExternalId",
"SeriesPresentationUniqueKey",
"ShowId",
"OwnerId"
};
private static readonly string _retrieveItemColumnsSelectQuery = $"select {string.Join(',', _retrieveItemColumns)} from TypedBaseItems where guid = @guid";
private static readonly string[] _mediaStreamSaveColumns =
{
"ItemId",
"StreamIndex",
"StreamType",
"Codec",
"Language",
"ChannelLayout",
"Profile",
"AspectRatio",
"Path",
"IsInterlaced",
"BitRate",
"Channels",
"SampleRate",
"IsDefault",
"IsForced",
"IsExternal",
"Height",
"Width",
"AverageFrameRate",
"RealFrameRate",
"Level",
"PixelFormat",
"BitDepth",
"IsAnamorphic",
"RefFrames",
"CodecTag",
"Comment",
"NalLengthSize",
"IsAvc",
"Title",
"TimeBase",
"CodecTimeBase",
"ColorPrimaries",
"ColorSpace",
"ColorTransfer",
"DvVersionMajor",
"DvVersionMinor",
"DvProfile",
"DvLevel",
"RpuPresentFlag",
"ElPresentFlag",
"BlPresentFlag",
"DvBlSignalCompatibilityId",
"IsHearingImpaired"
};
private static readonly string _mediaStreamSaveColumnsInsertQuery =
$"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values ";
private static readonly string _mediaStreamSaveColumnsSelectQuery =
$"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId";
private static readonly string[] _mediaAttachmentSaveColumns =
{
"ItemId",
"AttachmentIndex",
"Codec",
"CodecTag",
"Comment",
"Filename",
"MIMEType"
};
private static readonly string _mediaAttachmentSaveColumnsSelectQuery =
$"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId";
private static readonly string _mediaAttachmentInsertPrefix;
private static readonly BaseItemKind[] _programTypes = new[]
{
BaseItemKind.Program,
BaseItemKind.TvChannel,
BaseItemKind.LiveTvProgram,
BaseItemKind.LiveTvChannel
};
private static readonly BaseItemKind[] _programExcludeParentTypes = new[]
{
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.PhotoAlbum
};
private static readonly BaseItemKind[] _serviceTypes = new[]
{
BaseItemKind.TvChannel,
BaseItemKind.LiveTvChannel
};
private static readonly BaseItemKind[] _startDateTypes = new[]
{
BaseItemKind.Program,
BaseItemKind.LiveTvProgram
};
private static readonly BaseItemKind[] _seriesTypes = new[]
{
BaseItemKind.Book,
BaseItemKind.AudioBook,
BaseItemKind.Episode,
BaseItemKind.Season
};
private static readonly BaseItemKind[] _artistExcludeParentTypes = new[]
{
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.PhotoAlbum
};
private static readonly BaseItemKind[] _artistsTypes = new[]
{
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo,
BaseItemKind.AudioBook
};
private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
{ BaseItemKind.Audio, typeof(Audio).FullName },
{ BaseItemKind.AudioBook, typeof(AudioBook).FullName },
{ BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
{ BaseItemKind.Book, typeof(Book).FullName },
{ BaseItemKind.BoxSet, typeof(BoxSet).FullName },
{ BaseItemKind.Channel, typeof(Channel).FullName },
{ BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
{ BaseItemKind.Episode, typeof(Episode).FullName },
{ BaseItemKind.Folder, typeof(Folder).FullName },
{ BaseItemKind.Genre, typeof(Genre).FullName },
{ BaseItemKind.Movie, typeof(Movie).FullName },
{ BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
{ BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
{ BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
{ BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
{ BaseItemKind.Person, typeof(Person).FullName },
{ BaseItemKind.Photo, typeof(Photo).FullName },
{ BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
{ BaseItemKind.Playlist, typeof(Playlist).FullName },
{ BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
{ BaseItemKind.Season, typeof(Season).FullName },
{ BaseItemKind.Series, typeof(Series).FullName },
{ BaseItemKind.Studio, typeof(Studio).FullName },
{ BaseItemKind.Trailer, typeof(Trailer).FullName },
{ BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
{ BaseItemKind.UserView, typeof(UserView).FullName },
{ BaseItemKind.Video, typeof(Video).FullName },
{ BaseItemKind.Year, typeof(Year).FullName }
};
static SqliteItemRepository()
{
var queryPrefixText = new StringBuilder();
queryPrefixText.Append("insert into mediaattachments (");
foreach (var column in _mediaAttachmentSaveColumns)
{
queryPrefixText.Append(column)
.Append(',');
}
queryPrefixText.Length -= 1;
queryPrefixText.Append(") values ");
_mediaAttachmentInsertPrefix = queryPrefixText.ToString();
}
8 years ago
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
/// </summary>
/// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{SqliteItemRepository}"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <exception cref="ArgumentNullException">config is null.</exception>
public SqliteItemRepository(
IServerConfigurationManager config,
IServerApplicationHost appHost,
ILogger<SqliteItemRepository> logger,
ILocalizationManager localization,
IImageProcessor imageProcessor,
IConfiguration configuration)
: base(logger)
8 years ago
{
_config = config;
_appHost = appHost;
_localization = localization;
_imageProcessor = imageProcessor;
8 years ago
_typeMapper = new TypeMapper();
_jsonOptions = JsonDefaults.Options;
8 years ago
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
CacheSize = configuration.GetSqliteCacheSize();
ReadConnectionsCount = Environment.ProcessorCount * 2;
8 years ago
}
5 years ago
/// <inheritdoc />
protected override int? CacheSize { get; }
5 years ago
/// <inheritdoc />
protected override TempStoreMode TempStore => TempStoreMode.Memory;
8 years ago
/// <summary>
/// Opens the connection to the database.
8 years ago
/// </summary>
public override void Initialize()
8 years ago
{
base.Initialize();
5 years ago
const string CreateMediaStreamsTableCommand
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, IsHearingImpaired BIT NULL, PRIMARY KEY (ItemId, StreamIndex))";
const string CreateMediaAttachmentsTableCommand
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
8 years ago
5 years ago
string[] queries =
{
"create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)",
8 years ago
5 years ago
"create table if not exists AncestorIds (ItemId GUID NOT NULL, AncestorId GUID NOT NULL, AncestorIdText TEXT NOT NULL, PRIMARY KEY (ItemId, AncestorId))",
"create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
"create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)",
8 years ago
5 years ago
"create table if not exists ItemValues (ItemId GUID NOT NULL, Type INT NOT NULL, Value TEXT NOT NULL, CleanValue TEXT NOT NULL)",
8 years ago
5 years ago
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
8 years ago
5 years ago
"drop index if exists idxPeopleItemId",
"create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)",
"create index if not exists idxPeopleName on People(Name)",
8 years ago
5 years ago
"create table if not exists " + ChaptersTableName + " (ItemId GUID, ChapterIndex INT NOT NULL, StartPositionTicks BIGINT NOT NULL, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
8 years ago
5 years ago
CreateMediaStreamsTableCommand,
CreateMediaAttachmentsTableCommand,
8 years ago
5 years ago
"pragma shrink_memory"
};
string[] postQueries =
{
"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_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)",
"create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)",
"create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)",
// covering index
"create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)",
// series
"create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)",
// series counts
// seriesdateplayed sort order
"create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)",
// live tv programs
"create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
// covering index for getitemvalues
"create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)",
// used by movie suggestions
"create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)",
"create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)",
// latest items
"create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)",
"create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)",
// resume
"create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)",
// items by name
"create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)",
"create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)",
// Used to update inherited tags
"create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)",
"CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type)",
"CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder)"
5 years ago
};
8 years ago
5 years ago
using (var connection = GetConnection())
using (var transaction = connection.BeginTransaction())
{
connection.Execute(string.Join(';', queries));
var existingColumnNames = GetColumnNames(connection, "AncestorIds");
AddColumn(connection, "AncestorIds", "AncestorIdText", "Text", existingColumnNames);
existingColumnNames = GetColumnNames(connection, "TypedBaseItems");
AddColumn(connection, "TypedBaseItems", "Path", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "StartDate", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "EndDate", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ChannelId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsMovie", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "CommunityRating", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "CustomRating", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IndexNumber", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsLocked", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Name", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "OfficialRating", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "MediaType", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Overview", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ParentIndexNumber", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "PremiereDate", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ProductionYear", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ParentId", "GUID", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Genres", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SortName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "EpisodeTitle", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsRepeat", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "PreferredMetadataLanguage", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "PreferredMetadataCountryCode", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "DateLastRefreshed", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "DateLastSaved", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsInMixedFolder", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "LockedFields", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Studios", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Audio", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ExternalServiceId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Tags", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsFolder", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "InheritedParentalRatingValue", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "UnratedType", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "TopParentId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "TrailerTypes", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "CriticRating", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "CleanName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "PresentationUniqueKey", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "OriginalTitle", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeasonName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeasonId", "GUID", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Tagline", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Images", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ProductionLocations", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ExtraIds", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "TotalBitrate", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ExtraType", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Artists", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ExternalId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Width", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Height", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Size", "BIGINT", existingColumnNames);
existingColumnNames = GetColumnNames(connection, "ItemValues");
AddColumn(connection, "ItemValues", "CleanValue", "Text", existingColumnNames);
existingColumnNames = GetColumnNames(connection, ChaptersTableName);
AddColumn(connection, ChaptersTableName, "ImageDateModified", "DATETIME", existingColumnNames);
existingColumnNames = GetColumnNames(connection, "MediaStreams");
AddColumn(connection, "MediaStreams", "IsAvc", "BIT", existingColumnNames);
AddColumn(connection, "MediaStreams", "TimeBase", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "CodecTimeBase", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "Title", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "NalLengthSize", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "Comment", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "CodecTag", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "PixelFormat", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "BitDepth", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "RefFrames", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "KeyFrames", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "IsAnamorphic", "BIT", existingColumnNames);
AddColumn(connection, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
AddColumn(connection, "MediaStreams", "DvVersionMajor", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "DvVersionMinor", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "DvProfile", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "DvLevel", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "RpuPresentFlag", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
AddColumn(connection, "MediaStreams", "IsHearingImpaired", "BIT", existingColumnNames);
connection.Execute(string.Join(';', postQueries));
transaction.Commit();
}
8 years ago
}
public void SaveImages(BaseItem item)
{
ArgumentNullException.ThrowIfNull(item);
CheckDisposed();
var images = SerializeImages(item.ImageInfos);
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
using var saveImagesStatement = PrepareStatement(connection, "Update TypedBaseItems set Images=@Images where guid=@Id");
saveImagesStatement.TryBind("@Id", item.Id);
saveImagesStatement.TryBind("@Images", images);
saveImagesStatement.ExecuteNonQuery();
transaction.Commit();
}
8 years ago
/// <summary>
/// Saves the items.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="items"/> or <paramref name="cancellationToken"/> is <c>null</c>.
8 years ago
/// </exception>
public void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken)
8 years ago
{
ArgumentNullException.ThrowIfNull(items);
8 years ago
cancellationToken.ThrowIfCancellationRequested();
CheckDisposed();
var itemsLen = items.Count;
var tuples = new ValueTuple<BaseItem, List<Guid>, BaseItem, string, List<string>>[itemsLen];
for (int i = 0; i < itemsLen; i++)
{
var item = items[i];
var ancestorIds = item.SupportsAncestors ?
item.GetAncestorIds().Distinct().ToList() :
null;
var topParent = item.GetTopParent();
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
var inheritedTags = item.GetInheritedTags();
tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags);
}
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
SaveItemsInTransaction(connection, tuples);
transaction.Commit();
8 years ago
}
1 year ago
private void SaveItemsInTransaction(SqliteConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
8 years ago
{
using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText))
using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId"))
{
6 years ago
var requiresReset = false;
foreach (var tuple in tuples)
8 years ago
{
6 years ago
if (requiresReset)
8 years ago
{
saveItemStatement.Parameters.Clear();
deleteAncestorsStatement.Parameters.Clear();
6 years ago
}
8 years ago
var item = tuple.Item;
var topParent = tuple.TopParent;
var userDataKey = tuple.UserDataKey;
6 years ago
SaveItem(item, topParent, userDataKey, saveItemStatement);
8 years ago
var inheritedTags = tuple.InheritedTags;
6 years ago
if (item.SupportsAncestors)
{
UpdateAncestors(item.Id, tuple.AncestorIds, db, deleteAncestorsStatement);
6 years ago
}
8 years ago
6 years ago
UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db);
8 years ago
6 years ago
requiresReset = true;
8 years ago
}
}
}
private string GetPathToSave(string path)
{
if (path is null)
{
return null;
}
return _appHost.ReverseVirtualPath(path);
}
private string RestorePath(string path)
{
return _appHost.ExpandVirtualPath(path);
}
1 year ago
private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, SqliteCommand saveItemStatement)
8 years ago
{
Type type = item.GetType();
8 years ago
saveItemStatement.TryBind("@guid", item.Id);
saveItemStatement.TryBind("@type", type.FullName);
8 years ago
if (TypeRequiresDeserialization(type))
8 years ago
{
saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions), true);
8 years ago
}
else
{
saveItemStatement.TryBindNull("@data");
}
saveItemStatement.TryBind("@Path", GetPathToSave(item.Path));
8 years ago
if (item is IHasStartDate hasStartDate)
8 years ago
{
saveItemStatement.TryBind("@StartDate", hasStartDate.StartDate);
}
else
{
saveItemStatement.TryBindNull("@StartDate");
}
if (item.EndDate.HasValue)
{
saveItemStatement.TryBind("@EndDate", item.EndDate.Value);
}
else
{
saveItemStatement.TryBindNull("@EndDate");
}
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
8 years ago
if (item is IHasProgramAttributes hasProgramAttributes)
8 years ago
{
saveItemStatement.TryBind("@IsMovie", hasProgramAttributes.IsMovie);
saveItemStatement.TryBind("@IsSeries", hasProgramAttributes.IsSeries);
saveItemStatement.TryBind("@EpisodeTitle", hasProgramAttributes.EpisodeTitle);
saveItemStatement.TryBind("@IsRepeat", hasProgramAttributes.IsRepeat);
}
else
{
saveItemStatement.TryBindNull("@IsMovie");
saveItemStatement.TryBindNull("@IsSeries");
saveItemStatement.TryBindNull("@EpisodeTitle");
saveItemStatement.TryBindNull("@IsRepeat");
}
saveItemStatement.TryBind("@CommunityRating", item.CommunityRating);
saveItemStatement.TryBind("@CustomRating", item.CustomRating);
saveItemStatement.TryBind("@IndexNumber", item.IndexNumber);
saveItemStatement.TryBind("@IsLocked", item.IsLocked);
saveItemStatement.TryBind("@Name", item.Name);
saveItemStatement.TryBind("@OfficialRating", item.OfficialRating);
saveItemStatement.TryBind("@MediaType", item.MediaType.ToString());
8 years ago
saveItemStatement.TryBind("@Overview", item.Overview);
saveItemStatement.TryBind("@ParentIndexNumber", item.ParentIndexNumber);
saveItemStatement.TryBind("@PremiereDate", item.PremiereDate);
saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
var parentId = item.ParentId;
if (parentId.Equals(default))
8 years ago
{
saveItemStatement.TryBindNull("@ParentId");
}
else
{
saveItemStatement.TryBind("@ParentId", parentId);
8 years ago
}
if (item.Genres.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@Genres", string.Join('|', item.Genres));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@Genres");
}
saveItemStatement.TryBind("@InheritedParentalRatingValue", item.InheritedParentalRatingValue);
8 years ago
saveItemStatement.TryBind("@SortName", item.SortName);
saveItemStatement.TryBind("@ForcedSortName", item.ForcedSortName);
8 years ago
saveItemStatement.TryBind("@RunTimeTicks", item.RunTimeTicks);
saveItemStatement.TryBind("@Size", item.Size);
8 years ago
saveItemStatement.TryBind("@DateCreated", item.DateCreated);
saveItemStatement.TryBind("@DateModified", item.DateModified);
saveItemStatement.TryBind("@PreferredMetadataLanguage", item.PreferredMetadataLanguage);
saveItemStatement.TryBind("@PreferredMetadataCountryCode", item.PreferredMetadataCountryCode);
if (item.Width > 0)
{
saveItemStatement.TryBind("@Width", item.Width);
}
else
{
saveItemStatement.TryBindNull("@Width");
}
if (item.Height > 0)
{
saveItemStatement.TryBind("@Height", item.Height);
}
else
{
saveItemStatement.TryBindNull("@Height");
}
8 years ago
if (item.DateLastRefreshed != default(DateTime))
8 years ago
{
saveItemStatement.TryBind("@DateLastRefreshed", item.DateLastRefreshed);
}
else
{
saveItemStatement.TryBindNull("@DateLastRefreshed");
}
if (item.DateLastSaved != default(DateTime))
8 years ago
{
saveItemStatement.TryBind("@DateLastSaved", item.DateLastSaved);
8 years ago
}
else
{
saveItemStatement.TryBindNull("@DateLastSaved");
}
saveItemStatement.TryBind("@IsInMixedFolder", item.IsInMixedFolder);
if (item.LockedFields.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@LockedFields", string.Join('|', item.LockedFields));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@LockedFields");
}
if (item.Studios.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@Studios", string.Join('|', item.Studios));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@Studios");
}
if (item.Audio.HasValue)
{
saveItemStatement.TryBind("@Audio", item.Audio.Value.ToString());
}
else
{
saveItemStatement.TryBindNull("@Audio");
}
if (item is LiveTvChannel liveTvChannel)
{
saveItemStatement.TryBind("@ExternalServiceId", liveTvChannel.ServiceName);
}
else
{
saveItemStatement.TryBindNull("@ExternalServiceId");
}
8 years ago
if (item.Tags.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@Tags", string.Join('|', item.Tags));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@Tags");
}
saveItemStatement.TryBind("@IsFolder", item.IsFolder);
saveItemStatement.TryBind("@UnratedType", item.GetBlockUnratedType().ToString());
if (topParent is null)
8 years ago
{
saveItemStatement.TryBindNull("@TopParentId");
8 years ago
}
else
{
saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N", CultureInfo.InvariantCulture));
8 years ago
}
if (item is Trailer trailer && trailer.TrailerTypes.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@TrailerTypes", string.Join('|', trailer.TrailerTypes));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@TrailerTypes");
}
saveItemStatement.TryBind("@CriticRating", item.CriticRating);
if (string.IsNullOrWhiteSpace(item.Name))
{
saveItemStatement.TryBindNull("@CleanName");
}
else
{
saveItemStatement.TryBind("@CleanName", GetCleanValue(item.Name));
}
saveItemStatement.TryBind("@PresentationUniqueKey", item.PresentationUniqueKey);
8 years ago
saveItemStatement.TryBind("@OriginalTitle", item.OriginalTitle);
if (item is Video video)
8 years ago
{
saveItemStatement.TryBind("@PrimaryVersionId", video.PrimaryVersionId);
}
else
{
saveItemStatement.TryBindNull("@PrimaryVersionId");
}
if (item is Folder folder && folder.DateLastMediaAdded.HasValue)
8 years ago
{
saveItemStatement.TryBind("@DateLastMediaAdded", folder.DateLastMediaAdded.Value);
}
else
{
saveItemStatement.TryBindNull("@DateLastMediaAdded");
}
saveItemStatement.TryBind("@Album", item.Album);
saveItemStatement.TryBind("@LUFS", item.LUFS);
8 years ago
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
if (item is IHasSeries hasSeriesName)
8 years ago
{
saveItemStatement.TryBind("@SeriesName", hasSeriesName.SeriesName);
8 years ago
}
else
{
saveItemStatement.TryBindNull("@SeriesName");
}
if (string.IsNullOrWhiteSpace(userDataKey))
{
saveItemStatement.TryBindNull("@UserDataKey");
}
else
{
saveItemStatement.TryBind("@UserDataKey", userDataKey);
}
8 years ago
if (item is Episode episode)
8 years ago
{
saveItemStatement.TryBind("@SeasonName", episode.SeasonName);
var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId;
saveItemStatement.TryBind("@SeasonId", nullableSeasonId);
8 years ago
}
else
{
saveItemStatement.TryBindNull("@SeasonName");
saveItemStatement.TryBindNull("@SeasonId");
}
if (item is IHasSeries hasSeries)
8 years ago
{
var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId;
saveItemStatement.TryBind("@SeriesId", nullableSeriesId);
saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey);
8 years ago
}
else
{
saveItemStatement.TryBindNull("@SeriesId");
saveItemStatement.TryBindNull("@SeriesPresentationUniqueKey");
8 years ago
}
saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId);
saveItemStatement.TryBind("@Tagline", item.Tagline);
saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds));
saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
8 years ago
if (item.ProductionLocations.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@ProductionLocations", string.Join('|', item.ProductionLocations));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@ProductionLocations");
}
if (item.ExtraIds.Length > 0)
8 years ago
{
saveItemStatement.TryBind("@ExtraIds", string.Join('|', item.ExtraIds));
8 years ago
}
else
{
saveItemStatement.TryBindNull("@ExtraIds");
8 years ago
}
saveItemStatement.TryBind("@TotalBitrate", item.TotalBitrate);
if (item.ExtraType.HasValue)
{
saveItemStatement.TryBind("@ExtraType", item.ExtraType.Value.ToString());
}
else
{
saveItemStatement.TryBindNull("@ExtraType");
}
string artists = null;
if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0)
8 years ago
{
artists = string.Join('|', hasArtists.Artists);
8 years ago
}
8 years ago
saveItemStatement.TryBind("@Artists", artists);
string albumArtists = null;
if (item is IHasAlbumArtist hasAlbumArtists
&& hasAlbumArtists.AlbumArtists.Count > 0)
8 years ago
{
albumArtists = string.Join('|', hasAlbumArtists.AlbumArtists);
8 years ago
}
6 years ago
8 years ago
saveItemStatement.TryBind("@AlbumArtists", albumArtists);
saveItemStatement.TryBind("@ExternalId", item.ExternalId);
if (item is LiveTvProgram program)
{
saveItemStatement.TryBind("@ShowId", program.ShowId);
}
else
{
saveItemStatement.TryBindNull("@ShowId");
}
Guid ownerId = item.OwnerId;
if (ownerId.Equals(default))
{
saveItemStatement.TryBindNull("@OwnerId");
}
else
{
saveItemStatement.TryBind("@OwnerId", ownerId);
}
saveItemStatement.ExecuteNonQuery();
8 years ago
}
internal static string SerializeProviderIds(Dictionary<string, string> providerIds)
8 years ago
{
StringBuilder str = new StringBuilder();
foreach (var i in providerIds)
{
6 years ago
// Ideally we shouldn't need this IsNullOrWhiteSpace check,
// but we're seeing some cases of bad data slip through
if (string.IsNullOrWhiteSpace(i.Value))
{
continue;
}
6 years ago
str.Append(i.Key)
.Append('=')
.Append(i.Value)
.Append('|');
}
8 years ago
if (str.Length == 0)
8 years ago
{
return null;
}
6 years ago
str.Length -= 1; // Remove last |
return str.ToString();
8 years ago
}
internal static void DeserializeProviderIds(string value, IHasProviderIds item)
8 years ago
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
foreach (var part in value.SpanSplit('|'))
8 years ago
{
var providerDelimiterIndex = part.IndexOf('=');
if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
8 years ago
{
item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString());
8 years ago
}
}
}
internal string SerializeImages(ItemImageInfo[] images)
8 years ago
{
if (images.Length == 0)
8 years ago
{
return null;
}
6 years ago
StringBuilder str = new StringBuilder();
6 years ago
foreach (var i in images)
{
if (string.IsNullOrWhiteSpace(i.Path))
{
continue;
}
AppendItemImageInfo(str, i);
str.Append('|');
}
6 years ago
str.Length -= 1; // Remove last |
return str.ToString();
8 years ago
}
internal ItemImageInfo[] DeserializeImages(string value)
8 years ago
{
if (string.IsNullOrWhiteSpace(value))
{
return Array.Empty<ItemImageInfo>();
8 years ago
}
// TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed
var valueSpan = value.AsSpan();
var count = valueSpan.Count('|') + 1;
var position = 0;
var result = new ItemImageInfo[count];
foreach (var part in valueSpan.Split('|'))
8 years ago
{
var image = ItemImageInfoFromValueString(part);
if (image is not null)
{
result[position++] = image;
}
8 years ago
}
if (position == count)
{
return result;
}
if (position == 0)
{
return Array.Empty<ItemImageInfo>();
}
// Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array.
3 years ago
return result[..position];
8 years ago
}
private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
8 years ago
{
4 years ago
const char Delimiter = '*';
8 years ago
var path = image.Path ?? string.Empty;
bldr.Append(GetPathToSave(path))
4 years ago
.Append(Delimiter)
.Append(image.DateModified.Ticks)
4 years ago
.Append(Delimiter)
.Append(image.Type)
4 years ago
.Append(Delimiter)
.Append(image.Width)
4 years ago
.Append(Delimiter)
.Append(image.Height);
var hash = image.BlurHash;
if (!string.IsNullOrEmpty(hash))
{
bldr.Append(Delimiter)
// Replace delimiters with other characters.
// This can be removed when we migrate to a proper DB.
.Append(hash.Replace(Delimiter, '/').Replace('|', '\\'));
}
8 years ago
}
internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value)
8 years ago
{
const char Delimiter = '*';
var nextSegment = value.IndexOf(Delimiter);
if (nextSegment == -1)
{
return null;
}
8 years ago
ReadOnlySpan<char> path = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf(Delimiter);
if (nextSegment == -1)
{
return null;
}
ReadOnlySpan<char> dateModified = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf(Delimiter);
if (nextSegment == -1)
{
nextSegment = value.Length;
}
ReadOnlySpan<char> imageType = value[..nextSegment];
8 years ago
var image = new ItemImageInfo
{
Path = RestorePath(path.ToString())
};
if (long.TryParse(dateModified, CultureInfo.InvariantCulture, out var ticks)
&& ticks >= DateTime.MinValue.Ticks
&& ticks <= DateTime.MaxValue.Ticks)
{
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
}
else
{
return null;
}
if (Enum.TryParse(imageType, true, out ImageType type))
{
image.Type = type;
}
else
{
return null;
}
8 years ago
// Optional parameters: width*height*blurhash
if (nextSegment + 1 < value.Length - 1)
{
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf(Delimiter);
if (nextSegment == -1 || nextSegment == value.Length)
{
return image;
}
ReadOnlySpan<char> widthSpan = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf(Delimiter);
if (nextSegment == -1)
{
nextSegment = value.Length;
}
ReadOnlySpan<char> heightSpan = value[..nextSegment];
if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
&& int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
{
image.Width = width;
image.Height = height;
}
if (nextSegment < value.Length - 1)
{
value = value[(nextSegment + 1)..];
var length = value.Length;
Span<char> blurHashSpan = stackalloc char[length];
for (int i = 0; i < length; i++)
{
var c = value[i];
blurHashSpan[i] = c switch
{
'/' => Delimiter,
'\\' => '|',
_ => c
};
}
image.BlurHash = new string(blurHashSpan);
}
}
8 years ago
return image;
}
/// <summary>
/// Internal retrieve from items or users table.
8 years ago
/// </summary>
/// <param name="id">The id.</param>
/// <returns>BaseItem.</returns>
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception>
8 years ago
public BaseItem RetrieveItem(Guid id)
{
if (id.Equals(default))
8 years ago
{
throw new ArgumentException("Guid can't be empty", nameof(id));
8 years ago
}
CheckDisposed();
6 years ago
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
8 years ago
{
statement.TryBind("@guid", id);
foreach (var row in statement.ExecuteQuery())
{
return GetItem(row, new InternalItemsQuery());
8 years ago
}
}
6 years ago
return null;
8 years ago
}
private bool TypeRequiresDeserialization(Type type)
{
if (_config.Configuration.SkipDeserializationForBasicTypes)
{
if (type == typeof(Channel)
|| type == typeof(UserRootFolder))
8 years ago
{
return false;
}
}
return type != typeof(Season)
&& type != typeof(MusicArtist)
&& type != typeof(Person)
&& type != typeof(MusicGenre)
&& type != typeof(Genre)
&& type != typeof(Studio)
&& type != typeof(PlaylistsFolder)
&& type != typeof(PhotoAlbum)
&& type != typeof(Year)
&& type != typeof(Book)
&& type != typeof(LiveTvProgram)
&& type != typeof(AudioBook)
&& type != typeof(Audio)
&& type != typeof(MusicAlbum);
8 years ago
}
1 year ago
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));
}
1 year ago
private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
8 years ago
{
var typeString = reader.GetString(0);
var type = _typeMapper.GetType(typeString);
if (type is null)
8 years ago
{
return null;
}
BaseItem item = null;
if (TypeRequiresDeserialization(type))
{
try
8 years ago
{
1 year ago
item = JsonSerializer.Deserialize(reader.GetStream(1), type, _jsonOptions) as BaseItem;
}
catch (JsonException ex)
{
Logger.LogError(ex, "Error deserializing item with JSON: {Data}", reader.GetString(1));
8 years ago
}
}
if (item is null)
8 years ago
{
try
{
item = Activator.CreateInstance(type) as BaseItem;
}
catch
{
}
}
if (item is null)
8 years ago
{
return null;
}
var index = 2;
if (queryHasStartDate)
8 years ago
{
if (item is IHasStartDate hasStartDate && reader.TryReadDateTime(index, out var startDate))
8 years ago
{
4 years ago
hasStartDate.StartDate = startDate;
8 years ago
}
index++;
8 years ago
}
if (reader.TryReadDateTime(index++, out var endDate))
8 years ago
{
item.EndDate = endDate;
8 years ago
}
1 year ago
if (reader.TryGetGuid(index, out var guid))
8 years ago
{
1 year ago
item.ChannelId = guid;
8 years ago
}
index++;
8 years ago
if (enableProgramAttributes)
8 years ago
{
if (item is IHasProgramAttributes hasProgramAttributes)
8 years ago
{
if (reader.TryGetBoolean(index++, out var isMovie))
{
4 years ago
hasProgramAttributes.IsMovie = isMovie;
}
if (reader.TryGetBoolean(index++, out var isSeries))
{
4 years ago
hasProgramAttributes.IsSeries = isSeries;
}
if (reader.TryGetString(index++, out var episodeTitle))
{
hasProgramAttributes.EpisodeTitle = episodeTitle;
}
if (reader.TryGetBoolean(index++, out var isRepeat))
{
4 years ago
hasProgramAttributes.IsRepeat = isRepeat;
}
}
else
8 years ago
{
index += 4;
8 years ago
}
}
if (reader.TryGetSingle(index++, out var communityRating))
8 years ago
{
item.CommunityRating = communityRating;
8 years ago
}
if (HasField(query, ItemFields.CustomRating))
8 years ago
{
if (reader.TryGetString(index++, out var customRating))
8 years ago
{
item.CustomRating = customRating;
8 years ago
}
}
if (reader.TryGetInt32(index++, out var indexNumber))
8 years ago
{
item.IndexNumber = indexNumber;
8 years ago
}
if (HasField(query, ItemFields.Settings))
8 years ago
{
if (reader.TryGetBoolean(index++, out var isLocked))
8 years ago
{
4 years ago
item.IsLocked = isLocked;
8 years ago
}
if (reader.TryGetString(index++, out var preferredMetadataLanguage))
8 years ago
{
item.PreferredMetadataLanguage = preferredMetadataLanguage;
8 years ago
}
if (reader.TryGetString(index++, out var preferredMetadataCountryCode))
8 years ago
{
item.PreferredMetadataCountryCode = preferredMetadataCountryCode;
8 years ago
}
}
if (HasField(query, ItemFields.Width))
8 years ago
{
if (reader.TryGetInt32(index++, out var width))
{
4 years ago
item.Width = width;
}
8 years ago
}
if (HasField(query, ItemFields.Height))
8 years ago
{
if (reader.TryGetInt32(index++, out var height))
{
4 years ago
item.Height = height;
}
8 years ago
}
if (HasField(query, ItemFields.DateLastRefreshed))
8 years ago
{
if (reader.TryReadDateTime(index++, out var dateLastRefreshed))
{
4 years ago
item.DateLastRefreshed = dateLastRefreshed;
}
8 years ago
}
if (reader.TryGetString(index++, out var name))
8 years ago
{
item.Name = name;
8 years ago
}
if (reader.TryGetString(index++, out var restorePath))
8 years ago
{
item.Path = RestorePath(restorePath);
8 years ago
}
if (reader.TryReadDateTime(index++, out var premiereDate))
8 years ago
{
item.PremiereDate = premiereDate;
8 years ago
}
if (HasField(query, ItemFields.Overview))
8 years ago
{
if (reader.TryGetString(index++, out var overview))
8 years ago
{
item.Overview = overview;
8 years ago
}
}
if (reader.TryGetInt32(index++, out var parentIndexNumber))
8 years ago
{
item.ParentIndexNumber = parentIndexNumber;
8 years ago
}
if (reader.TryGetInt32(index++, out var productionYear))
8 years ago
{
item.ProductionYear = productionYear;
8 years ago
}
if (reader.TryGetString(index++, out var officialRating))
8 years ago
{
item.OfficialRating = officialRating;
8 years ago
}
if (HasField(query, ItemFields.SortName))
8 years ago
{
if (reader.TryGetString(index++, out var forcedSortName))
8 years ago
{
item.ForcedSortName = forcedSortName;
8 years ago
}
}
if (reader.TryGetInt64(index++, out var runTimeTicks))
8 years ago
{
item.RunTimeTicks = runTimeTicks;
8 years ago
}
if (reader.TryGetInt64(index++, out var size))
{
item.Size = size;
}
if (HasField(query, ItemFields.DateCreated))
8 years ago
{
if (reader.TryReadDateTime(index++, out var dateCreated))
8 years ago
{
4 years ago
item.DateCreated = dateCreated;
8 years ago
}
}
if (reader.TryReadDateTime(index++, out var dateModified))
8 years ago
{
4 years ago
item.DateModified = dateModified;
8 years ago
}
item.Id = reader.GetGuid(index++);
8 years ago
if (HasField(query, ItemFields.Genres))
8 years ago
{
if (reader.TryGetString(index++, out var genres))
8 years ago
{
item.Genres = genres.Split('|', StringSplitOptions.RemoveEmptyEntries);
8 years ago
}
}
if (reader.TryGetGuid(index++, out var parentId))
8 years ago
{
4 years ago
item.ParentId = parentId;
8 years ago
}
if (reader.TryGetString(index++, out var audioString))
8 years ago
{
if (Enum.TryParse(audioString, true, out ProgramAudio audio))
7 years ago
{
item.Audio = audio;
}
8 years ago
}
8 years ago
// TODO: Even if not needed by apps, the server needs it internally
// But get this excluded from contexts where it is not needed
if (hasServiceName)
8 years ago
{
if (item is LiveTvChannel liveTvChannel)
{
if (reader.TryGetString(index, out var serviceName))
{
liveTvChannel.ServiceName = serviceName;
}
}
index++;
8 years ago
}
if (reader.TryGetBoolean(index++, out var isInMixedFolder))
8 years ago
{
4 years ago
item.IsInMixedFolder = isInMixedFolder;
8 years ago
}
if (HasField(query, ItemFields.DateLastSaved))
8 years ago
{
if (reader.TryReadDateTime(index++, out var dateLastSaved))
{
4 years ago
item.DateLastSaved = dateLastSaved;
}
8 years ago
}
if (HasField(query, ItemFields.Settings))
8 years ago
{
if (reader.TryGetString(index++, out var lockedFields))
8 years ago
{
List<MetadataField> fields = null;
3 years ago
foreach (var i in lockedFields.AsSpan().Split('|'))
{
if (Enum.TryParse(i, true, out MetadataField parsedValue))
7 years ago
{
(fields ??= new List<MetadataField>()).Add(parsedValue);
}
}
item.LockedFields = fields?.ToArray() ?? Array.Empty<MetadataField>();
8 years ago
}
}
if (HasField(query, ItemFields.Studios))
8 years ago
{
if (reader.TryGetString(index++, out var studios))
8 years ago
{
item.Studios = studios.Split('|', StringSplitOptions.RemoveEmptyEntries);
8 years ago
}
}
if (HasField(query, ItemFields.Tags))
8 years ago
{
if (reader.TryGetString(index++, out var tags))
8 years ago
{
item.Tags = tags.Split('|', StringSplitOptions.RemoveEmptyEntries);
8 years ago
}
}
if (hasTrailerTypes)
8 years ago
{
if (item is Trailer trailer)
8 years ago
{
if (reader.TryGetString(index, out var trailerTypes))
{
List<TrailerType> types = null;
3 years ago
foreach (var i in trailerTypes.AsSpan().Split('|'))
{
if (Enum.TryParse(i, true, out TrailerType parsedValue))
7 years ago
{
(types ??= new List<TrailerType>()).Add(parsedValue);
}
}
3 years ago
trailer.TrailerTypes = types?.ToArray() ?? Array.Empty<TrailerType>();
}
8 years ago
}
index++;
8 years ago
}
if (HasField(query, ItemFields.OriginalTitle))
8 years ago
{
if (reader.TryGetString(index++, out var originalTitle))
8 years ago
{
item.OriginalTitle = originalTitle;
8 years ago
}
}
if (item is Video video)
8 years ago
{
if (reader.TryGetString(index, out var primaryVersionId))
8 years ago
{
video.PrimaryVersionId = primaryVersionId;
8 years ago
}
}
8 years ago
index++;
if (HasField(query, ItemFields.DateLastMediaAdded))
8 years ago
{
if (item is Folder folder && reader.TryReadDateTime(index, out var dateLastMediaAdded))
8 years ago
{
folder.DateLastMediaAdded = dateLastMediaAdded;
8 years ago
}
8 years ago
index++;
}
if (reader.TryGetString(index++, out var album))
8 years ago
{
item.Album = album;
8 years ago
}
if (reader.TryGetSingle(index++, out var lUFS))
{
item.LUFS = lUFS;
}
if (reader.TryGetSingle(index++, out var criticRating))
8 years ago
{
item.CriticRating = criticRating;
8 years ago
}
if (reader.TryGetBoolean(index++, out var isVirtualItem))
8 years ago
{
4 years ago
item.IsVirtualItem = isVirtualItem;
8 years ago
}
if (item is IHasSeries hasSeriesName)
8 years ago
{
if (reader.TryGetString(index, out var seriesName))
8 years ago
{
hasSeriesName.SeriesName = seriesName;
8 years ago
}
}
index++;
8 years ago
if (hasEpisodeAttributes)
8 years ago
{
if (item is Episode episode)
8 years ago
{
if (reader.TryGetString(index, out var seasonName))
{
episode.SeasonName = seasonName;
}
index++;
if (reader.TryGetGuid(index, out var seasonId))
{
4 years ago
episode.SeasonId = seasonId;
}
8 years ago
}
else
8 years ago
{
index++;
8 years ago
}
8 years ago
index++;
}
var hasSeries = item as IHasSeries;
8 years ago
if (hasSeriesFields)
8 years ago
{
if (hasSeries is not null)
8 years ago
{
if (reader.TryGetGuid(index, out var seriesId))
8 years ago
{
4 years ago
hasSeries.SeriesId = seriesId;
8 years ago
}
8 years ago
}
8 years ago
index++;
8 years ago
}
if (HasField(query, ItemFields.PresentationUniqueKey))
8 years ago
{
if (reader.TryGetString(index++, out var presentationUniqueKey))
{
item.PresentationUniqueKey = presentationUniqueKey;
}
8 years ago
}
if (HasField(query, ItemFields.InheritedParentalRatingValue))
8 years ago
{
if (reader.TryGetInt32(index++, out var parentalRating))
{
4 years ago
item.InheritedParentalRatingValue = parentalRating;
}
8 years ago
}
if (HasField(query, ItemFields.ExternalSeriesId))
8 years ago
{
if (reader.TryGetString(index++, out var externalSeriesId))
{
item.ExternalSeriesId = externalSeriesId;
}
8 years ago
}
if (HasField(query, ItemFields.Taglines))
8 years ago
{
if (reader.TryGetString(index++, out var tagLine))
8 years ago
{
item.Tagline = tagLine;
8 years ago
}
}
if (item.ProviderIds.Count == 0 && reader.TryGetString(index, out var providerIds))
8 years ago
{
DeserializeProviderIds(providerIds, item);
8 years ago
}
8 years ago
index++;
if (query.DtoOptions.EnableImages)
{
if (item.ImageInfos.Length == 0 && reader.TryGetString(index, out var imageInfos))
8 years ago
{
item.ImageInfos = DeserializeImages(imageInfos);
8 years ago
}
8 years ago
index++;
}
if (HasField(query, ItemFields.ProductionLocations))
8 years ago
{
if (reader.TryGetString(index++, out var productionLocations))
8 years ago
{
item.ProductionLocations = productionLocations.Split('|', StringSplitOptions.RemoveEmptyEntries);
8 years ago
}
}
if (HasField(query, ItemFields.ExtraIds))
8 years ago
{
if (reader.TryGetString(index++, out var extraIds))
8 years ago
{
item.ExtraIds = SplitToGuids(extraIds);
8 years ago
}
}
if (reader.TryGetInt32(index++, out var totalBitrate))
8 years ago
{
item.TotalBitrate = totalBitrate;
8 years ago
}
if (reader.TryGetString(index++, out var extraTypeString))
8 years ago
{
if (Enum.TryParse(extraTypeString, true, out ExtraType extraType))
7 years ago
{
item.ExtraType = extraType;
}
8 years ago
}
if (hasArtistFields)
8 years ago
{
if (item is IHasArtist hasArtists && reader.TryGetString(index, out var artists))
{
hasArtists.Artists = artists.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
index++;
8 years ago
if (item is IHasAlbumArtist hasAlbumArtists && reader.TryGetString(index, out var albumArtists))
{
hasAlbumArtists.AlbumArtists = albumArtists.Split('|', StringSplitOptions.RemoveEmptyEntries);
}
index++;
8 years ago
}
if (reader.TryGetString(index++, out var externalId))
8 years ago
{
item.ExternalId = externalId;
8 years ago
}
if (HasField(query, ItemFields.SeriesPresentationUniqueKey))
{
if (hasSeries is not null)
{
if (reader.TryGetString(index, out var seriesPresentationUniqueKey))
{
hasSeries.SeriesPresentationUniqueKey = seriesPresentationUniqueKey;
}
}
index++;
}
if (enableProgramAttributes)
{
if (item is LiveTvProgram program && reader.TryGetString(index, out var showId))
{
program.ShowId = showId;
}
index++;
}
if (reader.TryGetGuid(index, out var ownerId))
{
4 years ago
item.OwnerId = ownerId;
}
8 years ago
return item;
}
private static Guid[] SplitToGuids(string value)
{
var ids = value.Split('|');
var result = new Guid[ids.Length];
for (var i = 0; i < result.Length; i++)
{
result[i] = new Guid(ids[i]);
}
return result;
}
/// <inheritdoc />
public List<ChapterInfo> GetChapters(BaseItem item)
8 years ago
{
CheckDisposed();
var chapters = new List<ChapterInfo>();
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
8 years ago
{
statement.TryBind("@ItemId", item.Id);
6 years ago
foreach (var row in statement.ExecuteQuery())
8 years ago
{
chapters.Add(GetChapter(row, item));
8 years ago
}
}
return chapters;
8 years ago
}
/// <inheritdoc />
public ChapterInfo GetChapter(BaseItem item, int index)
8 years ago
{
CheckDisposed();
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex"))
8 years ago
{
statement.TryBind("@ItemId", item.Id);
statement.TryBind("@ChapterIndex", index);
foreach (var row in statement.ExecuteQuery())
{
return GetChapter(row, item);
8 years ago
}
}
6 years ago
8 years ago
return null;
}
/// <summary>
/// Gets the chapter.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="item">The item.</param>
8 years ago
/// <returns>ChapterInfo.</returns>
1 year ago
private ChapterInfo GetChapter(SqliteDataReader reader, BaseItem item)
8 years ago
{
var chapter = new ChapterInfo
{
StartPositionTicks = reader.GetInt64(0)
};
if (reader.TryGetString(1, out var chapterName))
8 years ago
{
chapter.Name = chapterName;
8 years ago
}
if (reader.TryGetString(2, out var imagePath))
8 years ago
{
chapter.ImagePath = imagePath;
chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter);
8 years ago
}
if (reader.TryReadDateTime(3, out var imageDateModified))
8 years ago
{
4 years ago
chapter.ImageDateModified = imageDateModified;
8 years ago
}
return chapter;
}
/// <summary>
/// Saves the chapters.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="chapters">The chapters.</param>
public void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters)
8 years ago
{
CheckDisposed();
if (id.Equals(default))
8 years ago
{
throw new ArgumentNullException(nameof(id));
8 years ago
}
ArgumentNullException.ThrowIfNull(chapters);
8 years ago
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
// First delete chapters
using var command = connection.PrepareStatement($"delete from {ChaptersTableName} where ItemId=@ItemId");
command.TryBind("@ItemId", id);
command.ExecuteNonQuery();
InsertChapters(id, chapters, connection);
transaction.Commit();
}
8 years ago
1 year ago
private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, SqliteConnection db)
{
var startIndex = 0;
var limit = 100;
var chapterIndex = 0;
8 years ago
const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ";
var insertText = new StringBuilder(StartInsertText, 256);
while (startIndex < chapters.Count)
{
var endIndex = Math.Min(chapters.Count, startIndex + limit);
for (var i = startIndex; i < endIndex; i++)
{
insertText.AppendFormat(CultureInfo.InvariantCulture, "(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
}
insertText.Length -= 1; // Remove trailing comma
6 years ago
using (var statement = PrepareStatement(db, insertText.ToString()))
{
statement.TryBind("@ItemId", idBlob);
for (var i = startIndex; i < endIndex; i++)
{
var index = i.ToString(CultureInfo.InvariantCulture);
var chapter = chapters[i];
statement.TryBind("@ChapterIndex" + index, chapterIndex);
statement.TryBind("@StartPositionTicks" + index, chapter.StartPositionTicks);
statement.TryBind("@Name" + index, chapter.Name);
statement.TryBind("@ImagePath" + index, chapter.ImagePath);
statement.TryBind("@ImageDateModified" + index, chapter.ImageDateModified);
chapterIndex++;
}
statement.ExecuteNonQuery();
}
startIndex += limit;
insertText.Length = StartInsertText.Length;
8 years ago
}
}
private static bool EnableJoinUserData(InternalItemsQuery query)
8 years ago
{
if (query.User is null)
8 years ago
{
return false;
}
var sortingFields = new HashSet<ItemSortBy>(query.OrderBy.Select(i => i.OrderBy));
8 years ago
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|| sortingFields.Contains(ItemSortBy.IsPlayed)
|| sortingFields.Contains(ItemSortBy.IsUnplayed)
|| sortingFields.Contains(ItemSortBy.PlayCount)
|| sortingFields.Contains(ItemSortBy.DatePlayed)
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|| query.IsFavoriteOrLiked.HasValue
|| query.IsFavorite.HasValue
|| query.IsResumable.HasValue
|| query.IsPlayed.HasValue
|| query.IsLiked.HasValue;
8 years ago
}
private bool HasField(InternalItemsQuery query, ItemFields name)
{
switch (name)
{
case ItemFields.Tags:
return query.DtoOptions.ContainsField(name) || HasProgramAttributes(query);
case ItemFields.CustomRating:
case ItemFields.ProductionLocations:
case ItemFields.Settings:
case ItemFields.OriginalTitle:
case ItemFields.Taglines:
case ItemFields.SortName:
case ItemFields.Studios:
case ItemFields.ExtraIds:
case ItemFields.DateCreated:
case ItemFields.Overview:
case ItemFields.Genres:
case ItemFields.DateLastMediaAdded:
case ItemFields.PresentationUniqueKey:
case ItemFields.InheritedParentalRatingValue:
case ItemFields.ExternalSeriesId:
case ItemFields.SeriesPresentationUniqueKey:
case ItemFields.DateLastRefreshed:
case ItemFields.DateLastSaved:
return query.DtoOptions.ContainsField(name);
case ItemFields.ServiceName:
return HasServiceName(query);
default:
return true;
}
}
private bool HasProgramAttributes(InternalItemsQuery query)
{
if (query.ParentType is not null && _programExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
}
private bool HasServiceName(InternalItemsQuery query)
{
if (query.ParentType is not null && _programExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
}
private bool HasStartDate(InternalItemsQuery query)
{
if (query.ParentType is not null && _programExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
}
private bool HasEpisodeAttributes(InternalItemsQuery query)
{
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Contains(BaseItemKind.Episode);
}
private bool HasTrailerTypes(InternalItemsQuery query)
{
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Contains(BaseItemKind.Trailer);
}
private bool HasArtistFields(InternalItemsQuery query)
{
if (query.ParentType is not null && _artistExcludeParentTypes.Contains(query.ParentType.Value))
{
return false;
}
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
}
8 years ago
private bool HasSeriesFields(InternalItemsQuery query)
{
if (query.ParentType == BaseItemKind.PhotoAlbum)
8 years ago
{
return false;
}
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
8 years ago
}
4 years ago
private void SetFinalColumnsToSelect(InternalItemsQuery query, List<string> columns)
8 years ago
{
foreach (var field in _allItemFields)
8 years ago
{
if (!HasField(query, field))
8 years ago
{
switch (field)
8 years ago
{
case ItemFields.Settings:
columns.Remove("IsLocked");
columns.Remove("PreferredMetadataCountryCode");
columns.Remove("PreferredMetadataLanguage");
columns.Remove("LockedFields");
break;
case ItemFields.ServiceName:
columns.Remove("ExternalServiceId");
break;
case ItemFields.SortName:
columns.Remove("ForcedSortName");
break;
case ItemFields.Taglines:
columns.Remove("Tagline");
break;
case ItemFields.Tags:
columns.Remove("Tags");
break;
case ItemFields.IsHD:
// do nothing
break;
default:
columns.Remove(field.ToString());
break;
8 years ago
}
}
}
if (!HasProgramAttributes(query))
{
columns.Remove("IsMovie");
columns.Remove("IsSeries");
columns.Remove("EpisodeTitle");
columns.Remove("IsRepeat");
columns.Remove("ShowId");
}
if (!HasEpisodeAttributes(query))
{
columns.Remove("SeasonName");
columns.Remove("SeasonId");
}
if (!HasStartDate(query))
{
columns.Remove("StartDate");
}
if (!HasTrailerTypes(query))
{
columns.Remove("TrailerTypes");
}
if (!HasArtistFields(query))
{
columns.Remove("AlbumArtists");
columns.Remove("Artists");
}
8 years ago
if (!HasSeriesFields(query))
{
columns.Remove("SeriesId");
8 years ago
}
if (!HasEpisodeAttributes(query))
{
columns.Remove("SeasonName");
columns.Remove("SeasonId");
}
8 years ago
if (!query.DtoOptions.EnableImages)
{
columns.Remove("Images");
8 years ago
}
if (EnableJoinUserData(query))
{
columns.Add("UserDatas.UserId");
columns.Add("UserDatas.lastPlayedDate");
columns.Add("UserDatas.playbackPositionTicks");
columns.Add("UserDatas.playcount");
columns.Add("UserDatas.isFavorite");
columns.Add("UserDatas.played");
columns.Add("UserDatas.rating");
8 years ago
}
if (query.SimilarTo is not null)
8 years ago
{
var item = query.SimilarTo;
var builder = new StringBuilder();
builder.Append('(');
8 years ago
if (item.InheritedParentalRatingValue == 0)
{
builder.Append("((InheritedParentalRatingValue=0) * 10)");
}
else
{
builder.Append(
@"(SELECT CASE WHEN COALESCE(InheritedParentalRatingValue, 0)=0
THEN 0
ELSE 10.0 / (1.0 + ABS(InheritedParentalRatingValue - @InheritedParentalRatingValue))
END)");
}
8 years ago
if (item.ProductionYear.HasValue)
{
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 10 Else 0 End )");
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 5 Else 0 End )");
}
8 years ago
// genres, tags, studios, person, year?
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))");
builder.Append("+ (Select count(1) * 10 from People where ItemId=Guid and Name in (select Name from People where ItemId=@SimilarItemId))");
if (item is MusicArtist)
{
// Match albums where the artist is AlbumArtist against other albums.
// It is assumed that similar albums => similar artists.
builder.Append(
@"+ (WITH artistValues AS (
SELECT DISTINCT albumValues.CleanValue
FROM ItemValues albumValues
INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId
INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND artistAlbums.TYPE = 1 AND artistItem.Guid = @SimilarItemId
), similarArtist AS (
SELECT albumValues.ItemId
FROM ItemValues albumValues
INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId
INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND artistAlbums.TYPE = 1 AND artistItem.Guid = A.Guid
) SELECT COUNT(DISTINCT(CleanValue)) * 10 FROM ItemValues WHERE ItemId IN (SELECT ItemId FROM similarArtist) AND CleanValue IN (SELECT CleanValue FROM artistValues))");
}
8 years ago
builder.Append(") as SimilarityScore");
columns.Add(builder.ToString());
8 years ago
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;
8 years ago
query.ExcludeProviderIds = item.ProviderIds;
}
if (!string.IsNullOrEmpty(query.SearchTerm))
{
var builder = new StringBuilder();
builder.Append('(');
builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)");
builder.Append("+ ((CleanName = @SearchTermStartsWith COLLATE NOCASE or (OriginalTitle not null and OriginalTitle = @SearchTermStartsWith COLLATE NOCASE)) * 10)");
if (query.SearchTerm.Length > 1)
{
builder.Append("+ ((CleanName like @SearchTermContains or (OriginalTitle not null and OriginalTitle like @SearchTermContains)) * 10)");
builder.Append("+ (SELECT COUNT(1) * 1 from ItemValues where ItemId=Guid and CleanValue like @SearchTermContains)");
builder.Append("+ (SELECT COUNT(1) * 2 from ItemValues where ItemId=Guid and CleanValue like @SearchTermStartsWith)");
builder.Append("+ (SELECT COUNT(1) * 10 from ItemValues where ItemId=Guid and CleanValue like @SearchTermEquals)");
}
builder.Append(") as SearchScore");
columns.Add(builder.ToString());
}
8 years ago
}
1 year ago
private void BindSearchParams(InternalItemsQuery query, SqliteCommand statement)
{
var searchTerm = query.SearchTerm;
if (string.IsNullOrEmpty(searchTerm))
{
return;
}
searchTerm = FixUnicodeChars(searchTerm);
searchTerm = GetCleanValue(searchTerm);
1 year ago
var commandText = statement.CommandText;
if (commandText.Contains("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@SearchTermStartsWith", searchTerm + "%");
}
if (commandText.Contains("@SearchTermContains", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@SearchTermContains", "%" + searchTerm + "%");
}
if (commandText.Contains("@SearchTermEquals", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@SearchTermEquals", searchTerm);
}
}
1 year ago
private void BindSimilarParams(InternalItemsQuery query, SqliteCommand statement)
8 years ago
{
var item = query.SimilarTo;
if (item is null)
8 years ago
{
return;
}
1 year ago
var commandText = statement.CommandText;
if (commandText.Contains("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@ItemOfficialRating", item.OfficialRating);
}
if (commandText.Contains("@ItemProductionYear", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0);
}
if (commandText.Contains("@SimilarItemId", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@SimilarItemId", item.Id);
}
if (commandText.Contains("@InheritedParentalRatingValue", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@InheritedParentalRatingValue", item.InheritedParentalRatingValue);
}
8 years ago
}
private string GetJoinUserDataText(InternalItemsQuery query)
{
if (!EnableJoinUserData(query))
{
return string.Empty;
}
return " left join UserDatas on UserDataKey=UserDatas.Key And (UserId=@UserId)";
8 years ago
}
8 years ago
private string GetGroupBy(InternalItemsQuery query)
{
var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(query);
if (enableGroupByPresentationUniqueKey && query.GroupBySeriesPresentationUniqueKey)
8 years ago
{
return " Group by PresentationUniqueKey, SeriesPresentationUniqueKey";
8 years ago
}
if (enableGroupByPresentationUniqueKey)
{
return " Group by PresentationUniqueKey";
}
if (query.GroupBySeriesPresentationUniqueKey)
8 years ago
{
return " Group by SeriesPresentationUniqueKey";
8 years ago
}
return string.Empty;
}
public int GetCount(InternalItemsQuery query)
{
ArgumentNullException.ThrowIfNull(query);
CheckDisposed();
// Hack for right now since we currently don't support filtering out these duplicates within a query
if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
{
query.Limit = query.Limit.Value + 4;
}
4 years ago
var columns = new List<string> { "count(distinct PresentationUniqueKey)" };
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 256)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
commandTextBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses);
}
var commandText = commandTextBuilder.ToString();
using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, commandText))
{
if (EnableJoinUserData(query))
{
statement.TryBind("@UserId", query.User.InternalId);
}
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
// Running this again will bind the params
GetWhereClauses(query, statement);
1 year ago
return statement.SelectScalarInt();
}
}
8 years ago
public List<BaseItem> GetItemList(InternalItemsQuery query)
{
ArgumentNullException.ThrowIfNull(query);
8 years ago
CheckDisposed();
8 years ago
// Hack for right now since we currently don't support filtering out these duplicates within a query
if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
{
query.Limit = query.Limit.Value + 4;
}
var columns = _retrieveItemColumns.ToList();
4 years ago
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 1024)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
8 years ago
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
commandTextBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses);
}
8 years ago
commandTextBuilder.Append(GetGroupBy(query))
.Append(GetOrderByText(query));
8 years ago
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
var offset = query.StartIndex ?? 0;
if (query.Limit.HasValue || offset > 0)
{
commandTextBuilder.Append(" LIMIT ")
4 years ago
.Append(query.Limit ?? int.MaxValue);
8 years ago
}
if (offset > 0)
{
commandTextBuilder.Append(" OFFSET ")
4 years ago
.Append(offset);
8 years ago
}
}
var commandText = commandTextBuilder.ToString();
5 years ago
var items = new List<BaseItem>();
using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, commandText))
8 years ago
{
if (EnableJoinUserData(query))
6 years ago
{
statement.TryBind("@UserId", query.User.InternalId);
}
8 years ago
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
8 years ago
// Running this again will bind the params
GetWhereClauses(query, statement);
8 years ago
var hasEpisodeAttributes = HasEpisodeAttributes(query);
var hasServiceName = HasServiceName(query);
var hasProgramAttributes = HasProgramAttributes(query);
var hasStartDate = HasStartDate(query);
var hasTrailerTypes = HasTrailerTypes(query);
var hasArtistFields = HasArtistFields(query);
var hasSeriesFields = HasSeriesFields(query);
foreach (var row in statement.ExecuteQuery())
{
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
if (item is not null)
6 years ago
{
items.Add(item);
}
6 years ago
}
}
// 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 items)
6 years ago
{
AddItem(newList, item);
6 years ago
if (newList.Count >= limit)
{
break;
}
}
6 years ago
items = newList;
}
6 years ago
5 years ago
return items;
8 years ago
}
private string FixUnicodeChars(string buffer)
{
buffer = buffer.Replace('\u2013', '-'); // en dash
buffer = buffer.Replace('\u2014', '-'); // em dash
buffer = buffer.Replace('\u2015', '-'); // horizontal bar
buffer = buffer.Replace('\u2017', '_'); // double low line
buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
buffer = buffer.Replace('\u2032', '\''); // prime
buffer = buffer.Replace('\u2033', '\"'); // double prime
buffer = buffer.Replace('\u0060', '\''); // grave accent
return buffer.Replace('\u00B4', '\''); // acute accent
}
8 years ago
private void AddItem(List<BaseItem> items, BaseItem newItem)
{
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
foreach (var providerId in newItem.ProviderIds)
8 years ago
{
if (string.Equals(providerId.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.Ordinal))
8 years ago
{
continue;
}
if (string.Equals(item.GetProviderId(providerId.Key), providerId.Value, StringComparison.Ordinal))
8 years ago
{
if (newItem.SourceType == SourceType.Library)
{
items[i] = newItem;
}
8 years ago
return;
}
}
}
items.Add(newItem);
}
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
{
ArgumentNullException.ThrowIfNull(query);
8 years ago
CheckDisposed();
if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
{
var returnList = GetItemList(query);
return new QueryResult<BaseItem>(
query.StartIndex,
returnList.Count,
returnList);
8 years ago
}
// Hack for right now since we currently don't support filtering out these duplicates within a query
if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
{
query.Limit = query.Limit.Value + 4;
}
var columns = _retrieveItemColumns.ToList();
4 years ago
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 512)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
8 years ago
var whereClauses = GetWhereClauses(query, null);
var whereText = whereClauses.Count == 0 ?
string.Empty :
string.Join(" AND ", whereClauses);
8 years ago
if (!string.IsNullOrEmpty(whereText))
{
commandTextBuilder.Append(" where ")
.Append(whereText);
}
commandTextBuilder.Append(GetGroupBy(query))
.Append(GetOrderByText(query));
8 years ago
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
var offset = query.StartIndex ?? 0;
if (query.Limit.HasValue || offset > 0)
{
commandTextBuilder.Append(" LIMIT ")
4 years ago
.Append(query.Limit ?? int.MaxValue);
8 years ago
}
if (offset > 0)
{
commandTextBuilder.Append(" OFFSET ")
4 years ago
.Append(offset);
8 years ago
}
}
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
var itemQuery = string.Empty;
var totalRecordCountQuery = string.Empty;
if (!isReturningZeroItems)
{
itemQuery = commandTextBuilder.ToString();
}
if (query.EnableTotalRecordCount)
{
commandTextBuilder.Clear();
commandTextBuilder.Append(" select ");
4 years ago
List<string> columnsToSelect;
if (EnableGroupByPresentationUniqueKey(query))
{
4 years ago
columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" };
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
4 years ago
columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" };
}
else
{
4 years ago
columnsToSelect = new List<string> { "count (guid)" };
}
4 years ago
SetFinalColumnsToSelect(query, columnsToSelect);
commandTextBuilder.AppendJoin(',', columnsToSelect)
.Append(FromText)
.Append(GetJoinUserDataText(query));
if (!string.IsNullOrEmpty(whereText))
{
commandTextBuilder.Append(" where ")
.Append(whereText);
}
totalRecordCountQuery = commandTextBuilder.ToString();
}
5 years ago
var list = new List<BaseItem>();
var result = new QueryResult<BaseItem>();
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
if (!isReturningZeroItems)
8 years ago
{
using (new QueryTimeLogger(Logger, itemQuery, "GetItems.ItemQuery"))
using (var statement = PrepareStatement(connection, itemQuery))
{
if (EnableJoinUserData(query))
6 years ago
{
statement.TryBind("@UserId", query.User.InternalId);
}
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
// Running this again will bind the params
GetWhereClauses(query, statement);
var hasEpisodeAttributes = HasEpisodeAttributes(query);
var hasServiceName = HasServiceName(query);
var hasProgramAttributes = HasProgramAttributes(query);
var hasStartDate = HasStartDate(query);
var hasTrailerTypes = HasTrailerTypes(query);
var hasArtistFields = HasArtistFields(query);
var hasSeriesFields = HasSeriesFields(query);
foreach (var row in statement.ExecuteQuery())
{
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
if (item is not null)
8 years ago
{
list.Add(item);
3 years ago
}
}
}
}
8 years ago
if (query.EnableTotalRecordCount)
{
using (new QueryTimeLogger(Logger, totalRecordCountQuery, "GetItems.TotalRecordCount"))
using (var statement = PrepareStatement(connection, totalRecordCountQuery))
{
if (EnableJoinUserData(query))
{
statement.TryBind("@UserId", query.User.InternalId);
}
8 years ago
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
8 years ago
// Running this again will bind the params
GetWhereClauses(query, statement);
8 years ago
result.TotalRecordCount = statement.SelectScalarInt();
}
8 years ago
}
5 years ago
transaction.Commit();
result.StartIndex = query.StartIndex ?? 0;
result.Items = list;
5 years ago
return result;
8 years ago
}
private string GetOrderByText(InternalItemsQuery query)
{
var orderBy = query.OrderBy;
bool hasSimilar = query.SimilarTo is not null;
bool hasSearch = !string.IsNullOrEmpty(query.SearchTerm);
if (hasSimilar || hasSearch)
8 years ago
{
List<(ItemSortBy, SortOrder)> prepend = new List<(ItemSortBy, SortOrder)>(4);
if (hasSearch)
6 years ago
{
prepend.Add((ItemSortBy.SearchScore, SortOrder.Descending));
prepend.Add((ItemSortBy.SortName, SortOrder.Ascending));
6 years ago
}
if (hasSimilar)
6 years ago
{
prepend.Add((ItemSortBy.SimilarityScore, SortOrder.Descending));
prepend.Add((ItemSortBy.Random, SortOrder.Ascending));
}
8 years ago
var arr = new (ItemSortBy, SortOrder)[prepend.Count + orderBy.Count];
prepend.CopyTo(arr, 0);
orderBy.CopyTo(arr, prepend.Count);
orderBy = query.OrderBy = arr;
}
else if (orderBy.Count == 0)
8 years ago
{
return string.Empty;
}
return " ORDER BY " + string.Join(',', orderBy.Select(i =>
8 years ago
{
var sortBy = MapOrderByField(i.OrderBy, query);
var sortOrder = i.SortOrder == SortOrder.Ascending ? "ASC" : "DESC";
return sortBy + " " + sortOrder;
}));
8 years ago
}
private string MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query)
8 years ago
{
return sortBy switch
{
ItemSortBy.AirTime => "SortName", // TODO
ItemSortBy.Runtime => "RuntimeTicks",
ItemSortBy.Random => "RANDOM()",
ItemSortBy.DatePlayed when query.GroupBySeriesPresentationUniqueKey => "MAX(LastPlayedDate)",
ItemSortBy.DatePlayed => "LastPlayedDate",
ItemSortBy.PlayCount => "PlayCount",
ItemSortBy.IsFavoriteOrLiked => "(Select Case When IsFavorite is null Then 0 Else IsFavorite End )",
ItemSortBy.IsFolder => "IsFolder",
ItemSortBy.IsPlayed => "played",
ItemSortBy.IsUnplayed => "played",
ItemSortBy.DateLastContentAdded => "DateLastMediaAdded",
ItemSortBy.Artist => "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)",
ItemSortBy.AlbumArtist => "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)",
ItemSortBy.OfficialRating => "InheritedParentalRatingValue",
ItemSortBy.Studio => "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)",
ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)",
ItemSortBy.SeriesSortName => "SeriesName",
ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder",
ItemSortBy.Album => "Album",
ItemSortBy.DateCreated => "DateCreated",
ItemSortBy.PremiereDate => "PremiereDate",
ItemSortBy.StartDate => "StartDate",
ItemSortBy.Name => "Name",
ItemSortBy.CommunityRating => "CommunityRating",
ItemSortBy.ProductionYear => "ProductionYear",
ItemSortBy.CriticRating => "CriticRating",
ItemSortBy.VideoBitRate => "VideoBitRate",
ItemSortBy.ParentIndexNumber => "ParentIndexNumber",
ItemSortBy.IndexNumber => "IndexNumber",
ItemSortBy.SimilarityScore => "SimilarityScore",
ItemSortBy.SearchScore => "SearchScore",
_ => "SortName"
};
8 years ago
}
public List<Guid> GetItemIdsList(InternalItemsQuery query)
{
ArgumentNullException.ThrowIfNull(query);
8 years ago
CheckDisposed();
4 years ago
var columns = new List<string> { "guid" };
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 256)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
8 years ago
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
commandTextBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses);
}
8 years ago
commandTextBuilder.Append(GetGroupBy(query))
.Append(GetOrderByText(query));
8 years ago
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
var offset = query.StartIndex ?? 0;
if (query.Limit.HasValue || offset > 0)
{
commandTextBuilder.Append(" LIMIT ")
4 years ago
.Append(query.Limit ?? int.MaxValue);
8 years ago
}
if (offset > 0)
{
commandTextBuilder.Append(" OFFSET ")
4 years ago
.Append(offset);
8 years ago
}
}
var commandText = commandTextBuilder.ToString();
5 years ago
var list = new List<Guid>();
using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, commandText))
8 years ago
{
if (EnableJoinUserData(query))
6 years ago
{
statement.TryBind("@UserId", query.User.InternalId);
}
8 years ago
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
8 years ago
// Running this again will bind the params
GetWhereClauses(query, statement);
8 years ago
foreach (var row in statement.ExecuteQuery())
{
1 year ago
list.Add(row.GetGuid(0));
6 years ago
}
8 years ago
}
5 years ago
return list;
8 years ago
}
private bool IsAlphaNumeric(string str)
{
if (string.IsNullOrWhiteSpace(str))
{
return false;
}
for (int i = 0; i < str.Length; i++)
{
5 years ago
if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
{
return false;
}
}
return true;
}
private bool IsValidPersonType(string value)
{
return IsAlphaNumeric(value);
}
#nullable enable
1 year ago
private List<string> GetWhereClauses(InternalItemsQuery query, SqliteCommand? statement)
8 years ago
{
8 years ago
if (query.IsResumable ?? false)
{
query.IsVirtualItem = false;
}
var minWidth = query.MinWidth;
var maxWidth = query.MaxWidth;
8 years ago
if (query.IsHD.HasValue)
{
const int Threshold = 1200;
if (query.IsHD.Value)
{
minWidth = Threshold;
}
else
{
maxWidth = Threshold - 1;
}
}
if (query.Is4K.HasValue)
{
const int Threshold = 3800;
if (query.Is4K.Value)
{
minWidth = Threshold;
}
else
{
maxWidth = Threshold - 1;
}
}
6 years ago
var whereClauses = new List<string>();
if (minWidth.HasValue)
{
whereClauses.Add("Width>=@MinWidth");
statement?.TryBind("@MinWidth", minWidth);
}
if (query.MinHeight.HasValue)
{
whereClauses.Add("Height>=@MinHeight");
statement?.TryBind("@MinHeight", query.MinHeight);
}
if (maxWidth.HasValue)
{
whereClauses.Add("Width<=@MaxWidth");
statement?.TryBind("@MaxWidth", maxWidth);
}
if (query.MaxHeight.HasValue)
{
whereClauses.Add("Height<=@MaxHeight");
statement?.TryBind("@MaxHeight", query.MaxHeight);
8 years ago
}
8 years ago
if (query.IsLocked.HasValue)
{
whereClauses.Add("IsLocked=@IsLocked");
statement?.TryBind("@IsLocked", query.IsLocked);
8 years ago
}
var tags = query.Tags.ToList();
var excludeTags = query.ExcludeTags.ToList();
if (query.IsMovie == true)
8 years ago
{
if (query.IncludeItemTypes.Length == 0
|| query.IncludeItemTypes.Contains(BaseItemKind.Movie)
|| query.IncludeItemTypes.Contains(BaseItemKind.Trailer))
8 years ago
{
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
8 years ago
}
else
8 years ago
{
whereClauses.Add("IsMovie=@IsMovie");
8 years ago
}
statement?.TryBind("@IsMovie", true);
8 years ago
}
else if (query.IsMovie.HasValue)
8 years ago
{
whereClauses.Add("IsMovie=@IsMovie");
statement?.TryBind("@IsMovie", query.IsMovie);
}
8 years ago
if (query.IsSeries.HasValue)
{
whereClauses.Add("IsSeries=@IsSeries");
statement?.TryBind("@IsSeries", query.IsSeries);
}
8 years ago
if (query.IsSports.HasValue)
{
if (query.IsSports.Value)
{
tags.Add("Sports");
8 years ago
}
else
8 years ago
{
excludeTags.Add("Sports");
8 years ago
}
}
if (query.IsNews.HasValue)
{
if (query.IsNews.Value)
8 years ago
{
tags.Add("News");
8 years ago
}
else
8 years ago
{
excludeTags.Add("News");
8 years ago
}
}
if (query.IsKids.HasValue)
{
if (query.IsKids.Value)
8 years ago
{
tags.Add("Kids");
8 years ago
}
else
8 years ago
{
excludeTags.Add("Kids");
8 years ago
}
}
if (query.SimilarTo is not null && query.MinSimilarityScore > 0)
8 years ago
{
whereClauses.Add("SimilarityScore > " + (query.MinSimilarityScore - 1).ToString(CultureInfo.InvariantCulture));
8 years ago
}
if (!string.IsNullOrEmpty(query.SearchTerm))
{
whereClauses.Add("SearchScore > 0");
}
8 years ago
if (query.IsFolder.HasValue)
{
whereClauses.Add("IsFolder=@IsFolder");
statement?.TryBind("@IsFolder", query.IsFolder);
8 years ago
}
var includeTypes = query.IncludeItemTypes;
// Only specify excluded types if no included types are specified
if (query.IncludeItemTypes.Length == 0)
8 years ago
{
var excludeTypes = query.ExcludeItemTypes;
if (excludeTypes.Length == 1)
{
if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
{
whereClauses.Add("type<>@type");
statement?.TryBind("@type", excludeTypeName);
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeTypes[0]);
}
}
else if (excludeTypes.Length > 1)
8 years ago
{
var whereBuilder = new StringBuilder("type not in (");
foreach (var excludeType in excludeTypes)
{
if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
{
whereBuilder
.Append('\'')
.Append(baseItemKindName)
.Append("',");
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeType);
}
}
// Remove trailing comma.
whereBuilder.Length--;
whereBuilder.Append(')');
whereClauses.Add(whereBuilder.ToString());
8 years ago
}
}
else if (includeTypes.Length == 1)
{
if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
{
whereClauses.Add("type=@type");
statement?.TryBind("@type", includeTypeName);
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeTypes[0]);
}
}
else if (includeTypes.Length > 1)
{
var whereBuilder = new StringBuilder("type in (");
foreach (var includeType in includeTypes)
{
if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
{
whereBuilder
.Append('\'')
.Append(baseItemKindName)
.Append("',");
}
else
{
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeType);
}
}
// Remove trailing comma.
whereBuilder.Length--;
whereBuilder.Append(')');
whereClauses.Add(whereBuilder.ToString());
}
8 years ago
if (query.ChannelIds.Count == 1)
8 years ago
{
whereClauses.Add("ChannelId=@ChannelId");
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
8 years ago
}
else if (query.ChannelIds.Count > 1)
8 years ago
{
var inClause = string.Join(',', query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add($"ChannelId in ({inClause})");
8 years ago
}
if (!query.ParentId.Equals(default))
8 years ago
{
whereClauses.Add("ParentId=@ParentId");
statement?.TryBind("@ParentId", query.ParentId);
8 years ago
}
if (!string.IsNullOrWhiteSpace(query.Path))
{
whereClauses.Add("Path=@Path");
statement?.TryBind("@Path", GetPathToSave(query.Path));
8 years ago
}
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
{
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
8 years ago
}
if (query.MinCommunityRating.HasValue)
{
whereClauses.Add("CommunityRating>=@MinCommunityRating");
statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
8 years ago
}
if (query.MinIndexNumber.HasValue)
{
whereClauses.Add("IndexNumber>=@MinIndexNumber");
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
8 years ago
}
if (query.MinParentAndIndexNumber.HasValue)
{
whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndexNumberIndex) or ParentIndexNumber>@MinParentAndIndexNumberParent)");
statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumber);
statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber);
}
8 years ago
if (query.MinDateCreated.HasValue)
{
whereClauses.Add("DateCreated>=@MinDateCreated");
statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
8 years ago
}
if (query.MinDateLastSaved.HasValue)
{
7 years ago
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
8 years ago
}
if (query.MinDateLastSavedForUser.HasValue)
{
7 years ago
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
}
8 years ago
if (query.IndexNumber.HasValue)
{
whereClauses.Add("IndexNumber=@IndexNumber");
statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
8 years ago
}
8 years ago
if (query.ParentIndexNumber.HasValue)
{
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
8 years ago
}
8 years ago
if (query.ParentIndexNumberNotEquals.HasValue)
{
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
8 years ago
}
var minEndDate = query.MinEndDate;
var maxEndDate = query.MaxEndDate;
if (query.HasAired.HasValue)
{
if (query.HasAired.Value)
{
maxEndDate = DateTime.UtcNow;
}
else
{
minEndDate = DateTime.UtcNow;
}
}
if (minEndDate.HasValue)
8 years ago
{
whereClauses.Add("EndDate>=@MinEndDate");
statement?.TryBind("@MinEndDate", minEndDate.Value);
8 years ago
}
if (maxEndDate.HasValue)
8 years ago
{
whereClauses.Add("EndDate<=@MaxEndDate");
statement?.TryBind("@MaxEndDate", maxEndDate.Value);
8 years ago
}
if (query.MinStartDate.HasValue)
{
whereClauses.Add("StartDate>=@MinStartDate");
statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
8 years ago
}
if (query.MaxStartDate.HasValue)
{
whereClauses.Add("StartDate<=@MaxStartDate");
statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
8 years ago
}
if (query.MinPremiereDate.HasValue)
{
whereClauses.Add("PremiereDate>=@MinPremiereDate");
statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
8 years ago
}
8 years ago
if (query.MaxPremiereDate.HasValue)
{
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
8 years ago
}
StringBuilder clauseBuilder = new StringBuilder();
const string Or = " OR ";
var trailerTypes = query.TrailerTypes;
int trailerTypesLen = trailerTypes.Length;
if (trailerTypesLen > 0)
8 years ago
{
clauseBuilder.Append('(');
for (int i = 0; i < trailerTypesLen; i++)
8 years ago
{
var paramName = "@TrailerTypes" + i;
clauseBuilder.Append("TrailerTypes like ")
.Append(paramName)
5 years ago
.Append(Or);
statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
8 years ago
}
clauseBuilder.Length -= Or.Length;
clauseBuilder.Append(')');
whereClauses.Add(clauseBuilder.ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (query.IsAiring.HasValue)
{
if (query.IsAiring.Value)
{
whereClauses.Add("StartDate<=@MaxStartDate");
statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
8 years ago
whereClauses.Add("EndDate>=@MinEndDate");
statement?.TryBind("@MinEndDate", DateTime.UtcNow);
8 years ago
}
else
{
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
8 years ago
}
}
int personIdsLen = query.PersonIds.Length;
if (personIdsLen > 0)
8 years ago
{
// TODO: Should this query with CleanName ?
clauseBuilder.Append('(');
Span<byte> idBytes = stackalloc byte[16];
for (int i = 0; i < personIdsLen; i++)
{
string paramName = "@PersonId" + i;
clauseBuilder.Append("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=")
.Append(paramName)
.Append("))) OR ");
statement?.TryBind(paramName, query.PersonIds[i]);
}
clauseBuilder.Length -= Or.Length;
clauseBuilder.Append(')');
whereClauses.Add(clauseBuilder.ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (!string.IsNullOrWhiteSpace(query.Person))
{
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
statement?.TryBind("@PersonName", query.Person);
8 years ago
}
if (!string.IsNullOrWhiteSpace(query.MinSortName))
{
whereClauses.Add("SortName>=@MinSortName");
statement?.TryBind("@MinSortName", query.MinSortName);
8 years ago
}
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
{
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
8 years ago
}
if (!string.IsNullOrWhiteSpace(query.ExternalId))
{
whereClauses.Add("ExternalId=@ExternalId");
statement?.TryBind("@ExternalId", query.ExternalId);
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.Name))
{
whereClauses.Add("CleanName=@Name");
statement?.TryBind("@Name", GetCleanValue(query.Name));
8 years ago
}
// These are the same, for now
var nameContains = query.NameContains;
if (!string.IsNullOrWhiteSpace(nameContains))
8 years ago
{
whereClauses.Add("(CleanName like @NameContains or OriginalTitle like @NameContains)");
if (statement is not null)
8 years ago
{
nameContains = FixUnicodeChars(nameContains);
statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%");
8 years ago
}
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
{
whereClauses.Add("SortName like @NameStartsWith");
statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
8 years ago
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
{
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
// lowercase this because SortName is stored as lowercase
statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
8 years ago
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
{
whereClauses.Add("SortName < @NameLessThan");
// lowercase this because SortName is stored as lowercase
statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
8 years ago
}
if (query.ImageTypes.Length > 0)
{
foreach (var requiredImage in query.ImageTypes)
{
whereClauses.Add("Images like '%" + requiredImage + "%'");
}
}
if (query.IsLiked.HasValue)
{
if (query.IsLiked.Value)
{
whereClauses.Add("rating>=@UserRating");
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
8 years ago
}
else
{
whereClauses.Add("(rating is null or rating<@UserRating)");
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
8 years ago
}
}
if (query.IsFavoriteOrLiked.HasValue)
{
if (query.IsFavoriteOrLiked.Value)
{
whereClauses.Add("IsFavorite=@IsFavoriteOrLiked");
}
else
{
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
}
statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
8 years ago
}
if (query.IsFavorite.HasValue)
{
if (query.IsFavorite.Value)
{
whereClauses.Add("IsFavorite=@IsFavorite");
}
else
{
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
}
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
8 years ago
}
if (EnableJoinUserData(query))
{
if (query.IsPlayed.HasValue)
{
// We should probably figure this out for all folders, but for right now, this is the only place where we need it
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series)
8 years ago
{
if (query.IsPlayed.Value)
{
whereClauses.Add("PresentationUniqueKey not in (select S.SeriesPresentationUniqueKey from TypedBaseitems S left join UserDatas UD on S.UserDataKey=UD.Key And UD.UserId=@UserId where Coalesce(UD.Played, 0)=0 and S.IsFolder=0 and S.IsVirtualItem=0 and S.SeriesPresentationUniqueKey not null)");
}
else
{
whereClauses.Add("PresentationUniqueKey in (select S.SeriesPresentationUniqueKey from TypedBaseitems S left join UserDatas UD on S.UserDataKey=UD.Key And UD.UserId=@UserId where Coalesce(UD.Played, 0)=0 and S.IsFolder=0 and S.IsVirtualItem=0 and S.SeriesPresentationUniqueKey not null)");
}
8 years ago
}
else
{
if (query.IsPlayed.Value)
{
whereClauses.Add("(played=@IsPlayed)");
}
else
{
whereClauses.Add("(played is null or played=@IsPlayed)");
}
statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
8 years ago
}
}
}
if (query.IsResumable.HasValue)
{
if (query.IsResumable.Value)
{
whereClauses.Add("playbackPositionTicks > 0");
}
else
{
whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)");
}
}
if (query.ArtistIds.Length > 0)
8 years ago
{
clauseBuilder.Append('(');
for (var i = 0; i < query.ArtistIds.Length; i++)
8 years ago
{
clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@ArtistIds")
.Append(i)
.Append(") and Type<=1)) OR ");
statement?.TryBind("@ArtistIds" + i, query.ArtistIds[i]);
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
}
if (query.AlbumArtistIds.Length > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.AlbumArtistIds.Length; i++)
{
clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@ArtistIds")
.Append(i)
.Append(") and Type=1)) OR ");
statement?.TryBind("@ArtistIds" + i, query.AlbumArtistIds[i]);
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
}
if (query.ContributingArtistIds.Length > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.ContributingArtistIds.Length; i++)
{
clauseBuilder.Append("((select CleanName from TypedBaseItems where guid=@ArtistIds")
.Append(i)
.Append(") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=@ArtistIds")
.Append(i)
.Append(") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1)) OR ");
statement?.TryBind("@ArtistIds" + i, query.ContributingArtistIds[i]);
8 years ago
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (query.AlbumIds.Length > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.AlbumIds.Length; i++)
{
clauseBuilder.Append("Album in (select Name from typedbaseitems where guid=@AlbumIds")
.Append(i)
.Append(") OR ");
statement?.TryBind("@AlbumIds" + i, query.AlbumIds[i]);
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
}
8 years ago
if (query.ExcludeArtistIds.Length > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.ExcludeArtistIds.Length; i++)
8 years ago
{
clauseBuilder.Append("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@ExcludeArtistId")
.Append(i)
.Append(") and Type<=1)) OR ");
statement?.TryBind("@ExcludeArtistId" + i, query.ExcludeArtistIds[i]);
8 years ago
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (query.GenreIds.Count > 0)
8 years ago
{
clauseBuilder.Append('(');
for (var i = 0; i < query.GenreIds.Count; i++)
{
clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@GenreId")
.Append(i)
.Append(") and Type=2)) OR ");
statement?.TryBind("@GenreId" + i, query.GenreIds[i]);
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (query.Genres.Count > 0)
8 years ago
{
clauseBuilder.Append('(');
for (var i = 0; i < query.Genres.Count; i++)
8 years ago
{
clauseBuilder.Append("@Genre")
.Append(i)
.Append(" in (select CleanValue from ItemValues where ItemId=Guid and Type=2) OR ");
statement?.TryBind("@Genre" + i, GetCleanValue(query.Genres[i]));
8 years ago
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (tags.Count > 0)
8 years ago
{
clauseBuilder.Append('(');
for (var i = 0; i < tags.Count; i++)
8 years ago
{
clauseBuilder.Append("@Tag")
.Append(i)
.Append(" in (select CleanValue from ItemValues where ItemId=Guid and Type=4) OR ");
statement?.TryBind("@Tag" + i, GetCleanValue(tags[i]));
8 years ago
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (excludeTags.Count > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < excludeTags.Count; i++)
{
clauseBuilder.Append("@ExcludeTag")
.Append(i)
.Append(" not in (select CleanValue from ItemValues where ItemId=Guid and Type=4) OR ");
statement?.TryBind("@ExcludeTag" + i, GetCleanValue(excludeTags[i]));
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
}
8 years ago
if (query.StudioIds.Length > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.StudioIds.Length; i++)
{
clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@StudioId")
.Append(i)
.Append(") and Type=3)) OR ");
statement?.TryBind("@StudioId" + i, query.StudioIds[i]);
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (query.OfficialRatings.Length > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.OfficialRatings.Length; i++)
8 years ago
{
clauseBuilder.Append("OfficialRating=@OfficialRating").Append(i).Append(Or);
statement?.TryBind("@OfficialRating" + i, query.OfficialRatings[i]);
8 years ago
}
clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
clauseBuilder.Append('(');
if (query.HasParentalRating ?? false)
8 years ago
{
clauseBuilder.Append("InheritedParentalRatingValue not null");
if (query.MinParentalRating.HasValue)
8 years ago
{
clauseBuilder.Append(" AND InheritedParentalRatingValue >= @MinParentalRating");
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
8 years ago
}
if (query.MaxParentalRating.HasValue)
8 years ago
{
clauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
8 years ago
}
}
else if (query.BlockUnratedItems.Length > 0)
{
const string ParamName = "@UnratedType";
clauseBuilder.Append("(InheritedParentalRatingValue is null AND UnratedType not in (");
for (int i = 0; i < query.BlockUnratedItems.Length; i++)
{
clauseBuilder.Append(ParamName).Append(i).Append(',');
statement?.TryBind(ParamName + i, query.BlockUnratedItems[i].ToString());
}
// Remove trailing comma
clauseBuilder.Length--;
clauseBuilder.Append("))");
if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
{
clauseBuilder.Append(" OR (");
}
if (query.MinParentalRating.HasValue)
{
clauseBuilder.Append("InheritedParentalRatingValue >= @MinParentalRating");
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
}
if (query.MaxParentalRating.HasValue)
{
if (query.MinParentalRating.HasValue)
{
clauseBuilder.Append(" AND ");
}
clauseBuilder.Append("InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
}
if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
{
clauseBuilder.Append(')');
}
8 years ago
if (!(query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue))
{
clauseBuilder.Append(" OR InheritedParentalRatingValue not null");
}
}
else if (query.MinParentalRating.HasValue)
8 years ago
{
clauseBuilder.Append("InheritedParentalRatingValue is null OR (InheritedParentalRatingValue >= @MinParentalRating");
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
if (query.MaxParentalRating.HasValue)
8 years ago
{
clauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
8 years ago
}
clauseBuilder.Append(')');
}
else if (query.MaxParentalRating.HasValue)
{
clauseBuilder.Append("InheritedParentalRatingValue is null OR InheritedParentalRatingValue <= @MaxParentalRating");
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
}
else if (!query.HasParentalRating ?? false)
{
clauseBuilder.Append("InheritedParentalRatingValue is null");
}
if (clauseBuilder.Length > 1)
{
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
8 years ago
}
if (query.HasOfficialRating.HasValue)
{
if (query.HasOfficialRating.Value)
{
whereClauses.Add("(OfficialRating not null AND OfficialRating<>'')");
}
else
{
whereClauses.Add("(OfficialRating is null OR OfficialRating='')");
}
}
8 years ago
if (query.HasOverview.HasValue)
{
if (query.HasOverview.Value)
{
whereClauses.Add("(Overview not null AND Overview<>'')");
}
else
{
whereClauses.Add("(Overview is null OR Overview='')");
}
}
if (query.HasOwnerId.HasValue)
{
if (query.HasOwnerId.Value)
{
whereClauses.Add("OwnerId not null");
}
else
{
whereClauses.Add("OwnerId is null");
}
}
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
}
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
{
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
}
if (query.HasSubtitles.HasValue)
{
if (query.HasSubtitles.Value)
{
whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) not null)");
}
else
{
whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) is null)");
}
}
if (query.HasChapterImages.HasValue)
{
if (query.HasChapterImages.Value)
{
whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) not null)");
}
else
{
whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) is null)");
}
}
if (query.HasDeadParentId.HasValue && query.HasDeadParentId.Value)
8 years ago
{
whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)");
8 years ago
}
if (query.IsDeadArtist.HasValue && query.IsDeadArtist.Value)
{
whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type in (0,1))");
}
if (query.IsDeadStudio.HasValue && query.IsDeadStudio.Value)
{
whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type = 3)");
}
if (query.IsDeadPerson.HasValue && query.IsDeadPerson.Value)
{
whereClauses.Add("Name not in (Select Name From People)");
}
8 years ago
if (query.Years.Length == 1)
{
whereClauses.Add("ProductionYear=@Years");
statement?.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
8 years ago
}
else if (query.Years.Length > 1)
{
var val = string.Join(',', query.Years);
8 years ago
whereClauses.Add("ProductionYear in (" + val + ")");
}
var isVirtualItem = query.IsVirtualItem ?? query.IsMissing;
if (isVirtualItem.HasValue)
8 years ago
{
whereClauses.Add("IsVirtualItem=@IsVirtualItem");
statement?.TryBind("@IsVirtualItem", isVirtualItem.Value);
8 years ago
}
8 years ago
if (query.IsSpecialSeason.HasValue)
{
if (query.IsSpecialSeason.Value)
{
whereClauses.Add("IndexNumber = 0");
}
else
{
whereClauses.Add("IndexNumber <> 0");
}
}
8 years ago
if (query.IsUnaired.HasValue)
{
if (query.IsUnaired.Value)
{
whereClauses.Add("PremiereDate >= DATETIME('now')");
}
else
{
whereClauses.Add("PremiereDate < DATETIME('now')");
}
}
if (query.MediaTypes.Length == 1)
8 years ago
{
whereClauses.Add("MediaType=@MediaTypes");
statement?.TryBind("@MediaTypes", query.MediaTypes[0].ToString());
8 years ago
}
else if (query.MediaTypes.Length > 1)
8 years ago
{
var val = string.Join(',', query.MediaTypes.Select(i => $"'{i}'"));
8 years ago
whereClauses.Add("MediaType in (" + val + ")");
}
8 years ago
if (query.ItemIds.Length > 0)
{
var includeIds = new List<string>();
var index = 0;
foreach (var id in query.ItemIds)
{
includeIds.Add("Guid = @IncludeId" + index);
statement?.TryBind("@IncludeId" + index, id);
8 years ago
index++;
}
whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")");
8 years ago
}
8 years ago
if (query.ExcludeItemIds.Length > 0)
{
var excludeIds = new List<string>();
var index = 0;
foreach (var id in query.ExcludeItemIds)
{
excludeIds.Add("Guid <> @ExcludeId" + index);
statement?.TryBind("@ExcludeId" + index, id);
8 years ago
index++;
}
whereClauses.Add(string.Join(" AND ", excludeIds));
8 years ago
}
if (query.ExcludeProviderIds is not null && query.ExcludeProviderIds.Count > 0)
8 years ago
{
var excludeIds = new List<string>();
var index = 0;
foreach (var pair in query.ExcludeProviderIds)
{
if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase))
8 years ago
{
continue;
}
var paramName = "@ExcludeProviderId" + index;
excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")");
statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
8 years ago
index++;
break;
}
if (excludeIds.Count > 0)
{
whereClauses.Add(string.Join(" AND ", excludeIds));
}
}
if (query.HasAnyProviderId is not null && query.HasAnyProviderId.Count > 0)
{
var hasProviderIds = new List<string>();
var index = 0;
foreach (var pair in query.HasAnyProviderId)
{
if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase))
{
continue;
}
// TODO this seems to be an idea for a better schema where ProviderIds are their own table
// but this is not implemented
4 years ago
// hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
// TODO this is a really BAD way to do it since the pair:
// Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567
// and maybe even NotTmdb=1234.
// this is a placeholder for this specific pair to correlate it in the bigger query
var paramName = "@HasAnyProviderId" + index;
// this is a search for the placeholder
hasProviderIds.Add("ProviderIds like " + paramName);
// this replaces the placeholder with a value, here: %key=val%
statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
index++;
break;
}
if (hasProviderIds.Count > 0)
{
whereClauses.Add("(" + string.Join(" OR ", hasProviderIds) + ")");
}
8 years ago
}
if (query.HasImdbId.HasValue)
8 years ago
{
whereClauses.Add(GetProviderIdClause(query.HasImdbId.Value, "imdb"));
8 years ago
}
if (query.HasTmdbId.HasValue)
8 years ago
{
whereClauses.Add(GetProviderIdClause(query.HasTmdbId.Value, "tmdb"));
8 years ago
}
if (query.HasTvdbId.HasValue)
8 years ago
{
whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb"));
8 years ago
}
var queryTopParentIds = query.TopParentIds;
if (queryTopParentIds.Length > 0)
8 years ago
{
var includedItemByNameTypes = GetItemByNameTypesInQuery(query);
var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
if (queryTopParentIds.Length == 1)
8 years ago
{
if (enableItemsByName && includedItemByNameTypes.Count == 1)
8 years ago
{
whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)");
statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
}
else
{
whereClauses.Add("(TopParentId=@TopParentId)");
8 years ago
}
statement?.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
8 years ago
}
else if (queryTopParentIds.Length > 1)
8 years ago
{
var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
if (enableItemsByName && includedItemByNameTypes.Count == 1)
8 years ago
{
whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))");
statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
}
else
{
whereClauses.Add("TopParentId in (" + val + ")");
8 years ago
}
}
}
if (query.AncestorIds.Length == 1)
{
whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
statement?.TryBind("@AncestorId", query.AncestorIds[0]);
8 years ago
}
8 years ago
if (query.AncestorIds.Length > 1)
{
var inClause = string.Join(',', query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
4 years ago
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
8 years ago
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
{
var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
4 years ago
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
statement?.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
}
if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey))
{
whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey");
statement?.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
8 years ago
}
if (query.ExcludeInheritedTags.Length > 0)
8 years ago
{
var paramName = "@ExcludeInheritedTags";
if (statement is null)
{
int index = 0;
3 years ago
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
}
else
{
for (int index = 0; index < query.ExcludeInheritedTags.Length; index++)
{
statement.TryBind(paramName + index, GetCleanValue(query.ExcludeInheritedTags[index]));
}
}
8 years ago
}
if (query.IncludeInheritedTags.Length > 0)
{
var paramName = "@IncludeInheritedTags";
if (statement is null)
{
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)");
}
else
{
for (int index = 0; index < query.IncludeInheritedTags.Length; index++)
{
statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index]));
}
}
}
if (query.SeriesStatuses.Length > 0)
{
var statuses = new List<string>();
foreach (var seriesStatus in query.SeriesStatuses)
{
statuses.Add("data like '%" + seriesStatus + "%'");
}
whereClauses.Add("(" + string.Join(" OR ", statuses) + ")");
}
if (query.BoxSetLibraryFolders.Length > 0)
{
var folderIdQueries = new List<string>();
foreach (var folderId in query.BoxSetLibraryFolders)
{
folderIdQueries.Add("data like '%" + folderId.ToString("N", CultureInfo.InvariantCulture) + "%'");
}
whereClauses.Add("(" + string.Join(" OR ", folderIdQueries) + ")");
}
if (query.VideoTypes.Length > 0)
{
var videoTypes = new List<string>();
foreach (var videoType in query.VideoTypes)
{
videoTypes.Add("data like '%\"VideoType\":\"" + videoType + "\"%'");
}
whereClauses.Add("(" + string.Join(" OR ", videoTypes) + ")");
}
if (query.Is3D.HasValue)
{
if (query.Is3D.Value)
{
whereClauses.Add("data like '%Video3DFormat%'");
}
else
{
whereClauses.Add("data not like '%Video3DFormat%'");
}
}
if (query.IsPlaceHolder.HasValue)
{
if (query.IsPlaceHolder.Value)
{
whereClauses.Add("data like '%\"IsPlaceHolder\":true%'");
}
else
{
whereClauses.Add("(data is null or data not like '%\"IsPlaceHolder\":true%')");
}
}
if (query.HasSpecialFeature.HasValue)
{
if (query.HasSpecialFeature.Value)
{
whereClauses.Add("ExtraIds not null");
}
else
{
whereClauses.Add("ExtraIds is null");
}
}
if (query.HasTrailer.HasValue)
{
if (query.HasTrailer.Value)
{
whereClauses.Add("ExtraIds not null");
}
else
{
whereClauses.Add("ExtraIds is null");
}
}
if (query.HasThemeSong.HasValue)
{
if (query.HasThemeSong.Value)
{
whereClauses.Add("ExtraIds not null");
}
else
{
whereClauses.Add("ExtraIds is null");
}
}
if (query.HasThemeVideo.HasValue)
{
if (query.HasThemeVideo.Value)
{
whereClauses.Add("ExtraIds not null");
}
else
{
whereClauses.Add("ExtraIds is null");
}
}
8 years ago
return whereClauses;
}
/// <summary>
/// Formats a where clause for the specified provider.
/// </summary>
/// <param name="includeResults">Whether or not to include items with this provider's ids.</param>
/// <param name="provider">Provider name.</param>
/// <returns>Formatted SQL clause.</returns>
private string GetProviderIdClause(bool includeResults, string provider)
{
return string.Format(
CultureInfo.InvariantCulture,
"ProviderIds {0} like '%{1}=%'",
includeResults ? string.Empty : "not",
provider);
}
#nullable disable
7 years ago
private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
{
var list = new List<string>();
if (IsTypeInQuery(BaseItemKind.Person, query))
7 years ago
{
list.Add(typeof(Person).FullName);
7 years ago
}
if (IsTypeInQuery(BaseItemKind.Genre, query))
7 years ago
{
list.Add(typeof(Genre).FullName);
7 years ago
}
if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
7 years ago
{
list.Add(typeof(MusicGenre).FullName);
7 years ago
}
if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
7 years ago
{
list.Add(typeof(MusicArtist).FullName);
7 years ago
}
if (IsTypeInQuery(BaseItemKind.Studio, query))
7 years ago
{
list.Add(typeof(Studio).FullName);
7 years ago
}
return list;
}
private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
7 years ago
{
if (query.ExcludeItemTypes.Contains(type))
7 years ago
{
return false;
}
return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
7 years ago
}
8 years ago
private string GetCleanValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return value;
}
return value.RemoveDiacritics().ToLowerInvariant();
8 years ago
}
private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
{
if (!query.GroupByPresentationUniqueKey)
{
return false;
}
if (query.GroupBySeriesPresentationUniqueKey)
{
return false;
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
{
return false;
}
if (query.User is null)
8 years ago
{
return false;
}
if (query.IncludeItemTypes.Length == 0)
{
return true;
}
return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
|| query.IncludeItemTypes.Contains(BaseItemKind.Video)
|| query.IncludeItemTypes.Contains(BaseItemKind.Movie)
|| query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
|| query.IncludeItemTypes.Contains(BaseItemKind.Series)
|| query.IncludeItemTypes.Contains(BaseItemKind.Season);
8 years ago
}
public void UpdateInheritedValues()
7 years ago
{
const string Statements = """
delete from ItemValues where type = 6;
insert into ItemValues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4;
insert into ItemValues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
7 years ago
FROM AncestorIds
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4;
""";
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
connection.Execute(Statements);
transaction.Commit();
8 years ago
}
public void DeleteItem(Guid id)
8 years ago
{
if (id.Equals(default))
8 years ago
{
throw new ArgumentNullException(nameof(id));
8 years ago
}
CheckDisposed();
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
// Delete people
ExecuteWithSingleParam(connection, "delete from People where ItemId=@Id", id);
// Delete chapters
ExecuteWithSingleParam(connection, "delete from " + ChaptersTableName + " where ItemId=@Id", id);
8 years ago
// Delete media streams
ExecuteWithSingleParam(connection, "delete from mediastreams where ItemId=@Id", id);
8 years ago
// Delete ancestors
ExecuteWithSingleParam(connection, "delete from AncestorIds where ItemId=@Id", id);
8 years ago
// Delete item values
ExecuteWithSingleParam(connection, "delete from ItemValues where ItemId=@Id", id);
8 years ago
// Delete the item
ExecuteWithSingleParam(connection, "delete from TypedBaseItems where guid=@Id", id);
8 years ago
transaction.Commit();
8 years ago
}
private void ExecuteWithSingleParam(SqliteConnection db, string query, Guid value)
8 years ago
{
using (var statement = PrepareStatement(db, query))
8 years ago
{
statement.TryBind("@Id", value);
statement.ExecuteNonQuery();
8 years ago
}
}
public List<string> GetPeopleNames(InternalPeopleQuery query)
{
ArgumentNullException.ThrowIfNull(query);
8 years ago
CheckDisposed();
var commandText = new StringBuilder("select Distinct p.Name from People p");
8 years ago
var whereClauses = GetPeopleWhereClauses(query, null);
if (whereClauses.Count != 0)
8 years ago
{
commandText.Append(" where ").AppendJoin(" AND ", whereClauses);
8 years ago
}
commandText.Append(" order by ListOrder");
8 years ago
5 years ago
if (query.Limit > 0)
{
commandText.Append(" LIMIT ").Append(query.Limit);
5 years ago
}
var list = new List<string>();
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, commandText.ToString()))
8 years ago
{
// Run this again to bind the params
GetPeopleWhereClauses(query, statement);
foreach (var row in statement.ExecuteQuery())
{
list.Add(row.GetString(0));
8 years ago
}
}
return list;
8 years ago
}
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
ArgumentNullException.ThrowIfNull(query);
8 years ago
CheckDisposed();
StringBuilder commandText = new StringBuilder("select ItemId, Name, Role, PersonType, SortOrder from People p");
8 years ago
var whereClauses = GetPeopleWhereClauses(query, null);
if (whereClauses.Count != 0)
8 years ago
{
commandText.Append(" where ").AppendJoin(" AND ", whereClauses);
8 years ago
}
commandText.Append(" order by ListOrder");
8 years ago
5 years ago
if (query.Limit > 0)
{
commandText.Append(" LIMIT ").Append(query.Limit);
5 years ago
}
var list = new List<PersonInfo>();
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, commandText.ToString()))
8 years ago
{
// Run this again to bind the params
GetPeopleWhereClauses(query, statement);
6 years ago
foreach (var row in statement.ExecuteQuery())
8 years ago
{
list.Add(GetPerson(row));
8 years ago
}
}
return list;
8 years ago
}
1 year ago
private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, SqliteCommand statement)
8 years ago
{
var whereClauses = new List<string>();
if (query.User is not null && query.IsFavorite.HasValue)
{
whereClauses.Add(@"p.Name IN (
SELECT Name FROM TypedBaseItems WHERE UserDataKey IN (
SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId)
AND Type = @InternalPersonType)");
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
statement?.TryBind("@InternalPersonType", typeof(Person).FullName);
statement?.TryBind("@UserId", query.User.InternalId);
}
if (!query.ItemId.Equals(default))
8 years ago
{
whereClauses.Add("ItemId=@ItemId");
statement?.TryBind("@ItemId", query.ItemId);
8 years ago
}
if (!query.AppearsInItemId.Equals(default))
8 years ago
{
whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)");
statement?.TryBind("@AppearsInItemId", query.AppearsInItemId);
8 years ago
}
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
if (queryPersonTypes.Count == 1)
8 years ago
{
whereClauses.Add("PersonType=@PersonType");
statement?.TryBind("@PersonType", queryPersonTypes[0]);
8 years ago
}
else if (queryPersonTypes.Count > 1)
8 years ago
{
var val = string.Join(',', queryPersonTypes.Select(i => "'" + i + "'"));
8 years ago
whereClauses.Add("PersonType in (" + val + ")");
}
var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList();
if (queryExcludePersonTypes.Count == 1)
8 years ago
{
whereClauses.Add("PersonType<>@PersonType");
statement?.TryBind("@PersonType", queryExcludePersonTypes[0]);
8 years ago
}
else if (queryExcludePersonTypes.Count > 1)
8 years ago
{
var val = string.Join(',', queryExcludePersonTypes.Select(i => "'" + i + "'"));
8 years ago
whereClauses.Add("PersonType not in (" + val + ")");
}
8 years ago
if (query.MaxListOrder.HasValue)
{
whereClauses.Add("ListOrder<=@MaxListOrder");
statement?.TryBind("@MaxListOrder", query.MaxListOrder.Value);
8 years ago
}
8 years ago
if (!string.IsNullOrWhiteSpace(query.NameContains))
{
whereClauses.Add("p.Name like @NameContains");
statement?.TryBind("@NameContains", "%" + query.NameContains + "%");
}
8 years ago
return whereClauses;
}
1 year ago
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement)
8 years ago
{
if (itemId.Equals(default))
8 years ago
{
throw new ArgumentNullException(nameof(itemId));
8 years ago
}
ArgumentNullException.ThrowIfNull(ancestorIds);
8 years ago
CheckDisposed();
// First delete
1 year ago
deleteAncestorsStatement.TryBind("@ItemId", itemId);
deleteAncestorsStatement.ExecuteNonQuery();
8 years ago
if (ancestorIds.Count == 0)
{
return;
}
var insertText = new StringBuilder("insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values ");
for (var i = 0; i < ancestorIds.Count; i++)
{
insertText.AppendFormat(
CultureInfo.InvariantCulture,
"(@ItemId, @AncestorId{0}, @AncestorIdText{0}),",
i.ToString(CultureInfo.InvariantCulture));
}
// Remove trailing comma
insertText.Length--;
6 years ago
using (var statement = PrepareStatement(db, insertText.ToString()))
8 years ago
{
1 year ago
statement.TryBind("@ItemId", itemId);
for (var i = 0; i < ancestorIds.Count; i++)
{
var index = i.ToString(CultureInfo.InvariantCulture);
var ancestorId = ancestorIds[i];
1 year ago
statement.TryBind("@AncestorId" + index, ancestorId);
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
}
statement.ExecuteNonQuery();
8 years ago
}
}
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
8 years ago
{
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
}
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
8 years ago
{
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
}
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
8 years ago
{
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
}
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
8 years ago
{
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
}
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
8 years ago
{
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
}
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
8 years ago
{
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
}
public List<string> GetStudioNames()
{
return GetItemValueNames(new[] { 3 }, Array.Empty<string>(), Array.Empty<string>());
8 years ago
}
public List<string> GetAllArtistNames()
{
return GetItemValueNames(new[] { 0, 1 }, Array.Empty<string>(), Array.Empty<string>());
8 years ago
}
public List<string> GetMusicGenreNames()
{
return GetItemValueNames(
new[] { 2 },
new string[]
{
typeof(Audio).FullName,
typeof(MusicVideo).FullName,
typeof(MusicAlbum).FullName,
typeof(MusicArtist).FullName
},
Array.Empty<string>());
8 years ago
}
public List<string> GetGenreNames()
{
return GetItemValueNames(
new[] { 2 },
Array.Empty<string>(),
new string[]
{
typeof(Audio).FullName,
typeof(MusicVideo).FullName,
typeof(MusicAlbum).FullName,
typeof(MusicArtist).FullName
});
8 years ago
}
private List<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes)
8 years ago
{
CheckDisposed();
4 years ago
var stringBuilder = new StringBuilder("Select Value From ItemValues where Type", 128);
if (itemValueTypes.Length == 1)
{
stringBuilder.Append('=')
4 years ago
.Append(itemValueTypes[0]);
}
else
{
stringBuilder.Append(" in (")
4 years ago
.AppendJoin(',', itemValueTypes)
.Append(')');
}
8 years ago
if (withItemTypes.Count > 0)
{
stringBuilder.Append(" AND ItemId In (select guid from typedbaseitems where type in (")
4 years ago
.AppendJoinInSingleQuotes(',', withItemTypes)
.Append("))");
8 years ago
}
8 years ago
if (excludeItemTypes.Count > 0)
{
stringBuilder.Append(" AND ItemId not In (select guid from typedbaseitems where type in (")
4 years ago
.AppendJoinInSingleQuotes(',', excludeItemTypes)
.Append("))");
8 years ago
}
stringBuilder.Append(" Group By CleanValue");
var commandText = stringBuilder.ToString();
8 years ago
5 years ago
var list = new List<string>();
using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, commandText))
8 years ago
{
foreach (var row in statement.ExecuteQuery())
6 years ago
{
if (row.TryGetString(0, out var result))
{
list.Add(result);
}
6 years ago
}
8 years ago
}
5 years ago
return list;
8 years ago
}
private QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
8 years ago
{
ArgumentNullException.ThrowIfNull(query);
8 years ago
if (!query.Limit.HasValue)
{
query.EnableTotalRecordCount = false;
}
CheckDisposed();
var typeClause = itemValueTypes.Length == 1 ?
4 years ago
("Type=" + itemValueTypes[0]) :
("Type in (" + string.Join(',', itemValueTypes) + ")");
8 years ago
InternalItemsQuery typeSubQuery = null;
string itemCountColumns = null;
4 years ago
var stringBuilder = new StringBuilder(1024);
var typesToCount = query.IncludeItemTypes;
if (typesToCount.Length > 0)
{
stringBuilder.Append("(select group_concat(type, '|') from TypedBaseItems B");
typeSubQuery = new InternalItemsQuery(query.User)
{
ExcludeItemTypes = query.ExcludeItemTypes,
IncludeItemTypes = query.IncludeItemTypes,
MediaTypes = query.MediaTypes,
AncestorIds = query.AncestorIds,
ExcludeItemIds = query.ExcludeItemIds,
ItemIds = query.ItemIds,
TopParentIds = query.TopParentIds,
ParentId = query.ParentId,
IsPlayed = query.IsPlayed
};
var whereClauses = GetWhereClauses(typeSubQuery, null);
stringBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses)
4 years ago
.Append(" AND ")
.Append("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND ")
.Append(typeClause)
.Append(")) as itemTypes");
itemCountColumns = stringBuilder.ToString();
stringBuilder.Clear();
}
List<string> columns = _retrieveItemColumns.ToList();
// Unfortunately we need to add it to columns to ensure the order of the columns in the select
if (!string.IsNullOrEmpty(itemCountColumns))
{
columns.Add(itemCountColumns);
}
// do this first before calling GetFinalColumnsToSelect, otherwise ExcludeItemIds will be set by SimilarTo
var innerQuery = new InternalItemsQuery(query.User)
{
ExcludeItemTypes = query.ExcludeItemTypes,
IncludeItemTypes = query.IncludeItemTypes,
MediaTypes = query.MediaTypes,
AncestorIds = query.AncestorIds,
ItemIds = query.ItemIds,
TopParentIds = query.TopParentIds,
ParentId = query.ParentId,
IsAiring = query.IsAiring,
IsMovie = query.IsMovie,
IsSports = query.IsSports,
IsKids = query.IsKids,
IsNews = query.IsNews,
IsSeries = query.IsSeries
};
4 years ago
SetFinalColumnsToSelect(query, columns);
var innerWhereClauses = GetWhereClauses(innerQuery, null);
stringBuilder.Append(" where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where ")
.Append(typeClause)
.Append(" AND ItemId in (select guid from TypedBaseItems");
if (innerWhereClauses.Count > 0)
{
stringBuilder.Append(" where ")
.AppendJoin(" AND ", innerWhereClauses);
}
stringBuilder.Append("))");
var outerQuery = new InternalItemsQuery(query.User)
{
IsPlayed = query.IsPlayed,
IsFavorite = query.IsFavorite,
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
IsLiked = query.IsLiked,
IsLocked = query.IsLocked,
NameLessThan = query.NameLessThan,
NameStartsWith = query.NameStartsWith,
NameStartsWithOrGreater = query.NameStartsWithOrGreater,
Tags = query.Tags,
OfficialRatings = query.OfficialRatings,
StudioIds = query.StudioIds,
GenreIds = query.GenreIds,
Genres = query.Genres,
Years = query.Years,
NameContains = query.NameContains,
SearchTerm = query.SearchTerm,
SimilarTo = query.SimilarTo,
ExcludeItemIds = query.ExcludeItemIds
};
var outerWhereClauses = GetWhereClauses(outerQuery, null);
if (outerWhereClauses.Count != 0)
{
stringBuilder.Append(" AND ")
.AppendJoin(" AND ", outerWhereClauses);
}
var whereText = stringBuilder.ToString();
stringBuilder.Clear();
stringBuilder.Append("select ")
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query))
.Append(whereText)
.Append(" group by PresentationUniqueKey");
if (query.OrderBy.Count != 0
|| query.SimilarTo is not null
|| !string.IsNullOrEmpty(query.SearchTerm))
{
stringBuilder.Append(GetOrderByText(query));
}
else
{
stringBuilder.Append(" order by SortName");
}
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
var offset = query.StartIndex ?? 0;
if (query.Limit.HasValue || offset > 0)
{
stringBuilder.Append(" LIMIT ")
4 years ago
.Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
stringBuilder.Append(" OFFSET ")
4 years ago
.Append(offset);
}
}
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
string commandText = string.Empty;
if (!isReturningZeroItems)
{
commandText = stringBuilder.ToString();
}
5 years ago
string countText = string.Empty;
if (query.EnableTotalRecordCount)
{
stringBuilder.Clear();
4 years ago
var columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" };
SetFinalColumnsToSelect(query, columnsToSelect);
stringBuilder.Append("select ")
4 years ago
.AppendJoin(',', columnsToSelect)
.Append(FromText)
.Append(GetJoinUserDataText(query))
.Append(whereText);
countText = stringBuilder.ToString();
}
5 years ago
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))
8 years ago
{
if (!isReturningZeroItems)
{
using (var statement = PrepareStatement(connection, commandText))
6 years ago
{
statement.TryBind("@SelectType", returnType);
if (EnableJoinUserData(query))
{
statement.TryBind("@UserId", query.User.InternalId);
}
if (typeSubQuery is not null)
{
GetWhereClauses(typeSubQuery, null);
}
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
GetWhereClauses(innerQuery, statement);
GetWhereClauses(outerQuery, statement);
var hasEpisodeAttributes = HasEpisodeAttributes(query);
var hasProgramAttributes = HasProgramAttributes(query);
var hasServiceName = HasServiceName(query);
var hasStartDate = HasStartDate(query);
var hasTrailerTypes = HasTrailerTypes(query);
var hasArtistFields = HasArtistFields(query);
var hasSeriesFields = HasSeriesFields(query);
foreach (var row in statement.ExecuteQuery())
6 years ago
{
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
if (item is not null)
6 years ago
{
var countStartColumn = columns.Count - 1;
list.Add((item, GetItemCounts(row, countStartColumn, typesToCount)));
5 years ago
}
}
}
}
if (query.EnableTotalRecordCount)
{
using (var statement = PrepareStatement(connection, countText))
{
statement.TryBind("@SelectType", returnType);
if (EnableJoinUserData(query))
{
statement.TryBind("@UserId", query.User.InternalId);
}
if (typeSubQuery is not null)
{
GetWhereClauses(typeSubQuery, null);
}
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
GetWhereClauses(innerQuery, statement);
GetWhereClauses(outerQuery, statement);
result.TotalRecordCount = statement.SelectScalarInt();
}
}
transaction.Commit();
5 years ago
}
5 years ago
if (result.TotalRecordCount == 0)
{
result.TotalRecordCount = list.Count;
}
5 years ago
result.StartIndex = query.StartIndex ?? 0;
result.Items = list;
5 years ago
return result;
8 years ago
}
1 year ago
private static ItemCounts GetItemCounts(SqliteDataReader reader, int countStartColumn, BaseItemKind[] typesToCount)
8 years ago
{
var counts = new ItemCounts();
if (typesToCount.Length == 0)
8 years ago
{
return counts;
}
if (!reader.TryGetString(countStartColumn, out var typeString))
8 years ago
{
return counts;
}
foreach (var typeName in typeString.AsSpan().Split('|'))
8 years ago
{
if (typeName.Equals(typeof(Series).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.SeriesCount++;
8 years ago
}
else if (typeName.Equals(typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.EpisodeCount++;
8 years ago
}
else if (typeName.Equals(typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.MovieCount++;
8 years ago
}
else if (typeName.Equals(typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.AlbumCount++;
8 years ago
}
else if (typeName.Equals(typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.ArtistCount++;
8 years ago
}
else if (typeName.Equals(typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.SongCount++;
8 years ago
}
else if (typeName.Equals(typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase))
8 years ago
{
counts.TrailerCount++;
8 years ago
}
counts.ItemCount++;
8 years ago
}
return counts;
}
private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
8 years ago
{
var list = new List<(int, string)>();
8 years ago
if (item is IHasArtist hasArtist)
8 years ago
{
list.AddRange(hasArtist.Artists.Select(i => (0, i)));
8 years ago
}
if (item is IHasAlbumArtist hasAlbumArtist)
8 years ago
{
list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i)));
8 years ago
}
list.AddRange(item.Genres.Select(i => (2, i)));
list.AddRange(item.Studios.Select(i => (3, i)));
list.AddRange(item.Tags.Select(i => (4, i)));
8 years ago
// keywords was 5
list.AddRange(inheritedTags.Select(i => (6, i)));
2 years ago
// Remove all invalid values.
list.RemoveAll(i => string.IsNullOrEmpty(i.Item2));
8 years ago
return list;
}
1 year ago
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db)
8 years ago
{
if (itemId.Equals(default))
8 years ago
{
throw new ArgumentNullException(nameof(itemId));
8 years ago
}
ArgumentNullException.ThrowIfNull(values);
8 years ago
CheckDisposed();
// First delete
1 year ago
using var command = db.PrepareStatement("delete from ItemValues where ItemId=@Id");
1 year ago
command.TryBind("@Id", itemId);
1 year ago
command.ExecuteNonQuery();
8 years ago
1 year ago
InsertItemValues(itemId, values, db);
}
1 year ago
private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, SqliteConnection db)
{
const int Limit = 100;
var startIndex = 0;
const string StartInsertText = "insert into ItemValues (ItemId, Type, Value, CleanValue) values ";
var insertText = new StringBuilder(StartInsertText);
while (startIndex < values.Count)
8 years ago
{
var endIndex = Math.Min(values.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
insertText.AppendFormat(
CultureInfo.InvariantCulture,
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
i);
}
8 years ago
// Remove trailing comma
insertText.Length--;
6 years ago
using (var statement = PrepareStatement(db, insertText.ToString()))
{
1 year ago
statement.TryBind("@ItemId", id);
8 years ago
for (var i = startIndex; i < endIndex; i++)
8 years ago
{
var index = i.ToString(CultureInfo.InvariantCulture);
var currentValueInfo = values[i];
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));
8 years ago
}
statement.ExecuteNonQuery();
8 years ago
}
startIndex += Limit;
insertText.Length = StartInsertText.Length;
8 years ago
}
}
public void UpdatePeople(Guid itemId, List<PersonInfo> people)
8 years ago
{
if (itemId.Equals(default))
8 years ago
{
throw new ArgumentNullException(nameof(itemId));
8 years ago
}
ArgumentNullException.ThrowIfNull(people);
8 years ago
CheckDisposed();
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
// First delete chapters
using var command = connection.CreateCommand();
command.CommandText = "delete from People where ItemId=@ItemId";
command.TryBind("@ItemId", itemId);
command.ExecuteNonQuery();
InsertPeople(itemId, people, connection);
transaction.Commit();
}
1 year ago
private void InsertPeople(Guid id, List<PersonInfo> people, SqliteConnection db)
{
const int Limit = 100;
var startIndex = 0;
var listIndex = 0;
const string StartInsertText = "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ";
var insertText = new StringBuilder(StartInsertText);
while (startIndex < people.Count)
{
var endIndex = Math.Min(people.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
insertText.AppendFormat(
CultureInfo.InvariantCulture,
"(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),",
i.ToString(CultureInfo.InvariantCulture));
}
8 years ago
// Remove trailing comma
insertText.Length--;
6 years ago
using (var statement = PrepareStatement(db, insertText.ToString()))
{
1 year ago
statement.TryBind("@ItemId", id);
8 years ago
for (var i = startIndex; i < endIndex; i++)
8 years ago
{
var index = i.ToString(CultureInfo.InvariantCulture);
8 years ago
var person = people[i];
8 years ago
statement.TryBind("@Name" + index, person.Name);
statement.TryBind("@Role" + index, person.Role);
statement.TryBind("@PersonType" + index, person.Type.ToString());
statement.TryBind("@SortOrder" + index, person.SortOrder);
statement.TryBind("@ListOrder" + index, listIndex);
listIndex++;
8 years ago
}
statement.ExecuteNonQuery();
8 years ago
}
startIndex += Limit;
insertText.Length = StartInsertText.Length;
8 years ago
}
}
1 year ago
private PersonInfo GetPerson(SqliteDataReader reader)
8 years ago
{
var item = new PersonInfo
{
ItemId = reader.GetGuid(0),
Name = reader.GetString(1)
};
8 years ago
if (reader.TryGetString(2, out var role))
8 years ago
{
item.Role = role;
8 years ago
}
if (reader.TryGetString(3, out var type)
&& Enum.TryParse(type, true, out PersonKind personKind))
8 years ago
{
item.Type = personKind;
8 years ago
}
if (reader.TryGetInt32(4, out var sortOrder))
8 years ago
{
item.SortOrder = sortOrder;
8 years ago
}
return item;
}
public List<MediaStream> GetMediaStreams(MediaStreamQuery query)
8 years ago
{
CheckDisposed();
ArgumentNullException.ThrowIfNull(query);
8 years ago
var cmdText = _mediaStreamSaveColumnsSelectQuery;
8 years ago
if (query.Type.HasValue)
{
cmdText += " AND StreamType=@StreamType";
}
if (query.Index.HasValue)
{
cmdText += " AND StreamIndex=@StreamIndex";
}
cmdText += " order by StreamIndex ASC";
using (var connection = GetConnection())
8 years ago
{
6 years ago
var list = new List<MediaStream>();
6 years ago
using (var statement = PrepareStatement(connection, cmdText))
8 years ago
{
statement.TryBind("@ItemId", query.ItemId);
6 years ago
if (query.Type.HasValue)
8 years ago
{
6 years ago
statement.TryBind("@StreamType", query.Type.Value.ToString());
}
6 years ago
if (query.Index.HasValue)
{
statement.TryBind("@StreamIndex", query.Index.Value);
}
6 years ago
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetMediaStream(row));
}
8 years ago
}
6 years ago
return list;
8 years ago
}
}
public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)
8 years ago
{
CheckDisposed();
if (id.Equals(default))
8 years ago
{
throw new ArgumentNullException(nameof(id));
8 years ago
}
ArgumentNullException.ThrowIfNull(streams);
8 years ago
cancellationToken.ThrowIfCancellationRequested();
using var connection = GetConnection();
using var transaction = connection.BeginTransaction();
// Delete existing mediastreams
using var command = connection.PrepareStatement("delete from mediastreams where ItemId=@ItemId");
command.TryBind("@ItemId", id);
command.ExecuteNonQuery();
InsertMediaStreams(id, streams, connection);
transaction.Commit();
}
1 year ago
private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, SqliteConnection db)
{
const int Limit = 10;
var startIndex = 0;
var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
while (startIndex < streams.Count)
{
var endIndex = Math.Min(streams.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
if (i != startIndex)
{
insertText.Append(',');
}
var index = i.ToString(CultureInfo.InvariantCulture);
insertText.Append("(@ItemId, ");
foreach (var column in _mediaStreamSaveColumns.Skip(1))
{
insertText.Append('@').Append(column).Append(index).Append(',');
}
insertText.Length -= 1; // Remove the last comma
insertText.Append(')');
}
6 years ago
using (var statement = PrepareStatement(db, insertText.ToString()))
{
1 year ago
statement.TryBind("@ItemId", id);
8 years ago
for (var i = startIndex; i < endIndex; i++)
8 years ago
{
var index = i.ToString(CultureInfo.InvariantCulture);
var stream = streams[i];
statement.TryBind("@StreamIndex" + index, stream.Index);
statement.TryBind("@StreamType" + index, stream.Type.ToString());
statement.TryBind("@Codec" + index, stream.Codec);
statement.TryBind("@Language" + index, stream.Language);
statement.TryBind("@ChannelLayout" + index, stream.ChannelLayout);
statement.TryBind("@Profile" + index, stream.Profile);
statement.TryBind("@AspectRatio" + index, stream.AspectRatio);
statement.TryBind("@Path" + index, GetPathToSave(stream.Path));
statement.TryBind("@IsInterlaced" + index, stream.IsInterlaced);
statement.TryBind("@BitRate" + index, stream.BitRate);
statement.TryBind("@Channels" + index, stream.Channels);
statement.TryBind("@SampleRate" + index, stream.SampleRate);
statement.TryBind("@IsDefault" + index, stream.IsDefault);
statement.TryBind("@IsForced" + index, stream.IsForced);
statement.TryBind("@IsExternal" + index, stream.IsExternal);
// Yes these are backwards due to a mistake
statement.TryBind("@Width" + index, stream.Height);
statement.TryBind("@Height" + index, stream.Width);
statement.TryBind("@AverageFrameRate" + index, stream.AverageFrameRate);
statement.TryBind("@RealFrameRate" + index, stream.RealFrameRate);
statement.TryBind("@Level" + index, stream.Level);
statement.TryBind("@PixelFormat" + index, stream.PixelFormat);
statement.TryBind("@BitDepth" + index, stream.BitDepth);
1 year ago
statement.TryBind("@IsAnamorphic" + index, stream.IsAnamorphic);
statement.TryBind("@IsExternal" + index, stream.IsExternal);
statement.TryBind("@RefFrames" + index, stream.RefFrames);
statement.TryBind("@CodecTag" + index, stream.CodecTag);
statement.TryBind("@Comment" + index, stream.Comment);
statement.TryBind("@NalLengthSize" + index, stream.NalLengthSize);
statement.TryBind("@IsAvc" + index, stream.IsAVC);
statement.TryBind("@Title" + index, stream.Title);
statement.TryBind("@TimeBase" + index, stream.TimeBase);
statement.TryBind("@CodecTimeBase" + index, stream.CodecTimeBase);
statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries);
statement.TryBind("@ColorSpace" + index, stream.ColorSpace);
statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer);
statement.TryBind("@DvVersionMajor" + index, stream.DvVersionMajor);
statement.TryBind("@DvVersionMinor" + index, stream.DvVersionMinor);
statement.TryBind("@DvProfile" + index, stream.DvProfile);
statement.TryBind("@DvLevel" + index, stream.DvLevel);
statement.TryBind("@RpuPresentFlag" + index, stream.RpuPresentFlag);
statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag);
statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag);
statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId);
statement.TryBind("@IsHearingImpaired" + index, stream.IsHearingImpaired);
8 years ago
}
statement.ExecuteNonQuery();
8 years ago
}
startIndex += Limit;
insertText.Length = _mediaStreamSaveColumnsInsertQuery.Length;
8 years ago
}
}
/// <summary>
/// Gets the media stream.
8 years ago
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>MediaStream.</returns>
1 year ago
private MediaStream GetMediaStream(SqliteDataReader reader)
8 years ago
{
var item = new MediaStream
{
1 year ago
Index = reader.GetInt32(1),
Type = Enum.Parse<MediaStreamType>(reader.GetString(2), true)
8 years ago
};
if (reader.TryGetString(3, out var codec))
8 years ago
{
item.Codec = codec;
8 years ago
}
if (reader.TryGetString(4, out var language))
8 years ago
{
item.Language = language;
8 years ago
}
if (reader.TryGetString(5, out var channelLayout))
8 years ago
{
item.ChannelLayout = channelLayout;
8 years ago
}
if (reader.TryGetString(6, out var profile))
8 years ago
{
item.Profile = profile;
8 years ago
}
if (reader.TryGetString(7, out var aspectRatio))
8 years ago
{
item.AspectRatio = aspectRatio;
8 years ago
}
if (reader.TryGetString(8, out var path))
8 years ago
{
item.Path = RestorePath(path);
8 years ago
}
item.IsInterlaced = reader.GetBoolean(9);
if (reader.TryGetInt32(10, out var bitrate))
8 years ago
{
item.BitRate = bitrate;
8 years ago
}
if (reader.TryGetInt32(11, out var channels))
8 years ago
{
item.Channels = channels;
8 years ago
}
if (reader.TryGetInt32(12, out var sampleRate))
8 years ago
{
item.SampleRate = sampleRate;
8 years ago
}
item.IsDefault = reader.GetBoolean(13);
item.IsForced = reader.GetBoolean(14);
item.IsExternal = reader.GetBoolean(15);
if (reader.TryGetInt32(16, out var width))
8 years ago
{
item.Width = width;
8 years ago
}
if (reader.TryGetInt32(17, out var height))
8 years ago
{
item.Height = height;
8 years ago
}
if (reader.TryGetSingle(18, out var averageFrameRate))
8 years ago
{
item.AverageFrameRate = averageFrameRate;
8 years ago
}
if (reader.TryGetSingle(19, out var realFrameRate))
8 years ago
{
item.RealFrameRate = realFrameRate;
8 years ago
}
if (reader.TryGetSingle(20, out var level))
8 years ago
{
item.Level = level;
8 years ago
}
if (reader.TryGetString(21, out var pixelFormat))
8 years ago
{
item.PixelFormat = pixelFormat;
8 years ago
}
if (reader.TryGetInt32(22, out var bitDepth))
8 years ago
{
item.BitDepth = bitDepth;
8 years ago
}
if (reader.TryGetBoolean(23, out var isAnamorphic))
8 years ago
{
item.IsAnamorphic = isAnamorphic;
8 years ago
}
if (reader.TryGetInt32(24, out var refFrames))
8 years ago
{
item.RefFrames = refFrames;
8 years ago
}
if (reader.TryGetString(25, out var codecTag))
8 years ago
{
item.CodecTag = codecTag;
8 years ago
}
if (reader.TryGetString(26, out var comment))
8 years ago
{
item.Comment = comment;
8 years ago
}
if (reader.TryGetString(27, out var nalLengthSize))
8 years ago
{
item.NalLengthSize = nalLengthSize;
8 years ago
}
if (reader.TryGetBoolean(28, out var isAVC))
8 years ago
{
item.IsAVC = isAVC;
8 years ago
}
if (reader.TryGetString(29, out var title))
8 years ago
{
item.Title = title;
8 years ago
}
if (reader.TryGetString(30, out var timeBase))
8 years ago
{
item.TimeBase = timeBase;
8 years ago
}
if (reader.TryGetString(31, out var codecTimeBase))
8 years ago
{
item.CodecTimeBase = codecTimeBase;
8 years ago
}
if (reader.TryGetString(32, out var colorPrimaries))
{
item.ColorPrimaries = colorPrimaries;
}
if (reader.TryGetString(33, out var colorSpace))
{
item.ColorSpace = colorSpace;
}
if (reader.TryGetString(34, out var colorTransfer))
{
item.ColorTransfer = colorTransfer;
}
if (reader.TryGetInt32(35, out var dvVersionMajor))
{
item.DvVersionMajor = dvVersionMajor;
}
if (reader.TryGetInt32(36, out var dvVersionMinor))
{
item.DvVersionMinor = dvVersionMinor;
}
if (reader.TryGetInt32(37, out var dvProfile))
{
item.DvProfile = dvProfile;
}
if (reader.TryGetInt32(38, out var dvLevel))
{
item.DvLevel = dvLevel;
}
if (reader.TryGetInt32(39, out var rpuPresentFlag))
{
item.RpuPresentFlag = rpuPresentFlag;
}
if (reader.TryGetInt32(40, out var elPresentFlag))
{
item.ElPresentFlag = elPresentFlag;
}
if (reader.TryGetInt32(41, out var blPresentFlag))
{
item.BlPresentFlag = blPresentFlag;
}
if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId))
{
item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId;
}
item.IsHearingImpaired = reader.TryGetBoolean(43, out var result) && result;
if (item.Type == 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");
}
8 years ago
return item;
}
public List<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
{
CheckDisposed();
ArgumentNullException.ThrowIfNull(query);
var cmdText = _mediaAttachmentSaveColumnsSelectQuery;
if (query.Index.HasValue)
{
cmdText += " AND AttachmentIndex=@AttachmentIndex";
}
cmdText += " order by AttachmentIndex ASC";
var list = new List<MediaAttachment>();
using (var connection = GetConnection())
using (var statement = PrepareStatement(connection, cmdText))
{
statement.TryBind("@ItemId", query.ItemId);
if (query.Index.HasValue)
{
statement.TryBind("@AttachmentIndex", query.Index.Value);
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetMediaAttachment(row));
}
}
return list;
}
public void SaveMediaAttachments(
Guid id,
IReadOnlyList<MediaAttachment> attachments,
CancellationToken cancellationToken)
{
CheckDisposed();
if (id.Equals(default))
{
throw new ArgumentException("Guid can't be empty.", nameof(id));
}
ArgumentNullException.ThrowIfNull(attachments);
cancellationToken.ThrowIfCancellationRequested();
using (var connection = GetConnection())
using (var transaction = connection.BeginTransaction())
using (var command = connection.PrepareStatement("delete from mediaattachments where ItemId=@ItemId"))
{
command.TryBind("@ItemId", id);
command.ExecuteNonQuery();
InsertMediaAttachments(id, attachments, connection, cancellationToken);
transaction.Commit();
}
}
private void InsertMediaAttachments(
1 year ago
Guid id,
IReadOnlyList<MediaAttachment> attachments,
1 year ago
SqliteConnection db,
CancellationToken cancellationToken)
{
const int InsertAtOnce = 10;
var insertText = new StringBuilder(_mediaAttachmentInsertPrefix);
for (var startIndex = 0; startIndex < attachments.Count; startIndex += InsertAtOnce)
{
var endIndex = Math.Min(attachments.Count, startIndex + InsertAtOnce);
for (var i = startIndex; i < endIndex; i++)
{
insertText.Append("(@ItemId, ");
foreach (var column in _mediaAttachmentSaveColumns.Skip(1))
{
insertText.Append('@')
.Append(column)
.Append(i)
.Append(',');
}
insertText.Length -= 1;
insertText.Append("),");
}
insertText.Length--;
cancellationToken.ThrowIfCancellationRequested();
using (var statement = PrepareStatement(db, insertText.ToString()))
{
1 year ago
statement.TryBind("@ItemId", id);
for (var i = startIndex; i < endIndex; i++)
{
var index = i.ToString(CultureInfo.InvariantCulture);
var attachment = attachments[i];
statement.TryBind("@AttachmentIndex" + index, attachment.Index);
statement.TryBind("@Codec" + index, attachment.Codec);
statement.TryBind("@CodecTag" + index, attachment.CodecTag);
statement.TryBind("@Comment" + index, attachment.Comment);
statement.TryBind("@Filename" + index, attachment.FileName);
statement.TryBind("@MIMEType" + index, attachment.MimeType);
}
statement.ExecuteNonQuery();
}
insertText.Length = _mediaAttachmentInsertPrefix.Length;
}
}
/// <summary>
/// Gets the attachment.
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>MediaAttachment.</returns>
1 year ago
private MediaAttachment GetMediaAttachment(SqliteDataReader reader)
{
var item = new MediaAttachment
{
1 year ago
Index = reader.GetInt32(1)
};
if (reader.TryGetString(2, out var codec))
{
item.Codec = codec;
}
if (reader.TryGetString(3, out var codecTag))
{
item.CodecTag = codecTag;
}
if (reader.TryGetString(4, out var comment))
{
item.Comment = comment;
}
if (reader.TryGetString(5, out var fileName))
{
item.FileName = fileName;
}
if (reader.TryGetString(6, out var mimeType))
{
item.MimeType = mimeType;
}
return item;
}
#nullable enable
private readonly struct QueryTimeLogger : IDisposable
{
private readonly ILogger _logger;
private readonly string _commandText;
private readonly string _methodName;
private readonly long _startTimestamp;
public QueryTimeLogger(ILogger logger, string commandText, [CallerMemberName] string methodName = "")
{
_logger = logger;
_commandText = commandText;
_methodName = methodName;
_startTimestamp = logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : -1;
}
public void Dispose()
{
if (_startTimestamp == -1)
{
return;
}
var elapsedMs = Stopwatch.GetElapsedTime(_startTimestamp).TotalMilliseconds;
#if DEBUG
const int SlowThreshold = 100;
#else
const int SlowThreshold = 10;
#endif
if (elapsedMs >= SlowThreshold)
{
_logger.LogDebug(
"{Method} query time (slow): {ElapsedMs}ms. Query: {Query}",
_methodName,
elapsedMs,
_commandText);
}
}
}
8 years ago
}
6 years ago
}