| | 1 | | #nullable disable |
| | 2 | |
|
| | 3 | | #pragma warning disable CS1591 |
| | 4 | |
|
| | 5 | | using System; |
| | 6 | | using System.Collections.Generic; |
| | 7 | | using System.Diagnostics; |
| | 8 | | using System.Globalization; |
| | 9 | | using System.IO; |
| | 10 | | using System.Linq; |
| | 11 | | using System.Runtime.CompilerServices; |
| | 12 | | using System.Text; |
| | 13 | | using System.Text.Json; |
| | 14 | | using System.Threading; |
| | 15 | | using Emby.Server.Implementations.Playlists; |
| | 16 | | using Jellyfin.Data.Enums; |
| | 17 | | using Jellyfin.Extensions; |
| | 18 | | using Jellyfin.Extensions.Json; |
| | 19 | | using MediaBrowser.Controller; |
| | 20 | | using MediaBrowser.Controller.Channels; |
| | 21 | | using MediaBrowser.Controller.Configuration; |
| | 22 | | using MediaBrowser.Controller.Drawing; |
| | 23 | | using MediaBrowser.Controller.Entities; |
| | 24 | | using MediaBrowser.Controller.Entities.Audio; |
| | 25 | | using MediaBrowser.Controller.Entities.Movies; |
| | 26 | | using MediaBrowser.Controller.Entities.TV; |
| | 27 | | using MediaBrowser.Controller.Extensions; |
| | 28 | | using MediaBrowser.Controller.LiveTv; |
| | 29 | | using MediaBrowser.Controller.Persistence; |
| | 30 | | using MediaBrowser.Controller.Playlists; |
| | 31 | | using MediaBrowser.Model.Dto; |
| | 32 | | using MediaBrowser.Model.Entities; |
| | 33 | | using MediaBrowser.Model.Globalization; |
| | 34 | | using MediaBrowser.Model.LiveTv; |
| | 35 | | using MediaBrowser.Model.Querying; |
| | 36 | | using Microsoft.Data.Sqlite; |
| | 37 | | using Microsoft.Extensions.Configuration; |
| | 38 | | using Microsoft.Extensions.Logging; |
| | 39 | |
|
| | 40 | | namespace Emby.Server.Implementations.Data |
| | 41 | | { |
| | 42 | | /// <summary> |
| | 43 | | /// Class SQLiteItemRepository. |
| | 44 | | /// </summary> |
| | 45 | | public class SqliteItemRepository : BaseSqliteRepository, IItemRepository |
| | 46 | | { |
| | 47 | | private const string FromText = " from TypedBaseItems A"; |
| | 48 | | private const string ChaptersTableName = "Chapters2"; |
| | 49 | |
|
| | 50 | | private const string SaveItemCommandText = |
| | 51 | | @"replace into TypedBaseItems |
| | 52 | | (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,Cust |
| | 53 | | values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@C |
| | 54 | |
|
| | 55 | | private readonly IServerConfigurationManager _config; |
| | 56 | | private readonly IServerApplicationHost _appHost; |
| | 57 | | private readonly ILocalizationManager _localization; |
| | 58 | | // TODO: Remove this dependency. GetImageCacheTag() is the only method used and it can be converted to a static |
| | 59 | | private readonly IImageProcessor _imageProcessor; |
| | 60 | |
|
| | 61 | | private readonly TypeMapper _typeMapper; |
| | 62 | | private readonly JsonSerializerOptions _jsonOptions; |
| | 63 | |
|
| 46 | 64 | | private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>(); |
| | 65 | |
|
| 1 | 66 | | private static readonly string[] _retrieveItemColumns = |
| 1 | 67 | | { |
| 1 | 68 | | "type", |
| 1 | 69 | | "data", |
| 1 | 70 | | "StartDate", |
| 1 | 71 | | "EndDate", |
| 1 | 72 | | "ChannelId", |
| 1 | 73 | | "IsMovie", |
| 1 | 74 | | "IsSeries", |
| 1 | 75 | | "EpisodeTitle", |
| 1 | 76 | | "IsRepeat", |
| 1 | 77 | | "CommunityRating", |
| 1 | 78 | | "CustomRating", |
| 1 | 79 | | "IndexNumber", |
| 1 | 80 | | "IsLocked", |
| 1 | 81 | | "PreferredMetadataLanguage", |
| 1 | 82 | | "PreferredMetadataCountryCode", |
| 1 | 83 | | "Width", |
| 1 | 84 | | "Height", |
| 1 | 85 | | "DateLastRefreshed", |
| 1 | 86 | | "Name", |
| 1 | 87 | | "Path", |
| 1 | 88 | | "PremiereDate", |
| 1 | 89 | | "Overview", |
| 1 | 90 | | "ParentIndexNumber", |
| 1 | 91 | | "ProductionYear", |
| 1 | 92 | | "OfficialRating", |
| 1 | 93 | | "ForcedSortName", |
| 1 | 94 | | "RunTimeTicks", |
| 1 | 95 | | "Size", |
| 1 | 96 | | "DateCreated", |
| 1 | 97 | | "DateModified", |
| 1 | 98 | | "guid", |
| 1 | 99 | | "Genres", |
| 1 | 100 | | "ParentId", |
| 1 | 101 | | "Audio", |
| 1 | 102 | | "ExternalServiceId", |
| 1 | 103 | | "IsInMixedFolder", |
| 1 | 104 | | "DateLastSaved", |
| 1 | 105 | | "LockedFields", |
| 1 | 106 | | "Studios", |
| 1 | 107 | | "Tags", |
| 1 | 108 | | "TrailerTypes", |
| 1 | 109 | | "OriginalTitle", |
| 1 | 110 | | "PrimaryVersionId", |
| 1 | 111 | | "DateLastMediaAdded", |
| 1 | 112 | | "Album", |
| 1 | 113 | | "LUFS", |
| 1 | 114 | | "NormalizationGain", |
| 1 | 115 | | "CriticRating", |
| 1 | 116 | | "IsVirtualItem", |
| 1 | 117 | | "SeriesName", |
| 1 | 118 | | "SeasonName", |
| 1 | 119 | | "SeasonId", |
| 1 | 120 | | "SeriesId", |
| 1 | 121 | | "PresentationUniqueKey", |
| 1 | 122 | | "InheritedParentalRatingValue", |
| 1 | 123 | | "ExternalSeriesId", |
| 1 | 124 | | "Tagline", |
| 1 | 125 | | "ProviderIds", |
| 1 | 126 | | "Images", |
| 1 | 127 | | "ProductionLocations", |
| 1 | 128 | | "ExtraIds", |
| 1 | 129 | | "TotalBitrate", |
| 1 | 130 | | "ExtraType", |
| 1 | 131 | | "Artists", |
| 1 | 132 | | "AlbumArtists", |
| 1 | 133 | | "ExternalId", |
| 1 | 134 | | "SeriesPresentationUniqueKey", |
| 1 | 135 | | "ShowId", |
| 1 | 136 | | "OwnerId" |
| 1 | 137 | | }; |
| | 138 | |
|
| 1 | 139 | | private static readonly string _retrieveItemColumnsSelectQuery = $"select {string.Join(',', _retrieveItemColumns |
| | 140 | |
|
| 1 | 141 | | private static readonly string[] _mediaStreamSaveColumns = |
| 1 | 142 | | { |
| 1 | 143 | | "ItemId", |
| 1 | 144 | | "StreamIndex", |
| 1 | 145 | | "StreamType", |
| 1 | 146 | | "Codec", |
| 1 | 147 | | "Language", |
| 1 | 148 | | "ChannelLayout", |
| 1 | 149 | | "Profile", |
| 1 | 150 | | "AspectRatio", |
| 1 | 151 | | "Path", |
| 1 | 152 | | "IsInterlaced", |
| 1 | 153 | | "BitRate", |
| 1 | 154 | | "Channels", |
| 1 | 155 | | "SampleRate", |
| 1 | 156 | | "IsDefault", |
| 1 | 157 | | "IsForced", |
| 1 | 158 | | "IsExternal", |
| 1 | 159 | | "Height", |
| 1 | 160 | | "Width", |
| 1 | 161 | | "AverageFrameRate", |
| 1 | 162 | | "RealFrameRate", |
| 1 | 163 | | "Level", |
| 1 | 164 | | "PixelFormat", |
| 1 | 165 | | "BitDepth", |
| 1 | 166 | | "IsAnamorphic", |
| 1 | 167 | | "RefFrames", |
| 1 | 168 | | "CodecTag", |
| 1 | 169 | | "Comment", |
| 1 | 170 | | "NalLengthSize", |
| 1 | 171 | | "IsAvc", |
| 1 | 172 | | "Title", |
| 1 | 173 | | "TimeBase", |
| 1 | 174 | | "CodecTimeBase", |
| 1 | 175 | | "ColorPrimaries", |
| 1 | 176 | | "ColorSpace", |
| 1 | 177 | | "ColorTransfer", |
| 1 | 178 | | "DvVersionMajor", |
| 1 | 179 | | "DvVersionMinor", |
| 1 | 180 | | "DvProfile", |
| 1 | 181 | | "DvLevel", |
| 1 | 182 | | "RpuPresentFlag", |
| 1 | 183 | | "ElPresentFlag", |
| 1 | 184 | | "BlPresentFlag", |
| 1 | 185 | | "DvBlSignalCompatibilityId", |
| 1 | 186 | | "IsHearingImpaired", |
| 1 | 187 | | "Rotation" |
| 1 | 188 | | }; |
| | 189 | |
|
| 1 | 190 | | private static readonly string _mediaStreamSaveColumnsInsertQuery = |
| 1 | 191 | | $"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values "; |
| | 192 | |
|
| 1 | 193 | | private static readonly string _mediaStreamSaveColumnsSelectQuery = |
| 1 | 194 | | $"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId"; |
| | 195 | |
|
| 1 | 196 | | private static readonly string[] _mediaAttachmentSaveColumns = |
| 1 | 197 | | { |
| 1 | 198 | | "ItemId", |
| 1 | 199 | | "AttachmentIndex", |
| 1 | 200 | | "Codec", |
| 1 | 201 | | "CodecTag", |
| 1 | 202 | | "Comment", |
| 1 | 203 | | "Filename", |
| 1 | 204 | | "MIMEType" |
| 1 | 205 | | }; |
| | 206 | |
|
| 1 | 207 | | private static readonly string _mediaAttachmentSaveColumnsSelectQuery = |
| 1 | 208 | | $"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId"; |
| | 209 | |
|
| 1 | 210 | | private static readonly string _mediaAttachmentInsertPrefix = BuildMediaAttachmentInsertPrefix(); |
| | 211 | |
|
| 1 | 212 | | private static readonly BaseItemKind[] _programTypes = new[] |
| 1 | 213 | | { |
| 1 | 214 | | BaseItemKind.Program, |
| 1 | 215 | | BaseItemKind.TvChannel, |
| 1 | 216 | | BaseItemKind.LiveTvProgram, |
| 1 | 217 | | BaseItemKind.LiveTvChannel |
| 1 | 218 | | }; |
| | 219 | |
|
| 1 | 220 | | private static readonly BaseItemKind[] _programExcludeParentTypes = new[] |
| 1 | 221 | | { |
| 1 | 222 | | BaseItemKind.Series, |
| 1 | 223 | | BaseItemKind.Season, |
| 1 | 224 | | BaseItemKind.MusicAlbum, |
| 1 | 225 | | BaseItemKind.MusicArtist, |
| 1 | 226 | | BaseItemKind.PhotoAlbum |
| 1 | 227 | | }; |
| | 228 | |
|
| 1 | 229 | | private static readonly BaseItemKind[] _serviceTypes = new[] |
| 1 | 230 | | { |
| 1 | 231 | | BaseItemKind.TvChannel, |
| 1 | 232 | | BaseItemKind.LiveTvChannel |
| 1 | 233 | | }; |
| | 234 | |
|
| 1 | 235 | | private static readonly BaseItemKind[] _startDateTypes = new[] |
| 1 | 236 | | { |
| 1 | 237 | | BaseItemKind.Program, |
| 1 | 238 | | BaseItemKind.LiveTvProgram |
| 1 | 239 | | }; |
| | 240 | |
|
| 1 | 241 | | private static readonly BaseItemKind[] _seriesTypes = new[] |
| 1 | 242 | | { |
| 1 | 243 | | BaseItemKind.Book, |
| 1 | 244 | | BaseItemKind.AudioBook, |
| 1 | 245 | | BaseItemKind.Episode, |
| 1 | 246 | | BaseItemKind.Season |
| 1 | 247 | | }; |
| | 248 | |
|
| 1 | 249 | | private static readonly BaseItemKind[] _artistExcludeParentTypes = new[] |
| 1 | 250 | | { |
| 1 | 251 | | BaseItemKind.Series, |
| 1 | 252 | | BaseItemKind.Season, |
| 1 | 253 | | BaseItemKind.PhotoAlbum |
| 1 | 254 | | }; |
| | 255 | |
|
| 1 | 256 | | private static readonly BaseItemKind[] _artistsTypes = new[] |
| 1 | 257 | | { |
| 1 | 258 | | BaseItemKind.Audio, |
| 1 | 259 | | BaseItemKind.MusicAlbum, |
| 1 | 260 | | BaseItemKind.MusicVideo, |
| 1 | 261 | | BaseItemKind.AudioBook |
| 1 | 262 | | }; |
| | 263 | |
|
| 1 | 264 | | private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new() |
| 1 | 265 | | { |
| 1 | 266 | | { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName }, |
| 1 | 267 | | { BaseItemKind.Audio, typeof(Audio).FullName }, |
| 1 | 268 | | { BaseItemKind.AudioBook, typeof(AudioBook).FullName }, |
| 1 | 269 | | { BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName }, |
| 1 | 270 | | { BaseItemKind.Book, typeof(Book).FullName }, |
| 1 | 271 | | { BaseItemKind.BoxSet, typeof(BoxSet).FullName }, |
| 1 | 272 | | { BaseItemKind.Channel, typeof(Channel).FullName }, |
| 1 | 273 | | { BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName }, |
| 1 | 274 | | { BaseItemKind.Episode, typeof(Episode).FullName }, |
| 1 | 275 | | { BaseItemKind.Folder, typeof(Folder).FullName }, |
| 1 | 276 | | { BaseItemKind.Genre, typeof(Genre).FullName }, |
| 1 | 277 | | { BaseItemKind.Movie, typeof(Movie).FullName }, |
| 1 | 278 | | { BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName }, |
| 1 | 279 | | { BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName }, |
| 1 | 280 | | { BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName }, |
| 1 | 281 | | { BaseItemKind.MusicArtist, typeof(MusicArtist).FullName }, |
| 1 | 282 | | { BaseItemKind.MusicGenre, typeof(MusicGenre).FullName }, |
| 1 | 283 | | { BaseItemKind.MusicVideo, typeof(MusicVideo).FullName }, |
| 1 | 284 | | { BaseItemKind.Person, typeof(Person).FullName }, |
| 1 | 285 | | { BaseItemKind.Photo, typeof(Photo).FullName }, |
| 1 | 286 | | { BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName }, |
| 1 | 287 | | { BaseItemKind.Playlist, typeof(Playlist).FullName }, |
| 1 | 288 | | { BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName }, |
| 1 | 289 | | { BaseItemKind.Season, typeof(Season).FullName }, |
| 1 | 290 | | { BaseItemKind.Series, typeof(Series).FullName }, |
| 1 | 291 | | { BaseItemKind.Studio, typeof(Studio).FullName }, |
| 1 | 292 | | { BaseItemKind.Trailer, typeof(Trailer).FullName }, |
| 1 | 293 | | { BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName }, |
| 1 | 294 | | { BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName }, |
| 1 | 295 | | { BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName }, |
| 1 | 296 | | { BaseItemKind.UserView, typeof(UserView).FullName }, |
| 1 | 297 | | { BaseItemKind.Video, typeof(Video).FullName }, |
| 1 | 298 | | { BaseItemKind.Year, typeof(Year).FullName } |
| 1 | 299 | | }; |
| | 300 | |
|
| | 301 | | /// <summary> |
| | 302 | | /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. |
| | 303 | | /// </summary> |
| | 304 | | /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> |
| | 305 | | /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param> |
| | 306 | | /// <param name="logger">Instance of the <see cref="ILogger{SqliteItemRepository}"/> interface.</param> |
| | 307 | | /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> |
| | 308 | | /// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param> |
| | 309 | | /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param> |
| | 310 | | /// <exception cref="ArgumentNullException">config is null.</exception> |
| | 311 | | public SqliteItemRepository( |
| | 312 | | IServerConfigurationManager config, |
| | 313 | | IServerApplicationHost appHost, |
| | 314 | | ILogger<SqliteItemRepository> logger, |
| | 315 | | ILocalizationManager localization, |
| | 316 | | IImageProcessor imageProcessor, |
| | 317 | | IConfiguration configuration) |
| 46 | 318 | | : base(logger) |
| | 319 | | { |
| 46 | 320 | | _config = config; |
| 46 | 321 | | _appHost = appHost; |
| 46 | 322 | | _localization = localization; |
| 46 | 323 | | _imageProcessor = imageProcessor; |
| | 324 | |
|
| 46 | 325 | | _typeMapper = new TypeMapper(); |
| 46 | 326 | | _jsonOptions = JsonDefaults.Options; |
| | 327 | |
|
| 46 | 328 | | DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); |
| | 329 | |
|
| 46 | 330 | | CacheSize = configuration.GetSqliteCacheSize(); |
| 46 | 331 | | } |
| | 332 | |
|
| | 333 | | /// <inheritdoc /> |
| | 334 | | protected override int? CacheSize { get; } |
| | 335 | |
|
| | 336 | | /// <inheritdoc /> |
| 674 | 337 | | protected override TempStoreMode TempStore => TempStoreMode.Memory; |
| | 338 | |
|
| | 339 | | /// <summary> |
| | 340 | | /// Opens the connection to the database. |
| | 341 | | /// </summary> |
| | 342 | | public override void Initialize() |
| | 343 | | { |
| 22 | 344 | | base.Initialize(); |
| | 345 | |
|
| | 346 | | const string CreateMediaStreamsTableCommand |
| | 347 | | = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEX |
| | 348 | |
|
| | 349 | | const string CreateMediaAttachmentsTableCommand |
| | 350 | | = "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecT |
| | 351 | |
|
| 22 | 352 | | string[] queries = |
| 22 | 353 | | { |
| 22 | 354 | | "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLO |
| 22 | 355 | |
|
| 22 | 356 | | "create table if not exists AncestorIds (ItemId GUID NOT NULL, AncestorId GUID NOT NULL, AncestorIdText |
| 22 | 357 | | "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", |
| 22 | 358 | | "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", |
| 22 | 359 | |
|
| 22 | 360 | | "create table if not exists ItemValues (ItemId GUID NOT NULL, Type INT NOT NULL, Value TEXT NOT NULL, Cl |
| 22 | 361 | |
|
| 22 | 362 | | "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrd |
| 22 | 363 | |
|
| 22 | 364 | | "drop index if exists idxPeopleItemId", |
| 22 | 365 | | "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", |
| 22 | 366 | | "create index if not exists idxPeopleName on People(Name)", |
| 22 | 367 | |
|
| 22 | 368 | | "create table if not exists " + ChaptersTableName + " (ItemId GUID, ChapterIndex INT NOT NULL, StartPosi |
| 22 | 369 | |
|
| 22 | 370 | | CreateMediaStreamsTableCommand, |
| 22 | 371 | | CreateMediaAttachmentsTableCommand, |
| 22 | 372 | |
|
| 22 | 373 | | "pragma shrink_memory" |
| 22 | 374 | | }; |
| | 375 | |
|
| 22 | 376 | | string[] postQueries = |
| 22 | 377 | | { |
| 22 | 378 | | "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", |
| 22 | 379 | | "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", |
| 22 | 380 | |
|
| 22 | 381 | | "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", |
| 22 | 382 | | "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVir |
| 22 | 383 | | "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", |
| 22 | 384 | |
|
| 22 | 385 | | // covering index |
| 22 | 386 | | "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", |
| 22 | 387 | |
|
| 22 | 388 | | // series |
| 22 | 389 | | "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentati |
| 22 | 390 | |
|
| 22 | 391 | | // series counts |
| 22 | 392 | | // seriesdateplayed sort order |
| 22 | 393 | | "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUni |
| 22 | 394 | |
|
| 22 | 395 | | // live tv programs |
| 22 | 396 | | "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", |
| 22 | 397 | |
|
| 22 | 398 | | // covering index for getitemvalues |
| 22 | 399 | | "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", |
| 22 | 400 | |
|
| 22 | 401 | | // used by movie suggestions |
| 22 | 402 | | "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniq |
| 22 | 403 | | "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", |
| 22 | 404 | |
|
| 22 | 405 | | // latest items |
| 22 | 406 | | "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,Presen |
| 22 | 407 | | "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,Pr |
| 22 | 408 | |
|
| 22 | 409 | | // resume |
| 22 | 410 | | "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,P |
| 22 | 411 | |
|
| 22 | 412 | | // items by name |
| 22 | 413 | | "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", |
| 22 | 414 | | "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", |
| 22 | 415 | |
|
| 22 | 416 | | // Used to update inherited tags |
| 22 | 417 | | "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", |
| 22 | 418 | |
|
| 22 | 419 | | "CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type)", |
| 22 | 420 | | "CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder)" |
| 22 | 421 | | }; |
| | 422 | |
|
| 22 | 423 | | using (var connection = GetConnection()) |
| 22 | 424 | | using (var transaction = connection.BeginTransaction()) |
| | 425 | | { |
| 22 | 426 | | connection.Execute(string.Join(';', queries)); |
| | 427 | |
|
| 22 | 428 | | var existingColumnNames = GetColumnNames(connection, "AncestorIds"); |
| 22 | 429 | | AddColumn(connection, "AncestorIds", "AncestorIdText", "Text", existingColumnNames); |
| | 430 | |
|
| 22 | 431 | | existingColumnNames = GetColumnNames(connection, "TypedBaseItems"); |
| | 432 | |
|
| 22 | 433 | | AddColumn(connection, "TypedBaseItems", "Path", "Text", existingColumnNames); |
| 22 | 434 | | AddColumn(connection, "TypedBaseItems", "StartDate", "DATETIME", existingColumnNames); |
| 22 | 435 | | AddColumn(connection, "TypedBaseItems", "EndDate", "DATETIME", existingColumnNames); |
| 22 | 436 | | AddColumn(connection, "TypedBaseItems", "ChannelId", "Text", existingColumnNames); |
| 22 | 437 | | AddColumn(connection, "TypedBaseItems", "IsMovie", "BIT", existingColumnNames); |
| 22 | 438 | | AddColumn(connection, "TypedBaseItems", "CommunityRating", "Float", existingColumnNames); |
| 22 | 439 | | AddColumn(connection, "TypedBaseItems", "CustomRating", "Text", existingColumnNames); |
| 22 | 440 | | AddColumn(connection, "TypedBaseItems", "IndexNumber", "INT", existingColumnNames); |
| 22 | 441 | | AddColumn(connection, "TypedBaseItems", "IsLocked", "BIT", existingColumnNames); |
| 22 | 442 | | AddColumn(connection, "TypedBaseItems", "Name", "Text", existingColumnNames); |
| 22 | 443 | | AddColumn(connection, "TypedBaseItems", "OfficialRating", "Text", existingColumnNames); |
| 22 | 444 | | AddColumn(connection, "TypedBaseItems", "MediaType", "Text", existingColumnNames); |
| 22 | 445 | | AddColumn(connection, "TypedBaseItems", "Overview", "Text", existingColumnNames); |
| 22 | 446 | | AddColumn(connection, "TypedBaseItems", "ParentIndexNumber", "INT", existingColumnNames); |
| 22 | 447 | | AddColumn(connection, "TypedBaseItems", "PremiereDate", "DATETIME", existingColumnNames); |
| 22 | 448 | | AddColumn(connection, "TypedBaseItems", "ProductionYear", "INT", existingColumnNames); |
| 22 | 449 | | AddColumn(connection, "TypedBaseItems", "ParentId", "GUID", existingColumnNames); |
| 22 | 450 | | AddColumn(connection, "TypedBaseItems", "Genres", "Text", existingColumnNames); |
| 22 | 451 | | AddColumn(connection, "TypedBaseItems", "SortName", "Text", existingColumnNames); |
| 22 | 452 | | AddColumn(connection, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames); |
| 22 | 453 | | AddColumn(connection, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames); |
| 22 | 454 | | AddColumn(connection, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames); |
| 22 | 455 | | AddColumn(connection, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames); |
| 22 | 456 | | AddColumn(connection, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames); |
| 22 | 457 | | AddColumn(connection, "TypedBaseItems", "EpisodeTitle", "Text", existingColumnNames); |
| 22 | 458 | | AddColumn(connection, "TypedBaseItems", "IsRepeat", "BIT", existingColumnNames); |
| 22 | 459 | | AddColumn(connection, "TypedBaseItems", "PreferredMetadataLanguage", "Text", existingColumnNames); |
| 22 | 460 | | AddColumn(connection, "TypedBaseItems", "PreferredMetadataCountryCode", "Text", existingColumnNames); |
| 22 | 461 | | AddColumn(connection, "TypedBaseItems", "DateLastRefreshed", "DATETIME", existingColumnNames); |
| 22 | 462 | | AddColumn(connection, "TypedBaseItems", "DateLastSaved", "DATETIME", existingColumnNames); |
| 22 | 463 | | AddColumn(connection, "TypedBaseItems", "IsInMixedFolder", "BIT", existingColumnNames); |
| 22 | 464 | | AddColumn(connection, "TypedBaseItems", "LockedFields", "Text", existingColumnNames); |
| 22 | 465 | | AddColumn(connection, "TypedBaseItems", "Studios", "Text", existingColumnNames); |
| 22 | 466 | | AddColumn(connection, "TypedBaseItems", "Audio", "Text", existingColumnNames); |
| 22 | 467 | | AddColumn(connection, "TypedBaseItems", "ExternalServiceId", "Text", existingColumnNames); |
| 22 | 468 | | AddColumn(connection, "TypedBaseItems", "Tags", "Text", existingColumnNames); |
| 22 | 469 | | AddColumn(connection, "TypedBaseItems", "IsFolder", "BIT", existingColumnNames); |
| 22 | 470 | | AddColumn(connection, "TypedBaseItems", "InheritedParentalRatingValue", "INT", existingColumnNames); |
| 22 | 471 | | AddColumn(connection, "TypedBaseItems", "UnratedType", "Text", existingColumnNames); |
| 22 | 472 | | AddColumn(connection, "TypedBaseItems", "TopParentId", "Text", existingColumnNames); |
| 22 | 473 | | AddColumn(connection, "TypedBaseItems", "TrailerTypes", "Text", existingColumnNames); |
| 22 | 474 | | AddColumn(connection, "TypedBaseItems", "CriticRating", "Float", existingColumnNames); |
| 22 | 475 | | AddColumn(connection, "TypedBaseItems", "CleanName", "Text", existingColumnNames); |
| 22 | 476 | | AddColumn(connection, "TypedBaseItems", "PresentationUniqueKey", "Text", existingColumnNames); |
| 22 | 477 | | AddColumn(connection, "TypedBaseItems", "OriginalTitle", "Text", existingColumnNames); |
| 22 | 478 | | AddColumn(connection, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames); |
| 22 | 479 | | AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames); |
| 22 | 480 | | AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames); |
| 22 | 481 | | AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames); |
| 22 | 482 | | AddColumn(connection, "TypedBaseItems", "NormalizationGain", "Float", existingColumnNames); |
| 22 | 483 | | AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames); |
| 22 | 484 | | AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames); |
| 22 | 485 | | AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames); |
| 22 | 486 | | AddColumn(connection, "TypedBaseItems", "SeasonName", "Text", existingColumnNames); |
| 22 | 487 | | AddColumn(connection, "TypedBaseItems", "SeasonId", "GUID", existingColumnNames); |
| 22 | 488 | | AddColumn(connection, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames); |
| 22 | 489 | | AddColumn(connection, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames); |
| 22 | 490 | | AddColumn(connection, "TypedBaseItems", "Tagline", "Text", existingColumnNames); |
| 22 | 491 | | AddColumn(connection, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames); |
| 22 | 492 | | AddColumn(connection, "TypedBaseItems", "Images", "Text", existingColumnNames); |
| 22 | 493 | | AddColumn(connection, "TypedBaseItems", "ProductionLocations", "Text", existingColumnNames); |
| 22 | 494 | | AddColumn(connection, "TypedBaseItems", "ExtraIds", "Text", existingColumnNames); |
| 22 | 495 | | AddColumn(connection, "TypedBaseItems", "TotalBitrate", "INT", existingColumnNames); |
| 22 | 496 | | AddColumn(connection, "TypedBaseItems", "ExtraType", "Text", existingColumnNames); |
| 22 | 497 | | AddColumn(connection, "TypedBaseItems", "Artists", "Text", existingColumnNames); |
| 22 | 498 | | AddColumn(connection, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames); |
| 22 | 499 | | AddColumn(connection, "TypedBaseItems", "ExternalId", "Text", existingColumnNames); |
| 22 | 500 | | AddColumn(connection, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames); |
| 22 | 501 | | AddColumn(connection, "TypedBaseItems", "ShowId", "Text", existingColumnNames); |
| 22 | 502 | | AddColumn(connection, "TypedBaseItems", "OwnerId", "Text", existingColumnNames); |
| 22 | 503 | | AddColumn(connection, "TypedBaseItems", "Width", "INT", existingColumnNames); |
| 22 | 504 | | AddColumn(connection, "TypedBaseItems", "Height", "INT", existingColumnNames); |
| 22 | 505 | | AddColumn(connection, "TypedBaseItems", "Size", "BIGINT", existingColumnNames); |
| | 506 | |
|
| 22 | 507 | | existingColumnNames = GetColumnNames(connection, "ItemValues"); |
| 22 | 508 | | AddColumn(connection, "ItemValues", "CleanValue", "Text", existingColumnNames); |
| | 509 | |
|
| 22 | 510 | | existingColumnNames = GetColumnNames(connection, ChaptersTableName); |
| 22 | 511 | | AddColumn(connection, ChaptersTableName, "ImageDateModified", "DATETIME", existingColumnNames); |
| | 512 | |
|
| 22 | 513 | | existingColumnNames = GetColumnNames(connection, "MediaStreams"); |
| 22 | 514 | | AddColumn(connection, "MediaStreams", "IsAvc", "BIT", existingColumnNames); |
| 22 | 515 | | AddColumn(connection, "MediaStreams", "TimeBase", "TEXT", existingColumnNames); |
| 22 | 516 | | AddColumn(connection, "MediaStreams", "CodecTimeBase", "TEXT", existingColumnNames); |
| 22 | 517 | | AddColumn(connection, "MediaStreams", "Title", "TEXT", existingColumnNames); |
| 22 | 518 | | AddColumn(connection, "MediaStreams", "NalLengthSize", "TEXT", existingColumnNames); |
| 22 | 519 | | AddColumn(connection, "MediaStreams", "Comment", "TEXT", existingColumnNames); |
| 22 | 520 | | AddColumn(connection, "MediaStreams", "CodecTag", "TEXT", existingColumnNames); |
| 22 | 521 | | AddColumn(connection, "MediaStreams", "PixelFormat", "TEXT", existingColumnNames); |
| 22 | 522 | | AddColumn(connection, "MediaStreams", "BitDepth", "INT", existingColumnNames); |
| 22 | 523 | | AddColumn(connection, "MediaStreams", "RefFrames", "INT", existingColumnNames); |
| 22 | 524 | | AddColumn(connection, "MediaStreams", "KeyFrames", "TEXT", existingColumnNames); |
| 22 | 525 | | AddColumn(connection, "MediaStreams", "IsAnamorphic", "BIT", existingColumnNames); |
| | 526 | |
|
| 22 | 527 | | AddColumn(connection, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames); |
| 22 | 528 | | AddColumn(connection, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames); |
| 22 | 529 | | AddColumn(connection, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames); |
| | 530 | |
|
| 22 | 531 | | AddColumn(connection, "MediaStreams", "DvVersionMajor", "INT", existingColumnNames); |
| 22 | 532 | | AddColumn(connection, "MediaStreams", "DvVersionMinor", "INT", existingColumnNames); |
| 22 | 533 | | AddColumn(connection, "MediaStreams", "DvProfile", "INT", existingColumnNames); |
| 22 | 534 | | AddColumn(connection, "MediaStreams", "DvLevel", "INT", existingColumnNames); |
| 22 | 535 | | AddColumn(connection, "MediaStreams", "RpuPresentFlag", "INT", existingColumnNames); |
| 22 | 536 | | AddColumn(connection, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames); |
| 22 | 537 | | AddColumn(connection, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames); |
| 22 | 538 | | AddColumn(connection, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames); |
| | 539 | |
|
| 22 | 540 | | AddColumn(connection, "MediaStreams", "IsHearingImpaired", "BIT", existingColumnNames); |
| | 541 | |
|
| 22 | 542 | | AddColumn(connection, "MediaStreams", "Rotation", "INT", existingColumnNames); |
| | 543 | |
|
| 22 | 544 | | connection.Execute(string.Join(';', postQueries)); |
| | 545 | |
|
| 22 | 546 | | transaction.Commit(); |
| 22 | 547 | | } |
| 22 | 548 | | } |
| | 549 | |
|
| | 550 | | public void SaveImages(BaseItem item) |
| | 551 | | { |
| 0 | 552 | | ArgumentNullException.ThrowIfNull(item); |
| | 553 | |
|
| 0 | 554 | | CheckDisposed(); |
| | 555 | |
|
| 0 | 556 | | var images = SerializeImages(item.ImageInfos); |
| 0 | 557 | | using var connection = GetConnection(); |
| 0 | 558 | | using var transaction = connection.BeginTransaction(); |
| 0 | 559 | | using var saveImagesStatement = PrepareStatement(connection, "Update TypedBaseItems set Images=@Images where |
| 0 | 560 | | saveImagesStatement.TryBind("@Id", item.Id); |
| 0 | 561 | | saveImagesStatement.TryBind("@Images", images); |
| | 562 | |
|
| 0 | 563 | | saveImagesStatement.ExecuteNonQuery(); |
| 0 | 564 | | transaction.Commit(); |
| 0 | 565 | | } |
| | 566 | |
|
| | 567 | | /// <summary> |
| | 568 | | /// Saves the items. |
| | 569 | | /// </summary> |
| | 570 | | /// <param name="items">The items.</param> |
| | 571 | | /// <param name="cancellationToken">The cancellation token.</param> |
| | 572 | | /// <exception cref="ArgumentNullException"> |
| | 573 | | /// <paramref name="items"/> or <paramref name="cancellationToken"/> is <c>null</c>. |
| | 574 | | /// </exception> |
| | 575 | | public void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken) |
| | 576 | | { |
| 59 | 577 | | ArgumentNullException.ThrowIfNull(items); |
| | 578 | |
|
| 59 | 579 | | cancellationToken.ThrowIfCancellationRequested(); |
| | 580 | |
|
| 59 | 581 | | CheckDisposed(); |
| | 582 | |
|
| 59 | 583 | | var itemsLen = items.Count; |
| 59 | 584 | | var tuples = new ValueTuple<BaseItem, List<Guid>, BaseItem, string, List<string>>[itemsLen]; |
| 236 | 585 | | for (int i = 0; i < itemsLen; i++) |
| | 586 | | { |
| 59 | 587 | | var item = items[i]; |
| 59 | 588 | | var ancestorIds = item.SupportsAncestors ? |
| 59 | 589 | | item.GetAncestorIds().Distinct().ToList() : |
| 59 | 590 | | null; |
| | 591 | |
|
| 59 | 592 | | var topParent = item.GetTopParent(); |
| | 593 | |
|
| 59 | 594 | | var userdataKey = item.GetUserDataKeys().FirstOrDefault(); |
| 59 | 595 | | var inheritedTags = item.GetInheritedTags(); |
| | 596 | |
|
| 59 | 597 | | tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags); |
| | 598 | | } |
| | 599 | |
|
| 59 | 600 | | using var connection = GetConnection(); |
| 59 | 601 | | using var transaction = connection.BeginTransaction(); |
| 59 | 602 | | SaveItemsInTransaction(connection, tuples); |
| 59 | 603 | | transaction.Commit(); |
| 118 | 604 | | } |
| | 605 | |
|
| | 606 | | private void SaveItemsInTransaction(ManagedConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, Ba |
| | 607 | | { |
| 59 | 608 | | using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText)) |
| 59 | 609 | | using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId")) |
| | 610 | | { |
| 59 | 611 | | var requiresReset = false; |
| 236 | 612 | | foreach (var tuple in tuples) |
| | 613 | | { |
| 59 | 614 | | if (requiresReset) |
| | 615 | | { |
| 0 | 616 | | saveItemStatement.Parameters.Clear(); |
| 0 | 617 | | deleteAncestorsStatement.Parameters.Clear(); |
| | 618 | | } |
| | 619 | |
|
| 59 | 620 | | var item = tuple.Item; |
| 59 | 621 | | var topParent = tuple.TopParent; |
| 59 | 622 | | var userDataKey = tuple.UserDataKey; |
| | 623 | |
|
| 59 | 624 | | SaveItem(item, topParent, userDataKey, saveItemStatement); |
| | 625 | |
|
| 59 | 626 | | var inheritedTags = tuple.InheritedTags; |
| | 627 | |
|
| 59 | 628 | | if (item.SupportsAncestors) |
| | 629 | | { |
| 59 | 630 | | UpdateAncestors(item.Id, tuple.AncestorIds, db, deleteAncestorsStatement); |
| | 631 | | } |
| | 632 | |
|
| 59 | 633 | | UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db); |
| | 634 | |
|
| 59 | 635 | | requiresReset = true; |
| | 636 | | } |
| | 637 | | } |
| 59 | 638 | | } |
| | 639 | |
|
| | 640 | | private string GetPathToSave(string path) |
| | 641 | | { |
| 64 | 642 | | if (path is null) |
| | 643 | | { |
| 0 | 644 | | return null; |
| | 645 | | } |
| | 646 | |
|
| 64 | 647 | | return _appHost.ReverseVirtualPath(path); |
| | 648 | | } |
| | 649 | |
|
| | 650 | | private string RestorePath(string path) |
| | 651 | | { |
| 100 | 652 | | return _appHost.ExpandVirtualPath(path); |
| | 653 | | } |
| | 654 | |
|
| | 655 | | private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, SqliteCommand saveItemStatement) |
| | 656 | | { |
| 59 | 657 | | Type type = item.GetType(); |
| | 658 | |
|
| 59 | 659 | | saveItemStatement.TryBind("@guid", item.Id); |
| 59 | 660 | | saveItemStatement.TryBind("@type", type.FullName); |
| | 661 | |
|
| 59 | 662 | | if (TypeRequiresDeserialization(type)) |
| | 663 | | { |
| 20 | 664 | | saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions), true); |
| | 665 | | } |
| | 666 | | else |
| | 667 | | { |
| 39 | 668 | | saveItemStatement.TryBindNull("@data"); |
| | 669 | | } |
| | 670 | |
|
| 59 | 671 | | saveItemStatement.TryBind("@Path", GetPathToSave(item.Path)); |
| | 672 | |
|
| 59 | 673 | | if (item is IHasStartDate hasStartDate) |
| | 674 | | { |
| 0 | 675 | | saveItemStatement.TryBind("@StartDate", hasStartDate.StartDate); |
| | 676 | | } |
| | 677 | | else |
| | 678 | | { |
| 59 | 679 | | saveItemStatement.TryBindNull("@StartDate"); |
| | 680 | | } |
| | 681 | |
|
| 59 | 682 | | if (item.EndDate.HasValue) |
| | 683 | | { |
| 0 | 684 | | saveItemStatement.TryBind("@EndDate", item.EndDate.Value); |
| | 685 | | } |
| | 686 | | else |
| | 687 | | { |
| 59 | 688 | | saveItemStatement.TryBindNull("@EndDate"); |
| | 689 | | } |
| | 690 | |
|
| 59 | 691 | | saveItemStatement.TryBind("@ChannelId", item.ChannelId.IsEmpty() ? null : item.ChannelId.ToString("N", Cultu |
| | 692 | |
|
| 59 | 693 | | if (item is IHasProgramAttributes hasProgramAttributes) |
| | 694 | | { |
| 0 | 695 | | saveItemStatement.TryBind("@IsMovie", hasProgramAttributes.IsMovie); |
| 0 | 696 | | saveItemStatement.TryBind("@IsSeries", hasProgramAttributes.IsSeries); |
| 0 | 697 | | saveItemStatement.TryBind("@EpisodeTitle", hasProgramAttributes.EpisodeTitle); |
| 0 | 698 | | saveItemStatement.TryBind("@IsRepeat", hasProgramAttributes.IsRepeat); |
| | 699 | | } |
| | 700 | | else |
| | 701 | | { |
| 59 | 702 | | saveItemStatement.TryBindNull("@IsMovie"); |
| 59 | 703 | | saveItemStatement.TryBindNull("@IsSeries"); |
| 59 | 704 | | saveItemStatement.TryBindNull("@EpisodeTitle"); |
| 59 | 705 | | saveItemStatement.TryBindNull("@IsRepeat"); |
| | 706 | | } |
| | 707 | |
|
| 59 | 708 | | saveItemStatement.TryBind("@CommunityRating", item.CommunityRating); |
| 59 | 709 | | saveItemStatement.TryBind("@CustomRating", item.CustomRating); |
| 59 | 710 | | saveItemStatement.TryBind("@IndexNumber", item.IndexNumber); |
| 59 | 711 | | saveItemStatement.TryBind("@IsLocked", item.IsLocked); |
| 59 | 712 | | saveItemStatement.TryBind("@Name", item.Name); |
| 59 | 713 | | saveItemStatement.TryBind("@OfficialRating", item.OfficialRating); |
| 59 | 714 | | saveItemStatement.TryBind("@MediaType", item.MediaType.ToString()); |
| 59 | 715 | | saveItemStatement.TryBind("@Overview", item.Overview); |
| 59 | 716 | | saveItemStatement.TryBind("@ParentIndexNumber", item.ParentIndexNumber); |
| 59 | 717 | | saveItemStatement.TryBind("@PremiereDate", item.PremiereDate); |
| 59 | 718 | | saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); |
| | 719 | |
|
| 59 | 720 | | var parentId = item.ParentId; |
| 59 | 721 | | if (parentId.IsEmpty()) |
| | 722 | | { |
| 35 | 723 | | saveItemStatement.TryBindNull("@ParentId"); |
| | 724 | | } |
| | 725 | | else |
| | 726 | | { |
| 24 | 727 | | saveItemStatement.TryBind("@ParentId", parentId); |
| | 728 | | } |
| | 729 | |
|
| 59 | 730 | | if (item.Genres.Length > 0) |
| | 731 | | { |
| 0 | 732 | | saveItemStatement.TryBind("@Genres", string.Join('|', item.Genres)); |
| | 733 | | } |
| | 734 | | else |
| | 735 | | { |
| 59 | 736 | | saveItemStatement.TryBindNull("@Genres"); |
| | 737 | | } |
| | 738 | |
|
| 59 | 739 | | saveItemStatement.TryBind("@InheritedParentalRatingValue", item.InheritedParentalRatingValue); |
| | 740 | |
|
| 59 | 741 | | saveItemStatement.TryBind("@SortName", item.SortName); |
| | 742 | |
|
| 59 | 743 | | saveItemStatement.TryBind("@ForcedSortName", item.ForcedSortName); |
| | 744 | |
|
| 59 | 745 | | saveItemStatement.TryBind("@RunTimeTicks", item.RunTimeTicks); |
| 59 | 746 | | saveItemStatement.TryBind("@Size", item.Size); |
| | 747 | |
|
| 59 | 748 | | saveItemStatement.TryBind("@DateCreated", item.DateCreated); |
| 59 | 749 | | saveItemStatement.TryBind("@DateModified", item.DateModified); |
| | 750 | |
|
| 59 | 751 | | saveItemStatement.TryBind("@PreferredMetadataLanguage", item.PreferredMetadataLanguage); |
| 59 | 752 | | saveItemStatement.TryBind("@PreferredMetadataCountryCode", item.PreferredMetadataCountryCode); |
| | 753 | |
|
| 59 | 754 | | if (item.Width > 0) |
| | 755 | | { |
| 0 | 756 | | saveItemStatement.TryBind("@Width", item.Width); |
| | 757 | | } |
| | 758 | | else |
| | 759 | | { |
| 59 | 760 | | saveItemStatement.TryBindNull("@Width"); |
| | 761 | | } |
| | 762 | |
|
| 59 | 763 | | if (item.Height > 0) |
| | 764 | | { |
| 0 | 765 | | saveItemStatement.TryBind("@Height", item.Height); |
| | 766 | | } |
| | 767 | | else |
| | 768 | | { |
| 59 | 769 | | saveItemStatement.TryBindNull("@Height"); |
| | 770 | | } |
| | 771 | |
|
| 59 | 772 | | if (item.DateLastRefreshed != default(DateTime)) |
| | 773 | | { |
| 36 | 774 | | saveItemStatement.TryBind("@DateLastRefreshed", item.DateLastRefreshed); |
| | 775 | | } |
| | 776 | | else |
| | 777 | | { |
| 23 | 778 | | saveItemStatement.TryBindNull("@DateLastRefreshed"); |
| | 779 | | } |
| | 780 | |
|
| 59 | 781 | | if (item.DateLastSaved != default(DateTime)) |
| | 782 | | { |
| 58 | 783 | | saveItemStatement.TryBind("@DateLastSaved", item.DateLastSaved); |
| | 784 | | } |
| | 785 | | else |
| | 786 | | { |
| 1 | 787 | | saveItemStatement.TryBindNull("@DateLastSaved"); |
| | 788 | | } |
| | 789 | |
|
| 59 | 790 | | saveItemStatement.TryBind("@IsInMixedFolder", item.IsInMixedFolder); |
| | 791 | |
|
| 59 | 792 | | if (item.LockedFields.Length > 0) |
| | 793 | | { |
| 0 | 794 | | saveItemStatement.TryBind("@LockedFields", string.Join('|', item.LockedFields)); |
| | 795 | | } |
| | 796 | | else |
| | 797 | | { |
| 59 | 798 | | saveItemStatement.TryBindNull("@LockedFields"); |
| | 799 | | } |
| | 800 | |
|
| 59 | 801 | | if (item.Studios.Length > 0) |
| | 802 | | { |
| 0 | 803 | | saveItemStatement.TryBind("@Studios", string.Join('|', item.Studios)); |
| | 804 | | } |
| | 805 | | else |
| | 806 | | { |
| 59 | 807 | | saveItemStatement.TryBindNull("@Studios"); |
| | 808 | | } |
| | 809 | |
|
| 59 | 810 | | if (item.Audio.HasValue) |
| | 811 | | { |
| 0 | 812 | | saveItemStatement.TryBind("@Audio", item.Audio.Value.ToString()); |
| | 813 | | } |
| | 814 | | else |
| | 815 | | { |
| 59 | 816 | | saveItemStatement.TryBindNull("@Audio"); |
| | 817 | | } |
| | 818 | |
|
| 59 | 819 | | if (item is LiveTvChannel liveTvChannel) |
| | 820 | | { |
| 0 | 821 | | saveItemStatement.TryBind("@ExternalServiceId", liveTvChannel.ServiceName); |
| | 822 | | } |
| | 823 | | else |
| | 824 | | { |
| 59 | 825 | | saveItemStatement.TryBindNull("@ExternalServiceId"); |
| | 826 | | } |
| | 827 | |
|
| 59 | 828 | | if (item.Tags.Length > 0) |
| | 829 | | { |
| 0 | 830 | | saveItemStatement.TryBind("@Tags", string.Join('|', item.Tags)); |
| | 831 | | } |
| | 832 | | else |
| | 833 | | { |
| 59 | 834 | | saveItemStatement.TryBindNull("@Tags"); |
| | 835 | | } |
| | 836 | |
|
| 59 | 837 | | saveItemStatement.TryBind("@IsFolder", item.IsFolder); |
| | 838 | |
|
| 59 | 839 | | saveItemStatement.TryBind("@UnratedType", item.GetBlockUnratedType().ToString()); |
| | 840 | |
|
| 59 | 841 | | if (topParent is null) |
| | 842 | | { |
| 37 | 843 | | saveItemStatement.TryBindNull("@TopParentId"); |
| | 844 | | } |
| | 845 | | else |
| | 846 | | { |
| 22 | 847 | | saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N", CultureInfo.InvariantCulture)); |
| | 848 | | } |
| | 849 | |
|
| 59 | 850 | | if (item is Trailer trailer && trailer.TrailerTypes.Length > 0) |
| | 851 | | { |
| 0 | 852 | | saveItemStatement.TryBind("@TrailerTypes", string.Join('|', trailer.TrailerTypes)); |
| | 853 | | } |
| | 854 | | else |
| | 855 | | { |
| 59 | 856 | | saveItemStatement.TryBindNull("@TrailerTypes"); |
| | 857 | | } |
| | 858 | |
|
| 59 | 859 | | saveItemStatement.TryBind("@CriticRating", item.CriticRating); |
| | 860 | |
|
| 59 | 861 | | if (string.IsNullOrWhiteSpace(item.Name)) |
| | 862 | | { |
| 0 | 863 | | saveItemStatement.TryBindNull("@CleanName"); |
| | 864 | | } |
| | 865 | | else |
| | 866 | | { |
| 59 | 867 | | saveItemStatement.TryBind("@CleanName", GetCleanValue(item.Name)); |
| | 868 | | } |
| | 869 | |
|
| 59 | 870 | | saveItemStatement.TryBind("@PresentationUniqueKey", item.PresentationUniqueKey); |
| 59 | 871 | | saveItemStatement.TryBind("@OriginalTitle", item.OriginalTitle); |
| | 872 | |
|
| 59 | 873 | | if (item is Video video) |
| | 874 | | { |
| 0 | 875 | | saveItemStatement.TryBind("@PrimaryVersionId", video.PrimaryVersionId); |
| | 876 | | } |
| | 877 | | else |
| | 878 | | { |
| 59 | 879 | | saveItemStatement.TryBindNull("@PrimaryVersionId"); |
| | 880 | | } |
| | 881 | |
|
| 59 | 882 | | if (item is Folder folder && folder.DateLastMediaAdded.HasValue) |
| | 883 | | { |
| 0 | 884 | | saveItemStatement.TryBind("@DateLastMediaAdded", folder.DateLastMediaAdded.Value); |
| | 885 | | } |
| | 886 | | else |
| | 887 | | { |
| 59 | 888 | | saveItemStatement.TryBindNull("@DateLastMediaAdded"); |
| | 889 | | } |
| | 890 | |
|
| 59 | 891 | | saveItemStatement.TryBind("@Album", item.Album); |
| 59 | 892 | | saveItemStatement.TryBind("@LUFS", item.LUFS); |
| 59 | 893 | | saveItemStatement.TryBind("@NormalizationGain", item.NormalizationGain); |
| 59 | 894 | | saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem); |
| | 895 | |
|
| 59 | 896 | | if (item is IHasSeries hasSeriesName) |
| | 897 | | { |
| 0 | 898 | | saveItemStatement.TryBind("@SeriesName", hasSeriesName.SeriesName); |
| | 899 | | } |
| | 900 | | else |
| | 901 | | { |
| 59 | 902 | | saveItemStatement.TryBindNull("@SeriesName"); |
| | 903 | | } |
| | 904 | |
|
| 59 | 905 | | if (string.IsNullOrWhiteSpace(userDataKey)) |
| | 906 | | { |
| 0 | 907 | | saveItemStatement.TryBindNull("@UserDataKey"); |
| | 908 | | } |
| | 909 | | else |
| | 910 | | { |
| 59 | 911 | | saveItemStatement.TryBind("@UserDataKey", userDataKey); |
| | 912 | | } |
| | 913 | |
|
| 59 | 914 | | if (item is Episode episode) |
| | 915 | | { |
| 0 | 916 | | saveItemStatement.TryBind("@SeasonName", episode.SeasonName); |
| | 917 | |
|
| 0 | 918 | | var nullableSeasonId = episode.SeasonId.IsEmpty() ? (Guid?)null : episode.SeasonId; |
| | 919 | |
|
| 0 | 920 | | saveItemStatement.TryBind("@SeasonId", nullableSeasonId); |
| | 921 | | } |
| | 922 | | else |
| | 923 | | { |
| 59 | 924 | | saveItemStatement.TryBindNull("@SeasonName"); |
| 59 | 925 | | saveItemStatement.TryBindNull("@SeasonId"); |
| | 926 | | } |
| | 927 | |
|
| 59 | 928 | | if (item is IHasSeries hasSeries) |
| | 929 | | { |
| 0 | 930 | | var nullableSeriesId = hasSeries.SeriesId.IsEmpty() ? (Guid?)null : hasSeries.SeriesId; |
| | 931 | |
|
| 0 | 932 | | saveItemStatement.TryBind("@SeriesId", nullableSeriesId); |
| 0 | 933 | | saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); |
| | 934 | | } |
| | 935 | | else |
| | 936 | | { |
| 59 | 937 | | saveItemStatement.TryBindNull("@SeriesId"); |
| 59 | 938 | | saveItemStatement.TryBindNull("@SeriesPresentationUniqueKey"); |
| | 939 | | } |
| | 940 | |
|
| 59 | 941 | | saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); |
| 59 | 942 | | saveItemStatement.TryBind("@Tagline", item.Tagline); |
| | 943 | |
|
| 59 | 944 | | saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds)); |
| 59 | 945 | | saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); |
| | 946 | |
|
| 59 | 947 | | if (item.ProductionLocations.Length > 0) |
| | 948 | | { |
| 0 | 949 | | saveItemStatement.TryBind("@ProductionLocations", string.Join('|', item.ProductionLocations)); |
| | 950 | | } |
| | 951 | | else |
| | 952 | | { |
| 59 | 953 | | saveItemStatement.TryBindNull("@ProductionLocations"); |
| | 954 | | } |
| | 955 | |
|
| 59 | 956 | | if (item.ExtraIds.Length > 0) |
| | 957 | | { |
| 0 | 958 | | saveItemStatement.TryBind("@ExtraIds", string.Join('|', item.ExtraIds)); |
| | 959 | | } |
| | 960 | | else |
| | 961 | | { |
| 59 | 962 | | saveItemStatement.TryBindNull("@ExtraIds"); |
| | 963 | | } |
| | 964 | |
|
| 59 | 965 | | saveItemStatement.TryBind("@TotalBitrate", item.TotalBitrate); |
| 59 | 966 | | if (item.ExtraType.HasValue) |
| | 967 | | { |
| 0 | 968 | | saveItemStatement.TryBind("@ExtraType", item.ExtraType.Value.ToString()); |
| | 969 | | } |
| | 970 | | else |
| | 971 | | { |
| 59 | 972 | | saveItemStatement.TryBindNull("@ExtraType"); |
| | 973 | | } |
| | 974 | |
|
| 59 | 975 | | string artists = null; |
| 59 | 976 | | if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0) |
| | 977 | | { |
| 0 | 978 | | artists = string.Join('|', hasArtists.Artists); |
| | 979 | | } |
| | 980 | |
|
| 59 | 981 | | saveItemStatement.TryBind("@Artists", artists); |
| | 982 | |
|
| 59 | 983 | | string albumArtists = null; |
| 59 | 984 | | if (item is IHasAlbumArtist hasAlbumArtists |
| 59 | 985 | | && hasAlbumArtists.AlbumArtists.Count > 0) |
| | 986 | | { |
| 0 | 987 | | albumArtists = string.Join('|', hasAlbumArtists.AlbumArtists); |
| | 988 | | } |
| | 989 | |
|
| 59 | 990 | | saveItemStatement.TryBind("@AlbumArtists", albumArtists); |
| 59 | 991 | | saveItemStatement.TryBind("@ExternalId", item.ExternalId); |
| | 992 | |
|
| 59 | 993 | | if (item is LiveTvProgram program) |
| | 994 | | { |
| 0 | 995 | | saveItemStatement.TryBind("@ShowId", program.ShowId); |
| | 996 | | } |
| | 997 | | else |
| | 998 | | { |
| 59 | 999 | | saveItemStatement.TryBindNull("@ShowId"); |
| | 1000 | | } |
| | 1001 | |
|
| 59 | 1002 | | Guid ownerId = item.OwnerId; |
| 59 | 1003 | | if (ownerId.IsEmpty()) |
| | 1004 | | { |
| 59 | 1005 | | saveItemStatement.TryBindNull("@OwnerId"); |
| | 1006 | | } |
| | 1007 | | else |
| | 1008 | | { |
| 0 | 1009 | | saveItemStatement.TryBind("@OwnerId", ownerId); |
| | 1010 | | } |
| | 1011 | |
|
| 59 | 1012 | | saveItemStatement.ExecuteNonQuery(); |
| 59 | 1013 | | } |
| | 1014 | |
|
| | 1015 | | internal static string SerializeProviderIds(Dictionary<string, string> providerIds) |
| | 1016 | | { |
| 62 | 1017 | | StringBuilder str = new StringBuilder(); |
| 142 | 1018 | | foreach (var i in providerIds) |
| | 1019 | | { |
| | 1020 | | // Ideally we shouldn't need this IsNullOrWhiteSpace check, |
| | 1021 | | // but we're seeing some cases of bad data slip through |
| 9 | 1022 | | if (string.IsNullOrWhiteSpace(i.Value)) |
| | 1023 | | { |
| | 1024 | | continue; |
| | 1025 | | } |
| | 1026 | |
|
| 9 | 1027 | | str.Append(i.Key) |
| 9 | 1028 | | .Append('=') |
| 9 | 1029 | | .Append(i.Value) |
| 9 | 1030 | | .Append('|'); |
| | 1031 | | } |
| | 1032 | |
|
| 62 | 1033 | | if (str.Length == 0) |
| | 1034 | | { |
| 59 | 1035 | | return null; |
| | 1036 | | } |
| | 1037 | |
|
| 3 | 1038 | | str.Length -= 1; // Remove last | |
| 3 | 1039 | | return str.ToString(); |
| | 1040 | | } |
| | 1041 | |
|
| | 1042 | | internal static void DeserializeProviderIds(string value, IHasProviderIds item) |
| | 1043 | | { |
| 3 | 1044 | | if (string.IsNullOrWhiteSpace(value)) |
| | 1045 | | { |
| 0 | 1046 | | return; |
| | 1047 | | } |
| | 1048 | |
|
| 24 | 1049 | | foreach (var part in value.SpanSplit('|')) |
| | 1050 | | { |
| 9 | 1051 | | var providerDelimiterIndex = part.IndexOf('='); |
| | 1052 | | // Don't let empty values through |
| 9 | 1053 | | if (providerDelimiterIndex != -1 && part.Length != providerDelimiterIndex + 1) |
| | 1054 | | { |
| 9 | 1055 | | item.SetProviderId(part[..providerDelimiterIndex].ToString(), part[(providerDelimiterIndex + 1)..].T |
| | 1056 | | } |
| | 1057 | | } |
| 3 | 1058 | | } |
| | 1059 | |
|
| | 1060 | | internal string SerializeImages(ItemImageInfo[] images) |
| | 1061 | | { |
| 61 | 1062 | | if (images.Length == 0) |
| | 1063 | | { |
| 59 | 1064 | | return null; |
| | 1065 | | } |
| | 1066 | |
|
| 2 | 1067 | | StringBuilder str = new StringBuilder(); |
| 14 | 1068 | | foreach (var i in images) |
| | 1069 | | { |
| 5 | 1070 | | if (string.IsNullOrWhiteSpace(i.Path)) |
| | 1071 | | { |
| | 1072 | | continue; |
| | 1073 | | } |
| | 1074 | |
|
| 5 | 1075 | | AppendItemImageInfo(str, i); |
| 5 | 1076 | | str.Append('|'); |
| | 1077 | | } |
| | 1078 | |
|
| 2 | 1079 | | str.Length -= 1; // Remove last | |
| 2 | 1080 | | return str.ToString(); |
| | 1081 | | } |
| | 1082 | |
|
| | 1083 | | internal ItemImageInfo[] DeserializeImages(string value) |
| | 1084 | | { |
| 5 | 1085 | | if (string.IsNullOrWhiteSpace(value)) |
| | 1086 | | { |
| 1 | 1087 | | return Array.Empty<ItemImageInfo>(); |
| | 1088 | | } |
| | 1089 | |
|
| | 1090 | | // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the |
| 4 | 1091 | | var valueSpan = value.AsSpan(); |
| 4 | 1092 | | var count = valueSpan.Count('|') + 1; |
| | 1093 | |
|
| 4 | 1094 | | var position = 0; |
| 4 | 1095 | | var result = new ItemImageInfo[count]; |
| 30 | 1096 | | foreach (var part in valueSpan.Split('|')) |
| | 1097 | | { |
| 11 | 1098 | | var image = ItemImageInfoFromValueString(part); |
| | 1099 | |
|
| 11 | 1100 | | if (image is not null) |
| | 1101 | | { |
| 6 | 1102 | | result[position++] = image; |
| | 1103 | | } |
| | 1104 | | } |
| | 1105 | |
|
| 4 | 1106 | | if (position == count) |
| | 1107 | | { |
| 2 | 1108 | | return result; |
| | 1109 | | } |
| | 1110 | |
|
| 2 | 1111 | | if (position == 0) |
| | 1112 | | { |
| 1 | 1113 | | return Array.Empty<ItemImageInfo>(); |
| | 1114 | | } |
| | 1115 | |
|
| | 1116 | | // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array. |
| 1 | 1117 | | return result[..position]; |
| | 1118 | | } |
| | 1119 | |
|
| | 1120 | | private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) |
| | 1121 | | { |
| | 1122 | | const char Delimiter = '*'; |
| | 1123 | |
|
| 5 | 1124 | | var path = image.Path ?? string.Empty; |
| | 1125 | |
|
| 5 | 1126 | | bldr.Append(GetPathToSave(path)) |
| 5 | 1127 | | .Append(Delimiter) |
| 5 | 1128 | | .Append(image.DateModified.Ticks) |
| 5 | 1129 | | .Append(Delimiter) |
| 5 | 1130 | | .Append(image.Type) |
| 5 | 1131 | | .Append(Delimiter) |
| 5 | 1132 | | .Append(image.Width) |
| 5 | 1133 | | .Append(Delimiter) |
| 5 | 1134 | | .Append(image.Height); |
| | 1135 | |
|
| 5 | 1136 | | var hash = image.BlurHash; |
| 5 | 1137 | | if (!string.IsNullOrEmpty(hash)) |
| | 1138 | | { |
| 1 | 1139 | | bldr.Append(Delimiter) |
| 1 | 1140 | | // Replace delimiters with other characters. |
| 1 | 1141 | | // This can be removed when we migrate to a proper DB. |
| 1 | 1142 | | .Append(hash.Replace(Delimiter, '/').Replace('|', '\\')); |
| | 1143 | | } |
| 5 | 1144 | | } |
| | 1145 | |
|
| | 1146 | | internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value) |
| | 1147 | | { |
| | 1148 | | const char Delimiter = '*'; |
| | 1149 | |
|
| 22 | 1150 | | var nextSegment = value.IndexOf(Delimiter); |
| 22 | 1151 | | if (nextSegment == -1) |
| | 1152 | | { |
| 6 | 1153 | | return null; |
| | 1154 | | } |
| | 1155 | |
|
| 16 | 1156 | | ReadOnlySpan<char> path = value[..nextSegment]; |
| 16 | 1157 | | value = value[(nextSegment + 1)..]; |
| 16 | 1158 | | nextSegment = value.IndexOf(Delimiter); |
| 16 | 1159 | | if (nextSegment == -1) |
| | 1160 | | { |
| 2 | 1161 | | return null; |
| | 1162 | | } |
| | 1163 | |
|
| 14 | 1164 | | ReadOnlySpan<char> dateModified = value[..nextSegment]; |
| 14 | 1165 | | value = value[(nextSegment + 1)..]; |
| 14 | 1166 | | nextSegment = value.IndexOf(Delimiter); |
| 14 | 1167 | | if (nextSegment == -1) |
| | 1168 | | { |
| 3 | 1169 | | nextSegment = value.Length; |
| | 1170 | | } |
| | 1171 | |
|
| 14 | 1172 | | ReadOnlySpan<char> imageType = value[..nextSegment]; |
| | 1173 | |
|
| 14 | 1174 | | var image = new ItemImageInfo |
| 14 | 1175 | | { |
| 14 | 1176 | | Path = RestorePath(path.ToString()) |
| 14 | 1177 | | }; |
| | 1178 | |
|
| 14 | 1179 | | if (long.TryParse(dateModified, CultureInfo.InvariantCulture, out var ticks) |
| 14 | 1180 | | && ticks >= DateTime.MinValue.Ticks |
| 14 | 1181 | | && ticks <= DateTime.MaxValue.Ticks) |
| | 1182 | | { |
| 12 | 1183 | | image.DateModified = new DateTime(ticks, DateTimeKind.Utc); |
| | 1184 | | } |
| | 1185 | | else |
| | 1186 | | { |
| 2 | 1187 | | return null; |
| | 1188 | | } |
| | 1189 | |
|
| 12 | 1190 | | if (Enum.TryParse(imageType, true, out ImageType type)) |
| | 1191 | | { |
| 11 | 1192 | | image.Type = type; |
| | 1193 | | } |
| | 1194 | | else |
| | 1195 | | { |
| 1 | 1196 | | return null; |
| | 1197 | | } |
| | 1198 | |
|
| | 1199 | | // Optional parameters: width*height*blurhash |
| 11 | 1200 | | if (nextSegment + 1 < value.Length - 1) |
| | 1201 | | { |
| 10 | 1202 | | value = value[(nextSegment + 1)..]; |
| 10 | 1203 | | nextSegment = value.IndexOf(Delimiter); |
| 10 | 1204 | | if (nextSegment == -1 || nextSegment == value.Length) |
| | 1205 | | { |
| 1 | 1206 | | return image; |
| | 1207 | | } |
| | 1208 | |
|
| 9 | 1209 | | ReadOnlySpan<char> widthSpan = value[..nextSegment]; |
| | 1210 | |
|
| 9 | 1211 | | value = value[(nextSegment + 1)..]; |
| 9 | 1212 | | nextSegment = value.IndexOf(Delimiter); |
| 9 | 1213 | | if (nextSegment == -1) |
| | 1214 | | { |
| 6 | 1215 | | nextSegment = value.Length; |
| | 1216 | | } |
| | 1217 | |
|
| 9 | 1218 | | ReadOnlySpan<char> heightSpan = value[..nextSegment]; |
| | 1219 | |
|
| 9 | 1220 | | if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) |
| 9 | 1221 | | && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) |
| | 1222 | | { |
| 9 | 1223 | | image.Width = width; |
| 9 | 1224 | | image.Height = height; |
| | 1225 | | } |
| | 1226 | |
|
| 9 | 1227 | | if (nextSegment < value.Length - 1) |
| | 1228 | | { |
| 3 | 1229 | | value = value[(nextSegment + 1)..]; |
| 3 | 1230 | | var length = value.Length; |
| | 1231 | |
|
| 3 | 1232 | | Span<char> blurHashSpan = stackalloc char[length]; |
| 318 | 1233 | | for (int i = 0; i < length; i++) |
| | 1234 | | { |
| 156 | 1235 | | var c = value[i]; |
| 156 | 1236 | | blurHashSpan[i] = c switch |
| 156 | 1237 | | { |
| 3 | 1238 | | '/' => Delimiter, |
| 0 | 1239 | | '\\' => '|', |
| 153 | 1240 | | _ => c |
| 156 | 1241 | | }; |
| | 1242 | | } |
| | 1243 | |
|
| 3 | 1244 | | image.BlurHash = new string(blurHashSpan); |
| | 1245 | | } |
| | 1246 | | } |
| | 1247 | |
|
| 10 | 1248 | | return image; |
| | 1249 | | } |
| | 1250 | |
|
| | 1251 | | /// <summary> |
| | 1252 | | /// Internal retrieve from items or users table. |
| | 1253 | | /// </summary> |
| | 1254 | | /// <param name="id">The id.</param> |
| | 1255 | | /// <returns>BaseItem.</returns> |
| | 1256 | | /// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception> |
| | 1257 | | /// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception> |
| | 1258 | | public BaseItem RetrieveItem(Guid id) |
| | 1259 | | { |
| 240 | 1260 | | if (id.IsEmpty()) |
| | 1261 | | { |
| 0 | 1262 | | throw new ArgumentException("Guid can't be empty", nameof(id)); |
| | 1263 | | } |
| | 1264 | |
|
| 240 | 1265 | | CheckDisposed(); |
| | 1266 | |
|
| 240 | 1267 | | using (var connection = GetConnection(true)) |
| 240 | 1268 | | using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery)) |
| | 1269 | | { |
| 240 | 1270 | | statement.TryBind("@guid", id); |
| | 1271 | |
|
| 480 | 1272 | | foreach (var row in statement.ExecuteQuery()) |
| | 1273 | | { |
| 0 | 1274 | | return GetItem(row, new InternalItemsQuery()); |
| | 1275 | | } |
| | 1276 | | } |
| | 1277 | |
|
| 240 | 1278 | | return null; |
| 0 | 1279 | | } |
| | 1280 | |
|
| | 1281 | | private bool TypeRequiresDeserialization(Type type) |
| | 1282 | | { |
| 145 | 1283 | | if (_config.Configuration.SkipDeserializationForBasicTypes) |
| | 1284 | | { |
| 145 | 1285 | | if (type == typeof(Channel) |
| 145 | 1286 | | || type == typeof(UserRootFolder)) |
| | 1287 | | { |
| 17 | 1288 | | return false; |
| | 1289 | | } |
| | 1290 | | } |
| | 1291 | |
|
| 128 | 1292 | | return type != typeof(Season) |
| 128 | 1293 | | && type != typeof(MusicArtist) |
| 128 | 1294 | | && type != typeof(Person) |
| 128 | 1295 | | && type != typeof(MusicGenre) |
| 128 | 1296 | | && type != typeof(Genre) |
| 128 | 1297 | | && type != typeof(Studio) |
| 128 | 1298 | | && type != typeof(PlaylistsFolder) |
| 128 | 1299 | | && type != typeof(PhotoAlbum) |
| 128 | 1300 | | && type != typeof(Year) |
| 128 | 1301 | | && type != typeof(Book) |
| 128 | 1302 | | && type != typeof(LiveTvProgram) |
| 128 | 1303 | | && type != typeof(AudioBook) |
| 128 | 1304 | | && type != typeof(MusicAlbum); |
| | 1305 | | } |
| | 1306 | |
|
| | 1307 | | private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query) |
| | 1308 | | { |
| 0 | 1309 | | return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query |
| | 1310 | | } |
| | 1311 | |
|
| | 1312 | | private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool h |
| | 1313 | | { |
| 86 | 1314 | | var typeString = reader.GetString(0); |
| | 1315 | |
|
| 86 | 1316 | | var type = _typeMapper.GetType(typeString); |
| | 1317 | |
|
| 86 | 1318 | | if (type is null) |
| | 1319 | | { |
| 0 | 1320 | | return null; |
| | 1321 | | } |
| | 1322 | |
|
| 86 | 1323 | | BaseItem item = null; |
| | 1324 | |
|
| 86 | 1325 | | if (TypeRequiresDeserialization(type) && !skipDeserialization) |
| | 1326 | | { |
| | 1327 | | try |
| | 1328 | | { |
| 6 | 1329 | | item = JsonSerializer.Deserialize(reader.GetStream(1), type, _jsonOptions) as BaseItem; |
| 6 | 1330 | | } |
| 0 | 1331 | | catch (JsonException ex) |
| | 1332 | | { |
| 0 | 1333 | | Logger.LogError(ex, "Error deserializing item with JSON: {Data}", reader.GetString(1)); |
| 0 | 1334 | | } |
| | 1335 | | } |
| | 1336 | |
|
| 86 | 1337 | | if (item is null) |
| | 1338 | | { |
| | 1339 | | try |
| | 1340 | | { |
| 80 | 1341 | | item = Activator.CreateInstance(type) as BaseItem; |
| 80 | 1342 | | } |
| 0 | 1343 | | catch |
| | 1344 | | { |
| 0 | 1345 | | } |
| | 1346 | | } |
| | 1347 | |
|
| 86 | 1348 | | if (item is null) |
| | 1349 | | { |
| 0 | 1350 | | return null; |
| | 1351 | | } |
| | 1352 | |
|
| 86 | 1353 | | var index = 2; |
| | 1354 | |
|
| 86 | 1355 | | if (queryHasStartDate) |
| | 1356 | | { |
| 86 | 1357 | | if (item is IHasStartDate hasStartDate && reader.TryReadDateTime(index, out var startDate)) |
| | 1358 | | { |
| 0 | 1359 | | hasStartDate.StartDate = startDate; |
| | 1360 | | } |
| | 1361 | |
|
| 86 | 1362 | | index++; |
| | 1363 | | } |
| | 1364 | |
|
| 86 | 1365 | | if (reader.TryReadDateTime(index++, out var endDate)) |
| | 1366 | | { |
| 0 | 1367 | | item.EndDate = endDate; |
| | 1368 | | } |
| | 1369 | |
|
| 86 | 1370 | | if (reader.TryGetGuid(index, out var guid)) |
| | 1371 | | { |
| 0 | 1372 | | item.ChannelId = guid; |
| | 1373 | | } |
| | 1374 | |
|
| 86 | 1375 | | index++; |
| | 1376 | |
|
| 86 | 1377 | | if (enableProgramAttributes) |
| | 1378 | | { |
| 86 | 1379 | | if (item is IHasProgramAttributes hasProgramAttributes) |
| | 1380 | | { |
| 0 | 1381 | | if (reader.TryGetBoolean(index++, out var isMovie)) |
| | 1382 | | { |
| 0 | 1383 | | hasProgramAttributes.IsMovie = isMovie; |
| | 1384 | | } |
| | 1385 | |
|
| 0 | 1386 | | if (reader.TryGetBoolean(index++, out var isSeries)) |
| | 1387 | | { |
| 0 | 1388 | | hasProgramAttributes.IsSeries = isSeries; |
| | 1389 | | } |
| | 1390 | |
|
| 0 | 1391 | | if (reader.TryGetString(index++, out var episodeTitle)) |
| | 1392 | | { |
| 0 | 1393 | | hasProgramAttributes.EpisodeTitle = episodeTitle; |
| | 1394 | | } |
| | 1395 | |
|
| 0 | 1396 | | if (reader.TryGetBoolean(index++, out var isRepeat)) |
| | 1397 | | { |
| 0 | 1398 | | hasProgramAttributes.IsRepeat = isRepeat; |
| | 1399 | | } |
| | 1400 | | } |
| | 1401 | | else |
| | 1402 | | { |
| 86 | 1403 | | index += 4; |
| | 1404 | | } |
| | 1405 | | } |
| | 1406 | |
|
| 86 | 1407 | | if (reader.TryGetSingle(index++, out var communityRating)) |
| | 1408 | | { |
| 0 | 1409 | | item.CommunityRating = communityRating; |
| | 1410 | | } |
| | 1411 | |
|
| 86 | 1412 | | if (HasField(query, ItemFields.CustomRating)) |
| | 1413 | | { |
| 86 | 1414 | | if (reader.TryGetString(index++, out var customRating)) |
| | 1415 | | { |
| 0 | 1416 | | item.CustomRating = customRating; |
| | 1417 | | } |
| | 1418 | | } |
| | 1419 | |
|
| 86 | 1420 | | if (reader.TryGetInt32(index++, out var indexNumber)) |
| | 1421 | | { |
| 0 | 1422 | | item.IndexNumber = indexNumber; |
| | 1423 | | } |
| | 1424 | |
|
| 86 | 1425 | | if (HasField(query, ItemFields.Settings)) |
| | 1426 | | { |
| 86 | 1427 | | if (reader.TryGetBoolean(index++, out var isLocked)) |
| | 1428 | | { |
| 86 | 1429 | | item.IsLocked = isLocked; |
| | 1430 | | } |
| | 1431 | |
|
| 86 | 1432 | | if (reader.TryGetString(index++, out var preferredMetadataLanguage)) |
| | 1433 | | { |
| 0 | 1434 | | item.PreferredMetadataLanguage = preferredMetadataLanguage; |
| | 1435 | | } |
| | 1436 | |
|
| 86 | 1437 | | if (reader.TryGetString(index++, out var preferredMetadataCountryCode)) |
| | 1438 | | { |
| 0 | 1439 | | item.PreferredMetadataCountryCode = preferredMetadataCountryCode; |
| | 1440 | | } |
| | 1441 | | } |
| | 1442 | |
|
| 86 | 1443 | | if (HasField(query, ItemFields.Width)) |
| | 1444 | | { |
| 86 | 1445 | | if (reader.TryGetInt32(index++, out var width)) |
| | 1446 | | { |
| 0 | 1447 | | item.Width = width; |
| | 1448 | | } |
| | 1449 | | } |
| | 1450 | |
|
| 86 | 1451 | | if (HasField(query, ItemFields.Height)) |
| | 1452 | | { |
| 86 | 1453 | | if (reader.TryGetInt32(index++, out var height)) |
| | 1454 | | { |
| 0 | 1455 | | item.Height = height; |
| | 1456 | | } |
| | 1457 | | } |
| | 1458 | |
|
| 86 | 1459 | | if (HasField(query, ItemFields.DateLastRefreshed)) |
| | 1460 | | { |
| 86 | 1461 | | if (reader.TryReadDateTime(index++, out var dateLastRefreshed)) |
| | 1462 | | { |
| 6 | 1463 | | item.DateLastRefreshed = dateLastRefreshed; |
| | 1464 | | } |
| | 1465 | | } |
| | 1466 | |
|
| 86 | 1467 | | if (reader.TryGetString(index++, out var name)) |
| | 1468 | | { |
| 86 | 1469 | | item.Name = name; |
| | 1470 | | } |
| | 1471 | |
|
| 86 | 1472 | | if (reader.TryGetString(index++, out var restorePath)) |
| | 1473 | | { |
| 86 | 1474 | | item.Path = RestorePath(restorePath); |
| | 1475 | | } |
| | 1476 | |
|
| 86 | 1477 | | if (reader.TryReadDateTime(index++, out var premiereDate)) |
| | 1478 | | { |
| 0 | 1479 | | item.PremiereDate = premiereDate; |
| | 1480 | | } |
| | 1481 | |
|
| 86 | 1482 | | if (HasField(query, ItemFields.Overview)) |
| | 1483 | | { |
| 86 | 1484 | | if (reader.TryGetString(index++, out var overview)) |
| | 1485 | | { |
| 0 | 1486 | | item.Overview = overview; |
| | 1487 | | } |
| | 1488 | | } |
| | 1489 | |
|
| 86 | 1490 | | if (reader.TryGetInt32(index++, out var parentIndexNumber)) |
| | 1491 | | { |
| 0 | 1492 | | item.ParentIndexNumber = parentIndexNumber; |
| | 1493 | | } |
| | 1494 | |
|
| 86 | 1495 | | if (reader.TryGetInt32(index++, out var productionYear)) |
| | 1496 | | { |
| 0 | 1497 | | item.ProductionYear = productionYear; |
| | 1498 | | } |
| | 1499 | |
|
| 86 | 1500 | | if (reader.TryGetString(index++, out var officialRating)) |
| | 1501 | | { |
| 0 | 1502 | | item.OfficialRating = officialRating; |
| | 1503 | | } |
| | 1504 | |
|
| 86 | 1505 | | if (HasField(query, ItemFields.SortName)) |
| | 1506 | | { |
| 86 | 1507 | | if (reader.TryGetString(index++, out var forcedSortName)) |
| | 1508 | | { |
| 0 | 1509 | | item.ForcedSortName = forcedSortName; |
| | 1510 | | } |
| | 1511 | | } |
| | 1512 | |
|
| 86 | 1513 | | if (reader.TryGetInt64(index++, out var runTimeTicks)) |
| | 1514 | | { |
| 0 | 1515 | | item.RunTimeTicks = runTimeTicks; |
| | 1516 | | } |
| | 1517 | |
|
| 86 | 1518 | | if (reader.TryGetInt64(index++, out var size)) |
| | 1519 | | { |
| 0 | 1520 | | item.Size = size; |
| | 1521 | | } |
| | 1522 | |
|
| 86 | 1523 | | if (HasField(query, ItemFields.DateCreated)) |
| | 1524 | | { |
| 86 | 1525 | | if (reader.TryReadDateTime(index++, out var dateCreated)) |
| | 1526 | | { |
| 86 | 1527 | | item.DateCreated = dateCreated; |
| | 1528 | | } |
| | 1529 | | } |
| | 1530 | |
|
| 86 | 1531 | | if (reader.TryReadDateTime(index++, out var dateModified)) |
| | 1532 | | { |
| 86 | 1533 | | item.DateModified = dateModified; |
| | 1534 | | } |
| | 1535 | |
|
| 86 | 1536 | | item.Id = reader.GetGuid(index++); |
| | 1537 | |
|
| 86 | 1538 | | if (HasField(query, ItemFields.Genres)) |
| | 1539 | | { |
| 86 | 1540 | | if (reader.TryGetString(index++, out var genres)) |
| | 1541 | | { |
| 0 | 1542 | | item.Genres = genres.Split('|', StringSplitOptions.RemoveEmptyEntries); |
| | 1543 | | } |
| | 1544 | | } |
| | 1545 | |
|
| 86 | 1546 | | if (reader.TryGetGuid(index++, out var parentId)) |
| | 1547 | | { |
| 86 | 1548 | | item.ParentId = parentId; |
| | 1549 | | } |
| | 1550 | |
|
| 86 | 1551 | | if (reader.TryGetString(index++, out var audioString)) |
| | 1552 | | { |
| 0 | 1553 | | if (Enum.TryParse(audioString, true, out ProgramAudio audio)) |
| | 1554 | | { |
| 0 | 1555 | | item.Audio = audio; |
| | 1556 | | } |
| | 1557 | | } |
| | 1558 | |
|
| | 1559 | | // TODO: Even if not needed by apps, the server needs it internally |
| | 1560 | | // But get this excluded from contexts where it is not needed |
| 86 | 1561 | | if (hasServiceName) |
| | 1562 | | { |
| 86 | 1563 | | if (item is LiveTvChannel liveTvChannel) |
| | 1564 | | { |
| 0 | 1565 | | if (reader.TryGetString(index, out var serviceName)) |
| | 1566 | | { |
| 0 | 1567 | | liveTvChannel.ServiceName = serviceName; |
| | 1568 | | } |
| | 1569 | | } |
| | 1570 | |
|
| 86 | 1571 | | index++; |
| | 1572 | | } |
| | 1573 | |
|
| 86 | 1574 | | if (reader.TryGetBoolean(index++, out var isInMixedFolder)) |
| | 1575 | | { |
| 86 | 1576 | | item.IsInMixedFolder = isInMixedFolder; |
| | 1577 | | } |
| | 1578 | |
|
| 86 | 1579 | | if (HasField(query, ItemFields.DateLastSaved)) |
| | 1580 | | { |
| 86 | 1581 | | if (reader.TryReadDateTime(index++, out var dateLastSaved)) |
| | 1582 | | { |
| 86 | 1583 | | item.DateLastSaved = dateLastSaved; |
| | 1584 | | } |
| | 1585 | | } |
| | 1586 | |
|
| 86 | 1587 | | if (HasField(query, ItemFields.Settings)) |
| | 1588 | | { |
| 86 | 1589 | | if (reader.TryGetString(index++, out var lockedFields)) |
| | 1590 | | { |
| 0 | 1591 | | List<MetadataField> fields = null; |
| 0 | 1592 | | foreach (var i in lockedFields.AsSpan().Split('|')) |
| | 1593 | | { |
| 0 | 1594 | | if (Enum.TryParse(i, true, out MetadataField parsedValue)) |
| | 1595 | | { |
| 0 | 1596 | | (fields ??= new List<MetadataField>()).Add(parsedValue); |
| | 1597 | | } |
| | 1598 | | } |
| | 1599 | |
|
| 0 | 1600 | | item.LockedFields = fields?.ToArray() ?? Array.Empty<MetadataField>(); |
| | 1601 | | } |
| | 1602 | | } |
| | 1603 | |
|
| 86 | 1604 | | if (HasField(query, ItemFields.Studios)) |
| | 1605 | | { |
| 86 | 1606 | | if (reader.TryGetString(index++, out var studios)) |
| | 1607 | | { |
| 0 | 1608 | | item.Studios = studios.Split('|', StringSplitOptions.RemoveEmptyEntries); |
| | 1609 | | } |
| | 1610 | | } |
| | 1611 | |
|
| 86 | 1612 | | if (HasField(query, ItemFields.Tags)) |
| | 1613 | | { |
| 86 | 1614 | | if (reader.TryGetString(index++, out var tags)) |
| | 1615 | | { |
| 0 | 1616 | | item.Tags = tags.Split('|', StringSplitOptions.RemoveEmptyEntries); |
| | 1617 | | } |
| | 1618 | | } |
| | 1619 | |
|
| 86 | 1620 | | if (hasTrailerTypes) |
| | 1621 | | { |
| 86 | 1622 | | if (item is Trailer trailer) |
| | 1623 | | { |
| 0 | 1624 | | if (reader.TryGetString(index, out var trailerTypes)) |
| | 1625 | | { |
| 0 | 1626 | | List<TrailerType> types = null; |
| 0 | 1627 | | foreach (var i in trailerTypes.AsSpan().Split('|')) |
| | 1628 | | { |
| 0 | 1629 | | if (Enum.TryParse(i, true, out TrailerType parsedValue)) |
| | 1630 | | { |
| 0 | 1631 | | (types ??= new List<TrailerType>()).Add(parsedValue); |
| | 1632 | | } |
| | 1633 | | } |
| | 1634 | |
|
| 0 | 1635 | | trailer.TrailerTypes = types?.ToArray() ?? Array.Empty<TrailerType>(); |
| | 1636 | | } |
| | 1637 | | } |
| | 1638 | |
|
| 86 | 1639 | | index++; |
| | 1640 | | } |
| | 1641 | |
|
| 86 | 1642 | | if (HasField(query, ItemFields.OriginalTitle)) |
| | 1643 | | { |
| 86 | 1644 | | if (reader.TryGetString(index++, out var originalTitle)) |
| | 1645 | | { |
| 0 | 1646 | | item.OriginalTitle = originalTitle; |
| | 1647 | | } |
| | 1648 | | } |
| | 1649 | |
|
| 86 | 1650 | | if (item is Video video) |
| | 1651 | | { |
| 0 | 1652 | | if (reader.TryGetString(index, out var primaryVersionId)) |
| | 1653 | | { |
| 0 | 1654 | | video.PrimaryVersionId = primaryVersionId; |
| | 1655 | | } |
| | 1656 | | } |
| | 1657 | |
|
| 86 | 1658 | | index++; |
| | 1659 | |
|
| 86 | 1660 | | if (HasField(query, ItemFields.DateLastMediaAdded)) |
| | 1661 | | { |
| 86 | 1662 | | if (item is Folder folder && reader.TryReadDateTime(index, out var dateLastMediaAdded)) |
| | 1663 | | { |
| 0 | 1664 | | folder.DateLastMediaAdded = dateLastMediaAdded; |
| | 1665 | | } |
| | 1666 | |
|
| 86 | 1667 | | index++; |
| | 1668 | | } |
| | 1669 | |
|
| 86 | 1670 | | if (reader.TryGetString(index++, out var album)) |
| | 1671 | | { |
| 0 | 1672 | | item.Album = album; |
| | 1673 | | } |
| | 1674 | |
|
| 86 | 1675 | | if (reader.TryGetSingle(index++, out var lUFS)) |
| | 1676 | | { |
| 0 | 1677 | | item.LUFS = lUFS; |
| | 1678 | | } |
| | 1679 | |
|
| 86 | 1680 | | if (reader.TryGetSingle(index++, out var normalizationGain)) |
| | 1681 | | { |
| 0 | 1682 | | item.NormalizationGain = normalizationGain; |
| | 1683 | | } |
| | 1684 | |
|
| 86 | 1685 | | if (reader.TryGetSingle(index++, out var criticRating)) |
| | 1686 | | { |
| 0 | 1687 | | item.CriticRating = criticRating; |
| | 1688 | | } |
| | 1689 | |
|
| 86 | 1690 | | if (reader.TryGetBoolean(index++, out var isVirtualItem)) |
| | 1691 | | { |
| 86 | 1692 | | item.IsVirtualItem = isVirtualItem; |
| | 1693 | | } |
| | 1694 | |
|
| 86 | 1695 | | if (item is IHasSeries hasSeriesName) |
| | 1696 | | { |
| 0 | 1697 | | if (reader.TryGetString(index, out var seriesName)) |
| | 1698 | | { |
| 0 | 1699 | | hasSeriesName.SeriesName = seriesName; |
| | 1700 | | } |
| | 1701 | | } |
| | 1702 | |
|
| 86 | 1703 | | index++; |
| | 1704 | |
|
| 86 | 1705 | | if (hasEpisodeAttributes) |
| | 1706 | | { |
| 86 | 1707 | | if (item is Episode episode) |
| | 1708 | | { |
| 0 | 1709 | | if (reader.TryGetString(index, out var seasonName)) |
| | 1710 | | { |
| 0 | 1711 | | episode.SeasonName = seasonName; |
| | 1712 | | } |
| | 1713 | |
|
| 0 | 1714 | | index++; |
| 0 | 1715 | | if (reader.TryGetGuid(index, out var seasonId)) |
| | 1716 | | { |
| 0 | 1717 | | episode.SeasonId = seasonId; |
| | 1718 | | } |
| | 1719 | | } |
| | 1720 | | else |
| | 1721 | | { |
| 86 | 1722 | | index++; |
| | 1723 | | } |
| | 1724 | |
|
| 86 | 1725 | | index++; |
| | 1726 | | } |
| | 1727 | |
|
| 86 | 1728 | | var hasSeries = item as IHasSeries; |
| 86 | 1729 | | if (hasSeriesFields) |
| | 1730 | | { |
| 86 | 1731 | | if (hasSeries is not null) |
| | 1732 | | { |
| 0 | 1733 | | if (reader.TryGetGuid(index, out var seriesId)) |
| | 1734 | | { |
| 0 | 1735 | | hasSeries.SeriesId = seriesId; |
| | 1736 | | } |
| | 1737 | | } |
| | 1738 | |
|
| 86 | 1739 | | index++; |
| | 1740 | | } |
| | 1741 | |
|
| 86 | 1742 | | if (HasField(query, ItemFields.PresentationUniqueKey)) |
| | 1743 | | { |
| 86 | 1744 | | if (reader.TryGetString(index++, out var presentationUniqueKey)) |
| | 1745 | | { |
| 6 | 1746 | | item.PresentationUniqueKey = presentationUniqueKey; |
| | 1747 | | } |
| | 1748 | | } |
| | 1749 | |
|
| 86 | 1750 | | if (HasField(query, ItemFields.InheritedParentalRatingValue)) |
| | 1751 | | { |
| 86 | 1752 | | if (reader.TryGetInt32(index++, out var parentalRating)) |
| | 1753 | | { |
| 0 | 1754 | | item.InheritedParentalRatingValue = parentalRating; |
| | 1755 | | } |
| | 1756 | | } |
| | 1757 | |
|
| 86 | 1758 | | if (HasField(query, ItemFields.ExternalSeriesId)) |
| | 1759 | | { |
| 86 | 1760 | | if (reader.TryGetString(index++, out var externalSeriesId)) |
| | 1761 | | { |
| 0 | 1762 | | item.ExternalSeriesId = externalSeriesId; |
| | 1763 | | } |
| | 1764 | | } |
| | 1765 | |
|
| 86 | 1766 | | if (HasField(query, ItemFields.Taglines)) |
| | 1767 | | { |
| 86 | 1768 | | if (reader.TryGetString(index++, out var tagLine)) |
| | 1769 | | { |
| 0 | 1770 | | item.Tagline = tagLine; |
| | 1771 | | } |
| | 1772 | | } |
| | 1773 | |
|
| 86 | 1774 | | if (item.ProviderIds.Count == 0 && reader.TryGetString(index, out var providerIds)) |
| | 1775 | | { |
| 0 | 1776 | | DeserializeProviderIds(providerIds, item); |
| | 1777 | | } |
| | 1778 | |
|
| 86 | 1779 | | index++; |
| | 1780 | |
|
| 86 | 1781 | | if (query.DtoOptions.EnableImages) |
| | 1782 | | { |
| 86 | 1783 | | if (item.ImageInfos.Length == 0 && reader.TryGetString(index, out var imageInfos)) |
| | 1784 | | { |
| 0 | 1785 | | item.ImageInfos = DeserializeImages(imageInfos); |
| | 1786 | | } |
| | 1787 | |
|
| 86 | 1788 | | index++; |
| | 1789 | | } |
| | 1790 | |
|
| 86 | 1791 | | if (HasField(query, ItemFields.ProductionLocations)) |
| | 1792 | | { |
| 86 | 1793 | | if (reader.TryGetString(index++, out var productionLocations)) |
| | 1794 | | { |
| 0 | 1795 | | item.ProductionLocations = productionLocations.Split('|', StringSplitOptions.RemoveEmptyEntries); |
| | 1796 | | } |
| | 1797 | | } |
| | 1798 | |
|
| 86 | 1799 | | if (HasField(query, ItemFields.ExtraIds)) |
| | 1800 | | { |
| 86 | 1801 | | if (reader.TryGetString(index++, out var extraIds)) |
| | 1802 | | { |
| 0 | 1803 | | item.ExtraIds = SplitToGuids(extraIds); |
| | 1804 | | } |
| | 1805 | | } |
| | 1806 | |
|
| 86 | 1807 | | if (reader.TryGetInt32(index++, out var totalBitrate)) |
| | 1808 | | { |
| 0 | 1809 | | item.TotalBitrate = totalBitrate; |
| | 1810 | | } |
| | 1811 | |
|
| 86 | 1812 | | if (reader.TryGetString(index++, out var extraTypeString)) |
| | 1813 | | { |
| 0 | 1814 | | if (Enum.TryParse(extraTypeString, true, out ExtraType extraType)) |
| | 1815 | | { |
| 0 | 1816 | | item.ExtraType = extraType; |
| | 1817 | | } |
| | 1818 | | } |
| | 1819 | |
|
| 86 | 1820 | | if (hasArtistFields) |
| | 1821 | | { |
| 86 | 1822 | | if (item is IHasArtist hasArtists && reader.TryGetString(index, out var artists)) |
| | 1823 | | { |
| 0 | 1824 | | hasArtists.Artists = artists.Split('|', StringSplitOptions.RemoveEmptyEntries); |
| | 1825 | | } |
| | 1826 | |
|
| 86 | 1827 | | index++; |
| | 1828 | |
|
| 86 | 1829 | | if (item is IHasAlbumArtist hasAlbumArtists && reader.TryGetString(index, out var albumArtists)) |
| | 1830 | | { |
| 0 | 1831 | | hasAlbumArtists.AlbumArtists = albumArtists.Split('|', StringSplitOptions.RemoveEmptyEntries); |
| | 1832 | | } |
| | 1833 | |
|
| 86 | 1834 | | index++; |
| | 1835 | | } |
| | 1836 | |
|
| 86 | 1837 | | if (reader.TryGetString(index++, out var externalId)) |
| | 1838 | | { |
| 0 | 1839 | | item.ExternalId = externalId; |
| | 1840 | | } |
| | 1841 | |
|
| 86 | 1842 | | if (HasField(query, ItemFields.SeriesPresentationUniqueKey)) |
| | 1843 | | { |
| 86 | 1844 | | if (hasSeries is not null) |
| | 1845 | | { |
| 0 | 1846 | | if (reader.TryGetString(index, out var seriesPresentationUniqueKey)) |
| | 1847 | | { |
| 0 | 1848 | | hasSeries.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; |
| | 1849 | | } |
| | 1850 | | } |
| | 1851 | |
|
| 86 | 1852 | | index++; |
| | 1853 | | } |
| | 1854 | |
|
| 86 | 1855 | | if (enableProgramAttributes) |
| | 1856 | | { |
| 86 | 1857 | | if (item is LiveTvProgram program && reader.TryGetString(index, out var showId)) |
| | 1858 | | { |
| 0 | 1859 | | program.ShowId = showId; |
| | 1860 | | } |
| | 1861 | |
|
| 86 | 1862 | | index++; |
| | 1863 | | } |
| | 1864 | |
|
| 86 | 1865 | | if (reader.TryGetGuid(index, out var ownerId)) |
| | 1866 | | { |
| 0 | 1867 | | item.OwnerId = ownerId; |
| | 1868 | | } |
| | 1869 | |
|
| 86 | 1870 | | return item; |
| | 1871 | | } |
| | 1872 | |
|
| | 1873 | | private static Guid[] SplitToGuids(string value) |
| | 1874 | | { |
| 0 | 1875 | | var ids = value.Split('|'); |
| | 1876 | |
|
| 0 | 1877 | | var result = new Guid[ids.Length]; |
| | 1878 | |
|
| 0 | 1879 | | for (var i = 0; i < result.Length; i++) |
| | 1880 | | { |
| 0 | 1881 | | result[i] = new Guid(ids[i]); |
| | 1882 | | } |
| | 1883 | |
|
| 0 | 1884 | | return result; |
| | 1885 | | } |
| | 1886 | |
|
| | 1887 | | /// <inheritdoc /> |
| | 1888 | | public List<ChapterInfo> GetChapters(BaseItem item) |
| | 1889 | | { |
| 0 | 1890 | | CheckDisposed(); |
| | 1891 | |
|
| 0 | 1892 | | var chapters = new List<ChapterInfo>(); |
| 0 | 1893 | | using (var connection = GetConnection(true)) |
| 0 | 1894 | | using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModif |
| | 1895 | | { |
| 0 | 1896 | | statement.TryBind("@ItemId", item.Id); |
| | 1897 | |
|
| 0 | 1898 | | foreach (var row in statement.ExecuteQuery()) |
| | 1899 | | { |
| 0 | 1900 | | chapters.Add(GetChapter(row, item)); |
| | 1901 | | } |
| | 1902 | | } |
| | 1903 | |
|
| 0 | 1904 | | return chapters; |
| | 1905 | | } |
| | 1906 | |
|
| | 1907 | | /// <inheritdoc /> |
| | 1908 | | public ChapterInfo GetChapter(BaseItem item, int index) |
| | 1909 | | { |
| 0 | 1910 | | CheckDisposed(); |
| | 1911 | |
|
| 0 | 1912 | | using (var connection = GetConnection(true)) |
| 0 | 1913 | | using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModif |
| | 1914 | | { |
| 0 | 1915 | | statement.TryBind("@ItemId", item.Id); |
| 0 | 1916 | | statement.TryBind("@ChapterIndex", index); |
| | 1917 | |
|
| 0 | 1918 | | foreach (var row in statement.ExecuteQuery()) |
| | 1919 | | { |
| 0 | 1920 | | return GetChapter(row, item); |
| | 1921 | | } |
| | 1922 | | } |
| | 1923 | |
|
| 0 | 1924 | | return null; |
| 0 | 1925 | | } |
| | 1926 | |
|
| | 1927 | | /// <summary> |
| | 1928 | | /// Gets the chapter. |
| | 1929 | | /// </summary> |
| | 1930 | | /// <param name="reader">The reader.</param> |
| | 1931 | | /// <param name="item">The item.</param> |
| | 1932 | | /// <returns>ChapterInfo.</returns> |
| | 1933 | | private ChapterInfo GetChapter(SqliteDataReader reader, BaseItem item) |
| | 1934 | | { |
| 0 | 1935 | | var chapter = new ChapterInfo |
| 0 | 1936 | | { |
| 0 | 1937 | | StartPositionTicks = reader.GetInt64(0) |
| 0 | 1938 | | }; |
| | 1939 | |
|
| 0 | 1940 | | if (reader.TryGetString(1, out var chapterName)) |
| | 1941 | | { |
| 0 | 1942 | | chapter.Name = chapterName; |
| | 1943 | | } |
| | 1944 | |
|
| 0 | 1945 | | if (reader.TryGetString(2, out var imagePath)) |
| | 1946 | | { |
| 0 | 1947 | | chapter.ImagePath = imagePath; |
| 0 | 1948 | | chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter); |
| | 1949 | | } |
| | 1950 | |
|
| 0 | 1951 | | if (reader.TryReadDateTime(3, out var imageDateModified)) |
| | 1952 | | { |
| 0 | 1953 | | chapter.ImageDateModified = imageDateModified; |
| | 1954 | | } |
| | 1955 | |
|
| 0 | 1956 | | return chapter; |
| | 1957 | | } |
| | 1958 | |
|
| | 1959 | | /// <summary> |
| | 1960 | | /// Saves the chapters. |
| | 1961 | | /// </summary> |
| | 1962 | | /// <param name="id">The item id.</param> |
| | 1963 | | /// <param name="chapters">The chapters.</param> |
| | 1964 | | public void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters) |
| | 1965 | | { |
| 0 | 1966 | | CheckDisposed(); |
| | 1967 | |
|
| 0 | 1968 | | if (id.IsEmpty()) |
| | 1969 | | { |
| 0 | 1970 | | throw new ArgumentNullException(nameof(id)); |
| | 1971 | | } |
| | 1972 | |
|
| 0 | 1973 | | ArgumentNullException.ThrowIfNull(chapters); |
| | 1974 | |
|
| 0 | 1975 | | using var connection = GetConnection(); |
| 0 | 1976 | | using var transaction = connection.BeginTransaction(); |
| | 1977 | | // First delete chapters |
| 0 | 1978 | | using var command = connection.PrepareStatement($"delete from {ChaptersTableName} where ItemId=@ItemId"); |
| 0 | 1979 | | command.TryBind("@ItemId", id); |
| 0 | 1980 | | command.ExecuteNonQuery(); |
| | 1981 | |
|
| 0 | 1982 | | InsertChapters(id, chapters, connection); |
| 0 | 1983 | | transaction.Commit(); |
| 0 | 1984 | | } |
| | 1985 | |
|
| | 1986 | | private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, ManagedConnection db) |
| | 1987 | | { |
| 0 | 1988 | | var startIndex = 0; |
| 0 | 1989 | | var limit = 100; |
| 0 | 1990 | | var chapterIndex = 0; |
| | 1991 | |
|
| | 1992 | | const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTi |
| 0 | 1993 | | var insertText = new StringBuilder(StartInsertText, 256); |
| | 1994 | |
|
| 0 | 1995 | | while (startIndex < chapters.Count) |
| | 1996 | | { |
| 0 | 1997 | | var endIndex = Math.Min(chapters.Count, startIndex + limit); |
| | 1998 | |
|
| 0 | 1999 | | for (var i = startIndex; i < endIndex; i++) |
| | 2000 | | { |
| 0 | 2001 | | insertText.AppendFormat(CultureInfo.InvariantCulture, "(@ItemId, @ChapterIndex{0}, @StartPositionTic |
| | 2002 | | } |
| | 2003 | |
|
| 0 | 2004 | | insertText.Length -= 1; // Remove trailing comma |
| | 2005 | |
|
| 0 | 2006 | | using (var statement = PrepareStatement(db, insertText.ToString())) |
| | 2007 | | { |
| 0 | 2008 | | statement.TryBind("@ItemId", idBlob); |
| | 2009 | |
|
| 0 | 2010 | | for (var i = startIndex; i < endIndex; i++) |
| | 2011 | | { |
| 0 | 2012 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| | 2013 | |
|
| 0 | 2014 | | var chapter = chapters[i]; |
| | 2015 | |
|
| 0 | 2016 | | statement.TryBind("@ChapterIndex" + index, chapterIndex); |
| 0 | 2017 | | statement.TryBind("@StartPositionTicks" + index, chapter.StartPositionTicks); |
| 0 | 2018 | | statement.TryBind("@Name" + index, chapter.Name); |
| 0 | 2019 | | statement.TryBind("@ImagePath" + index, chapter.ImagePath); |
| 0 | 2020 | | statement.TryBind("@ImageDateModified" + index, chapter.ImageDateModified); |
| | 2021 | |
|
| 0 | 2022 | | chapterIndex++; |
| | 2023 | | } |
| | 2024 | |
|
| 0 | 2025 | | statement.ExecuteNonQuery(); |
| 0 | 2026 | | } |
| | 2027 | |
|
| 0 | 2028 | | startIndex += limit; |
| 0 | 2029 | | insertText.Length = StartInsertText.Length; |
| | 2030 | | } |
| 0 | 2031 | | } |
| | 2032 | |
|
| | 2033 | | private static bool EnableJoinUserData(InternalItemsQuery query) |
| | 2034 | | { |
| 1680 | 2035 | | if (query.User is null) |
| | 2036 | | { |
| 1670 | 2037 | | return false; |
| | 2038 | | } |
| | 2039 | |
|
| 10 | 2040 | | var sortingFields = new HashSet<ItemSortBy>(query.OrderBy.Select(i => i.OrderBy)); |
| | 2041 | |
|
| 10 | 2042 | | return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked) |
| 10 | 2043 | | || sortingFields.Contains(ItemSortBy.IsPlayed) |
| 10 | 2044 | | || sortingFields.Contains(ItemSortBy.IsUnplayed) |
| 10 | 2045 | | || sortingFields.Contains(ItemSortBy.PlayCount) |
| 10 | 2046 | | || sortingFields.Contains(ItemSortBy.DatePlayed) |
| 10 | 2047 | | || sortingFields.Contains(ItemSortBy.SeriesDatePlayed) |
| 10 | 2048 | | || query.IsFavoriteOrLiked.HasValue |
| 10 | 2049 | | || query.IsFavorite.HasValue |
| 10 | 2050 | | || query.IsResumable.HasValue |
| 10 | 2051 | | || query.IsPlayed.HasValue |
| 10 | 2052 | | || query.IsLiked.HasValue; |
| | 2053 | | } |
| | 2054 | |
|
| | 2055 | | private bool HasField(InternalItemsQuery query, ItemFields name) |
| | 2056 | | { |
| | 2057 | | switch (name) |
| | 2058 | | { |
| | 2059 | | case ItemFields.Tags: |
| 422 | 2060 | | return query.DtoOptions.ContainsField(name) || HasProgramAttributes(query); |
| | 2061 | | case ItemFields.CustomRating: |
| | 2062 | | case ItemFields.ProductionLocations: |
| | 2063 | | case ItemFields.Settings: |
| | 2064 | | case ItemFields.OriginalTitle: |
| | 2065 | | case ItemFields.Taglines: |
| | 2066 | | case ItemFields.SortName: |
| | 2067 | | case ItemFields.Studios: |
| | 2068 | | case ItemFields.ExtraIds: |
| | 2069 | | case ItemFields.DateCreated: |
| | 2070 | | case ItemFields.Overview: |
| | 2071 | | case ItemFields.Genres: |
| | 2072 | | case ItemFields.DateLastMediaAdded: |
| | 2073 | | case ItemFields.PresentationUniqueKey: |
| | 2074 | | case ItemFields.InheritedParentalRatingValue: |
| | 2075 | | case ItemFields.ExternalSeriesId: |
| | 2076 | | case ItemFields.SeriesPresentationUniqueKey: |
| | 2077 | | case ItemFields.DateLastRefreshed: |
| | 2078 | | case ItemFields.DateLastSaved: |
| 7682 | 2079 | | return query.DtoOptions.ContainsField(name); |
| | 2080 | | case ItemFields.ServiceName: |
| 336 | 2081 | | return HasServiceName(query); |
| | 2082 | | default: |
| 13612 | 2083 | | return true; |
| | 2084 | | } |
| | 2085 | | } |
| | 2086 | |
|
| | 2087 | | private bool HasProgramAttributes(InternalItemsQuery query) |
| | 2088 | | { |
| 715 | 2089 | | if (query.ParentType is not null && _programExcludeParentTypes.Contains(query.ParentType.Value)) |
| | 2090 | | { |
| 0 | 2091 | | return false; |
| | 2092 | | } |
| | 2093 | |
|
| 715 | 2094 | | if (query.IncludeItemTypes.Length == 0) |
| | 2095 | | { |
| 448 | 2096 | | return true; |
| | 2097 | | } |
| | 2098 | |
|
| 267 | 2099 | | return query.IncludeItemTypes.Any(x => _programTypes.Contains(x)); |
| | 2100 | | } |
| | 2101 | |
|
| | 2102 | | private bool HasServiceName(InternalItemsQuery query) |
| | 2103 | | { |
| 653 | 2104 | | if (query.ParentType is not null && _programExcludeParentTypes.Contains(query.ParentType.Value)) |
| | 2105 | | { |
| 0 | 2106 | | return false; |
| | 2107 | | } |
| | 2108 | |
|
| 653 | 2109 | | if (query.IncludeItemTypes.Length == 0) |
| | 2110 | | { |
| 447 | 2111 | | return true; |
| | 2112 | | } |
| | 2113 | |
|
| 206 | 2114 | | return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x)); |
| | 2115 | | } |
| | 2116 | |
|
| | 2117 | | private bool HasStartDate(InternalItemsQuery query) |
| | 2118 | | { |
| 653 | 2119 | | if (query.ParentType is not null && _programExcludeParentTypes.Contains(query.ParentType.Value)) |
| | 2120 | | { |
| 0 | 2121 | | return false; |
| | 2122 | | } |
| | 2123 | |
|
| 653 | 2124 | | if (query.IncludeItemTypes.Length == 0) |
| | 2125 | | { |
| 447 | 2126 | | return true; |
| | 2127 | | } |
| | 2128 | |
|
| 206 | 2129 | | return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x)); |
| | 2130 | | } |
| | 2131 | |
|
| | 2132 | | private bool HasEpisodeAttributes(InternalItemsQuery query) |
| | 2133 | | { |
| 989 | 2134 | | if (query.IncludeItemTypes.Length == 0) |
| | 2135 | | { |
| 680 | 2136 | | return true; |
| | 2137 | | } |
| | 2138 | |
|
| 309 | 2139 | | return query.IncludeItemTypes.Contains(BaseItemKind.Episode); |
| | 2140 | | } |
| | 2141 | |
|
| | 2142 | | private bool HasTrailerTypes(InternalItemsQuery query) |
| | 2143 | | { |
| 653 | 2144 | | if (query.IncludeItemTypes.Length == 0) |
| | 2145 | | { |
| 447 | 2146 | | return true; |
| | 2147 | | } |
| | 2148 | |
|
| 206 | 2149 | | return query.IncludeItemTypes.Contains(BaseItemKind.Trailer); |
| | 2150 | | } |
| | 2151 | |
|
| | 2152 | | private bool HasArtistFields(InternalItemsQuery query) |
| | 2153 | | { |
| 653 | 2154 | | if (query.ParentType is not null && _artistExcludeParentTypes.Contains(query.ParentType.Value)) |
| | 2155 | | { |
| 0 | 2156 | | return false; |
| | 2157 | | } |
| | 2158 | |
|
| 653 | 2159 | | if (query.IncludeItemTypes.Length == 0) |
| | 2160 | | { |
| 447 | 2161 | | return true; |
| | 2162 | | } |
| | 2163 | |
|
| 206 | 2164 | | return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x)); |
| | 2165 | | } |
| | 2166 | |
|
| | 2167 | | private bool HasSeriesFields(InternalItemsQuery query) |
| | 2168 | | { |
| 653 | 2169 | | if (query.ParentType == BaseItemKind.PhotoAlbum) |
| | 2170 | | { |
| 0 | 2171 | | return false; |
| | 2172 | | } |
| | 2173 | |
|
| 653 | 2174 | | if (query.IncludeItemTypes.Length == 0) |
| | 2175 | | { |
| 447 | 2176 | | return true; |
| | 2177 | | } |
| | 2178 | |
|
| 206 | 2179 | | return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x)); |
| | 2180 | | } |
| | 2181 | |
|
| | 2182 | | private void SetFinalColumnsToSelect(InternalItemsQuery query, List<string> columns) |
| | 2183 | | { |
| 40992 | 2184 | | foreach (var field in _allItemFields) |
| | 2185 | | { |
| 20160 | 2186 | | if (!HasField(query, field)) |
| | 2187 | | { |
| | 2188 | | switch (field) |
| | 2189 | | { |
| | 2190 | | case ItemFields.Settings: |
| 62 | 2191 | | columns.Remove("IsLocked"); |
| 62 | 2192 | | columns.Remove("PreferredMetadataCountryCode"); |
| 62 | 2193 | | columns.Remove("PreferredMetadataLanguage"); |
| 62 | 2194 | | columns.Remove("LockedFields"); |
| 62 | 2195 | | break; |
| | 2196 | | case ItemFields.ServiceName: |
| 103 | 2197 | | columns.Remove("ExternalServiceId"); |
| 103 | 2198 | | break; |
| | 2199 | | case ItemFields.SortName: |
| 62 | 2200 | | columns.Remove("ForcedSortName"); |
| 62 | 2201 | | break; |
| | 2202 | | case ItemFields.Taglines: |
| 62 | 2203 | | columns.Remove("Tagline"); |
| 62 | 2204 | | break; |
| | 2205 | | case ItemFields.Tags: |
| 61 | 2206 | | columns.Remove("Tags"); |
| 61 | 2207 | | break; |
| | 2208 | | case ItemFields.IsHD: |
| | 2209 | | // do nothing |
| | 2210 | | break; |
| | 2211 | | default: |
| 930 | 2212 | | columns.Remove(field.ToString()); |
| | 2213 | | break; |
| | 2214 | | } |
| | 2215 | | } |
| | 2216 | | } |
| | 2217 | |
|
| 336 | 2218 | | if (!HasProgramAttributes(query)) |
| | 2219 | | { |
| 103 | 2220 | | columns.Remove("IsMovie"); |
| 103 | 2221 | | columns.Remove("IsSeries"); |
| 103 | 2222 | | columns.Remove("EpisodeTitle"); |
| 103 | 2223 | | columns.Remove("IsRepeat"); |
| 103 | 2224 | | columns.Remove("ShowId"); |
| | 2225 | | } |
| | 2226 | |
|
| 336 | 2227 | | if (!HasEpisodeAttributes(query)) |
| | 2228 | | { |
| 103 | 2229 | | columns.Remove("SeasonName"); |
| 103 | 2230 | | columns.Remove("SeasonId"); |
| | 2231 | | } |
| | 2232 | |
|
| 336 | 2233 | | if (!HasStartDate(query)) |
| | 2234 | | { |
| 103 | 2235 | | columns.Remove("StartDate"); |
| | 2236 | | } |
| | 2237 | |
|
| 336 | 2238 | | if (!HasTrailerTypes(query)) |
| | 2239 | | { |
| 103 | 2240 | | columns.Remove("TrailerTypes"); |
| | 2241 | | } |
| | 2242 | |
|
| 336 | 2243 | | if (!HasArtistFields(query)) |
| | 2244 | | { |
| 99 | 2245 | | columns.Remove("AlbumArtists"); |
| 99 | 2246 | | columns.Remove("Artists"); |
| | 2247 | | } |
| | 2248 | |
|
| 336 | 2249 | | if (!HasSeriesFields(query)) |
| | 2250 | | { |
| 103 | 2251 | | columns.Remove("SeriesId"); |
| | 2252 | | } |
| | 2253 | |
|
| 336 | 2254 | | if (!HasEpisodeAttributes(query)) |
| | 2255 | | { |
| 103 | 2256 | | columns.Remove("SeasonName"); |
| 103 | 2257 | | columns.Remove("SeasonId"); |
| | 2258 | | } |
| | 2259 | |
|
| 336 | 2260 | | if (!query.DtoOptions.EnableImages) |
| | 2261 | | { |
| 0 | 2262 | | columns.Remove("Images"); |
| | 2263 | | } |
| | 2264 | |
|
| 336 | 2265 | | if (EnableJoinUserData(query)) |
| | 2266 | | { |
| 1 | 2267 | | columns.Add("UserDatas.UserId"); |
| 1 | 2268 | | columns.Add("UserDatas.lastPlayedDate"); |
| 1 | 2269 | | columns.Add("UserDatas.playbackPositionTicks"); |
| 1 | 2270 | | columns.Add("UserDatas.playcount"); |
| 1 | 2271 | | columns.Add("UserDatas.isFavorite"); |
| 1 | 2272 | | columns.Add("UserDatas.played"); |
| 1 | 2273 | | columns.Add("UserDatas.rating"); |
| | 2274 | | } |
| | 2275 | |
|
| 336 | 2276 | | if (query.SimilarTo is not null) |
| | 2277 | | { |
| 0 | 2278 | | var item = query.SimilarTo; |
| | 2279 | |
|
| 0 | 2280 | | var builder = new StringBuilder(); |
| 0 | 2281 | | builder.Append('('); |
| | 2282 | |
|
| 0 | 2283 | | if (item.InheritedParentalRatingValue == 0) |
| | 2284 | | { |
| 0 | 2285 | | builder.Append("((InheritedParentalRatingValue=0) * 10)"); |
| | 2286 | | } |
| | 2287 | | else |
| | 2288 | | { |
| 0 | 2289 | | builder.Append( |
| 0 | 2290 | | @"(SELECT CASE WHEN COALESCE(InheritedParentalRatingValue, 0)=0 |
| 0 | 2291 | | THEN 0 |
| 0 | 2292 | | ELSE 10.0 / (1.0 + ABS(InheritedParentalRatingValue - @InheritedParentalRatingValue)) |
| 0 | 2293 | | END)"); |
| | 2294 | | } |
| | 2295 | |
|
| 0 | 2296 | | if (item.ProductionYear.HasValue) |
| | 2297 | | { |
| 0 | 2298 | | builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then |
| 0 | 2299 | | builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 5 |
| | 2300 | | } |
| | 2301 | |
|
| | 2302 | | // genres, tags, studios, person, year? |
| 0 | 2303 | | builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select Clea |
| 0 | 2304 | | builder.Append("+ (Select count(1) * 10 from People where ItemId=Guid and Name in (select Name from Peop |
| | 2305 | |
|
| 0 | 2306 | | if (item is MusicArtist) |
| | 2307 | | { |
| | 2308 | | // Match albums where the artist is AlbumArtist against other albums. |
| | 2309 | | // It is assumed that similar albums => similar artists. |
| 0 | 2310 | | builder.Append( |
| 0 | 2311 | | @"+ (WITH artistValues AS ( |
| 0 | 2312 | | SELECT DISTINCT albumValues.CleanValue |
| 0 | 2313 | | FROM ItemValues albumValues |
| 0 | 2314 | | INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId |
| 0 | 2315 | | INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND |
| 0 | 2316 | | ), similarArtist AS ( |
| 0 | 2317 | | SELECT albumValues.ItemId |
| 0 | 2318 | | FROM ItemValues albumValues |
| 0 | 2319 | | INNER JOIN ItemValues artistAlbums ON albumValues.ItemId = artistAlbums.ItemId |
| 0 | 2320 | | INNER JOIN TypedBaseItems artistItem ON artistAlbums.CleanValue = artistItem.CleanName AND |
| 0 | 2321 | | ) SELECT COUNT(DISTINCT(CleanValue)) * 10 FROM ItemValues WHERE ItemId IN (SELECT ItemId FRO |
| | 2322 | | } |
| | 2323 | |
|
| 0 | 2324 | | builder.Append(") as SimilarityScore"); |
| | 2325 | |
|
| 0 | 2326 | | columns.Add(builder.ToString()); |
| | 2327 | |
|
| 0 | 2328 | | query.ExcludeItemIds = [.. query.ExcludeItemIds, item.Id, .. item.ExtraIds]; |
| 0 | 2329 | | query.ExcludeProviderIds = item.ProviderIds; |
| | 2330 | | } |
| | 2331 | |
|
| 336 | 2332 | | if (!string.IsNullOrEmpty(query.SearchTerm)) |
| | 2333 | | { |
| 0 | 2334 | | var builder = new StringBuilder(); |
| 0 | 2335 | | builder.Append('('); |
| | 2336 | |
|
| 0 | 2337 | | builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like |
| 0 | 2338 | | builder.Append("+ ((CleanName = @SearchTermStartsWith COLLATE NOCASE or (OriginalTitle not null and Orig |
| | 2339 | |
|
| 0 | 2340 | | if (query.SearchTerm.Length > 1) |
| | 2341 | | { |
| 0 | 2342 | | builder.Append("+ ((CleanName like @SearchTermContains or (OriginalTitle not null and OriginalTitle |
| 0 | 2343 | | builder.Append("+ (SELECT COUNT(1) * 1 from ItemValues where ItemId=Guid and CleanValue like @Search |
| 0 | 2344 | | builder.Append("+ (SELECT COUNT(1) * 2 from ItemValues where ItemId=Guid and CleanValue like @Search |
| 0 | 2345 | | builder.Append("+ (SELECT COUNT(1) * 10 from ItemValues where ItemId=Guid and CleanValue like @Searc |
| | 2346 | | } |
| | 2347 | |
|
| 0 | 2348 | | builder.Append(") as SearchScore"); |
| | 2349 | |
|
| 0 | 2350 | | columns.Add(builder.ToString()); |
| | 2351 | | } |
| 336 | 2352 | | } |
| | 2353 | |
|
| | 2354 | | private void BindSearchParams(InternalItemsQuery query, SqliteCommand statement) |
| | 2355 | | { |
| 336 | 2356 | | var searchTerm = query.SearchTerm; |
| | 2357 | |
|
| 336 | 2358 | | if (string.IsNullOrEmpty(searchTerm)) |
| | 2359 | | { |
| 336 | 2360 | | return; |
| | 2361 | | } |
| | 2362 | |
|
| 0 | 2363 | | searchTerm = FixUnicodeChars(searchTerm); |
| 0 | 2364 | | searchTerm = GetCleanValue(searchTerm); |
| | 2365 | |
|
| 0 | 2366 | | var commandText = statement.CommandText; |
| 0 | 2367 | | if (commandText.Contains("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase)) |
| | 2368 | | { |
| 0 | 2369 | | statement.TryBind("@SearchTermStartsWith", searchTerm + "%"); |
| | 2370 | | } |
| | 2371 | |
|
| 0 | 2372 | | if (commandText.Contains("@SearchTermContains", StringComparison.OrdinalIgnoreCase)) |
| | 2373 | | { |
| 0 | 2374 | | statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); |
| | 2375 | | } |
| | 2376 | |
|
| 0 | 2377 | | if (commandText.Contains("@SearchTermEquals", StringComparison.OrdinalIgnoreCase)) |
| | 2378 | | { |
| 0 | 2379 | | statement.TryBind("@SearchTermEquals", searchTerm); |
| | 2380 | | } |
| 0 | 2381 | | } |
| | 2382 | |
|
| | 2383 | | private void BindSimilarParams(InternalItemsQuery query, SqliteCommand statement) |
| | 2384 | | { |
| 336 | 2385 | | var item = query.SimilarTo; |
| | 2386 | |
|
| 336 | 2387 | | if (item is null) |
| | 2388 | | { |
| 336 | 2389 | | return; |
| | 2390 | | } |
| | 2391 | |
|
| 0 | 2392 | | var commandText = statement.CommandText; |
| | 2393 | |
|
| 0 | 2394 | | if (commandText.Contains("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase)) |
| | 2395 | | { |
| 0 | 2396 | | statement.TryBind("@ItemOfficialRating", item.OfficialRating); |
| | 2397 | | } |
| | 2398 | |
|
| 0 | 2399 | | if (commandText.Contains("@ItemProductionYear", StringComparison.OrdinalIgnoreCase)) |
| | 2400 | | { |
| 0 | 2401 | | statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0); |
| | 2402 | | } |
| | 2403 | |
|
| 0 | 2404 | | if (commandText.Contains("@SimilarItemId", StringComparison.OrdinalIgnoreCase)) |
| | 2405 | | { |
| 0 | 2406 | | statement.TryBind("@SimilarItemId", item.Id); |
| | 2407 | | } |
| | 2408 | |
|
| 0 | 2409 | | if (commandText.Contains("@InheritedParentalRatingValue", StringComparison.OrdinalIgnoreCase)) |
| | 2410 | | { |
| 0 | 2411 | | statement.TryBind("@InheritedParentalRatingValue", item.InheritedParentalRatingValue); |
| | 2412 | | } |
| 0 | 2413 | | } |
| | 2414 | |
|
| | 2415 | | private string GetJoinUserDataText(InternalItemsQuery query) |
| | 2416 | | { |
| 336 | 2417 | | if (!EnableJoinUserData(query)) |
| | 2418 | | { |
| 335 | 2419 | | return string.Empty; |
| | 2420 | | } |
| | 2421 | |
|
| 1 | 2422 | | return " left join UserDatas on UserDataKey=UserDatas.Key And (UserId=@UserId)"; |
| | 2423 | | } |
| | 2424 | |
|
| | 2425 | | private string GetGroupBy(InternalItemsQuery query) |
| | 2426 | | { |
| 336 | 2427 | | var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(query); |
| 336 | 2428 | | if (enableGroupByPresentationUniqueKey && query.GroupBySeriesPresentationUniqueKey) |
| | 2429 | | { |
| 0 | 2430 | | return " Group by PresentationUniqueKey, SeriesPresentationUniqueKey"; |
| | 2431 | | } |
| | 2432 | |
|
| 336 | 2433 | | if (enableGroupByPresentationUniqueKey) |
| | 2434 | | { |
| 1 | 2435 | | return " Group by PresentationUniqueKey"; |
| | 2436 | | } |
| | 2437 | |
|
| 335 | 2438 | | if (query.GroupBySeriesPresentationUniqueKey) |
| | 2439 | | { |
| 0 | 2440 | | return " Group by SeriesPresentationUniqueKey"; |
| | 2441 | | } |
| | 2442 | |
|
| 335 | 2443 | | return string.Empty; |
| | 2444 | | } |
| | 2445 | |
|
| | 2446 | | public int GetCount(InternalItemsQuery query) |
| | 2447 | | { |
| 0 | 2448 | | ArgumentNullException.ThrowIfNull(query); |
| | 2449 | |
|
| 0 | 2450 | | CheckDisposed(); |
| | 2451 | |
|
| | 2452 | | // Hack for right now since we currently don't support filtering out these duplicates within a query |
| 0 | 2453 | | if (query.Limit.HasValue && query.EnableGroupByMetadataKey) |
| | 2454 | | { |
| 0 | 2455 | | query.Limit = query.Limit.Value + 4; |
| | 2456 | | } |
| | 2457 | |
|
| 0 | 2458 | | var columns = new List<string> { "count(distinct PresentationUniqueKey)" }; |
| 0 | 2459 | | SetFinalColumnsToSelect(query, columns); |
| 0 | 2460 | | var commandTextBuilder = new StringBuilder("select ", 256) |
| 0 | 2461 | | .AppendJoin(',', columns) |
| 0 | 2462 | | .Append(FromText) |
| 0 | 2463 | | .Append(GetJoinUserDataText(query)); |
| | 2464 | |
|
| 0 | 2465 | | var whereClauses = GetWhereClauses(query, null); |
| 0 | 2466 | | if (whereClauses.Count != 0) |
| | 2467 | | { |
| 0 | 2468 | | commandTextBuilder.Append(" where ") |
| 0 | 2469 | | .AppendJoin(" AND ", whereClauses); |
| | 2470 | | } |
| | 2471 | |
|
| 0 | 2472 | | var commandText = commandTextBuilder.ToString(); |
| | 2473 | |
|
| 0 | 2474 | | using (new QueryTimeLogger(Logger, commandText)) |
| 0 | 2475 | | using (var connection = GetConnection(true)) |
| 0 | 2476 | | using (var statement = PrepareStatement(connection, commandText)) |
| | 2477 | | { |
| 0 | 2478 | | if (EnableJoinUserData(query)) |
| | 2479 | | { |
| 0 | 2480 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 2481 | | } |
| | 2482 | |
|
| 0 | 2483 | | BindSimilarParams(query, statement); |
| 0 | 2484 | | BindSearchParams(query, statement); |
| | 2485 | |
|
| | 2486 | | // Running this again will bind the params |
| 0 | 2487 | | GetWhereClauses(query, statement); |
| | 2488 | |
|
| 0 | 2489 | | return statement.SelectScalarInt(); |
| | 2490 | | } |
| 0 | 2491 | | } |
| | 2492 | |
|
| | 2493 | | public List<BaseItem> GetItemList(InternalItemsQuery query) |
| | 2494 | | { |
| 317 | 2495 | | ArgumentNullException.ThrowIfNull(query); |
| | 2496 | |
|
| 317 | 2497 | | CheckDisposed(); |
| | 2498 | |
|
| | 2499 | | // Hack for right now since we currently don't support filtering out these duplicates within a query |
| 317 | 2500 | | if (query.Limit.HasValue && query.EnableGroupByMetadataKey) |
| | 2501 | | { |
| 0 | 2502 | | query.Limit = query.Limit.Value + 4; |
| | 2503 | | } |
| | 2504 | |
|
| 317 | 2505 | | var columns = _retrieveItemColumns.ToList(); |
| 317 | 2506 | | SetFinalColumnsToSelect(query, columns); |
| 317 | 2507 | | var commandTextBuilder = new StringBuilder("select ", 1024) |
| 317 | 2508 | | .AppendJoin(',', columns) |
| 317 | 2509 | | .Append(FromText) |
| 317 | 2510 | | .Append(GetJoinUserDataText(query)); |
| | 2511 | |
|
| 317 | 2512 | | var whereClauses = GetWhereClauses(query, null); |
| | 2513 | |
|
| 317 | 2514 | | if (whereClauses.Count != 0) |
| | 2515 | | { |
| 317 | 2516 | | commandTextBuilder.Append(" where ") |
| 317 | 2517 | | .AppendJoin(" AND ", whereClauses); |
| | 2518 | | } |
| | 2519 | |
|
| 317 | 2520 | | commandTextBuilder.Append(GetGroupBy(query)) |
| 317 | 2521 | | .Append(GetOrderByText(query)); |
| | 2522 | |
|
| 317 | 2523 | | if (query.Limit.HasValue || query.StartIndex.HasValue) |
| | 2524 | | { |
| 103 | 2525 | | var offset = query.StartIndex ?? 0; |
| | 2526 | |
|
| 103 | 2527 | | if (query.Limit.HasValue || offset > 0) |
| | 2528 | | { |
| 103 | 2529 | | commandTextBuilder.Append(" LIMIT ") |
| 103 | 2530 | | .Append(query.Limit ?? int.MaxValue); |
| | 2531 | | } |
| | 2532 | |
|
| 103 | 2533 | | if (offset > 0) |
| | 2534 | | { |
| 0 | 2535 | | commandTextBuilder.Append(" OFFSET ") |
| 0 | 2536 | | .Append(offset); |
| | 2537 | | } |
| | 2538 | | } |
| | 2539 | |
|
| 317 | 2540 | | var commandText = commandTextBuilder.ToString(); |
| 317 | 2541 | | var items = new List<BaseItem>(); |
| 317 | 2542 | | using (new QueryTimeLogger(Logger, commandText)) |
| 317 | 2543 | | using (var connection = GetConnection(true)) |
| 317 | 2544 | | using (var statement = PrepareStatement(connection, commandText)) |
| | 2545 | | { |
| 317 | 2546 | | if (EnableJoinUserData(query)) |
| | 2547 | | { |
| 1 | 2548 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 2549 | | } |
| | 2550 | |
|
| 317 | 2551 | | BindSimilarParams(query, statement); |
| 317 | 2552 | | BindSearchParams(query, statement); |
| | 2553 | |
|
| | 2554 | | // Running this again will bind the params |
| 317 | 2555 | | GetWhereClauses(query, statement); |
| | 2556 | |
|
| 317 | 2557 | | var hasEpisodeAttributes = HasEpisodeAttributes(query); |
| 317 | 2558 | | var hasServiceName = HasServiceName(query); |
| 317 | 2559 | | var hasProgramAttributes = HasProgramAttributes(query); |
| 317 | 2560 | | var hasStartDate = HasStartDate(query); |
| 317 | 2561 | | var hasTrailerTypes = HasTrailerTypes(query); |
| 317 | 2562 | | var hasArtistFields = HasArtistFields(query); |
| 317 | 2563 | | var hasSeriesFields = HasSeriesFields(query); |
| | 2564 | |
|
| 806 | 2565 | | foreach (var row in statement.ExecuteQuery()) |
| | 2566 | | { |
| 86 | 2567 | | var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartD |
| 86 | 2568 | | if (item is not null) |
| | 2569 | | { |
| 86 | 2570 | | items.Add(item); |
| | 2571 | | } |
| | 2572 | | } |
| | 2573 | | } |
| | 2574 | |
|
| | 2575 | | // Hack for right now since we currently don't support filtering out these duplicates within a query |
| 317 | 2576 | | if (query.EnableGroupByMetadataKey) |
| | 2577 | | { |
| 0 | 2578 | | var limit = query.Limit ?? int.MaxValue; |
| 0 | 2579 | | limit -= 4; |
| 0 | 2580 | | var newList = new List<BaseItem>(); |
| | 2581 | |
|
| 0 | 2582 | | foreach (var item in items) |
| | 2583 | | { |
| 0 | 2584 | | AddItem(newList, item); |
| | 2585 | |
|
| 0 | 2586 | | if (newList.Count >= limit) |
| | 2587 | | { |
| 0 | 2588 | | break; |
| | 2589 | | } |
| | 2590 | | } |
| | 2591 | |
|
| 0 | 2592 | | items = newList; |
| | 2593 | | } |
| | 2594 | |
|
| 317 | 2595 | | return items; |
| | 2596 | | } |
| | 2597 | |
|
| | 2598 | | private string FixUnicodeChars(string buffer) |
| | 2599 | | { |
| 0 | 2600 | | buffer = buffer.Replace('\u2013', '-'); // en dash |
| 0 | 2601 | | buffer = buffer.Replace('\u2014', '-'); // em dash |
| 0 | 2602 | | buffer = buffer.Replace('\u2015', '-'); // horizontal bar |
| 0 | 2603 | | buffer = buffer.Replace('\u2017', '_'); // double low line |
| 0 | 2604 | | buffer = buffer.Replace('\u2018', '\''); // left single quotation mark |
| 0 | 2605 | | buffer = buffer.Replace('\u2019', '\''); // right single quotation mark |
| 0 | 2606 | | buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark |
| 0 | 2607 | | buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark |
| 0 | 2608 | | buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark |
| 0 | 2609 | | buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark |
| 0 | 2610 | | buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark |
| 0 | 2611 | | buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis |
| 0 | 2612 | | buffer = buffer.Replace('\u2032', '\''); // prime |
| 0 | 2613 | | buffer = buffer.Replace('\u2033', '\"'); // double prime |
| 0 | 2614 | | buffer = buffer.Replace('\u0060', '\''); // grave accent |
| 0 | 2615 | | return buffer.Replace('\u00B4', '\''); // acute accent |
| | 2616 | | } |
| | 2617 | |
|
| | 2618 | | private void AddItem(List<BaseItem> items, BaseItem newItem) |
| | 2619 | | { |
| 0 | 2620 | | for (var i = 0; i < items.Count; i++) |
| | 2621 | | { |
| 0 | 2622 | | var item = items[i]; |
| | 2623 | |
|
| 0 | 2624 | | foreach (var providerId in newItem.ProviderIds) |
| | 2625 | | { |
| 0 | 2626 | | if (string.Equals(providerId.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.Ordinal) |
| | 2627 | | { |
| | 2628 | | continue; |
| | 2629 | | } |
| | 2630 | |
|
| 0 | 2631 | | if (string.Equals(item.GetProviderId(providerId.Key), providerId.Value, StringComparison.Ordinal)) |
| | 2632 | | { |
| 0 | 2633 | | if (newItem.SourceType == SourceType.Library) |
| | 2634 | | { |
| 0 | 2635 | | items[i] = newItem; |
| | 2636 | | } |
| | 2637 | |
|
| 0 | 2638 | | return; |
| | 2639 | | } |
| | 2640 | | } |
| | 2641 | | } |
| | 2642 | |
|
| 0 | 2643 | | items.Add(newItem); |
| 0 | 2644 | | } |
| | 2645 | |
|
| | 2646 | | public QueryResult<BaseItem> GetItems(InternalItemsQuery query) |
| | 2647 | | { |
| 1 | 2648 | | ArgumentNullException.ThrowIfNull(query); |
| | 2649 | |
|
| 1 | 2650 | | CheckDisposed(); |
| | 2651 | |
|
| 1 | 2652 | | if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0)) |
| | 2653 | | { |
| 1 | 2654 | | var returnList = GetItemList(query); |
| 1 | 2655 | | return new QueryResult<BaseItem>( |
| 1 | 2656 | | query.StartIndex, |
| 1 | 2657 | | returnList.Count, |
| 1 | 2658 | | returnList); |
| | 2659 | | } |
| | 2660 | |
|
| | 2661 | | // Hack for right now since we currently don't support filtering out these duplicates within a query |
| 0 | 2662 | | if (query.Limit.HasValue && query.EnableGroupByMetadataKey) |
| | 2663 | | { |
| 0 | 2664 | | query.Limit = query.Limit.Value + 4; |
| | 2665 | | } |
| | 2666 | |
|
| 0 | 2667 | | var columns = _retrieveItemColumns.ToList(); |
| 0 | 2668 | | SetFinalColumnsToSelect(query, columns); |
| 0 | 2669 | | var commandTextBuilder = new StringBuilder("select ", 512) |
| 0 | 2670 | | .AppendJoin(',', columns) |
| 0 | 2671 | | .Append(FromText) |
| 0 | 2672 | | .Append(GetJoinUserDataText(query)); |
| | 2673 | |
|
| 0 | 2674 | | var whereClauses = GetWhereClauses(query, null); |
| | 2675 | |
|
| 0 | 2676 | | var whereText = whereClauses.Count == 0 ? |
| 0 | 2677 | | string.Empty : |
| 0 | 2678 | | string.Join(" AND ", whereClauses); |
| | 2679 | |
|
| 0 | 2680 | | if (!string.IsNullOrEmpty(whereText)) |
| | 2681 | | { |
| 0 | 2682 | | commandTextBuilder.Append(" where ") |
| 0 | 2683 | | .Append(whereText); |
| | 2684 | | } |
| | 2685 | |
|
| 0 | 2686 | | commandTextBuilder.Append(GetGroupBy(query)) |
| 0 | 2687 | | .Append(GetOrderByText(query)); |
| | 2688 | |
|
| 0 | 2689 | | if (query.Limit.HasValue || query.StartIndex.HasValue) |
| | 2690 | | { |
| 0 | 2691 | | var offset = query.StartIndex ?? 0; |
| | 2692 | |
|
| 0 | 2693 | | if (query.Limit.HasValue || offset > 0) |
| | 2694 | | { |
| 0 | 2695 | | commandTextBuilder.Append(" LIMIT ") |
| 0 | 2696 | | .Append(query.Limit ?? int.MaxValue); |
| | 2697 | | } |
| | 2698 | |
|
| 0 | 2699 | | if (offset > 0) |
| | 2700 | | { |
| 0 | 2701 | | commandTextBuilder.Append(" OFFSET ") |
| 0 | 2702 | | .Append(offset); |
| | 2703 | | } |
| | 2704 | | } |
| | 2705 | |
|
| 0 | 2706 | | var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; |
| | 2707 | |
|
| 0 | 2708 | | var itemQuery = string.Empty; |
| 0 | 2709 | | var totalRecordCountQuery = string.Empty; |
| 0 | 2710 | | if (!isReturningZeroItems) |
| | 2711 | | { |
| 0 | 2712 | | itemQuery = commandTextBuilder.ToString(); |
| | 2713 | | } |
| | 2714 | |
|
| 0 | 2715 | | if (query.EnableTotalRecordCount) |
| | 2716 | | { |
| 0 | 2717 | | commandTextBuilder.Clear(); |
| | 2718 | |
|
| 0 | 2719 | | commandTextBuilder.Append(" select "); |
| | 2720 | |
|
| | 2721 | | List<string> columnsToSelect; |
| 0 | 2722 | | if (EnableGroupByPresentationUniqueKey(query)) |
| | 2723 | | { |
| 0 | 2724 | | columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" }; |
| | 2725 | | } |
| 0 | 2726 | | else if (query.GroupBySeriesPresentationUniqueKey) |
| | 2727 | | { |
| 0 | 2728 | | columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" }; |
| | 2729 | | } |
| | 2730 | | else |
| | 2731 | | { |
| 0 | 2732 | | columnsToSelect = new List<string> { "count (guid)" }; |
| | 2733 | | } |
| | 2734 | |
|
| 0 | 2735 | | SetFinalColumnsToSelect(query, columnsToSelect); |
| | 2736 | |
|
| 0 | 2737 | | commandTextBuilder.AppendJoin(',', columnsToSelect) |
| 0 | 2738 | | .Append(FromText) |
| 0 | 2739 | | .Append(GetJoinUserDataText(query)); |
| 0 | 2740 | | if (!string.IsNullOrEmpty(whereText)) |
| | 2741 | | { |
| 0 | 2742 | | commandTextBuilder.Append(" where ") |
| 0 | 2743 | | .Append(whereText); |
| | 2744 | | } |
| | 2745 | |
|
| 0 | 2746 | | totalRecordCountQuery = commandTextBuilder.ToString(); |
| | 2747 | | } |
| | 2748 | |
|
| 0 | 2749 | | var list = new List<BaseItem>(); |
| 0 | 2750 | | var result = new QueryResult<BaseItem>(); |
| 0 | 2751 | | using var connection = GetConnection(true); |
| 0 | 2752 | | using var transaction = connection.BeginTransaction(); |
| 0 | 2753 | | if (!isReturningZeroItems) |
| | 2754 | | { |
| 0 | 2755 | | using (new QueryTimeLogger(Logger, itemQuery, "GetItems.ItemQuery")) |
| 0 | 2756 | | using (var statement = PrepareStatement(connection, itemQuery)) |
| | 2757 | | { |
| 0 | 2758 | | if (EnableJoinUserData(query)) |
| | 2759 | | { |
| 0 | 2760 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 2761 | | } |
| | 2762 | |
|
| 0 | 2763 | | BindSimilarParams(query, statement); |
| 0 | 2764 | | BindSearchParams(query, statement); |
| | 2765 | |
|
| | 2766 | | // Running this again will bind the params |
| 0 | 2767 | | GetWhereClauses(query, statement); |
| | 2768 | |
|
| 0 | 2769 | | var hasEpisodeAttributes = HasEpisodeAttributes(query); |
| 0 | 2770 | | var hasServiceName = HasServiceName(query); |
| 0 | 2771 | | var hasProgramAttributes = HasProgramAttributes(query); |
| 0 | 2772 | | var hasStartDate = HasStartDate(query); |
| 0 | 2773 | | var hasTrailerTypes = HasTrailerTypes(query); |
| 0 | 2774 | | var hasArtistFields = HasArtistFields(query); |
| 0 | 2775 | | var hasSeriesFields = HasSeriesFields(query); |
| | 2776 | |
|
| 0 | 2777 | | foreach (var row in statement.ExecuteQuery()) |
| | 2778 | | { |
| 0 | 2779 | | var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasSt |
| 0 | 2780 | | if (item is not null) |
| | 2781 | | { |
| 0 | 2782 | | list.Add(item); |
| | 2783 | | } |
| | 2784 | | } |
| | 2785 | | } |
| | 2786 | | } |
| | 2787 | |
|
| 0 | 2788 | | if (query.EnableTotalRecordCount) |
| | 2789 | | { |
| 0 | 2790 | | using (new QueryTimeLogger(Logger, totalRecordCountQuery, "GetItems.TotalRecordCount")) |
| 0 | 2791 | | using (var statement = PrepareStatement(connection, totalRecordCountQuery)) |
| | 2792 | | { |
| 0 | 2793 | | if (EnableJoinUserData(query)) |
| | 2794 | | { |
| 0 | 2795 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 2796 | | } |
| | 2797 | |
|
| 0 | 2798 | | BindSimilarParams(query, statement); |
| 0 | 2799 | | BindSearchParams(query, statement); |
| | 2800 | |
|
| | 2801 | | // Running this again will bind the params |
| 0 | 2802 | | GetWhereClauses(query, statement); |
| | 2803 | |
|
| 0 | 2804 | | result.TotalRecordCount = statement.SelectScalarInt(); |
| 0 | 2805 | | } |
| | 2806 | | } |
| | 2807 | |
|
| 0 | 2808 | | transaction.Commit(); |
| | 2809 | |
|
| 0 | 2810 | | result.StartIndex = query.StartIndex ?? 0; |
| 0 | 2811 | | result.Items = list; |
| 0 | 2812 | | return result; |
| 0 | 2813 | | } |
| | 2814 | |
|
| | 2815 | | private string GetOrderByText(InternalItemsQuery query) |
| | 2816 | | { |
| 336 | 2817 | | var orderBy = query.OrderBy; |
| 336 | 2818 | | bool hasSimilar = query.SimilarTo is not null; |
| 336 | 2819 | | bool hasSearch = !string.IsNullOrEmpty(query.SearchTerm); |
| | 2820 | |
|
| 336 | 2821 | | if (hasSimilar || hasSearch) |
| | 2822 | | { |
| 0 | 2823 | | List<(ItemSortBy, SortOrder)> prepend = new List<(ItemSortBy, SortOrder)>(4); |
| 0 | 2824 | | if (hasSearch) |
| | 2825 | | { |
| 0 | 2826 | | prepend.Add((ItemSortBy.SearchScore, SortOrder.Descending)); |
| 0 | 2827 | | prepend.Add((ItemSortBy.SortName, SortOrder.Ascending)); |
| | 2828 | | } |
| | 2829 | |
|
| 0 | 2830 | | if (hasSimilar) |
| | 2831 | | { |
| 0 | 2832 | | prepend.Add((ItemSortBy.SimilarityScore, SortOrder.Descending)); |
| 0 | 2833 | | prepend.Add((ItemSortBy.Random, SortOrder.Ascending)); |
| | 2834 | | } |
| | 2835 | |
|
| 0 | 2836 | | orderBy = query.OrderBy = [.. prepend, .. orderBy]; |
| | 2837 | | } |
| 336 | 2838 | | else if (orderBy.Count == 0) |
| | 2839 | | { |
| 232 | 2840 | | return string.Empty; |
| | 2841 | | } |
| | 2842 | |
|
| 104 | 2843 | | return " ORDER BY " + string.Join(',', orderBy.Select(i => |
| 104 | 2844 | | { |
| 104 | 2845 | | var sortBy = MapOrderByField(i.OrderBy, query); |
| 104 | 2846 | | var sortOrder = i.SortOrder == SortOrder.Ascending ? "ASC" : "DESC"; |
| 104 | 2847 | | return sortBy + " " + sortOrder; |
| 104 | 2848 | | })); |
| | 2849 | | } |
| | 2850 | |
|
| | 2851 | | private string MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query) |
| | 2852 | | { |
| 146 | 2853 | | return sortBy switch |
| 146 | 2854 | | { |
| 0 | 2855 | | ItemSortBy.AirTime => "SortName", // TODO |
| 0 | 2856 | | ItemSortBy.Runtime => "RuntimeTicks", |
| 61 | 2857 | | ItemSortBy.Random => "RANDOM()", |
| 1 | 2858 | | ItemSortBy.DatePlayed when query.GroupBySeriesPresentationUniqueKey => "MAX(LastPlayedDate)", |
| 1 | 2859 | | ItemSortBy.DatePlayed => "LastPlayedDate", |
| 0 | 2860 | | ItemSortBy.PlayCount => "PlayCount", |
| 0 | 2861 | | ItemSortBy.IsFavoriteOrLiked => "(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", |
| 42 | 2862 | | ItemSortBy.IsFolder => "IsFolder", |
| 0 | 2863 | | ItemSortBy.IsPlayed => "played", |
| 0 | 2864 | | ItemSortBy.IsUnplayed => "played", |
| 0 | 2865 | | ItemSortBy.DateLastContentAdded => "DateLastMediaAdded", |
| 0 | 2866 | | ItemSortBy.Artist => "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)", |
| 0 | 2867 | | ItemSortBy.AlbumArtist => "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)", |
| 0 | 2868 | | ItemSortBy.OfficialRating => "InheritedParentalRatingValue", |
| 0 | 2869 | | ItemSortBy.Studio => "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)", |
| 0 | 2870 | | ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText |
| 0 | 2871 | | ItemSortBy.SeriesSortName => "SeriesName", |
| 0 | 2872 | | ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder", |
| 0 | 2873 | | ItemSortBy.Album => "Album", |
| 0 | 2874 | | ItemSortBy.DateCreated => "DateCreated", |
| 0 | 2875 | | ItemSortBy.PremiereDate => "PremiereDate", |
| 0 | 2876 | | ItemSortBy.StartDate => "StartDate", |
| 0 | 2877 | | ItemSortBy.Name => "Name", |
| 0 | 2878 | | ItemSortBy.CommunityRating => "CommunityRating", |
| 0 | 2879 | | ItemSortBy.ProductionYear => "ProductionYear", |
| 0 | 2880 | | ItemSortBy.CriticRating => "CriticRating", |
| 0 | 2881 | | ItemSortBy.VideoBitRate => "VideoBitRate", |
| 0 | 2882 | | ItemSortBy.ParentIndexNumber => "ParentIndexNumber", |
| 0 | 2883 | | ItemSortBy.IndexNumber => "IndexNumber", |
| 0 | 2884 | | ItemSortBy.SimilarityScore => "SimilarityScore", |
| 0 | 2885 | | ItemSortBy.SearchScore => "SearchScore", |
| 42 | 2886 | | _ => "SortName" |
| 146 | 2887 | | }; |
| | 2888 | | } |
| | 2889 | |
|
| | 2890 | | public List<Guid> GetItemIdsList(InternalItemsQuery query) |
| | 2891 | | { |
| 19 | 2892 | | ArgumentNullException.ThrowIfNull(query); |
| | 2893 | |
|
| 19 | 2894 | | CheckDisposed(); |
| | 2895 | |
|
| 19 | 2896 | | var columns = new List<string> { "guid" }; |
| 19 | 2897 | | SetFinalColumnsToSelect(query, columns); |
| 19 | 2898 | | var commandTextBuilder = new StringBuilder("select ", 256) |
| 19 | 2899 | | .AppendJoin(',', columns) |
| 19 | 2900 | | .Append(FromText) |
| 19 | 2901 | | .Append(GetJoinUserDataText(query)); |
| | 2902 | |
|
| 19 | 2903 | | var whereClauses = GetWhereClauses(query, null); |
| 19 | 2904 | | if (whereClauses.Count != 0) |
| | 2905 | | { |
| 19 | 2906 | | commandTextBuilder.Append(" where ") |
| 19 | 2907 | | .AppendJoin(" AND ", whereClauses); |
| | 2908 | | } |
| | 2909 | |
|
| 19 | 2910 | | commandTextBuilder.Append(GetGroupBy(query)) |
| 19 | 2911 | | .Append(GetOrderByText(query)); |
| | 2912 | |
|
| 19 | 2913 | | if (query.Limit.HasValue || query.StartIndex.HasValue) |
| | 2914 | | { |
| 0 | 2915 | | var offset = query.StartIndex ?? 0; |
| | 2916 | |
|
| 0 | 2917 | | if (query.Limit.HasValue || offset > 0) |
| | 2918 | | { |
| 0 | 2919 | | commandTextBuilder.Append(" LIMIT ") |
| 0 | 2920 | | .Append(query.Limit ?? int.MaxValue); |
| | 2921 | | } |
| | 2922 | |
|
| 0 | 2923 | | if (offset > 0) |
| | 2924 | | { |
| 0 | 2925 | | commandTextBuilder.Append(" OFFSET ") |
| 0 | 2926 | | .Append(offset); |
| | 2927 | | } |
| | 2928 | | } |
| | 2929 | |
|
| 19 | 2930 | | var commandText = commandTextBuilder.ToString(); |
| 19 | 2931 | | var list = new List<Guid>(); |
| 19 | 2932 | | using (new QueryTimeLogger(Logger, commandText)) |
| 19 | 2933 | | using (var connection = GetConnection(true)) |
| 19 | 2934 | | using (var statement = PrepareStatement(connection, commandText)) |
| | 2935 | | { |
| 19 | 2936 | | if (EnableJoinUserData(query)) |
| | 2937 | | { |
| 0 | 2938 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 2939 | | } |
| | 2940 | |
|
| 19 | 2941 | | BindSimilarParams(query, statement); |
| 19 | 2942 | | BindSearchParams(query, statement); |
| | 2943 | |
|
| | 2944 | | // Running this again will bind the params |
| 19 | 2945 | | GetWhereClauses(query, statement); |
| | 2946 | |
|
| 38 | 2947 | | foreach (var row in statement.ExecuteQuery()) |
| | 2948 | | { |
| 0 | 2949 | | list.Add(row.GetGuid(0)); |
| | 2950 | | } |
| | 2951 | | } |
| | 2952 | |
|
| 19 | 2953 | | return list; |
| | 2954 | | } |
| | 2955 | |
|
| | 2956 | | private bool IsAlphaNumeric(string str) |
| | 2957 | | { |
| 0 | 2958 | | if (string.IsNullOrWhiteSpace(str)) |
| | 2959 | | { |
| 0 | 2960 | | return false; |
| | 2961 | | } |
| | 2962 | |
|
| 0 | 2963 | | for (int i = 0; i < str.Length; i++) |
| | 2964 | | { |
| 0 | 2965 | | if (!char.IsLetter(str[i]) && !char.IsNumber(str[i])) |
| | 2966 | | { |
| 0 | 2967 | | return false; |
| | 2968 | | } |
| | 2969 | | } |
| | 2970 | |
|
| 0 | 2971 | | return true; |
| | 2972 | | } |
| | 2973 | |
|
| | 2974 | | private bool IsValidPersonType(string value) |
| | 2975 | | { |
| 0 | 2976 | | return IsAlphaNumeric(value); |
| | 2977 | | } |
| | 2978 | |
|
| | 2979 | | #nullable enable |
| | 2980 | | private List<string> GetWhereClauses(InternalItemsQuery query, SqliteCommand? statement) |
| | 2981 | | { |
| 672 | 2982 | | if (query.IsResumable ?? false) |
| | 2983 | | { |
| 2 | 2984 | | query.IsVirtualItem = false; |
| | 2985 | | } |
| | 2986 | |
|
| 672 | 2987 | | var minWidth = query.MinWidth; |
| 672 | 2988 | | var maxWidth = query.MaxWidth; |
| | 2989 | |
|
| 672 | 2990 | | if (query.IsHD.HasValue) |
| | 2991 | | { |
| | 2992 | | const int Threshold = 1200; |
| 0 | 2993 | | if (query.IsHD.Value) |
| | 2994 | | { |
| 0 | 2995 | | minWidth = Threshold; |
| | 2996 | | } |
| | 2997 | | else |
| | 2998 | | { |
| 0 | 2999 | | maxWidth = Threshold - 1; |
| | 3000 | | } |
| | 3001 | | } |
| | 3002 | |
|
| 672 | 3003 | | if (query.Is4K.HasValue) |
| | 3004 | | { |
| | 3005 | | const int Threshold = 3800; |
| 0 | 3006 | | if (query.Is4K.Value) |
| | 3007 | | { |
| 0 | 3008 | | minWidth = Threshold; |
| | 3009 | | } |
| | 3010 | | else |
| | 3011 | | { |
| 0 | 3012 | | maxWidth = Threshold - 1; |
| | 3013 | | } |
| | 3014 | | } |
| | 3015 | |
|
| 672 | 3016 | | var whereClauses = new List<string>(); |
| | 3017 | |
|
| 672 | 3018 | | if (minWidth.HasValue) |
| | 3019 | | { |
| 0 | 3020 | | whereClauses.Add("Width>=@MinWidth"); |
| 0 | 3021 | | statement?.TryBind("@MinWidth", minWidth); |
| | 3022 | | } |
| | 3023 | |
|
| 672 | 3024 | | if (query.MinHeight.HasValue) |
| | 3025 | | { |
| 0 | 3026 | | whereClauses.Add("Height>=@MinHeight"); |
| 0 | 3027 | | statement?.TryBind("@MinHeight", query.MinHeight); |
| | 3028 | | } |
| | 3029 | |
|
| 672 | 3030 | | if (maxWidth.HasValue) |
| | 3031 | | { |
| 0 | 3032 | | whereClauses.Add("Width<=@MaxWidth"); |
| 0 | 3033 | | statement?.TryBind("@MaxWidth", maxWidth); |
| | 3034 | | } |
| | 3035 | |
|
| 672 | 3036 | | if (query.MaxHeight.HasValue) |
| | 3037 | | { |
| 0 | 3038 | | whereClauses.Add("Height<=@MaxHeight"); |
| 0 | 3039 | | statement?.TryBind("@MaxHeight", query.MaxHeight); |
| | 3040 | | } |
| | 3041 | |
|
| 672 | 3042 | | if (query.IsLocked.HasValue) |
| | 3043 | | { |
| 76 | 3044 | | whereClauses.Add("IsLocked=@IsLocked"); |
| 76 | 3045 | | statement?.TryBind("@IsLocked", query.IsLocked); |
| | 3046 | | } |
| | 3047 | |
|
| 672 | 3048 | | var tags = query.Tags.ToList(); |
| 672 | 3049 | | var excludeTags = query.ExcludeTags.ToList(); |
| | 3050 | |
|
| 672 | 3051 | | if (query.IsMovie == true) |
| | 3052 | | { |
| 0 | 3053 | | if (query.IncludeItemTypes.Length == 0 |
| 0 | 3054 | | || query.IncludeItemTypes.Contains(BaseItemKind.Movie) |
| 0 | 3055 | | || query.IncludeItemTypes.Contains(BaseItemKind.Trailer)) |
| | 3056 | | { |
| 0 | 3057 | | whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); |
| | 3058 | | } |
| | 3059 | | else |
| | 3060 | | { |
| 0 | 3061 | | whereClauses.Add("IsMovie=@IsMovie"); |
| | 3062 | | } |
| | 3063 | |
|
| 0 | 3064 | | statement?.TryBind("@IsMovie", true); |
| | 3065 | | } |
| 672 | 3066 | | else if (query.IsMovie.HasValue) |
| | 3067 | | { |
| 0 | 3068 | | whereClauses.Add("IsMovie=@IsMovie"); |
| 0 | 3069 | | statement?.TryBind("@IsMovie", query.IsMovie); |
| | 3070 | | } |
| | 3071 | |
|
| 672 | 3072 | | if (query.IsSeries.HasValue) |
| | 3073 | | { |
| 0 | 3074 | | whereClauses.Add("IsSeries=@IsSeries"); |
| 0 | 3075 | | statement?.TryBind("@IsSeries", query.IsSeries); |
| | 3076 | | } |
| | 3077 | |
|
| 672 | 3078 | | if (query.IsSports.HasValue) |
| | 3079 | | { |
| 0 | 3080 | | if (query.IsSports.Value) |
| | 3081 | | { |
| 0 | 3082 | | tags.Add("Sports"); |
| | 3083 | | } |
| | 3084 | | else |
| | 3085 | | { |
| 0 | 3086 | | excludeTags.Add("Sports"); |
| | 3087 | | } |
| | 3088 | | } |
| | 3089 | |
|
| 672 | 3090 | | if (query.IsNews.HasValue) |
| | 3091 | | { |
| 0 | 3092 | | if (query.IsNews.Value) |
| | 3093 | | { |
| 0 | 3094 | | tags.Add("News"); |
| | 3095 | | } |
| | 3096 | | else |
| | 3097 | | { |
| 0 | 3098 | | excludeTags.Add("News"); |
| | 3099 | | } |
| | 3100 | | } |
| | 3101 | |
|
| 672 | 3102 | | if (query.IsKids.HasValue) |
| | 3103 | | { |
| 0 | 3104 | | if (query.IsKids.Value) |
| | 3105 | | { |
| 0 | 3106 | | tags.Add("Kids"); |
| | 3107 | | } |
| | 3108 | | else |
| | 3109 | | { |
| 0 | 3110 | | excludeTags.Add("Kids"); |
| | 3111 | | } |
| | 3112 | | } |
| | 3113 | |
|
| 672 | 3114 | | if (query.SimilarTo is not null && query.MinSimilarityScore > 0) |
| | 3115 | | { |
| 0 | 3116 | | whereClauses.Add("SimilarityScore > " + (query.MinSimilarityScore - 1).ToString(CultureInfo.InvariantCul |
| | 3117 | | } |
| | 3118 | |
|
| 672 | 3119 | | if (!string.IsNullOrEmpty(query.SearchTerm)) |
| | 3120 | | { |
| 0 | 3121 | | whereClauses.Add("SearchScore > 0"); |
| | 3122 | | } |
| | 3123 | |
|
| 672 | 3124 | | if (query.IsFolder.HasValue) |
| | 3125 | | { |
| 0 | 3126 | | whereClauses.Add("IsFolder=@IsFolder"); |
| 0 | 3127 | | statement?.TryBind("@IsFolder", query.IsFolder); |
| | 3128 | | } |
| | 3129 | |
|
| 672 | 3130 | | var includeTypes = query.IncludeItemTypes; |
| | 3131 | | // Only specify excluded types if no included types are specified |
| 672 | 3132 | | if (query.IncludeItemTypes.Length == 0) |
| | 3133 | | { |
| 466 | 3134 | | var excludeTypes = query.ExcludeItemTypes; |
| 466 | 3135 | | if (excludeTypes.Length == 1) |
| | 3136 | | { |
| 0 | 3137 | | if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName)) |
| | 3138 | | { |
| 0 | 3139 | | whereClauses.Add("type<>@type"); |
| 0 | 3140 | | statement?.TryBind("@type", excludeTypeName); |
| | 3141 | | } |
| | 3142 | | else |
| | 3143 | | { |
| 0 | 3144 | | Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeTypes[0]); |
| | 3145 | | } |
| | 3146 | | } |
| 466 | 3147 | | else if (excludeTypes.Length > 1) |
| | 3148 | | { |
| 0 | 3149 | | var whereBuilder = new StringBuilder("type not in ("); |
| 0 | 3150 | | foreach (var excludeType in excludeTypes) |
| | 3151 | | { |
| 0 | 3152 | | if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) |
| | 3153 | | { |
| 0 | 3154 | | whereBuilder |
| 0 | 3155 | | .Append('\'') |
| 0 | 3156 | | .Append(baseItemKindName) |
| 0 | 3157 | | .Append("',"); |
| | 3158 | | } |
| | 3159 | | else |
| | 3160 | | { |
| 0 | 3161 | | Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeType); |
| | 3162 | | } |
| | 3163 | | } |
| | 3164 | |
|
| | 3165 | | // Remove trailing comma. |
| 0 | 3166 | | whereBuilder.Length--; |
| 0 | 3167 | | whereBuilder.Append(')'); |
| 0 | 3168 | | whereClauses.Add(whereBuilder.ToString()); |
| | 3169 | | } |
| | 3170 | | } |
| 206 | 3171 | | else if (includeTypes.Length == 1) |
| | 3172 | | { |
| 84 | 3173 | | if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName)) |
| | 3174 | | { |
| 84 | 3175 | | whereClauses.Add("type=@type"); |
| 84 | 3176 | | statement?.TryBind("@type", includeTypeName); |
| | 3177 | | } |
| | 3178 | | else |
| | 3179 | | { |
| 0 | 3180 | | Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeTypes[0]); |
| | 3181 | | } |
| | 3182 | | } |
| 122 | 3183 | | else if (includeTypes.Length > 1) |
| | 3184 | | { |
| 122 | 3185 | | var whereBuilder = new StringBuilder("type in ("); |
| 780 | 3186 | | foreach (var includeType in includeTypes) |
| | 3187 | | { |
| 268 | 3188 | | if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName)) |
| | 3189 | | { |
| 268 | 3190 | | whereBuilder |
| 268 | 3191 | | .Append('\'') |
| 268 | 3192 | | .Append(baseItemKindName) |
| 268 | 3193 | | .Append("',"); |
| | 3194 | | } |
| | 3195 | | else |
| | 3196 | | { |
| 0 | 3197 | | Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeType); |
| | 3198 | | } |
| | 3199 | | } |
| | 3200 | |
|
| | 3201 | | // Remove trailing comma. |
| 122 | 3202 | | whereBuilder.Length--; |
| 122 | 3203 | | whereBuilder.Append(')'); |
| 122 | 3204 | | whereClauses.Add(whereBuilder.ToString()); |
| | 3205 | | } |
| | 3206 | |
|
| 672 | 3207 | | if (query.ChannelIds.Count == 1) |
| | 3208 | | { |
| 0 | 3209 | | whereClauses.Add("ChannelId=@ChannelId"); |
| 0 | 3210 | | statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); |
| | 3211 | | } |
| 672 | 3212 | | else if (query.ChannelIds.Count > 1) |
| | 3213 | | { |
| 0 | 3214 | | var inClause = string.Join(',', query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.Invariant |
| 0 | 3215 | | whereClauses.Add($"ChannelId in ({inClause})"); |
| | 3216 | | } |
| | 3217 | |
|
| 672 | 3218 | | if (!query.ParentId.IsEmpty()) |
| | 3219 | | { |
| 376 | 3220 | | whereClauses.Add("ParentId=@ParentId"); |
| 376 | 3221 | | statement?.TryBind("@ParentId", query.ParentId); |
| | 3222 | | } |
| | 3223 | |
|
| 672 | 3224 | | if (!string.IsNullOrWhiteSpace(query.Path)) |
| | 3225 | | { |
| 0 | 3226 | | whereClauses.Add("Path=@Path"); |
| 0 | 3227 | | statement?.TryBind("@Path", GetPathToSave(query.Path)); |
| | 3228 | | } |
| | 3229 | |
|
| 672 | 3230 | | if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) |
| | 3231 | | { |
| 0 | 3232 | | whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey"); |
| 0 | 3233 | | statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey); |
| | 3234 | | } |
| | 3235 | |
|
| 672 | 3236 | | if (query.MinCommunityRating.HasValue) |
| | 3237 | | { |
| 0 | 3238 | | whereClauses.Add("CommunityRating>=@MinCommunityRating"); |
| 0 | 3239 | | statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value); |
| | 3240 | | } |
| | 3241 | |
|
| 672 | 3242 | | if (query.MinIndexNumber.HasValue) |
| | 3243 | | { |
| 0 | 3244 | | whereClauses.Add("IndexNumber>=@MinIndexNumber"); |
| 0 | 3245 | | statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value); |
| | 3246 | | } |
| | 3247 | |
|
| 672 | 3248 | | if (query.MinParentAndIndexNumber.HasValue) |
| | 3249 | | { |
| 0 | 3250 | | whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndex |
| 0 | 3251 | | statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumb |
| 0 | 3252 | | statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber); |
| | 3253 | | } |
| | 3254 | |
|
| 672 | 3255 | | if (query.MinDateCreated.HasValue) |
| | 3256 | | { |
| 0 | 3257 | | whereClauses.Add("DateCreated>=@MinDateCreated"); |
| 0 | 3258 | | statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value); |
| | 3259 | | } |
| | 3260 | |
|
| 672 | 3261 | | if (query.MinDateLastSaved.HasValue) |
| | 3262 | | { |
| 0 | 3263 | | whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)"); |
| 0 | 3264 | | statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value); |
| | 3265 | | } |
| | 3266 | |
|
| 672 | 3267 | | if (query.MinDateLastSavedForUser.HasValue) |
| | 3268 | | { |
| 0 | 3269 | | whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)"); |
| 0 | 3270 | | statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value); |
| | 3271 | | } |
| | 3272 | |
|
| 672 | 3273 | | if (query.IndexNumber.HasValue) |
| | 3274 | | { |
| 0 | 3275 | | whereClauses.Add("IndexNumber=@IndexNumber"); |
| 0 | 3276 | | statement?.TryBind("@IndexNumber", query.IndexNumber.Value); |
| | 3277 | | } |
| | 3278 | |
|
| 672 | 3279 | | if (query.ParentIndexNumber.HasValue) |
| | 3280 | | { |
| 0 | 3281 | | whereClauses.Add("ParentIndexNumber=@ParentIndexNumber"); |
| 0 | 3282 | | statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); |
| | 3283 | | } |
| | 3284 | |
|
| 672 | 3285 | | if (query.ParentIndexNumberNotEquals.HasValue) |
| | 3286 | | { |
| 0 | 3287 | | whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)"); |
| 0 | 3288 | | statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value); |
| | 3289 | | } |
| | 3290 | |
|
| 672 | 3291 | | var minEndDate = query.MinEndDate; |
| 672 | 3292 | | var maxEndDate = query.MaxEndDate; |
| | 3293 | |
|
| 672 | 3294 | | if (query.HasAired.HasValue) |
| | 3295 | | { |
| 0 | 3296 | | if (query.HasAired.Value) |
| | 3297 | | { |
| 0 | 3298 | | maxEndDate = DateTime.UtcNow; |
| | 3299 | | } |
| | 3300 | | else |
| | 3301 | | { |
| 0 | 3302 | | minEndDate = DateTime.UtcNow; |
| | 3303 | | } |
| | 3304 | | } |
| | 3305 | |
|
| 672 | 3306 | | if (minEndDate.HasValue) |
| | 3307 | | { |
| 0 | 3308 | | whereClauses.Add("EndDate>=@MinEndDate"); |
| 0 | 3309 | | statement?.TryBind("@MinEndDate", minEndDate.Value); |
| | 3310 | | } |
| | 3311 | |
|
| 672 | 3312 | | if (maxEndDate.HasValue) |
| | 3313 | | { |
| 0 | 3314 | | whereClauses.Add("EndDate<=@MaxEndDate"); |
| 0 | 3315 | | statement?.TryBind("@MaxEndDate", maxEndDate.Value); |
| | 3316 | | } |
| | 3317 | |
|
| 672 | 3318 | | if (query.MinStartDate.HasValue) |
| | 3319 | | { |
| 0 | 3320 | | whereClauses.Add("StartDate>=@MinStartDate"); |
| 0 | 3321 | | statement?.TryBind("@MinStartDate", query.MinStartDate.Value); |
| | 3322 | | } |
| | 3323 | |
|
| 672 | 3324 | | if (query.MaxStartDate.HasValue) |
| | 3325 | | { |
| 0 | 3326 | | whereClauses.Add("StartDate<=@MaxStartDate"); |
| 0 | 3327 | | statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value); |
| | 3328 | | } |
| | 3329 | |
|
| 672 | 3330 | | if (query.MinPremiereDate.HasValue) |
| | 3331 | | { |
| 0 | 3332 | | whereClauses.Add("PremiereDate>=@MinPremiereDate"); |
| 0 | 3333 | | statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value); |
| | 3334 | | } |
| | 3335 | |
|
| 672 | 3336 | | if (query.MaxPremiereDate.HasValue) |
| | 3337 | | { |
| 0 | 3338 | | whereClauses.Add("PremiereDate<=@MaxPremiereDate"); |
| 0 | 3339 | | statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value); |
| | 3340 | | } |
| | 3341 | |
|
| 672 | 3342 | | StringBuilder clauseBuilder = new StringBuilder(); |
| | 3343 | | const string Or = " OR "; |
| | 3344 | |
|
| 672 | 3345 | | var trailerTypes = query.TrailerTypes; |
| 672 | 3346 | | int trailerTypesLen = trailerTypes.Length; |
| 672 | 3347 | | if (trailerTypesLen > 0) |
| | 3348 | | { |
| 0 | 3349 | | clauseBuilder.Append('('); |
| | 3350 | |
|
| 0 | 3351 | | for (int i = 0; i < trailerTypesLen; i++) |
| | 3352 | | { |
| 0 | 3353 | | var paramName = "@TrailerTypes" + i; |
| 0 | 3354 | | clauseBuilder.Append("TrailerTypes like ") |
| 0 | 3355 | | .Append(paramName) |
| 0 | 3356 | | .Append(Or); |
| 0 | 3357 | | statement?.TryBind(paramName, "%" + trailerTypes[i] + "%"); |
| | 3358 | | } |
| | 3359 | |
|
| 0 | 3360 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3361 | | clauseBuilder.Append(')'); |
| | 3362 | |
|
| 0 | 3363 | | whereClauses.Add(clauseBuilder.ToString()); |
| | 3364 | |
|
| 0 | 3365 | | clauseBuilder.Length = 0; |
| | 3366 | | } |
| | 3367 | |
|
| 672 | 3368 | | if (query.IsAiring.HasValue) |
| | 3369 | | { |
| 0 | 3370 | | if (query.IsAiring.Value) |
| | 3371 | | { |
| 0 | 3372 | | whereClauses.Add("StartDate<=@MaxStartDate"); |
| 0 | 3373 | | statement?.TryBind("@MaxStartDate", DateTime.UtcNow); |
| | 3374 | |
|
| 0 | 3375 | | whereClauses.Add("EndDate>=@MinEndDate"); |
| 0 | 3376 | | statement?.TryBind("@MinEndDate", DateTime.UtcNow); |
| | 3377 | | } |
| | 3378 | | else |
| | 3379 | | { |
| 0 | 3380 | | whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)"); |
| 0 | 3381 | | statement?.TryBind("@IsAiringDate", DateTime.UtcNow); |
| | 3382 | | } |
| | 3383 | | } |
| | 3384 | |
|
| 672 | 3385 | | int personIdsLen = query.PersonIds.Length; |
| 672 | 3386 | | if (personIdsLen > 0) |
| | 3387 | | { |
| | 3388 | | // TODO: Should this query with CleanName ? |
| | 3389 | |
|
| 0 | 3390 | | clauseBuilder.Append('('); |
| | 3391 | |
|
| 0 | 3392 | | Span<byte> idBytes = stackalloc byte[16]; |
| 0 | 3393 | | for (int i = 0; i < personIdsLen; i++) |
| | 3394 | | { |
| 0 | 3395 | | string paramName = "@PersonId" + i; |
| 0 | 3396 | | clauseBuilder.Append("(guid in (select itemid from People where Name = (select Name from TypedBaseIt |
| 0 | 3397 | | .Append(paramName) |
| 0 | 3398 | | .Append("))) OR "); |
| | 3399 | |
|
| 0 | 3400 | | statement?.TryBind(paramName, query.PersonIds[i]); |
| | 3401 | | } |
| | 3402 | |
|
| 0 | 3403 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3404 | | clauseBuilder.Append(')'); |
| | 3405 | |
|
| 0 | 3406 | | whereClauses.Add(clauseBuilder.ToString()); |
| | 3407 | |
|
| 0 | 3408 | | clauseBuilder.Length = 0; |
| | 3409 | | } |
| | 3410 | |
|
| 672 | 3411 | | if (!string.IsNullOrWhiteSpace(query.Person)) |
| | 3412 | | { |
| 0 | 3413 | | whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)"); |
| 0 | 3414 | | statement?.TryBind("@PersonName", query.Person); |
| | 3415 | | } |
| | 3416 | |
|
| 672 | 3417 | | if (!string.IsNullOrWhiteSpace(query.MinSortName)) |
| | 3418 | | { |
| 0 | 3419 | | whereClauses.Add("SortName>=@MinSortName"); |
| 0 | 3420 | | statement?.TryBind("@MinSortName", query.MinSortName); |
| | 3421 | | } |
| | 3422 | |
|
| 672 | 3423 | | if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId)) |
| | 3424 | | { |
| 0 | 3425 | | whereClauses.Add("ExternalSeriesId=@ExternalSeriesId"); |
| 0 | 3426 | | statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId); |
| | 3427 | | } |
| | 3428 | |
|
| 672 | 3429 | | if (!string.IsNullOrWhiteSpace(query.ExternalId)) |
| | 3430 | | { |
| 0 | 3431 | | whereClauses.Add("ExternalId=@ExternalId"); |
| 0 | 3432 | | statement?.TryBind("@ExternalId", query.ExternalId); |
| | 3433 | | } |
| | 3434 | |
|
| 672 | 3435 | | if (!string.IsNullOrWhiteSpace(query.Name)) |
| | 3436 | | { |
| 6 | 3437 | | whereClauses.Add("CleanName=@Name"); |
| 6 | 3438 | | statement?.TryBind("@Name", GetCleanValue(query.Name)); |
| | 3439 | | } |
| | 3440 | |
|
| | 3441 | | // These are the same, for now |
| 672 | 3442 | | var nameContains = query.NameContains; |
| 672 | 3443 | | if (!string.IsNullOrWhiteSpace(nameContains)) |
| | 3444 | | { |
| 0 | 3445 | | whereClauses.Add("(CleanName like @NameContains or OriginalTitle like @NameContains)"); |
| 0 | 3446 | | if (statement is not null) |
| | 3447 | | { |
| 0 | 3448 | | nameContains = FixUnicodeChars(nameContains); |
| 0 | 3449 | | statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%"); |
| | 3450 | | } |
| | 3451 | | } |
| | 3452 | |
|
| 672 | 3453 | | if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) |
| | 3454 | | { |
| 0 | 3455 | | whereClauses.Add("SortName like @NameStartsWith"); |
| 0 | 3456 | | statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%"); |
| | 3457 | | } |
| | 3458 | |
|
| 672 | 3459 | | if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) |
| | 3460 | | { |
| 0 | 3461 | | whereClauses.Add("SortName >= @NameStartsWithOrGreater"); |
| | 3462 | | // lowercase this because SortName is stored as lowercase |
| 0 | 3463 | | statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant()); |
| | 3464 | | } |
| | 3465 | |
|
| 672 | 3466 | | if (!string.IsNullOrWhiteSpace(query.NameLessThan)) |
| | 3467 | | { |
| 0 | 3468 | | whereClauses.Add("SortName < @NameLessThan"); |
| | 3469 | | // lowercase this because SortName is stored as lowercase |
| 0 | 3470 | | statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant()); |
| | 3471 | | } |
| | 3472 | |
|
| 672 | 3473 | | if (query.ImageTypes.Length > 0) |
| | 3474 | | { |
| 824 | 3475 | | foreach (var requiredImage in query.ImageTypes) |
| | 3476 | | { |
| 206 | 3477 | | whereClauses.Add("Images like '%" + requiredImage + "%'"); |
| | 3478 | | } |
| | 3479 | | } |
| | 3480 | |
|
| 672 | 3481 | | if (query.IsLiked.HasValue) |
| | 3482 | | { |
| 0 | 3483 | | if (query.IsLiked.Value) |
| | 3484 | | { |
| 0 | 3485 | | whereClauses.Add("rating>=@UserRating"); |
| 0 | 3486 | | statement?.TryBind("@UserRating", UserItemData.MinLikeValue); |
| | 3487 | | } |
| | 3488 | | else |
| | 3489 | | { |
| 0 | 3490 | | whereClauses.Add("(rating is null or rating<@UserRating)"); |
| 0 | 3491 | | statement?.TryBind("@UserRating", UserItemData.MinLikeValue); |
| | 3492 | | } |
| | 3493 | | } |
| | 3494 | |
|
| 672 | 3495 | | if (query.IsFavoriteOrLiked.HasValue) |
| | 3496 | | { |
| 0 | 3497 | | if (query.IsFavoriteOrLiked.Value) |
| | 3498 | | { |
| 0 | 3499 | | whereClauses.Add("IsFavorite=@IsFavoriteOrLiked"); |
| | 3500 | | } |
| | 3501 | | else |
| | 3502 | | { |
| 0 | 3503 | | whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)"); |
| | 3504 | | } |
| | 3505 | |
|
| 0 | 3506 | | statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value); |
| | 3507 | | } |
| | 3508 | |
|
| 672 | 3509 | | if (query.IsFavorite.HasValue) |
| | 3510 | | { |
| 0 | 3511 | | if (query.IsFavorite.Value) |
| | 3512 | | { |
| 0 | 3513 | | whereClauses.Add("IsFavorite=@IsFavorite"); |
| | 3514 | | } |
| | 3515 | | else |
| | 3516 | | { |
| 0 | 3517 | | whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)"); |
| | 3518 | | } |
| | 3519 | |
|
| 0 | 3520 | | statement?.TryBind("@IsFavorite", query.IsFavorite.Value); |
| | 3521 | | } |
| | 3522 | |
|
| 672 | 3523 | | if (EnableJoinUserData(query)) |
| | 3524 | | { |
| 2 | 3525 | | if (query.IsPlayed.HasValue) |
| | 3526 | | { |
| | 3527 | | // We should probably figure this out for all folders, but for right now, this is the only place whe |
| 0 | 3528 | | if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series) |
| | 3529 | | { |
| 0 | 3530 | | if (query.IsPlayed.Value) |
| | 3531 | | { |
| 0 | 3532 | | whereClauses.Add("PresentationUniqueKey not in (select S.SeriesPresentationUniqueKey from Ty |
| | 3533 | | } |
| | 3534 | | else |
| | 3535 | | { |
| 0 | 3536 | | whereClauses.Add("PresentationUniqueKey in (select S.SeriesPresentationUniqueKey from TypedB |
| | 3537 | | } |
| | 3538 | | } |
| | 3539 | | else |
| | 3540 | | { |
| 0 | 3541 | | if (query.IsPlayed.Value) |
| | 3542 | | { |
| 0 | 3543 | | whereClauses.Add("(played=@IsPlayed)"); |
| | 3544 | | } |
| | 3545 | | else |
| | 3546 | | { |
| 0 | 3547 | | whereClauses.Add("(played is null or played=@IsPlayed)"); |
| | 3548 | | } |
| | 3549 | |
|
| 0 | 3550 | | statement?.TryBind("@IsPlayed", query.IsPlayed.Value); |
| | 3551 | | } |
| | 3552 | | } |
| | 3553 | | } |
| | 3554 | |
|
| 672 | 3555 | | if (query.IsResumable.HasValue) |
| | 3556 | | { |
| 2 | 3557 | | if (query.IsResumable.Value) |
| | 3558 | | { |
| 2 | 3559 | | whereClauses.Add("playbackPositionTicks > 0"); |
| | 3560 | | } |
| | 3561 | | else |
| | 3562 | | { |
| 0 | 3563 | | whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)"); |
| | 3564 | | } |
| | 3565 | | } |
| | 3566 | |
|
| 672 | 3567 | | if (query.ArtistIds.Length > 0) |
| | 3568 | | { |
| 0 | 3569 | | clauseBuilder.Append('('); |
| 0 | 3570 | | for (var i = 0; i < query.ArtistIds.Length; i++) |
| | 3571 | | { |
| 0 | 3572 | | clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName f |
| 0 | 3573 | | .Append(i) |
| 0 | 3574 | | .Append(") and Type<=1)) OR "); |
| 0 | 3575 | | statement?.TryBind("@ArtistIds" + i, query.ArtistIds[i]); |
| | 3576 | | } |
| | 3577 | |
|
| 0 | 3578 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3579 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3580 | | clauseBuilder.Length = 0; |
| | 3581 | | } |
| | 3582 | |
|
| 672 | 3583 | | if (query.AlbumArtistIds.Length > 0) |
| | 3584 | | { |
| 0 | 3585 | | clauseBuilder.Append('('); |
| 0 | 3586 | | for (var i = 0; i < query.AlbumArtistIds.Length; i++) |
| | 3587 | | { |
| 0 | 3588 | | clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName f |
| 0 | 3589 | | .Append(i) |
| 0 | 3590 | | .Append(") and Type=1)) OR "); |
| 0 | 3591 | | statement?.TryBind("@ArtistIds" + i, query.AlbumArtistIds[i]); |
| | 3592 | | } |
| | 3593 | |
|
| 0 | 3594 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3595 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3596 | | clauseBuilder.Length = 0; |
| | 3597 | | } |
| | 3598 | |
|
| 672 | 3599 | | if (query.ContributingArtistIds.Length > 0) |
| | 3600 | | { |
| 0 | 3601 | | clauseBuilder.Append('('); |
| 0 | 3602 | | for (var i = 0; i < query.ContributingArtistIds.Length; i++) |
| | 3603 | | { |
| 0 | 3604 | | clauseBuilder.Append("((select CleanName from TypedBaseItems where guid=@ArtistIds") |
| 0 | 3605 | | .Append(i) |
| 0 | 3606 | | .Append(") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select Clean |
| 0 | 3607 | | .Append(i) |
| 0 | 3608 | | .Append(") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1)) OR "); |
| 0 | 3609 | | statement?.TryBind("@ArtistIds" + i, query.ContributingArtistIds[i]); |
| | 3610 | | } |
| | 3611 | |
|
| 0 | 3612 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3613 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3614 | | clauseBuilder.Length = 0; |
| | 3615 | | } |
| | 3616 | |
|
| 672 | 3617 | | if (query.AlbumIds.Length > 0) |
| | 3618 | | { |
| 0 | 3619 | | clauseBuilder.Append('('); |
| 0 | 3620 | | for (var i = 0; i < query.AlbumIds.Length; i++) |
| | 3621 | | { |
| 0 | 3622 | | clauseBuilder.Append("Album in (select Name from typedbaseitems where guid=@AlbumIds") |
| 0 | 3623 | | .Append(i) |
| 0 | 3624 | | .Append(") OR "); |
| 0 | 3625 | | statement?.TryBind("@AlbumIds" + i, query.AlbumIds[i]); |
| | 3626 | | } |
| | 3627 | |
|
| 0 | 3628 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3629 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3630 | | clauseBuilder.Length = 0; |
| | 3631 | | } |
| | 3632 | |
|
| 672 | 3633 | | if (query.ExcludeArtistIds.Length > 0) |
| | 3634 | | { |
| 0 | 3635 | | clauseBuilder.Append('('); |
| 0 | 3636 | | for (var i = 0; i < query.ExcludeArtistIds.Length; i++) |
| | 3637 | | { |
| 0 | 3638 | | clauseBuilder.Append("(guid not in (select itemid from ItemValues where CleanValue = (select CleanNa |
| 0 | 3639 | | .Append(i) |
| 0 | 3640 | | .Append(") and Type<=1)) OR "); |
| 0 | 3641 | | statement?.TryBind("@ExcludeArtistId" + i, query.ExcludeArtistIds[i]); |
| | 3642 | | } |
| | 3643 | |
|
| 0 | 3644 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3645 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3646 | | clauseBuilder.Length = 0; |
| | 3647 | | } |
| | 3648 | |
|
| 672 | 3649 | | if (query.GenreIds.Count > 0) |
| | 3650 | | { |
| 0 | 3651 | | clauseBuilder.Append('('); |
| 0 | 3652 | | for (var i = 0; i < query.GenreIds.Count; i++) |
| | 3653 | | { |
| 0 | 3654 | | clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName f |
| 0 | 3655 | | .Append(i) |
| 0 | 3656 | | .Append(") and Type=2)) OR "); |
| 0 | 3657 | | statement?.TryBind("@GenreId" + i, query.GenreIds[i]); |
| | 3658 | | } |
| | 3659 | |
|
| 0 | 3660 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3661 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3662 | | clauseBuilder.Length = 0; |
| | 3663 | | } |
| | 3664 | |
|
| 672 | 3665 | | if (query.Genres.Count > 0) |
| | 3666 | | { |
| 0 | 3667 | | clauseBuilder.Append('('); |
| 0 | 3668 | | for (var i = 0; i < query.Genres.Count; i++) |
| | 3669 | | { |
| 0 | 3670 | | clauseBuilder.Append("@Genre") |
| 0 | 3671 | | .Append(i) |
| 0 | 3672 | | .Append(" in (select CleanValue from ItemValues where ItemId=Guid and Type=2) OR "); |
| 0 | 3673 | | statement?.TryBind("@Genre" + i, GetCleanValue(query.Genres[i])); |
| | 3674 | | } |
| | 3675 | |
|
| 0 | 3676 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3677 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3678 | | clauseBuilder.Length = 0; |
| | 3679 | | } |
| | 3680 | |
|
| 672 | 3681 | | if (tags.Count > 0) |
| | 3682 | | { |
| 0 | 3683 | | clauseBuilder.Append('('); |
| 0 | 3684 | | for (var i = 0; i < tags.Count; i++) |
| | 3685 | | { |
| 0 | 3686 | | clauseBuilder.Append("@Tag") |
| 0 | 3687 | | .Append(i) |
| 0 | 3688 | | .Append(" in (select CleanValue from ItemValues where ItemId=Guid and Type=4) OR "); |
| 0 | 3689 | | statement?.TryBind("@Tag" + i, GetCleanValue(tags[i])); |
| | 3690 | | } |
| | 3691 | |
|
| 0 | 3692 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3693 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3694 | | clauseBuilder.Length = 0; |
| | 3695 | | } |
| | 3696 | |
|
| 672 | 3697 | | if (excludeTags.Count > 0) |
| | 3698 | | { |
| 0 | 3699 | | clauseBuilder.Append('('); |
| 0 | 3700 | | for (var i = 0; i < excludeTags.Count; i++) |
| | 3701 | | { |
| 0 | 3702 | | clauseBuilder.Append("@ExcludeTag") |
| 0 | 3703 | | .Append(i) |
| 0 | 3704 | | .Append(" not in (select CleanValue from ItemValues where ItemId=Guid and Type=4) OR "); |
| 0 | 3705 | | statement?.TryBind("@ExcludeTag" + i, GetCleanValue(excludeTags[i])); |
| | 3706 | | } |
| | 3707 | |
|
| 0 | 3708 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3709 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3710 | | clauseBuilder.Length = 0; |
| | 3711 | | } |
| | 3712 | |
|
| 672 | 3713 | | if (query.StudioIds.Length > 0) |
| | 3714 | | { |
| 0 | 3715 | | clauseBuilder.Append('('); |
| 0 | 3716 | | for (var i = 0; i < query.StudioIds.Length; i++) |
| | 3717 | | { |
| 0 | 3718 | | clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName f |
| 0 | 3719 | | .Append(i) |
| 0 | 3720 | | .Append(") and Type=3)) OR "); |
| 0 | 3721 | | statement?.TryBind("@StudioId" + i, query.StudioIds[i]); |
| | 3722 | | } |
| | 3723 | |
|
| 0 | 3724 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3725 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3726 | | clauseBuilder.Length = 0; |
| | 3727 | | } |
| | 3728 | |
|
| 672 | 3729 | | if (query.OfficialRatings.Length > 0) |
| | 3730 | | { |
| 0 | 3731 | | clauseBuilder.Append('('); |
| 0 | 3732 | | for (var i = 0; i < query.OfficialRatings.Length; i++) |
| | 3733 | | { |
| 0 | 3734 | | clauseBuilder.Append("OfficialRating=@OfficialRating").Append(i).Append(Or); |
| 0 | 3735 | | statement?.TryBind("@OfficialRating" + i, query.OfficialRatings[i]); |
| | 3736 | | } |
| | 3737 | |
|
| 0 | 3738 | | clauseBuilder.Length -= Or.Length; |
| 0 | 3739 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 0 | 3740 | | clauseBuilder.Length = 0; |
| | 3741 | | } |
| | 3742 | |
|
| 672 | 3743 | | clauseBuilder.Append('('); |
| 672 | 3744 | | if (query.HasParentalRating ?? false) |
| | 3745 | | { |
| 0 | 3746 | | clauseBuilder.Append("InheritedParentalRatingValue not null"); |
| 0 | 3747 | | if (query.MinParentalRating.HasValue) |
| | 3748 | | { |
| 0 | 3749 | | clauseBuilder.Append(" AND InheritedParentalRatingValue >= @MinParentalRating"); |
| 0 | 3750 | | statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value); |
| | 3751 | | } |
| | 3752 | |
|
| 0 | 3753 | | if (query.MaxParentalRating.HasValue) |
| | 3754 | | { |
| 0 | 3755 | | clauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating"); |
| 0 | 3756 | | statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value); |
| | 3757 | | } |
| | 3758 | | } |
| 672 | 3759 | | else if (query.BlockUnratedItems.Length > 0) |
| | 3760 | | { |
| | 3761 | | const string ParamName = "@UnratedType"; |
| 0 | 3762 | | clauseBuilder.Append("(InheritedParentalRatingValue is null AND UnratedType not in ("); |
| | 3763 | |
|
| 0 | 3764 | | for (int i = 0; i < query.BlockUnratedItems.Length; i++) |
| | 3765 | | { |
| 0 | 3766 | | clauseBuilder.Append(ParamName).Append(i).Append(','); |
| 0 | 3767 | | statement?.TryBind(ParamName + i, query.BlockUnratedItems[i].ToString()); |
| | 3768 | | } |
| | 3769 | |
|
| | 3770 | | // Remove trailing comma |
| 0 | 3771 | | clauseBuilder.Length--; |
| 0 | 3772 | | clauseBuilder.Append("))"); |
| | 3773 | |
|
| 0 | 3774 | | if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue) |
| | 3775 | | { |
| 0 | 3776 | | clauseBuilder.Append(" OR ("); |
| | 3777 | | } |
| | 3778 | |
|
| 0 | 3779 | | if (query.MinParentalRating.HasValue) |
| | 3780 | | { |
| 0 | 3781 | | clauseBuilder.Append("InheritedParentalRatingValue >= @MinParentalRating"); |
| 0 | 3782 | | statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value); |
| | 3783 | | } |
| | 3784 | |
|
| 0 | 3785 | | if (query.MaxParentalRating.HasValue) |
| | 3786 | | { |
| 0 | 3787 | | if (query.MinParentalRating.HasValue) |
| | 3788 | | { |
| 0 | 3789 | | clauseBuilder.Append(" AND "); |
| | 3790 | | } |
| | 3791 | |
|
| 0 | 3792 | | clauseBuilder.Append("InheritedParentalRatingValue <= @MaxParentalRating"); |
| 0 | 3793 | | statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value); |
| | 3794 | | } |
| | 3795 | |
|
| 0 | 3796 | | if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue) |
| | 3797 | | { |
| 0 | 3798 | | clauseBuilder.Append(')'); |
| | 3799 | | } |
| | 3800 | |
|
| 0 | 3801 | | if (!(query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)) |
| | 3802 | | { |
| 0 | 3803 | | clauseBuilder.Append(" OR InheritedParentalRatingValue not null"); |
| | 3804 | | } |
| | 3805 | | } |
| 672 | 3806 | | else if (query.MinParentalRating.HasValue) |
| | 3807 | | { |
| 0 | 3808 | | clauseBuilder.Append("InheritedParentalRatingValue is null OR (InheritedParentalRatingValue >= @MinParen |
| 0 | 3809 | | statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value); |
| | 3810 | |
|
| 0 | 3811 | | if (query.MaxParentalRating.HasValue) |
| | 3812 | | { |
| 0 | 3813 | | clauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating"); |
| 0 | 3814 | | statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value); |
| | 3815 | | } |
| | 3816 | |
|
| 0 | 3817 | | clauseBuilder.Append(')'); |
| | 3818 | | } |
| 672 | 3819 | | else if (query.MaxParentalRating.HasValue) |
| | 3820 | | { |
| 114 | 3821 | | clauseBuilder.Append("InheritedParentalRatingValue is null OR InheritedParentalRatingValue <= @MaxParent |
| 114 | 3822 | | statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value); |
| | 3823 | | } |
| 558 | 3824 | | else if (!query.HasParentalRating ?? false) |
| | 3825 | | { |
| 0 | 3826 | | clauseBuilder.Append("InheritedParentalRatingValue is null"); |
| | 3827 | | } |
| | 3828 | |
|
| 672 | 3829 | | if (clauseBuilder.Length > 1) |
| | 3830 | | { |
| 114 | 3831 | | whereClauses.Add(clauseBuilder.Append(')').ToString()); |
| 114 | 3832 | | clauseBuilder.Length = 0; |
| | 3833 | | } |
| | 3834 | |
|
| 672 | 3835 | | if (query.HasOfficialRating.HasValue) |
| | 3836 | | { |
| 0 | 3837 | | if (query.HasOfficialRating.Value) |
| | 3838 | | { |
| 0 | 3839 | | whereClauses.Add("(OfficialRating not null AND OfficialRating<>'')"); |
| | 3840 | | } |
| | 3841 | | else |
| | 3842 | | { |
| 0 | 3843 | | whereClauses.Add("(OfficialRating is null OR OfficialRating='')"); |
| | 3844 | | } |
| | 3845 | | } |
| | 3846 | |
|
| 672 | 3847 | | if (query.HasOverview.HasValue) |
| | 3848 | | { |
| 0 | 3849 | | if (query.HasOverview.Value) |
| | 3850 | | { |
| 0 | 3851 | | whereClauses.Add("(Overview not null AND Overview<>'')"); |
| | 3852 | | } |
| | 3853 | | else |
| | 3854 | | { |
| 0 | 3855 | | whereClauses.Add("(Overview is null OR Overview='')"); |
| | 3856 | | } |
| | 3857 | | } |
| | 3858 | |
|
| 672 | 3859 | | if (query.HasOwnerId.HasValue) |
| | 3860 | | { |
| 0 | 3861 | | if (query.HasOwnerId.Value) |
| | 3862 | | { |
| 0 | 3863 | | whereClauses.Add("OwnerId not null"); |
| | 3864 | | } |
| | 3865 | | else |
| | 3866 | | { |
| 0 | 3867 | | whereClauses.Add("OwnerId is null"); |
| | 3868 | | } |
| | 3869 | | } |
| | 3870 | |
|
| 672 | 3871 | | if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage)) |
| | 3872 | | { |
| 0 | 3873 | | whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams. |
| 0 | 3874 | | statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage); |
| | 3875 | | } |
| | 3876 | |
|
| 672 | 3877 | | if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage)) |
| | 3878 | | { |
| 0 | 3879 | | whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams. |
| 0 | 3880 | | statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLangua |
| | 3881 | | } |
| | 3882 | |
|
| 672 | 3883 | | if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage)) |
| | 3884 | | { |
| 0 | 3885 | | whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams. |
| 0 | 3886 | | statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLangua |
| | 3887 | | } |
| | 3888 | |
|
| 672 | 3889 | | if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage)) |
| | 3890 | | { |
| 0 | 3891 | | whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams. |
| 0 | 3892 | | statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage); |
| | 3893 | | } |
| | 3894 | |
|
| 672 | 3895 | | if (query.HasSubtitles.HasValue) |
| | 3896 | | { |
| 0 | 3897 | | if (query.HasSubtitles.Value) |
| | 3898 | | { |
| 0 | 3899 | | whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams. |
| | 3900 | | } |
| | 3901 | | else |
| | 3902 | | { |
| 0 | 3903 | | whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams. |
| | 3904 | | } |
| | 3905 | | } |
| | 3906 | |
|
| 672 | 3907 | | if (query.HasChapterImages.HasValue) |
| | 3908 | | { |
| 0 | 3909 | | if (query.HasChapterImages.Value) |
| | 3910 | | { |
| 0 | 3911 | | whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not |
| | 3912 | | } |
| | 3913 | | else |
| | 3914 | | { |
| 0 | 3915 | | whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not |
| | 3916 | | } |
| | 3917 | | } |
| | 3918 | |
|
| 672 | 3919 | | if (query.HasDeadParentId.HasValue && query.HasDeadParentId.Value) |
| | 3920 | | { |
| 38 | 3921 | | whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)"); |
| | 3922 | | } |
| | 3923 | |
|
| 672 | 3924 | | if (query.IsDeadArtist.HasValue && query.IsDeadArtist.Value) |
| | 3925 | | { |
| 38 | 3926 | | whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type in (0,1))"); |
| | 3927 | | } |
| | 3928 | |
|
| 672 | 3929 | | if (query.IsDeadStudio.HasValue && query.IsDeadStudio.Value) |
| | 3930 | | { |
| 38 | 3931 | | whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type = 3)"); |
| | 3932 | | } |
| | 3933 | |
|
| 672 | 3934 | | if (query.IsDeadPerson.HasValue && query.IsDeadPerson.Value) |
| | 3935 | | { |
| 0 | 3936 | | whereClauses.Add("Name not in (Select Name From People)"); |
| | 3937 | | } |
| | 3938 | |
|
| 672 | 3939 | | if (query.Years.Length == 1) |
| | 3940 | | { |
| 0 | 3941 | | whereClauses.Add("ProductionYear=@Years"); |
| 0 | 3942 | | statement?.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture)); |
| | 3943 | | } |
| 672 | 3944 | | else if (query.Years.Length > 1) |
| | 3945 | | { |
| 0 | 3946 | | var val = string.Join(',', query.Years); |
| 0 | 3947 | | whereClauses.Add("ProductionYear in (" + val + ")"); |
| | 3948 | | } |
| | 3949 | |
|
| 672 | 3950 | | var isVirtualItem = query.IsVirtualItem ?? query.IsMissing; |
| 672 | 3951 | | if (isVirtualItem.HasValue) |
| | 3952 | | { |
| 2 | 3953 | | whereClauses.Add("IsVirtualItem=@IsVirtualItem"); |
| 2 | 3954 | | statement?.TryBind("@IsVirtualItem", isVirtualItem.Value); |
| | 3955 | | } |
| | 3956 | |
|
| 672 | 3957 | | if (query.IsSpecialSeason.HasValue) |
| | 3958 | | { |
| 0 | 3959 | | if (query.IsSpecialSeason.Value) |
| | 3960 | | { |
| 0 | 3961 | | whereClauses.Add("IndexNumber = 0"); |
| | 3962 | | } |
| | 3963 | | else |
| | 3964 | | { |
| 0 | 3965 | | whereClauses.Add("IndexNumber <> 0"); |
| | 3966 | | } |
| | 3967 | | } |
| | 3968 | |
|
| 672 | 3969 | | if (query.IsUnaired.HasValue) |
| | 3970 | | { |
| 0 | 3971 | | if (query.IsUnaired.Value) |
| | 3972 | | { |
| 0 | 3973 | | whereClauses.Add("PremiereDate >= DATETIME('now')"); |
| | 3974 | | } |
| | 3975 | | else |
| | 3976 | | { |
| 0 | 3977 | | whereClauses.Add("PremiereDate < DATETIME('now')"); |
| | 3978 | | } |
| | 3979 | | } |
| | 3980 | |
|
| 672 | 3981 | | if (query.MediaTypes.Length == 1) |
| | 3982 | | { |
| 0 | 3983 | | whereClauses.Add("MediaType=@MediaTypes"); |
| 0 | 3984 | | statement?.TryBind("@MediaTypes", query.MediaTypes[0].ToString()); |
| | 3985 | | } |
| 672 | 3986 | | else if (query.MediaTypes.Length > 1) |
| | 3987 | | { |
| 0 | 3988 | | var val = string.Join(',', query.MediaTypes.Select(i => $"'{i}'")); |
| 0 | 3989 | | whereClauses.Add("MediaType in (" + val + ")"); |
| | 3990 | | } |
| | 3991 | |
|
| 672 | 3992 | | if (query.ItemIds.Length > 0) |
| | 3993 | | { |
| 0 | 3994 | | var includeIds = new List<string>(); |
| 0 | 3995 | | var index = 0; |
| 0 | 3996 | | foreach (var id in query.ItemIds) |
| | 3997 | | { |
| 0 | 3998 | | includeIds.Add("Guid = @IncludeId" + index); |
| 0 | 3999 | | statement?.TryBind("@IncludeId" + index, id); |
| 0 | 4000 | | index++; |
| | 4001 | | } |
| | 4002 | |
|
| 0 | 4003 | | whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")"); |
| | 4004 | | } |
| | 4005 | |
|
| 672 | 4006 | | if (query.ExcludeItemIds.Length > 0) |
| | 4007 | | { |
| 0 | 4008 | | var excludeIds = new List<string>(); |
| 0 | 4009 | | var index = 0; |
| 0 | 4010 | | foreach (var id in query.ExcludeItemIds) |
| | 4011 | | { |
| 0 | 4012 | | excludeIds.Add("Guid <> @ExcludeId" + index); |
| 0 | 4013 | | statement?.TryBind("@ExcludeId" + index, id); |
| 0 | 4014 | | index++; |
| | 4015 | | } |
| | 4016 | |
|
| 0 | 4017 | | whereClauses.Add(string.Join(" AND ", excludeIds)); |
| | 4018 | | } |
| | 4019 | |
|
| 672 | 4020 | | if (query.ExcludeProviderIds is not null && query.ExcludeProviderIds.Count > 0) |
| | 4021 | | { |
| 0 | 4022 | | var excludeIds = new List<string>(); |
| | 4023 | |
|
| 0 | 4024 | | var index = 0; |
| 0 | 4025 | | foreach (var pair in query.ExcludeProviderIds) |
| | 4026 | | { |
| 0 | 4027 | | if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreC |
| | 4028 | | { |
| | 4029 | | continue; |
| | 4030 | | } |
| | 4031 | |
|
| 0 | 4032 | | var paramName = "@ExcludeProviderId" + index; |
| 0 | 4033 | | excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")"); |
| 0 | 4034 | | statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); |
| 0 | 4035 | | index++; |
| | 4036 | |
|
| 0 | 4037 | | break; |
| | 4038 | | } |
| | 4039 | |
|
| 0 | 4040 | | if (excludeIds.Count > 0) |
| | 4041 | | { |
| 0 | 4042 | | whereClauses.Add(string.Join(" AND ", excludeIds)); |
| | 4043 | | } |
| | 4044 | | } |
| | 4045 | |
|
| 672 | 4046 | | if (query.HasAnyProviderId is not null && query.HasAnyProviderId.Count > 0) |
| | 4047 | | { |
| 0 | 4048 | | var hasProviderIds = new List<string>(); |
| | 4049 | |
|
| 0 | 4050 | | var index = 0; |
| 0 | 4051 | | foreach (var pair in query.HasAnyProviderId) |
| | 4052 | | { |
| 0 | 4053 | | if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreC |
| | 4054 | | { |
| | 4055 | | continue; |
| | 4056 | | } |
| | 4057 | |
|
| | 4058 | | // TODO this seems to be an idea for a better schema where ProviderIds are their own table |
| | 4059 | | // but this is not implemented |
| | 4060 | | // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pa |
| | 4061 | |
|
| | 4062 | | // TODO this is a really BAD way to do it since the pair: |
| | 4063 | | // Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567 |
| | 4064 | | // and maybe even NotTmdb=1234. |
| | 4065 | |
|
| | 4066 | | // this is a placeholder for this specific pair to correlate it in the bigger query |
| 0 | 4067 | | var paramName = "@HasAnyProviderId" + index; |
| | 4068 | |
|
| | 4069 | | // this is a search for the placeholder |
| 0 | 4070 | | hasProviderIds.Add("ProviderIds like " + paramName); |
| | 4071 | |
|
| | 4072 | | // this replaces the placeholder with a value, here: %key=val% |
| 0 | 4073 | | statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); |
| 0 | 4074 | | index++; |
| | 4075 | |
|
| 0 | 4076 | | break; |
| | 4077 | | } |
| | 4078 | |
|
| 0 | 4079 | | if (hasProviderIds.Count > 0) |
| | 4080 | | { |
| 0 | 4081 | | whereClauses.Add("(" + string.Join(" OR ", hasProviderIds) + ")"); |
| | 4082 | | } |
| | 4083 | | } |
| | 4084 | |
|
| 672 | 4085 | | if (query.HasImdbId.HasValue) |
| | 4086 | | { |
| 0 | 4087 | | whereClauses.Add(GetProviderIdClause(query.HasImdbId.Value, "imdb")); |
| | 4088 | | } |
| | 4089 | |
|
| 672 | 4090 | | if (query.HasTmdbId.HasValue) |
| | 4091 | | { |
| 0 | 4092 | | whereClauses.Add(GetProviderIdClause(query.HasTmdbId.Value, "tmdb")); |
| | 4093 | | } |
| | 4094 | |
|
| 672 | 4095 | | if (query.HasTvdbId.HasValue) |
| | 4096 | | { |
| 0 | 4097 | | whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb")); |
| | 4098 | | } |
| | 4099 | |
|
| 672 | 4100 | | var queryTopParentIds = query.TopParentIds; |
| | 4101 | |
|
| 672 | 4102 | | if (queryTopParentIds.Length > 0) |
| | 4103 | | { |
| 10 | 4104 | | var includedItemByNameTypes = GetItemByNameTypesInQuery(query); |
| 10 | 4105 | | var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; |
| | 4106 | |
|
| 10 | 4107 | | if (queryTopParentIds.Length == 1) |
| | 4108 | | { |
| 10 | 4109 | | if (enableItemsByName && includedItemByNameTypes.Count == 1) |
| | 4110 | | { |
| 0 | 4111 | | whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)"); |
| 0 | 4112 | | statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); |
| | 4113 | | } |
| 10 | 4114 | | else if (enableItemsByName && includedItemByNameTypes.Count > 1) |
| | 4115 | | { |
| 0 | 4116 | | var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'")); |
| 0 | 4117 | | whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))"); |
| | 4118 | | } |
| | 4119 | | else |
| | 4120 | | { |
| 10 | 4121 | | whereClauses.Add("(TopParentId=@TopParentId)"); |
| | 4122 | | } |
| | 4123 | |
|
| 10 | 4124 | | statement?.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)) |
| | 4125 | | } |
| 0 | 4126 | | else if (queryTopParentIds.Length > 1) |
| | 4127 | | { |
| 0 | 4128 | | var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.Invariant |
| | 4129 | |
|
| 0 | 4130 | | if (enableItemsByName && includedItemByNameTypes.Count == 1) |
| | 4131 | | { |
| 0 | 4132 | | whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))"); |
| 0 | 4133 | | statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); |
| | 4134 | | } |
| 0 | 4135 | | else if (enableItemsByName && includedItemByNameTypes.Count > 1) |
| | 4136 | | { |
| 0 | 4137 | | var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'")); |
| 0 | 4138 | | whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))"); |
| | 4139 | | } |
| | 4140 | | else |
| | 4141 | | { |
| 0 | 4142 | | whereClauses.Add("TopParentId in (" + val + ")"); |
| | 4143 | | } |
| | 4144 | | } |
| | 4145 | | } |
| | 4146 | |
|
| 672 | 4147 | | if (query.AncestorIds.Length == 1) |
| | 4148 | | { |
| 52 | 4149 | | whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)"); |
| 52 | 4150 | | statement?.TryBind("@AncestorId", query.AncestorIds[0]); |
| | 4151 | | } |
| | 4152 | |
|
| 672 | 4153 | | if (query.AncestorIds.Length > 1) |
| | 4154 | | { |
| 0 | 4155 | | var inClause = string.Join(',', query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.Invarian |
| 0 | 4156 | | whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds wh |
| | 4157 | | } |
| | 4158 | |
|
| 672 | 4159 | | if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) |
| | 4160 | | { |
| 0 | 4161 | | var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUni |
| 0 | 4162 | | whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds wh |
| 0 | 4163 | | statement?.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey); |
| | 4164 | | } |
| | 4165 | |
|
| 672 | 4166 | | if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey)) |
| | 4167 | | { |
| 0 | 4168 | | whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey"); |
| 0 | 4169 | | statement?.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey); |
| | 4170 | | } |
| | 4171 | |
|
| 672 | 4172 | | if (query.ExcludeInheritedTags.Length > 0) |
| | 4173 | | { |
| 0 | 4174 | | var paramName = "@ExcludeInheritedTags"; |
| 0 | 4175 | | if (statement is null) |
| | 4176 | | { |
| 0 | 4177 | | int index = 0; |
| 0 | 4178 | | string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++)); |
| 0 | 4179 | | whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in |
| | 4180 | | } |
| | 4181 | | else |
| | 4182 | | { |
| 0 | 4183 | | for (int index = 0; index < query.ExcludeInheritedTags.Length; index++) |
| | 4184 | | { |
| 0 | 4185 | | statement.TryBind(paramName + index, GetCleanValue(query.ExcludeInheritedTags[index])); |
| | 4186 | | } |
| | 4187 | | } |
| | 4188 | | } |
| | 4189 | |
|
| 672 | 4190 | | if (query.IncludeInheritedTags.Length > 0) |
| | 4191 | | { |
| 0 | 4192 | | var paramName = "@IncludeInheritedTags"; |
| 0 | 4193 | | if (statement is null) |
| | 4194 | | { |
| 0 | 4195 | | int index = 0; |
| 0 | 4196 | | string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++)); |
| | 4197 | | // Episodes do not store inherit tags from their parents in the database, and the tag may be still r |
| | 4198 | | // In addtion to the tags for the episodes themselves, we need to manually query its parent (the sea |
| 0 | 4199 | | if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode) |
| | 4200 | | { |
| 0 | 4201 | | whereClauses.Add($""" |
| 0 | 4202 | | ((select CleanValue from ItemValues where ItemId=Guid and Type=6 and CleanValu |
| 0 | 4203 | | OR (select CleanValue from ItemValues where ItemId=ParentId and Type=6 and Cle |
| 0 | 4204 | | """); |
| | 4205 | | } |
| | 4206 | | else |
| | 4207 | | { |
| 0 | 4208 | | whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalu |
| | 4209 | | } |
| | 4210 | | } |
| | 4211 | | else |
| | 4212 | | { |
| 0 | 4213 | | for (int index = 0; index < query.IncludeInheritedTags.Length; index++) |
| | 4214 | | { |
| 0 | 4215 | | statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index])); |
| | 4216 | | } |
| | 4217 | | } |
| | 4218 | | } |
| | 4219 | |
|
| 672 | 4220 | | if (query.SeriesStatuses.Length > 0) |
| | 4221 | | { |
| 0 | 4222 | | var statuses = new List<string>(); |
| | 4223 | |
|
| 0 | 4224 | | foreach (var seriesStatus in query.SeriesStatuses) |
| | 4225 | | { |
| 0 | 4226 | | statuses.Add("data like '%" + seriesStatus + "%'"); |
| | 4227 | | } |
| | 4228 | |
|
| 0 | 4229 | | whereClauses.Add("(" + string.Join(" OR ", statuses) + ")"); |
| | 4230 | | } |
| | 4231 | |
|
| 672 | 4232 | | if (query.BoxSetLibraryFolders.Length > 0) |
| | 4233 | | { |
| 0 | 4234 | | var folderIdQueries = new List<string>(); |
| | 4235 | |
|
| 0 | 4236 | | foreach (var folderId in query.BoxSetLibraryFolders) |
| | 4237 | | { |
| 0 | 4238 | | folderIdQueries.Add("data like '%" + folderId.ToString("N", CultureInfo.InvariantCulture) + "%'"); |
| | 4239 | | } |
| | 4240 | |
|
| 0 | 4241 | | whereClauses.Add("(" + string.Join(" OR ", folderIdQueries) + ")"); |
| | 4242 | | } |
| | 4243 | |
|
| 672 | 4244 | | if (query.VideoTypes.Length > 0) |
| | 4245 | | { |
| 0 | 4246 | | var videoTypes = new List<string>(); |
| | 4247 | |
|
| 0 | 4248 | | foreach (var videoType in query.VideoTypes) |
| | 4249 | | { |
| 0 | 4250 | | videoTypes.Add("data like '%\"VideoType\":\"" + videoType + "\"%'"); |
| | 4251 | | } |
| | 4252 | |
|
| 0 | 4253 | | whereClauses.Add("(" + string.Join(" OR ", videoTypes) + ")"); |
| | 4254 | | } |
| | 4255 | |
|
| 672 | 4256 | | if (query.Is3D.HasValue) |
| | 4257 | | { |
| 0 | 4258 | | if (query.Is3D.Value) |
| | 4259 | | { |
| 0 | 4260 | | whereClauses.Add("data like '%Video3DFormat%'"); |
| | 4261 | | } |
| | 4262 | | else |
| | 4263 | | { |
| 0 | 4264 | | whereClauses.Add("data not like '%Video3DFormat%'"); |
| | 4265 | | } |
| | 4266 | | } |
| | 4267 | |
|
| 672 | 4268 | | if (query.IsPlaceHolder.HasValue) |
| | 4269 | | { |
| 0 | 4270 | | if (query.IsPlaceHolder.Value) |
| | 4271 | | { |
| 0 | 4272 | | whereClauses.Add("data like '%\"IsPlaceHolder\":true%'"); |
| | 4273 | | } |
| | 4274 | | else |
| | 4275 | | { |
| 0 | 4276 | | whereClauses.Add("(data is null or data not like '%\"IsPlaceHolder\":true%')"); |
| | 4277 | | } |
| | 4278 | | } |
| | 4279 | |
|
| 672 | 4280 | | if (query.HasSpecialFeature.HasValue) |
| | 4281 | | { |
| 0 | 4282 | | if (query.HasSpecialFeature.Value) |
| | 4283 | | { |
| 0 | 4284 | | whereClauses.Add("ExtraIds not null"); |
| | 4285 | | } |
| | 4286 | | else |
| | 4287 | | { |
| 0 | 4288 | | whereClauses.Add("ExtraIds is null"); |
| | 4289 | | } |
| | 4290 | | } |
| | 4291 | |
|
| 672 | 4292 | | if (query.HasTrailer.HasValue) |
| | 4293 | | { |
| 0 | 4294 | | if (query.HasTrailer.Value) |
| | 4295 | | { |
| 0 | 4296 | | whereClauses.Add("ExtraIds not null"); |
| | 4297 | | } |
| | 4298 | | else |
| | 4299 | | { |
| 0 | 4300 | | whereClauses.Add("ExtraIds is null"); |
| | 4301 | | } |
| | 4302 | | } |
| | 4303 | |
|
| 672 | 4304 | | if (query.HasThemeSong.HasValue) |
| | 4305 | | { |
| 0 | 4306 | | if (query.HasThemeSong.Value) |
| | 4307 | | { |
| 0 | 4308 | | whereClauses.Add("ExtraIds not null"); |
| | 4309 | | } |
| | 4310 | | else |
| | 4311 | | { |
| 0 | 4312 | | whereClauses.Add("ExtraIds is null"); |
| | 4313 | | } |
| | 4314 | | } |
| | 4315 | |
|
| 672 | 4316 | | if (query.HasThemeVideo.HasValue) |
| | 4317 | | { |
| 0 | 4318 | | if (query.HasThemeVideo.Value) |
| | 4319 | | { |
| 0 | 4320 | | whereClauses.Add("ExtraIds not null"); |
| | 4321 | | } |
| | 4322 | | else |
| | 4323 | | { |
| 0 | 4324 | | whereClauses.Add("ExtraIds is null"); |
| | 4325 | | } |
| | 4326 | | } |
| | 4327 | |
|
| 672 | 4328 | | return whereClauses; |
| | 4329 | | } |
| | 4330 | |
|
| | 4331 | | /// <summary> |
| | 4332 | | /// Formats a where clause for the specified provider. |
| | 4333 | | /// </summary> |
| | 4334 | | /// <param name="includeResults">Whether or not to include items with this provider's ids.</param> |
| | 4335 | | /// <param name="provider">Provider name.</param> |
| | 4336 | | /// <returns>Formatted SQL clause.</returns> |
| | 4337 | | private string GetProviderIdClause(bool includeResults, string provider) |
| | 4338 | | { |
| 0 | 4339 | | return string.Format( |
| 0 | 4340 | | CultureInfo.InvariantCulture, |
| 0 | 4341 | | "ProviderIds {0} like '%{1}=%'", |
| 0 | 4342 | | includeResults ? string.Empty : "not", |
| 0 | 4343 | | provider); |
| | 4344 | | } |
| | 4345 | |
|
| | 4346 | | #nullable disable |
| | 4347 | | private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query) |
| | 4348 | | { |
| 10 | 4349 | | var list = new List<string>(); |
| | 4350 | |
|
| 10 | 4351 | | if (IsTypeInQuery(BaseItemKind.Person, query)) |
| | 4352 | | { |
| 2 | 4353 | | list.Add(typeof(Person).FullName); |
| | 4354 | | } |
| | 4355 | |
|
| 10 | 4356 | | if (IsTypeInQuery(BaseItemKind.Genre, query)) |
| | 4357 | | { |
| 2 | 4358 | | list.Add(typeof(Genre).FullName); |
| | 4359 | | } |
| | 4360 | |
|
| 10 | 4361 | | if (IsTypeInQuery(BaseItemKind.MusicGenre, query)) |
| | 4362 | | { |
| 2 | 4363 | | list.Add(typeof(MusicGenre).FullName); |
| | 4364 | | } |
| | 4365 | |
|
| 10 | 4366 | | if (IsTypeInQuery(BaseItemKind.MusicArtist, query)) |
| | 4367 | | { |
| 2 | 4368 | | list.Add(typeof(MusicArtist).FullName); |
| | 4369 | | } |
| | 4370 | |
|
| 10 | 4371 | | if (IsTypeInQuery(BaseItemKind.Studio, query)) |
| | 4372 | | { |
| 2 | 4373 | | list.Add(typeof(Studio).FullName); |
| | 4374 | | } |
| | 4375 | |
|
| 10 | 4376 | | return list; |
| | 4377 | | } |
| | 4378 | |
|
| | 4379 | | private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query) |
| | 4380 | | { |
| 50 | 4381 | | if (query.ExcludeItemTypes.Contains(type)) |
| | 4382 | | { |
| 0 | 4383 | | return false; |
| | 4384 | | } |
| | 4385 | |
|
| 50 | 4386 | | return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type); |
| | 4387 | | } |
| | 4388 | |
|
| | 4389 | | private string GetCleanValue(string value) |
| | 4390 | | { |
| 62 | 4391 | | if (string.IsNullOrWhiteSpace(value)) |
| | 4392 | | { |
| 0 | 4393 | | return value; |
| | 4394 | | } |
| | 4395 | |
|
| 62 | 4396 | | return value.RemoveDiacritics().ToLowerInvariant(); |
| | 4397 | | } |
| | 4398 | |
|
| | 4399 | | private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) |
| | 4400 | | { |
| 336 | 4401 | | if (!query.GroupByPresentationUniqueKey) |
| | 4402 | | { |
| 171 | 4403 | | return false; |
| | 4404 | | } |
| | 4405 | |
|
| 165 | 4406 | | if (query.GroupBySeriesPresentationUniqueKey) |
| | 4407 | | { |
| 0 | 4408 | | return false; |
| | 4409 | | } |
| | 4410 | |
|
| 165 | 4411 | | if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) |
| | 4412 | | { |
| 0 | 4413 | | return false; |
| | 4414 | | } |
| | 4415 | |
|
| 165 | 4416 | | if (query.User is null) |
| | 4417 | | { |
| 163 | 4418 | | return false; |
| | 4419 | | } |
| | 4420 | |
|
| 2 | 4421 | | if (query.IncludeItemTypes.Length == 0) |
| | 4422 | | { |
| 1 | 4423 | | return true; |
| | 4424 | | } |
| | 4425 | |
|
| 1 | 4426 | | return query.IncludeItemTypes.Contains(BaseItemKind.Episode) |
| 1 | 4427 | | || query.IncludeItemTypes.Contains(BaseItemKind.Video) |
| 1 | 4428 | | || query.IncludeItemTypes.Contains(BaseItemKind.Movie) |
| 1 | 4429 | | || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo) |
| 1 | 4430 | | || query.IncludeItemTypes.Contains(BaseItemKind.Series) |
| 1 | 4431 | | || query.IncludeItemTypes.Contains(BaseItemKind.Season); |
| | 4432 | | } |
| | 4433 | |
|
| | 4434 | | public void UpdateInheritedValues() |
| | 4435 | | { |
| | 4436 | | const string Statements = """ |
| | 4437 | | delete from ItemValues where type = 6; |
| | 4438 | | insert into ItemValues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type |
| | 4439 | | insert into ItemValues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.Clea |
| | 4440 | | FROM AncestorIds |
| | 4441 | | LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId) |
| | 4442 | | where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4; |
| | 4443 | | """; |
| 19 | 4444 | | using var connection = GetConnection(); |
| 19 | 4445 | | using var transaction = connection.BeginTransaction(); |
| 19 | 4446 | | connection.Execute(Statements); |
| 19 | 4447 | | transaction.Commit(); |
| 38 | 4448 | | } |
| | 4449 | |
|
| | 4450 | | public void DeleteItem(Guid id) |
| | 4451 | | { |
| 2 | 4452 | | if (id.IsEmpty()) |
| | 4453 | | { |
| 0 | 4454 | | throw new ArgumentNullException(nameof(id)); |
| | 4455 | | } |
| | 4456 | |
|
| 2 | 4457 | | CheckDisposed(); |
| | 4458 | |
|
| 2 | 4459 | | using var connection = GetConnection(); |
| 2 | 4460 | | using var transaction = connection.BeginTransaction(); |
| | 4461 | | // Delete people |
| 2 | 4462 | | ExecuteWithSingleParam(connection, "delete from People where ItemId=@Id", id); |
| | 4463 | |
|
| | 4464 | | // Delete chapters |
| 2 | 4465 | | ExecuteWithSingleParam(connection, "delete from " + ChaptersTableName + " where ItemId=@Id", id); |
| | 4466 | |
|
| | 4467 | | // Delete media streams |
| 2 | 4468 | | ExecuteWithSingleParam(connection, "delete from mediastreams where ItemId=@Id", id); |
| | 4469 | |
|
| | 4470 | | // Delete ancestors |
| 2 | 4471 | | ExecuteWithSingleParam(connection, "delete from AncestorIds where ItemId=@Id", id); |
| | 4472 | |
|
| | 4473 | | // Delete item values |
| 2 | 4474 | | ExecuteWithSingleParam(connection, "delete from ItemValues where ItemId=@Id", id); |
| | 4475 | |
|
| | 4476 | | // Delete the item |
| 2 | 4477 | | ExecuteWithSingleParam(connection, "delete from TypedBaseItems where guid=@Id", id); |
| | 4478 | |
|
| 2 | 4479 | | transaction.Commit(); |
| 4 | 4480 | | } |
| | 4481 | |
|
| | 4482 | | private void ExecuteWithSingleParam(ManagedConnection db, string query, Guid value) |
| | 4483 | | { |
| 12 | 4484 | | using (var statement = PrepareStatement(db, query)) |
| | 4485 | | { |
| 12 | 4486 | | statement.TryBind("@Id", value); |
| | 4487 | |
|
| 12 | 4488 | | statement.ExecuteNonQuery(); |
| 12 | 4489 | | } |
| 12 | 4490 | | } |
| | 4491 | |
|
| | 4492 | | public List<string> GetPeopleNames(InternalPeopleQuery query) |
| | 4493 | | { |
| 0 | 4494 | | ArgumentNullException.ThrowIfNull(query); |
| | 4495 | |
|
| 0 | 4496 | | CheckDisposed(); |
| | 4497 | |
|
| 0 | 4498 | | var commandText = new StringBuilder("select Distinct p.Name from People p"); |
| | 4499 | |
|
| 0 | 4500 | | var whereClauses = GetPeopleWhereClauses(query, null); |
| | 4501 | |
|
| 0 | 4502 | | if (whereClauses.Count != 0) |
| | 4503 | | { |
| 0 | 4504 | | commandText.Append(" where ").AppendJoin(" AND ", whereClauses); |
| | 4505 | | } |
| | 4506 | |
|
| 0 | 4507 | | commandText.Append(" order by ListOrder"); |
| | 4508 | |
|
| 0 | 4509 | | if (query.Limit > 0) |
| | 4510 | | { |
| 0 | 4511 | | commandText.Append(" LIMIT ").Append(query.Limit); |
| | 4512 | | } |
| | 4513 | |
|
| 0 | 4514 | | var list = new List<string>(); |
| 0 | 4515 | | using (var connection = GetConnection(true)) |
| 0 | 4516 | | using (var statement = PrepareStatement(connection, commandText.ToString())) |
| | 4517 | | { |
| | 4518 | | // Run this again to bind the params |
| 0 | 4519 | | GetPeopleWhereClauses(query, statement); |
| | 4520 | |
|
| 0 | 4521 | | foreach (var row in statement.ExecuteQuery()) |
| | 4522 | | { |
| 0 | 4523 | | list.Add(row.GetString(0)); |
| | 4524 | | } |
| | 4525 | | } |
| | 4526 | |
|
| 0 | 4527 | | return list; |
| | 4528 | | } |
| | 4529 | |
|
| | 4530 | | public List<PersonInfo> GetPeople(InternalPeopleQuery query) |
| | 4531 | | { |
| 0 | 4532 | | ArgumentNullException.ThrowIfNull(query); |
| | 4533 | |
|
| 0 | 4534 | | CheckDisposed(); |
| | 4535 | |
|
| 0 | 4536 | | StringBuilder commandText = new StringBuilder("select ItemId, Name, Role, PersonType, SortOrder from People |
| | 4537 | |
|
| 0 | 4538 | | var whereClauses = GetPeopleWhereClauses(query, null); |
| | 4539 | |
|
| 0 | 4540 | | if (whereClauses.Count != 0) |
| | 4541 | | { |
| 0 | 4542 | | commandText.Append(" where ").AppendJoin(" AND ", whereClauses); |
| | 4543 | | } |
| | 4544 | |
|
| 0 | 4545 | | commandText.Append(" order by ListOrder"); |
| | 4546 | |
|
| 0 | 4547 | | if (query.Limit > 0) |
| | 4548 | | { |
| 0 | 4549 | | commandText.Append(" LIMIT ").Append(query.Limit); |
| | 4550 | | } |
| | 4551 | |
|
| 0 | 4552 | | var list = new List<PersonInfo>(); |
| 0 | 4553 | | using (var connection = GetConnection(true)) |
| 0 | 4554 | | using (var statement = PrepareStatement(connection, commandText.ToString())) |
| | 4555 | | { |
| | 4556 | | // Run this again to bind the params |
| 0 | 4557 | | GetPeopleWhereClauses(query, statement); |
| | 4558 | |
|
| 0 | 4559 | | foreach (var row in statement.ExecuteQuery()) |
| | 4560 | | { |
| 0 | 4561 | | list.Add(GetPerson(row)); |
| | 4562 | | } |
| | 4563 | | } |
| | 4564 | |
|
| 0 | 4565 | | return list; |
| | 4566 | | } |
| | 4567 | |
|
| | 4568 | | private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, SqliteCommand statement) |
| | 4569 | | { |
| 0 | 4570 | | var whereClauses = new List<string>(); |
| | 4571 | |
|
| 0 | 4572 | | if (query.User is not null && query.IsFavorite.HasValue) |
| | 4573 | | { |
| 0 | 4574 | | whereClauses.Add(@"p.Name IN ( |
| 0 | 4575 | | SELECT Name FROM TypedBaseItems WHERE UserDataKey IN ( |
| 0 | 4576 | | SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId) |
| 0 | 4577 | | AND Type = @InternalPersonType)"); |
| 0 | 4578 | | statement?.TryBind("@IsFavorite", query.IsFavorite.Value); |
| 0 | 4579 | | statement?.TryBind("@InternalPersonType", typeof(Person).FullName); |
| 0 | 4580 | | statement?.TryBind("@UserId", query.User.InternalId); |
| | 4581 | | } |
| | 4582 | |
|
| 0 | 4583 | | if (!query.ItemId.IsEmpty()) |
| | 4584 | | { |
| 0 | 4585 | | whereClauses.Add("ItemId=@ItemId"); |
| 0 | 4586 | | statement?.TryBind("@ItemId", query.ItemId); |
| | 4587 | | } |
| | 4588 | |
|
| 0 | 4589 | | if (!query.AppearsInItemId.IsEmpty()) |
| | 4590 | | { |
| 0 | 4591 | | whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); |
| 0 | 4592 | | statement?.TryBind("@AppearsInItemId", query.AppearsInItemId); |
| | 4593 | | } |
| | 4594 | |
|
| 0 | 4595 | | var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); |
| | 4596 | |
|
| 0 | 4597 | | if (queryPersonTypes.Count == 1) |
| | 4598 | | { |
| 0 | 4599 | | whereClauses.Add("PersonType=@PersonType"); |
| 0 | 4600 | | statement?.TryBind("@PersonType", queryPersonTypes[0]); |
| | 4601 | | } |
| 0 | 4602 | | else if (queryPersonTypes.Count > 1) |
| | 4603 | | { |
| 0 | 4604 | | var val = string.Join(',', queryPersonTypes.Select(i => "'" + i + "'")); |
| | 4605 | |
|
| 0 | 4606 | | whereClauses.Add("PersonType in (" + val + ")"); |
| | 4607 | | } |
| | 4608 | |
|
| 0 | 4609 | | var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList(); |
| | 4610 | |
|
| 0 | 4611 | | if (queryExcludePersonTypes.Count == 1) |
| | 4612 | | { |
| 0 | 4613 | | whereClauses.Add("PersonType<>@PersonType"); |
| 0 | 4614 | | statement?.TryBind("@PersonType", queryExcludePersonTypes[0]); |
| | 4615 | | } |
| 0 | 4616 | | else if (queryExcludePersonTypes.Count > 1) |
| | 4617 | | { |
| 0 | 4618 | | var val = string.Join(',', queryExcludePersonTypes.Select(i => "'" + i + "'")); |
| | 4619 | |
|
| 0 | 4620 | | whereClauses.Add("PersonType not in (" + val + ")"); |
| | 4621 | | } |
| | 4622 | |
|
| 0 | 4623 | | if (query.MaxListOrder.HasValue) |
| | 4624 | | { |
| 0 | 4625 | | whereClauses.Add("ListOrder<=@MaxListOrder"); |
| 0 | 4626 | | statement?.TryBind("@MaxListOrder", query.MaxListOrder.Value); |
| | 4627 | | } |
| | 4628 | |
|
| 0 | 4629 | | if (!string.IsNullOrWhiteSpace(query.NameContains)) |
| | 4630 | | { |
| 0 | 4631 | | whereClauses.Add("p.Name like @NameContains"); |
| 0 | 4632 | | statement?.TryBind("@NameContains", "%" + query.NameContains + "%"); |
| | 4633 | | } |
| | 4634 | |
|
| 0 | 4635 | | return whereClauses; |
| | 4636 | | } |
| | 4637 | |
|
| | 4638 | | private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, ManagedConnection db, SqliteCommand deleteAnce |
| | 4639 | | { |
| 59 | 4640 | | if (itemId.IsEmpty()) |
| | 4641 | | { |
| 0 | 4642 | | throw new ArgumentNullException(nameof(itemId)); |
| | 4643 | | } |
| | 4644 | |
|
| 59 | 4645 | | ArgumentNullException.ThrowIfNull(ancestorIds); |
| | 4646 | |
|
| 59 | 4647 | | CheckDisposed(); |
| | 4648 | |
|
| | 4649 | | // First delete |
| 59 | 4650 | | deleteAncestorsStatement.TryBind("@ItemId", itemId); |
| 59 | 4651 | | deleteAncestorsStatement.ExecuteNonQuery(); |
| | 4652 | |
|
| 59 | 4653 | | if (ancestorIds.Count == 0) |
| | 4654 | | { |
| 57 | 4655 | | return; |
| | 4656 | | } |
| | 4657 | |
|
| 2 | 4658 | | var insertText = new StringBuilder("insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values "); |
| | 4659 | |
|
| 8 | 4660 | | for (var i = 0; i < ancestorIds.Count; i++) |
| | 4661 | | { |
| 2 | 4662 | | insertText.AppendFormat( |
| 2 | 4663 | | CultureInfo.InvariantCulture, |
| 2 | 4664 | | "(@ItemId, @AncestorId{0}, @AncestorIdText{0}),", |
| 2 | 4665 | | i.ToString(CultureInfo.InvariantCulture)); |
| | 4666 | | } |
| | 4667 | |
|
| | 4668 | | // Remove trailing comma |
| 2 | 4669 | | insertText.Length--; |
| | 4670 | |
|
| 2 | 4671 | | using (var statement = PrepareStatement(db, insertText.ToString())) |
| | 4672 | | { |
| 2 | 4673 | | statement.TryBind("@ItemId", itemId); |
| | 4674 | |
|
| 8 | 4675 | | for (var i = 0; i < ancestorIds.Count; i++) |
| | 4676 | | { |
| 2 | 4677 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| | 4678 | |
|
| 2 | 4679 | | var ancestorId = ancestorIds[i]; |
| | 4680 | |
|
| 2 | 4681 | | statement.TryBind("@AncestorId" + index, ancestorId); |
| 2 | 4682 | | statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture)) |
| | 4683 | | } |
| | 4684 | |
|
| 2 | 4685 | | statement.ExecuteNonQuery(); |
| 2 | 4686 | | } |
| 2 | 4687 | | } |
| | 4688 | |
|
| | 4689 | | public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query) |
| | 4690 | | { |
| 0 | 4691 | | return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName); |
| | 4692 | | } |
| | 4693 | |
|
| | 4694 | | public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query) |
| | 4695 | | { |
| 0 | 4696 | | return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName); |
| | 4697 | | } |
| | 4698 | |
|
| | 4699 | | public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query) |
| | 4700 | | { |
| 0 | 4701 | | return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName); |
| | 4702 | | } |
| | 4703 | |
|
| | 4704 | | public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query) |
| | 4705 | | { |
| 0 | 4706 | | return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName); |
| | 4707 | | } |
| | 4708 | |
|
| | 4709 | | public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query) |
| | 4710 | | { |
| 0 | 4711 | | return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); |
| | 4712 | | } |
| | 4713 | |
|
| | 4714 | | public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query) |
| | 4715 | | { |
| 0 | 4716 | | return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); |
| | 4717 | | } |
| | 4718 | |
|
| | 4719 | | public List<string> GetStudioNames() |
| | 4720 | | { |
| 19 | 4721 | | return GetItemValueNames(new[] { 3 }, Array.Empty<string>(), Array.Empty<string>()); |
| | 4722 | | } |
| | 4723 | |
|
| | 4724 | | public List<string> GetAllArtistNames() |
| | 4725 | | { |
| 19 | 4726 | | return GetItemValueNames(new[] { 0, 1 }, Array.Empty<string>(), Array.Empty<string>()); |
| | 4727 | | } |
| | 4728 | |
|
| | 4729 | | public List<string> GetMusicGenreNames() |
| | 4730 | | { |
| 19 | 4731 | | return GetItemValueNames( |
| 19 | 4732 | | new[] { 2 }, |
| 19 | 4733 | | new string[] |
| 19 | 4734 | | { |
| 19 | 4735 | | typeof(Audio).FullName, |
| 19 | 4736 | | typeof(MusicVideo).FullName, |
| 19 | 4737 | | typeof(MusicAlbum).FullName, |
| 19 | 4738 | | typeof(MusicArtist).FullName |
| 19 | 4739 | | }, |
| 19 | 4740 | | Array.Empty<string>()); |
| | 4741 | | } |
| | 4742 | |
|
| | 4743 | | public List<string> GetGenreNames() |
| | 4744 | | { |
| 19 | 4745 | | return GetItemValueNames( |
| 19 | 4746 | | new[] { 2 }, |
| 19 | 4747 | | Array.Empty<string>(), |
| 19 | 4748 | | new string[] |
| 19 | 4749 | | { |
| 19 | 4750 | | typeof(Audio).FullName, |
| 19 | 4751 | | typeof(MusicVideo).FullName, |
| 19 | 4752 | | typeof(MusicAlbum).FullName, |
| 19 | 4753 | | typeof(MusicArtist).FullName |
| 19 | 4754 | | }); |
| | 4755 | | } |
| | 4756 | |
|
| | 4757 | | private List<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList< |
| | 4758 | | { |
| 76 | 4759 | | CheckDisposed(); |
| | 4760 | |
|
| 76 | 4761 | | var stringBuilder = new StringBuilder("Select Value From ItemValues where Type", 128); |
| 76 | 4762 | | if (itemValueTypes.Length == 1) |
| | 4763 | | { |
| 57 | 4764 | | stringBuilder.Append('=') |
| 57 | 4765 | | .Append(itemValueTypes[0]); |
| | 4766 | | } |
| | 4767 | | else |
| | 4768 | | { |
| 19 | 4769 | | stringBuilder.Append(" in (") |
| 19 | 4770 | | .AppendJoin(',', itemValueTypes) |
| 19 | 4771 | | .Append(')'); |
| | 4772 | | } |
| | 4773 | |
|
| 76 | 4774 | | if (withItemTypes.Count > 0) |
| | 4775 | | { |
| 19 | 4776 | | stringBuilder.Append(" AND ItemId In (select guid from typedbaseitems where type in (") |
| 19 | 4777 | | .AppendJoinInSingleQuotes(',', withItemTypes) |
| 19 | 4778 | | .Append("))"); |
| | 4779 | | } |
| | 4780 | |
|
| 76 | 4781 | | if (excludeItemTypes.Count > 0) |
| | 4782 | | { |
| 19 | 4783 | | stringBuilder.Append(" AND ItemId not In (select guid from typedbaseitems where type in (") |
| 19 | 4784 | | .AppendJoinInSingleQuotes(',', excludeItemTypes) |
| 19 | 4785 | | .Append("))"); |
| | 4786 | | } |
| | 4787 | |
|
| 76 | 4788 | | stringBuilder.Append(" Group By CleanValue"); |
| 76 | 4789 | | var commandText = stringBuilder.ToString(); |
| | 4790 | |
|
| 76 | 4791 | | var list = new List<string>(); |
| 76 | 4792 | | using (new QueryTimeLogger(Logger, commandText)) |
| 76 | 4793 | | using (var connection = GetConnection(true)) |
| 76 | 4794 | | using (var statement = PrepareStatement(connection, commandText)) |
| | 4795 | | { |
| 152 | 4796 | | foreach (var row in statement.ExecuteQuery()) |
| | 4797 | | { |
| 0 | 4798 | | if (row.TryGetString(0, out var result)) |
| | 4799 | | { |
| 0 | 4800 | | list.Add(result); |
| | 4801 | | } |
| | 4802 | | } |
| | 4803 | | } |
| | 4804 | |
|
| 76 | 4805 | | return list; |
| | 4806 | | } |
| | 4807 | |
|
| | 4808 | | private QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemVa |
| | 4809 | | { |
| 0 | 4810 | | ArgumentNullException.ThrowIfNull(query); |
| | 4811 | |
|
| 0 | 4812 | | if (!query.Limit.HasValue) |
| | 4813 | | { |
| 0 | 4814 | | query.EnableTotalRecordCount = false; |
| | 4815 | | } |
| | 4816 | |
|
| 0 | 4817 | | CheckDisposed(); |
| | 4818 | |
|
| 0 | 4819 | | var typeClause = itemValueTypes.Length == 1 ? |
| 0 | 4820 | | ("Type=" + itemValueTypes[0]) : |
| 0 | 4821 | | ("Type in (" + string.Join(',', itemValueTypes) + ")"); |
| | 4822 | |
|
| 0 | 4823 | | InternalItemsQuery typeSubQuery = null; |
| | 4824 | |
|
| 0 | 4825 | | string itemCountColumns = null; |
| | 4826 | |
|
| 0 | 4827 | | var stringBuilder = new StringBuilder(1024); |
| 0 | 4828 | | var typesToCount = query.IncludeItemTypes; |
| | 4829 | |
|
| 0 | 4830 | | if (typesToCount.Length > 0) |
| | 4831 | | { |
| 0 | 4832 | | stringBuilder.Append("(select group_concat(type, '|') from TypedBaseItems B"); |
| | 4833 | |
|
| 0 | 4834 | | typeSubQuery = new InternalItemsQuery(query.User) |
| 0 | 4835 | | { |
| 0 | 4836 | | ExcludeItemTypes = query.ExcludeItemTypes, |
| 0 | 4837 | | IncludeItemTypes = query.IncludeItemTypes, |
| 0 | 4838 | | MediaTypes = query.MediaTypes, |
| 0 | 4839 | | AncestorIds = query.AncestorIds, |
| 0 | 4840 | | ExcludeItemIds = query.ExcludeItemIds, |
| 0 | 4841 | | ItemIds = query.ItemIds, |
| 0 | 4842 | | TopParentIds = query.TopParentIds, |
| 0 | 4843 | | ParentId = query.ParentId, |
| 0 | 4844 | | IsPlayed = query.IsPlayed |
| 0 | 4845 | | }; |
| 0 | 4846 | | var whereClauses = GetWhereClauses(typeSubQuery, null); |
| | 4847 | |
|
| 0 | 4848 | | stringBuilder.Append(" where ") |
| 0 | 4849 | | .AppendJoin(" AND ", whereClauses) |
| 0 | 4850 | | .Append(" AND ") |
| 0 | 4851 | | .Append("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND ") |
| 0 | 4852 | | .Append(typeClause) |
| 0 | 4853 | | .Append(")) as itemTypes"); |
| | 4854 | |
|
| 0 | 4855 | | itemCountColumns = stringBuilder.ToString(); |
| 0 | 4856 | | stringBuilder.Clear(); |
| | 4857 | | } |
| | 4858 | |
|
| 0 | 4859 | | List<string> columns = _retrieveItemColumns.ToList(); |
| | 4860 | | // Unfortunately we need to add it to columns to ensure the order of the columns in the select |
| 0 | 4861 | | if (!string.IsNullOrEmpty(itemCountColumns)) |
| | 4862 | | { |
| 0 | 4863 | | columns.Add(itemCountColumns); |
| | 4864 | | } |
| | 4865 | |
|
| | 4866 | | // do this first before calling GetFinalColumnsToSelect, otherwise ExcludeItemIds will be set by SimilarTo |
| 0 | 4867 | | var innerQuery = new InternalItemsQuery(query.User) |
| 0 | 4868 | | { |
| 0 | 4869 | | ExcludeItemTypes = query.ExcludeItemTypes, |
| 0 | 4870 | | IncludeItemTypes = query.IncludeItemTypes, |
| 0 | 4871 | | MediaTypes = query.MediaTypes, |
| 0 | 4872 | | AncestorIds = query.AncestorIds, |
| 0 | 4873 | | ItemIds = query.ItemIds, |
| 0 | 4874 | | TopParentIds = query.TopParentIds, |
| 0 | 4875 | | ParentId = query.ParentId, |
| 0 | 4876 | | IsAiring = query.IsAiring, |
| 0 | 4877 | | IsMovie = query.IsMovie, |
| 0 | 4878 | | IsSports = query.IsSports, |
| 0 | 4879 | | IsKids = query.IsKids, |
| 0 | 4880 | | IsNews = query.IsNews, |
| 0 | 4881 | | IsSeries = query.IsSeries |
| 0 | 4882 | | }; |
| | 4883 | |
|
| 0 | 4884 | | SetFinalColumnsToSelect(query, columns); |
| | 4885 | |
|
| 0 | 4886 | | var innerWhereClauses = GetWhereClauses(innerQuery, null); |
| | 4887 | |
|
| 0 | 4888 | | stringBuilder.Append(" where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where ") |
| 0 | 4889 | | .Append(typeClause) |
| 0 | 4890 | | .Append(" AND ItemId in (select guid from TypedBaseItems"); |
| 0 | 4891 | | if (innerWhereClauses.Count > 0) |
| | 4892 | | { |
| 0 | 4893 | | stringBuilder.Append(" where ") |
| 0 | 4894 | | .AppendJoin(" AND ", innerWhereClauses); |
| | 4895 | | } |
| | 4896 | |
|
| 0 | 4897 | | stringBuilder.Append("))"); |
| | 4898 | |
|
| 0 | 4899 | | var outerQuery = new InternalItemsQuery(query.User) |
| 0 | 4900 | | { |
| 0 | 4901 | | IsPlayed = query.IsPlayed, |
| 0 | 4902 | | IsFavorite = query.IsFavorite, |
| 0 | 4903 | | IsFavoriteOrLiked = query.IsFavoriteOrLiked, |
| 0 | 4904 | | IsLiked = query.IsLiked, |
| 0 | 4905 | | IsLocked = query.IsLocked, |
| 0 | 4906 | | NameLessThan = query.NameLessThan, |
| 0 | 4907 | | NameStartsWith = query.NameStartsWith, |
| 0 | 4908 | | NameStartsWithOrGreater = query.NameStartsWithOrGreater, |
| 0 | 4909 | | Tags = query.Tags, |
| 0 | 4910 | | OfficialRatings = query.OfficialRatings, |
| 0 | 4911 | | StudioIds = query.StudioIds, |
| 0 | 4912 | | GenreIds = query.GenreIds, |
| 0 | 4913 | | Genres = query.Genres, |
| 0 | 4914 | | Years = query.Years, |
| 0 | 4915 | | NameContains = query.NameContains, |
| 0 | 4916 | | SearchTerm = query.SearchTerm, |
| 0 | 4917 | | SimilarTo = query.SimilarTo, |
| 0 | 4918 | | ExcludeItemIds = query.ExcludeItemIds |
| 0 | 4919 | | }; |
| | 4920 | |
|
| 0 | 4921 | | var outerWhereClauses = GetWhereClauses(outerQuery, null); |
| 0 | 4922 | | if (outerWhereClauses.Count != 0) |
| | 4923 | | { |
| 0 | 4924 | | stringBuilder.Append(" AND ") |
| 0 | 4925 | | .AppendJoin(" AND ", outerWhereClauses); |
| | 4926 | | } |
| | 4927 | |
|
| 0 | 4928 | | var whereText = stringBuilder.ToString(); |
| 0 | 4929 | | stringBuilder.Clear(); |
| | 4930 | |
|
| 0 | 4931 | | stringBuilder.Append("select ") |
| 0 | 4932 | | .AppendJoin(',', columns) |
| 0 | 4933 | | .Append(FromText) |
| 0 | 4934 | | .Append(GetJoinUserDataText(query)) |
| 0 | 4935 | | .Append(whereText) |
| 0 | 4936 | | .Append(" group by PresentationUniqueKey"); |
| | 4937 | |
|
| 0 | 4938 | | if (query.OrderBy.Count != 0 |
| 0 | 4939 | | || query.SimilarTo is not null |
| 0 | 4940 | | || !string.IsNullOrEmpty(query.SearchTerm)) |
| | 4941 | | { |
| 0 | 4942 | | stringBuilder.Append(GetOrderByText(query)); |
| | 4943 | | } |
| | 4944 | | else |
| | 4945 | | { |
| 0 | 4946 | | stringBuilder.Append(" order by SortName"); |
| | 4947 | | } |
| | 4948 | |
|
| 0 | 4949 | | if (query.Limit.HasValue || query.StartIndex.HasValue) |
| | 4950 | | { |
| 0 | 4951 | | var offset = query.StartIndex ?? 0; |
| | 4952 | |
|
| 0 | 4953 | | if (query.Limit.HasValue || offset > 0) |
| | 4954 | | { |
| 0 | 4955 | | stringBuilder.Append(" LIMIT ") |
| 0 | 4956 | | .Append(query.Limit ?? int.MaxValue); |
| | 4957 | | } |
| | 4958 | |
|
| 0 | 4959 | | if (offset > 0) |
| | 4960 | | { |
| 0 | 4961 | | stringBuilder.Append(" OFFSET ") |
| 0 | 4962 | | .Append(offset); |
| | 4963 | | } |
| | 4964 | | } |
| | 4965 | |
|
| 0 | 4966 | | var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; |
| | 4967 | |
|
| 0 | 4968 | | string commandText = string.Empty; |
| | 4969 | |
|
| 0 | 4970 | | if (!isReturningZeroItems) |
| | 4971 | | { |
| 0 | 4972 | | commandText = stringBuilder.ToString(); |
| | 4973 | | } |
| | 4974 | |
|
| 0 | 4975 | | string countText = string.Empty; |
| 0 | 4976 | | if (query.EnableTotalRecordCount) |
| | 4977 | | { |
| 0 | 4978 | | stringBuilder.Clear(); |
| 0 | 4979 | | var columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" }; |
| 0 | 4980 | | SetFinalColumnsToSelect(query, columnsToSelect); |
| 0 | 4981 | | stringBuilder.Append("select ") |
| 0 | 4982 | | .AppendJoin(',', columnsToSelect) |
| 0 | 4983 | | .Append(FromText) |
| 0 | 4984 | | .Append(GetJoinUserDataText(query)) |
| 0 | 4985 | | .Append(whereText); |
| | 4986 | |
|
| 0 | 4987 | | countText = stringBuilder.ToString(); |
| | 4988 | | } |
| | 4989 | |
|
| 0 | 4990 | | var list = new List<(BaseItem, ItemCounts)>(); |
| 0 | 4991 | | var result = new QueryResult<(BaseItem, ItemCounts)>(); |
| 0 | 4992 | | using (new QueryTimeLogger(Logger, commandText)) |
| 0 | 4993 | | using (var connection = GetConnection(true)) |
| 0 | 4994 | | using (var transaction = connection.BeginTransaction()) |
| | 4995 | | { |
| 0 | 4996 | | if (!isReturningZeroItems) |
| | 4997 | | { |
| 0 | 4998 | | using (var statement = PrepareStatement(connection, commandText)) |
| | 4999 | | { |
| 0 | 5000 | | statement.TryBind("@SelectType", returnType); |
| 0 | 5001 | | if (EnableJoinUserData(query)) |
| | 5002 | | { |
| 0 | 5003 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 5004 | | } |
| | 5005 | |
|
| 0 | 5006 | | if (typeSubQuery is not null) |
| | 5007 | | { |
| 0 | 5008 | | GetWhereClauses(typeSubQuery, null); |
| | 5009 | | } |
| | 5010 | |
|
| 0 | 5011 | | BindSimilarParams(query, statement); |
| 0 | 5012 | | BindSearchParams(query, statement); |
| 0 | 5013 | | GetWhereClauses(innerQuery, statement); |
| 0 | 5014 | | GetWhereClauses(outerQuery, statement); |
| | 5015 | |
|
| 0 | 5016 | | var hasEpisodeAttributes = HasEpisodeAttributes(query); |
| 0 | 5017 | | var hasProgramAttributes = HasProgramAttributes(query); |
| 0 | 5018 | | var hasServiceName = HasServiceName(query); |
| 0 | 5019 | | var hasStartDate = HasStartDate(query); |
| 0 | 5020 | | var hasTrailerTypes = HasTrailerTypes(query); |
| 0 | 5021 | | var hasArtistFields = HasArtistFields(query); |
| 0 | 5022 | | var hasSeriesFields = HasSeriesFields(query); |
| | 5023 | |
|
| 0 | 5024 | | foreach (var row in statement.ExecuteQuery()) |
| | 5025 | | { |
| 0 | 5026 | | var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, h |
| 0 | 5027 | | if (item is not null) |
| | 5028 | | { |
| 0 | 5029 | | var countStartColumn = columns.Count - 1; |
| | 5030 | |
|
| 0 | 5031 | | list.Add((item, GetItemCounts(row, countStartColumn, typesToCount))); |
| | 5032 | | } |
| | 5033 | | } |
| | 5034 | | } |
| | 5035 | | } |
| | 5036 | |
|
| 0 | 5037 | | if (query.EnableTotalRecordCount) |
| | 5038 | | { |
| 0 | 5039 | | using (var statement = PrepareStatement(connection, countText)) |
| | 5040 | | { |
| 0 | 5041 | | statement.TryBind("@SelectType", returnType); |
| 0 | 5042 | | if (EnableJoinUserData(query)) |
| | 5043 | | { |
| 0 | 5044 | | statement.TryBind("@UserId", query.User.InternalId); |
| | 5045 | | } |
| | 5046 | |
|
| 0 | 5047 | | if (typeSubQuery is not null) |
| | 5048 | | { |
| 0 | 5049 | | GetWhereClauses(typeSubQuery, null); |
| | 5050 | | } |
| | 5051 | |
|
| 0 | 5052 | | BindSimilarParams(query, statement); |
| 0 | 5053 | | BindSearchParams(query, statement); |
| 0 | 5054 | | GetWhereClauses(innerQuery, statement); |
| 0 | 5055 | | GetWhereClauses(outerQuery, statement); |
| | 5056 | |
|
| 0 | 5057 | | result.TotalRecordCount = statement.SelectScalarInt(); |
| 0 | 5058 | | } |
| | 5059 | | } |
| | 5060 | |
|
| 0 | 5061 | | transaction.Commit(); |
| 0 | 5062 | | } |
| | 5063 | |
|
| 0 | 5064 | | if (result.TotalRecordCount == 0) |
| | 5065 | | { |
| 0 | 5066 | | result.TotalRecordCount = list.Count; |
| | 5067 | | } |
| | 5068 | |
|
| 0 | 5069 | | result.StartIndex = query.StartIndex ?? 0; |
| 0 | 5070 | | result.Items = list; |
| | 5071 | |
|
| 0 | 5072 | | return result; |
| | 5073 | | } |
| | 5074 | |
|
| | 5075 | | private static ItemCounts GetItemCounts(SqliteDataReader reader, int countStartColumn, BaseItemKind[] typesToCou |
| | 5076 | | { |
| 0 | 5077 | | var counts = new ItemCounts(); |
| | 5078 | |
|
| 0 | 5079 | | if (typesToCount.Length == 0) |
| | 5080 | | { |
| 0 | 5081 | | return counts; |
| | 5082 | | } |
| | 5083 | |
|
| 0 | 5084 | | if (!reader.TryGetString(countStartColumn, out var typeString)) |
| | 5085 | | { |
| 0 | 5086 | | return counts; |
| | 5087 | | } |
| | 5088 | |
|
| 0 | 5089 | | foreach (var typeName in typeString.AsSpan().Split('|')) |
| | 5090 | | { |
| 0 | 5091 | | if (typeName.Equals(typeof(Series).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5092 | | { |
| 0 | 5093 | | counts.SeriesCount++; |
| | 5094 | | } |
| 0 | 5095 | | else if (typeName.Equals(typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5096 | | { |
| 0 | 5097 | | counts.EpisodeCount++; |
| | 5098 | | } |
| 0 | 5099 | | else if (typeName.Equals(typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5100 | | { |
| 0 | 5101 | | counts.MovieCount++; |
| | 5102 | | } |
| 0 | 5103 | | else if (typeName.Equals(typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5104 | | { |
| 0 | 5105 | | counts.AlbumCount++; |
| | 5106 | | } |
| 0 | 5107 | | else if (typeName.Equals(typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5108 | | { |
| 0 | 5109 | | counts.ArtistCount++; |
| | 5110 | | } |
| 0 | 5111 | | else if (typeName.Equals(typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5112 | | { |
| 0 | 5113 | | counts.SongCount++; |
| | 5114 | | } |
| 0 | 5115 | | else if (typeName.Equals(typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase)) |
| | 5116 | | { |
| 0 | 5117 | | counts.TrailerCount++; |
| | 5118 | | } |
| | 5119 | |
|
| 0 | 5120 | | counts.ItemCount++; |
| | 5121 | | } |
| | 5122 | |
|
| 0 | 5123 | | return counts; |
| | 5124 | | } |
| | 5125 | |
|
| | 5126 | | private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags) |
| | 5127 | | { |
| 59 | 5128 | | var list = new List<(int, string)>(); |
| | 5129 | |
|
| 59 | 5130 | | if (item is IHasArtist hasArtist) |
| | 5131 | | { |
| 0 | 5132 | | list.AddRange(hasArtist.Artists.Select(i => (0, i))); |
| | 5133 | | } |
| | 5134 | |
|
| 59 | 5135 | | if (item is IHasAlbumArtist hasAlbumArtist) |
| | 5136 | | { |
| 0 | 5137 | | list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i))); |
| | 5138 | | } |
| | 5139 | |
|
| 59 | 5140 | | list.AddRange(item.Genres.Select(i => (2, i))); |
| 59 | 5141 | | list.AddRange(item.Studios.Select(i => (3, i))); |
| 59 | 5142 | | list.AddRange(item.Tags.Select(i => (4, i))); |
| | 5143 | |
|
| | 5144 | | // keywords was 5 |
| | 5145 | |
|
| 59 | 5146 | | list.AddRange(inheritedTags.Select(i => (6, i))); |
| | 5147 | |
|
| | 5148 | | // Remove all invalid values. |
| 59 | 5149 | | list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2)); |
| | 5150 | |
|
| 59 | 5151 | | return list; |
| | 5152 | | } |
| | 5153 | |
|
| | 5154 | | private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, ManagedConnection db) |
| | 5155 | | { |
| 59 | 5156 | | if (itemId.IsEmpty()) |
| | 5157 | | { |
| 0 | 5158 | | throw new ArgumentNullException(nameof(itemId)); |
| | 5159 | | } |
| | 5160 | |
|
| 59 | 5161 | | ArgumentNullException.ThrowIfNull(values); |
| | 5162 | |
|
| 59 | 5163 | | CheckDisposed(); |
| | 5164 | |
|
| | 5165 | | // First delete |
| 59 | 5166 | | using var command = db.PrepareStatement("delete from ItemValues where ItemId=@Id"); |
| 59 | 5167 | | command.TryBind("@Id", itemId); |
| 59 | 5168 | | command.ExecuteNonQuery(); |
| | 5169 | |
|
| 59 | 5170 | | InsertItemValues(itemId, values, db); |
| 118 | 5171 | | } |
| | 5172 | |
|
| | 5173 | | private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, ManagedConnection db) |
| | 5174 | | { |
| | 5175 | | const int Limit = 100; |
| 59 | 5176 | | var startIndex = 0; |
| | 5177 | |
|
| | 5178 | | const string StartInsertText = "insert into ItemValues (ItemId, Type, Value, CleanValue) values "; |
| 59 | 5179 | | var insertText = new StringBuilder(StartInsertText); |
| 59 | 5180 | | while (startIndex < values.Count) |
| | 5181 | | { |
| 0 | 5182 | | var endIndex = Math.Min(values.Count, startIndex + Limit); |
| | 5183 | |
|
| 0 | 5184 | | for (var i = startIndex; i < endIndex; i++) |
| | 5185 | | { |
| 0 | 5186 | | insertText.AppendFormat( |
| 0 | 5187 | | CultureInfo.InvariantCulture, |
| 0 | 5188 | | "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),", |
| 0 | 5189 | | i); |
| | 5190 | | } |
| | 5191 | |
|
| | 5192 | | // Remove trailing comma |
| 0 | 5193 | | insertText.Length--; |
| | 5194 | |
|
| 0 | 5195 | | using (var statement = PrepareStatement(db, insertText.ToString())) |
| | 5196 | | { |
| 0 | 5197 | | statement.TryBind("@ItemId", id); |
| | 5198 | |
|
| 0 | 5199 | | for (var i = startIndex; i < endIndex; i++) |
| | 5200 | | { |
| 0 | 5201 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| | 5202 | |
|
| 0 | 5203 | | var currentValueInfo = values[i]; |
| | 5204 | |
|
| 0 | 5205 | | var itemValue = currentValueInfo.Value; |
| | 5206 | |
|
| 0 | 5207 | | statement.TryBind("@Type" + index, currentValueInfo.MagicNumber); |
| 0 | 5208 | | statement.TryBind("@Value" + index, itemValue); |
| 0 | 5209 | | statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue)); |
| | 5210 | | } |
| | 5211 | |
|
| 0 | 5212 | | statement.ExecuteNonQuery(); |
| 0 | 5213 | | } |
| | 5214 | |
|
| 0 | 5215 | | startIndex += Limit; |
| 0 | 5216 | | insertText.Length = StartInsertText.Length; |
| | 5217 | | } |
| 59 | 5218 | | } |
| | 5219 | |
|
| | 5220 | | public void UpdatePeople(Guid itemId, List<PersonInfo> people) |
| | 5221 | | { |
| 0 | 5222 | | if (itemId.IsEmpty()) |
| | 5223 | | { |
| 0 | 5224 | | throw new ArgumentNullException(nameof(itemId)); |
| | 5225 | | } |
| | 5226 | |
|
| 0 | 5227 | | CheckDisposed(); |
| | 5228 | |
|
| 0 | 5229 | | using var connection = GetConnection(); |
| 0 | 5230 | | using var transaction = connection.BeginTransaction(); |
| | 5231 | | // Delete all existing people first |
| 0 | 5232 | | using var command = connection.CreateCommand(); |
| 0 | 5233 | | command.CommandText = "delete from People where ItemId=@ItemId"; |
| 0 | 5234 | | command.TryBind("@ItemId", itemId); |
| 0 | 5235 | | command.ExecuteNonQuery(); |
| | 5236 | |
|
| 0 | 5237 | | if (people is not null) |
| | 5238 | | { |
| 0 | 5239 | | InsertPeople(itemId, people, connection); |
| | 5240 | | } |
| | 5241 | |
|
| 0 | 5242 | | transaction.Commit(); |
| 0 | 5243 | | } |
| | 5244 | |
|
| | 5245 | | private void InsertPeople(Guid id, List<PersonInfo> people, ManagedConnection db) |
| | 5246 | | { |
| | 5247 | | const int Limit = 100; |
| 0 | 5248 | | var startIndex = 0; |
| 0 | 5249 | | var listIndex = 0; |
| | 5250 | |
|
| | 5251 | | const string StartInsertText = "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) va |
| 0 | 5252 | | var insertText = new StringBuilder(StartInsertText); |
| 0 | 5253 | | while (startIndex < people.Count) |
| | 5254 | | { |
| 0 | 5255 | | var endIndex = Math.Min(people.Count, startIndex + Limit); |
| 0 | 5256 | | for (var i = startIndex; i < endIndex; i++) |
| | 5257 | | { |
| 0 | 5258 | | insertText.AppendFormat( |
| 0 | 5259 | | CultureInfo.InvariantCulture, |
| 0 | 5260 | | "(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", |
| 0 | 5261 | | i.ToString(CultureInfo.InvariantCulture)); |
| | 5262 | | } |
| | 5263 | |
|
| | 5264 | | // Remove trailing comma |
| 0 | 5265 | | insertText.Length--; |
| | 5266 | |
|
| 0 | 5267 | | using (var statement = PrepareStatement(db, insertText.ToString())) |
| | 5268 | | { |
| 0 | 5269 | | statement.TryBind("@ItemId", id); |
| | 5270 | |
|
| 0 | 5271 | | for (var i = startIndex; i < endIndex; i++) |
| | 5272 | | { |
| 0 | 5273 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| | 5274 | |
|
| 0 | 5275 | | var person = people[i]; |
| | 5276 | |
|
| 0 | 5277 | | statement.TryBind("@Name" + index, person.Name); |
| 0 | 5278 | | statement.TryBind("@Role" + index, person.Role); |
| 0 | 5279 | | statement.TryBind("@PersonType" + index, person.Type.ToString()); |
| 0 | 5280 | | statement.TryBind("@SortOrder" + index, person.SortOrder); |
| 0 | 5281 | | statement.TryBind("@ListOrder" + index, listIndex); |
| | 5282 | |
|
| 0 | 5283 | | listIndex++; |
| | 5284 | | } |
| | 5285 | |
|
| 0 | 5286 | | statement.ExecuteNonQuery(); |
| 0 | 5287 | | } |
| | 5288 | |
|
| 0 | 5289 | | startIndex += Limit; |
| 0 | 5290 | | insertText.Length = StartInsertText.Length; |
| | 5291 | | } |
| 0 | 5292 | | } |
| | 5293 | |
|
| | 5294 | | private PersonInfo GetPerson(SqliteDataReader reader) |
| | 5295 | | { |
| 0 | 5296 | | var item = new PersonInfo |
| 0 | 5297 | | { |
| 0 | 5298 | | ItemId = reader.GetGuid(0), |
| 0 | 5299 | | Name = reader.GetString(1) |
| 0 | 5300 | | }; |
| | 5301 | |
|
| 0 | 5302 | | if (reader.TryGetString(2, out var role)) |
| | 5303 | | { |
| 0 | 5304 | | item.Role = role; |
| | 5305 | | } |
| | 5306 | |
|
| 0 | 5307 | | if (reader.TryGetString(3, out var type) |
| 0 | 5308 | | && Enum.TryParse(type, true, out PersonKind personKind)) |
| | 5309 | | { |
| 0 | 5310 | | item.Type = personKind; |
| | 5311 | | } |
| | 5312 | |
|
| 0 | 5313 | | if (reader.TryGetInt32(4, out var sortOrder)) |
| | 5314 | | { |
| 0 | 5315 | | item.SortOrder = sortOrder; |
| | 5316 | | } |
| | 5317 | |
|
| 0 | 5318 | | return item; |
| | 5319 | | } |
| | 5320 | |
|
| | 5321 | | public List<MediaStream> GetMediaStreams(MediaStreamQuery query) |
| | 5322 | | { |
| 0 | 5323 | | CheckDisposed(); |
| | 5324 | |
|
| 0 | 5325 | | ArgumentNullException.ThrowIfNull(query); |
| | 5326 | |
|
| 0 | 5327 | | var cmdText = _mediaStreamSaveColumnsSelectQuery; |
| | 5328 | |
|
| 0 | 5329 | | if (query.Type.HasValue) |
| | 5330 | | { |
| 0 | 5331 | | cmdText += " AND StreamType=@StreamType"; |
| | 5332 | | } |
| | 5333 | |
|
| 0 | 5334 | | if (query.Index.HasValue) |
| | 5335 | | { |
| 0 | 5336 | | cmdText += " AND StreamIndex=@StreamIndex"; |
| | 5337 | | } |
| | 5338 | |
|
| 0 | 5339 | | cmdText += " order by StreamIndex ASC"; |
| | 5340 | |
|
| 0 | 5341 | | using (var connection = GetConnection(true)) |
| | 5342 | | { |
| 0 | 5343 | | var list = new List<MediaStream>(); |
| | 5344 | |
|
| 0 | 5345 | | using (var statement = PrepareStatement(connection, cmdText)) |
| | 5346 | | { |
| 0 | 5347 | | statement.TryBind("@ItemId", query.ItemId); |
| | 5348 | |
|
| 0 | 5349 | | if (query.Type.HasValue) |
| | 5350 | | { |
| 0 | 5351 | | statement.TryBind("@StreamType", query.Type.Value.ToString()); |
| | 5352 | | } |
| | 5353 | |
|
| 0 | 5354 | | if (query.Index.HasValue) |
| | 5355 | | { |
| 0 | 5356 | | statement.TryBind("@StreamIndex", query.Index.Value); |
| | 5357 | | } |
| | 5358 | |
|
| 0 | 5359 | | foreach (var row in statement.ExecuteQuery()) |
| | 5360 | | { |
| 0 | 5361 | | list.Add(GetMediaStream(row)); |
| | 5362 | | } |
| | 5363 | | } |
| | 5364 | |
|
| 0 | 5365 | | return list; |
| | 5366 | | } |
| 0 | 5367 | | } |
| | 5368 | |
|
| | 5369 | | public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken) |
| | 5370 | | { |
| 0 | 5371 | | CheckDisposed(); |
| | 5372 | |
|
| 0 | 5373 | | if (id.IsEmpty()) |
| | 5374 | | { |
| 0 | 5375 | | throw new ArgumentNullException(nameof(id)); |
| | 5376 | | } |
| | 5377 | |
|
| 0 | 5378 | | ArgumentNullException.ThrowIfNull(streams); |
| | 5379 | |
|
| 0 | 5380 | | cancellationToken.ThrowIfCancellationRequested(); |
| | 5381 | |
|
| 0 | 5382 | | using var connection = GetConnection(); |
| 0 | 5383 | | using var transaction = connection.BeginTransaction(); |
| | 5384 | | // Delete existing mediastreams |
| 0 | 5385 | | using var command = connection.PrepareStatement("delete from mediastreams where ItemId=@ItemId"); |
| 0 | 5386 | | command.TryBind("@ItemId", id); |
| 0 | 5387 | | command.ExecuteNonQuery(); |
| | 5388 | |
|
| 0 | 5389 | | InsertMediaStreams(id, streams, connection); |
| | 5390 | |
|
| 0 | 5391 | | transaction.Commit(); |
| 0 | 5392 | | } |
| | 5393 | |
|
| | 5394 | | private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, ManagedConnection db) |
| | 5395 | | { |
| | 5396 | | const int Limit = 10; |
| 0 | 5397 | | var startIndex = 0; |
| | 5398 | |
|
| 0 | 5399 | | var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery); |
| 0 | 5400 | | while (startIndex < streams.Count) |
| | 5401 | | { |
| 0 | 5402 | | var endIndex = Math.Min(streams.Count, startIndex + Limit); |
| | 5403 | |
|
| 0 | 5404 | | for (var i = startIndex; i < endIndex; i++) |
| | 5405 | | { |
| 0 | 5406 | | if (i != startIndex) |
| | 5407 | | { |
| 0 | 5408 | | insertText.Append(','); |
| | 5409 | | } |
| | 5410 | |
|
| 0 | 5411 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| 0 | 5412 | | insertText.Append("(@ItemId, "); |
| | 5413 | |
|
| 0 | 5414 | | foreach (var column in _mediaStreamSaveColumns.Skip(1)) |
| | 5415 | | { |
| 0 | 5416 | | insertText.Append('@').Append(column).Append(index).Append(','); |
| | 5417 | | } |
| | 5418 | |
|
| 0 | 5419 | | insertText.Length -= 1; // Remove the last comma |
| | 5420 | |
|
| 0 | 5421 | | insertText.Append(')'); |
| | 5422 | | } |
| | 5423 | |
|
| 0 | 5424 | | using (var statement = PrepareStatement(db, insertText.ToString())) |
| | 5425 | | { |
| 0 | 5426 | | statement.TryBind("@ItemId", id); |
| | 5427 | |
|
| 0 | 5428 | | for (var i = startIndex; i < endIndex; i++) |
| | 5429 | | { |
| 0 | 5430 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| | 5431 | |
|
| 0 | 5432 | | var stream = streams[i]; |
| | 5433 | |
|
| 0 | 5434 | | statement.TryBind("@StreamIndex" + index, stream.Index); |
| 0 | 5435 | | statement.TryBind("@StreamType" + index, stream.Type.ToString()); |
| 0 | 5436 | | statement.TryBind("@Codec" + index, stream.Codec); |
| 0 | 5437 | | statement.TryBind("@Language" + index, stream.Language); |
| 0 | 5438 | | statement.TryBind("@ChannelLayout" + index, stream.ChannelLayout); |
| 0 | 5439 | | statement.TryBind("@Profile" + index, stream.Profile); |
| 0 | 5440 | | statement.TryBind("@AspectRatio" + index, stream.AspectRatio); |
| 0 | 5441 | | statement.TryBind("@Path" + index, GetPathToSave(stream.Path)); |
| | 5442 | |
|
| 0 | 5443 | | statement.TryBind("@IsInterlaced" + index, stream.IsInterlaced); |
| 0 | 5444 | | statement.TryBind("@BitRate" + index, stream.BitRate); |
| 0 | 5445 | | statement.TryBind("@Channels" + index, stream.Channels); |
| 0 | 5446 | | statement.TryBind("@SampleRate" + index, stream.SampleRate); |
| | 5447 | |
|
| 0 | 5448 | | statement.TryBind("@IsDefault" + index, stream.IsDefault); |
| 0 | 5449 | | statement.TryBind("@IsForced" + index, stream.IsForced); |
| 0 | 5450 | | statement.TryBind("@IsExternal" + index, stream.IsExternal); |
| | 5451 | |
|
| | 5452 | | // Yes these are backwards due to a mistake |
| 0 | 5453 | | statement.TryBind("@Width" + index, stream.Height); |
| 0 | 5454 | | statement.TryBind("@Height" + index, stream.Width); |
| | 5455 | |
|
| 0 | 5456 | | statement.TryBind("@AverageFrameRate" + index, stream.AverageFrameRate); |
| 0 | 5457 | | statement.TryBind("@RealFrameRate" + index, stream.RealFrameRate); |
| 0 | 5458 | | statement.TryBind("@Level" + index, stream.Level); |
| | 5459 | |
|
| 0 | 5460 | | statement.TryBind("@PixelFormat" + index, stream.PixelFormat); |
| 0 | 5461 | | statement.TryBind("@BitDepth" + index, stream.BitDepth); |
| 0 | 5462 | | statement.TryBind("@IsAnamorphic" + index, stream.IsAnamorphic); |
| 0 | 5463 | | statement.TryBind("@IsExternal" + index, stream.IsExternal); |
| 0 | 5464 | | statement.TryBind("@RefFrames" + index, stream.RefFrames); |
| | 5465 | |
|
| 0 | 5466 | | statement.TryBind("@CodecTag" + index, stream.CodecTag); |
| 0 | 5467 | | statement.TryBind("@Comment" + index, stream.Comment); |
| 0 | 5468 | | statement.TryBind("@NalLengthSize" + index, stream.NalLengthSize); |
| 0 | 5469 | | statement.TryBind("@IsAvc" + index, stream.IsAVC); |
| 0 | 5470 | | statement.TryBind("@Title" + index, stream.Title); |
| | 5471 | |
|
| 0 | 5472 | | statement.TryBind("@TimeBase" + index, stream.TimeBase); |
| 0 | 5473 | | statement.TryBind("@CodecTimeBase" + index, stream.CodecTimeBase); |
| | 5474 | |
|
| 0 | 5475 | | statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries); |
| 0 | 5476 | | statement.TryBind("@ColorSpace" + index, stream.ColorSpace); |
| 0 | 5477 | | statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer); |
| | 5478 | |
|
| 0 | 5479 | | statement.TryBind("@DvVersionMajor" + index, stream.DvVersionMajor); |
| 0 | 5480 | | statement.TryBind("@DvVersionMinor" + index, stream.DvVersionMinor); |
| 0 | 5481 | | statement.TryBind("@DvProfile" + index, stream.DvProfile); |
| 0 | 5482 | | statement.TryBind("@DvLevel" + index, stream.DvLevel); |
| 0 | 5483 | | statement.TryBind("@RpuPresentFlag" + index, stream.RpuPresentFlag); |
| 0 | 5484 | | statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag); |
| 0 | 5485 | | statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag); |
| 0 | 5486 | | statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId); |
| | 5487 | |
|
| 0 | 5488 | | statement.TryBind("@IsHearingImpaired" + index, stream.IsHearingImpaired); |
| | 5489 | |
|
| 0 | 5490 | | statement.TryBind("@Rotation" + index, stream.Rotation); |
| | 5491 | | } |
| | 5492 | |
|
| 0 | 5493 | | statement.ExecuteNonQuery(); |
| 0 | 5494 | | } |
| | 5495 | |
|
| 0 | 5496 | | startIndex += Limit; |
| 0 | 5497 | | insertText.Length = _mediaStreamSaveColumnsInsertQuery.Length; |
| | 5498 | | } |
| 0 | 5499 | | } |
| | 5500 | |
|
| | 5501 | | /// <summary> |
| | 5502 | | /// Gets the media stream. |
| | 5503 | | /// </summary> |
| | 5504 | | /// <param name="reader">The reader.</param> |
| | 5505 | | /// <returns>MediaStream.</returns> |
| | 5506 | | private MediaStream GetMediaStream(SqliteDataReader reader) |
| | 5507 | | { |
| 0 | 5508 | | var item = new MediaStream |
| 0 | 5509 | | { |
| 0 | 5510 | | Index = reader.GetInt32(1), |
| 0 | 5511 | | Type = Enum.Parse<MediaStreamType>(reader.GetString(2), true) |
| 0 | 5512 | | }; |
| | 5513 | |
|
| 0 | 5514 | | if (reader.TryGetString(3, out var codec)) |
| | 5515 | | { |
| 0 | 5516 | | item.Codec = codec; |
| | 5517 | | } |
| | 5518 | |
|
| 0 | 5519 | | if (reader.TryGetString(4, out var language)) |
| | 5520 | | { |
| 0 | 5521 | | item.Language = language; |
| | 5522 | | } |
| | 5523 | |
|
| 0 | 5524 | | if (reader.TryGetString(5, out var channelLayout)) |
| | 5525 | | { |
| 0 | 5526 | | item.ChannelLayout = channelLayout; |
| | 5527 | | } |
| | 5528 | |
|
| 0 | 5529 | | if (reader.TryGetString(6, out var profile)) |
| | 5530 | | { |
| 0 | 5531 | | item.Profile = profile; |
| | 5532 | | } |
| | 5533 | |
|
| 0 | 5534 | | if (reader.TryGetString(7, out var aspectRatio)) |
| | 5535 | | { |
| 0 | 5536 | | item.AspectRatio = aspectRatio; |
| | 5537 | | } |
| | 5538 | |
|
| 0 | 5539 | | if (reader.TryGetString(8, out var path)) |
| | 5540 | | { |
| 0 | 5541 | | item.Path = RestorePath(path); |
| | 5542 | | } |
| | 5543 | |
|
| 0 | 5544 | | item.IsInterlaced = reader.GetBoolean(9); |
| | 5545 | |
|
| 0 | 5546 | | if (reader.TryGetInt32(10, out var bitrate)) |
| | 5547 | | { |
| 0 | 5548 | | item.BitRate = bitrate; |
| | 5549 | | } |
| | 5550 | |
|
| 0 | 5551 | | if (reader.TryGetInt32(11, out var channels)) |
| | 5552 | | { |
| 0 | 5553 | | item.Channels = channels; |
| | 5554 | | } |
| | 5555 | |
|
| 0 | 5556 | | if (reader.TryGetInt32(12, out var sampleRate)) |
| | 5557 | | { |
| 0 | 5558 | | item.SampleRate = sampleRate; |
| | 5559 | | } |
| | 5560 | |
|
| 0 | 5561 | | item.IsDefault = reader.GetBoolean(13); |
| 0 | 5562 | | item.IsForced = reader.GetBoolean(14); |
| 0 | 5563 | | item.IsExternal = reader.GetBoolean(15); |
| | 5564 | |
|
| 0 | 5565 | | if (reader.TryGetInt32(16, out var width)) |
| | 5566 | | { |
| 0 | 5567 | | item.Width = width; |
| | 5568 | | } |
| | 5569 | |
|
| 0 | 5570 | | if (reader.TryGetInt32(17, out var height)) |
| | 5571 | | { |
| 0 | 5572 | | item.Height = height; |
| | 5573 | | } |
| | 5574 | |
|
| 0 | 5575 | | if (reader.TryGetSingle(18, out var averageFrameRate)) |
| | 5576 | | { |
| 0 | 5577 | | item.AverageFrameRate = averageFrameRate; |
| | 5578 | | } |
| | 5579 | |
|
| 0 | 5580 | | if (reader.TryGetSingle(19, out var realFrameRate)) |
| | 5581 | | { |
| 0 | 5582 | | item.RealFrameRate = realFrameRate; |
| | 5583 | | } |
| | 5584 | |
|
| 0 | 5585 | | if (reader.TryGetSingle(20, out var level)) |
| | 5586 | | { |
| 0 | 5587 | | item.Level = level; |
| | 5588 | | } |
| | 5589 | |
|
| 0 | 5590 | | if (reader.TryGetString(21, out var pixelFormat)) |
| | 5591 | | { |
| 0 | 5592 | | item.PixelFormat = pixelFormat; |
| | 5593 | | } |
| | 5594 | |
|
| 0 | 5595 | | if (reader.TryGetInt32(22, out var bitDepth)) |
| | 5596 | | { |
| 0 | 5597 | | item.BitDepth = bitDepth; |
| | 5598 | | } |
| | 5599 | |
|
| 0 | 5600 | | if (reader.TryGetBoolean(23, out var isAnamorphic)) |
| | 5601 | | { |
| 0 | 5602 | | item.IsAnamorphic = isAnamorphic; |
| | 5603 | | } |
| | 5604 | |
|
| 0 | 5605 | | if (reader.TryGetInt32(24, out var refFrames)) |
| | 5606 | | { |
| 0 | 5607 | | item.RefFrames = refFrames; |
| | 5608 | | } |
| | 5609 | |
|
| 0 | 5610 | | if (reader.TryGetString(25, out var codecTag)) |
| | 5611 | | { |
| 0 | 5612 | | item.CodecTag = codecTag; |
| | 5613 | | } |
| | 5614 | |
|
| 0 | 5615 | | if (reader.TryGetString(26, out var comment)) |
| | 5616 | | { |
| 0 | 5617 | | item.Comment = comment; |
| | 5618 | | } |
| | 5619 | |
|
| 0 | 5620 | | if (reader.TryGetString(27, out var nalLengthSize)) |
| | 5621 | | { |
| 0 | 5622 | | item.NalLengthSize = nalLengthSize; |
| | 5623 | | } |
| | 5624 | |
|
| 0 | 5625 | | if (reader.TryGetBoolean(28, out var isAVC)) |
| | 5626 | | { |
| 0 | 5627 | | item.IsAVC = isAVC; |
| | 5628 | | } |
| | 5629 | |
|
| 0 | 5630 | | if (reader.TryGetString(29, out var title)) |
| | 5631 | | { |
| 0 | 5632 | | item.Title = title; |
| | 5633 | | } |
| | 5634 | |
|
| 0 | 5635 | | if (reader.TryGetString(30, out var timeBase)) |
| | 5636 | | { |
| 0 | 5637 | | item.TimeBase = timeBase; |
| | 5638 | | } |
| | 5639 | |
|
| 0 | 5640 | | if (reader.TryGetString(31, out var codecTimeBase)) |
| | 5641 | | { |
| 0 | 5642 | | item.CodecTimeBase = codecTimeBase; |
| | 5643 | | } |
| | 5644 | |
|
| 0 | 5645 | | if (reader.TryGetString(32, out var colorPrimaries)) |
| | 5646 | | { |
| 0 | 5647 | | item.ColorPrimaries = colorPrimaries; |
| | 5648 | | } |
| | 5649 | |
|
| 0 | 5650 | | if (reader.TryGetString(33, out var colorSpace)) |
| | 5651 | | { |
| 0 | 5652 | | item.ColorSpace = colorSpace; |
| | 5653 | | } |
| | 5654 | |
|
| 0 | 5655 | | if (reader.TryGetString(34, out var colorTransfer)) |
| | 5656 | | { |
| 0 | 5657 | | item.ColorTransfer = colorTransfer; |
| | 5658 | | } |
| | 5659 | |
|
| 0 | 5660 | | if (reader.TryGetInt32(35, out var dvVersionMajor)) |
| | 5661 | | { |
| 0 | 5662 | | item.DvVersionMajor = dvVersionMajor; |
| | 5663 | | } |
| | 5664 | |
|
| 0 | 5665 | | if (reader.TryGetInt32(36, out var dvVersionMinor)) |
| | 5666 | | { |
| 0 | 5667 | | item.DvVersionMinor = dvVersionMinor; |
| | 5668 | | } |
| | 5669 | |
|
| 0 | 5670 | | if (reader.TryGetInt32(37, out var dvProfile)) |
| | 5671 | | { |
| 0 | 5672 | | item.DvProfile = dvProfile; |
| | 5673 | | } |
| | 5674 | |
|
| 0 | 5675 | | if (reader.TryGetInt32(38, out var dvLevel)) |
| | 5676 | | { |
| 0 | 5677 | | item.DvLevel = dvLevel; |
| | 5678 | | } |
| | 5679 | |
|
| 0 | 5680 | | if (reader.TryGetInt32(39, out var rpuPresentFlag)) |
| | 5681 | | { |
| 0 | 5682 | | item.RpuPresentFlag = rpuPresentFlag; |
| | 5683 | | } |
| | 5684 | |
|
| 0 | 5685 | | if (reader.TryGetInt32(40, out var elPresentFlag)) |
| | 5686 | | { |
| 0 | 5687 | | item.ElPresentFlag = elPresentFlag; |
| | 5688 | | } |
| | 5689 | |
|
| 0 | 5690 | | if (reader.TryGetInt32(41, out var blPresentFlag)) |
| | 5691 | | { |
| 0 | 5692 | | item.BlPresentFlag = blPresentFlag; |
| | 5693 | | } |
| | 5694 | |
|
| 0 | 5695 | | if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId)) |
| | 5696 | | { |
| 0 | 5697 | | item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId; |
| | 5698 | | } |
| | 5699 | |
|
| 0 | 5700 | | item.IsHearingImpaired = reader.TryGetBoolean(43, out var result) && result; |
| | 5701 | |
|
| 0 | 5702 | | if (reader.TryGetInt32(44, out var rotation)) |
| | 5703 | | { |
| 0 | 5704 | | item.Rotation = rotation; |
| | 5705 | | } |
| | 5706 | |
|
| 0 | 5707 | | if (item.Type is MediaStreamType.Audio or MediaStreamType.Subtitle) |
| | 5708 | | { |
| 0 | 5709 | | item.LocalizedDefault = _localization.GetLocalizedString("Default"); |
| 0 | 5710 | | item.LocalizedExternal = _localization.GetLocalizedString("External"); |
| | 5711 | |
|
| 0 | 5712 | | if (item.Type is MediaStreamType.Subtitle) |
| | 5713 | | { |
| 0 | 5714 | | item.LocalizedUndefined = _localization.GetLocalizedString("Undefined"); |
| 0 | 5715 | | item.LocalizedForced = _localization.GetLocalizedString("Forced"); |
| 0 | 5716 | | item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); |
| | 5717 | | } |
| | 5718 | | } |
| | 5719 | |
|
| 0 | 5720 | | return item; |
| | 5721 | | } |
| | 5722 | |
|
| | 5723 | | public List<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query) |
| | 5724 | | { |
| 0 | 5725 | | CheckDisposed(); |
| | 5726 | |
|
| 0 | 5727 | | ArgumentNullException.ThrowIfNull(query); |
| | 5728 | |
|
| 0 | 5729 | | var cmdText = _mediaAttachmentSaveColumnsSelectQuery; |
| | 5730 | |
|
| 0 | 5731 | | if (query.Index.HasValue) |
| | 5732 | | { |
| 0 | 5733 | | cmdText += " AND AttachmentIndex=@AttachmentIndex"; |
| | 5734 | | } |
| | 5735 | |
|
| 0 | 5736 | | cmdText += " order by AttachmentIndex ASC"; |
| | 5737 | |
|
| 0 | 5738 | | var list = new List<MediaAttachment>(); |
| 0 | 5739 | | using (var connection = GetConnection(true)) |
| 0 | 5740 | | using (var statement = PrepareStatement(connection, cmdText)) |
| | 5741 | | { |
| 0 | 5742 | | statement.TryBind("@ItemId", query.ItemId); |
| | 5743 | |
|
| 0 | 5744 | | if (query.Index.HasValue) |
| | 5745 | | { |
| 0 | 5746 | | statement.TryBind("@AttachmentIndex", query.Index.Value); |
| | 5747 | | } |
| | 5748 | |
|
| 0 | 5749 | | foreach (var row in statement.ExecuteQuery()) |
| | 5750 | | { |
| 0 | 5751 | | list.Add(GetMediaAttachment(row)); |
| | 5752 | | } |
| | 5753 | | } |
| | 5754 | |
|
| 0 | 5755 | | return list; |
| | 5756 | | } |
| | 5757 | |
|
| | 5758 | | public void SaveMediaAttachments( |
| | 5759 | | Guid id, |
| | 5760 | | IReadOnlyList<MediaAttachment> attachments, |
| | 5761 | | CancellationToken cancellationToken) |
| | 5762 | | { |
| 0 | 5763 | | CheckDisposed(); |
| 0 | 5764 | | if (id.IsEmpty()) |
| | 5765 | | { |
| 0 | 5766 | | throw new ArgumentException("Guid can't be empty.", nameof(id)); |
| | 5767 | | } |
| | 5768 | |
|
| 0 | 5769 | | ArgumentNullException.ThrowIfNull(attachments); |
| | 5770 | |
|
| 0 | 5771 | | cancellationToken.ThrowIfCancellationRequested(); |
| | 5772 | |
|
| 0 | 5773 | | using (var connection = GetConnection()) |
| 0 | 5774 | | using (var transaction = connection.BeginTransaction()) |
| 0 | 5775 | | using (var command = connection.PrepareStatement("delete from mediaattachments where ItemId=@ItemId")) |
| | 5776 | | { |
| 0 | 5777 | | command.TryBind("@ItemId", id); |
| 0 | 5778 | | command.ExecuteNonQuery(); |
| | 5779 | |
|
| 0 | 5780 | | InsertMediaAttachments(id, attachments, connection, cancellationToken); |
| | 5781 | |
|
| 0 | 5782 | | transaction.Commit(); |
| 0 | 5783 | | } |
| 0 | 5784 | | } |
| | 5785 | |
|
| | 5786 | | private void InsertMediaAttachments( |
| | 5787 | | Guid id, |
| | 5788 | | IReadOnlyList<MediaAttachment> attachments, |
| | 5789 | | ManagedConnection db, |
| | 5790 | | CancellationToken cancellationToken) |
| | 5791 | | { |
| | 5792 | | const int InsertAtOnce = 10; |
| | 5793 | |
|
| 0 | 5794 | | var insertText = new StringBuilder(_mediaAttachmentInsertPrefix); |
| 0 | 5795 | | for (var startIndex = 0; startIndex < attachments.Count; startIndex += InsertAtOnce) |
| | 5796 | | { |
| 0 | 5797 | | var endIndex = Math.Min(attachments.Count, startIndex + InsertAtOnce); |
| | 5798 | |
|
| 0 | 5799 | | for (var i = startIndex; i < endIndex; i++) |
| | 5800 | | { |
| 0 | 5801 | | insertText.Append("(@ItemId, "); |
| | 5802 | |
|
| 0 | 5803 | | foreach (var column in _mediaAttachmentSaveColumns.Skip(1)) |
| | 5804 | | { |
| 0 | 5805 | | insertText.Append('@') |
| 0 | 5806 | | .Append(column) |
| 0 | 5807 | | .Append(i) |
| 0 | 5808 | | .Append(','); |
| | 5809 | | } |
| | 5810 | |
|
| 0 | 5811 | | insertText.Length -= 1; |
| | 5812 | |
|
| 0 | 5813 | | insertText.Append("),"); |
| | 5814 | | } |
| | 5815 | |
|
| 0 | 5816 | | insertText.Length--; |
| | 5817 | |
|
| 0 | 5818 | | cancellationToken.ThrowIfCancellationRequested(); |
| | 5819 | |
|
| 0 | 5820 | | using (var statement = PrepareStatement(db, insertText.ToString())) |
| | 5821 | | { |
| 0 | 5822 | | statement.TryBind("@ItemId", id); |
| | 5823 | |
|
| 0 | 5824 | | for (var i = startIndex; i < endIndex; i++) |
| | 5825 | | { |
| 0 | 5826 | | var index = i.ToString(CultureInfo.InvariantCulture); |
| | 5827 | |
|
| 0 | 5828 | | var attachment = attachments[i]; |
| | 5829 | |
|
| 0 | 5830 | | statement.TryBind("@AttachmentIndex" + index, attachment.Index); |
| 0 | 5831 | | statement.TryBind("@Codec" + index, attachment.Codec); |
| 0 | 5832 | | statement.TryBind("@CodecTag" + index, attachment.CodecTag); |
| 0 | 5833 | | statement.TryBind("@Comment" + index, attachment.Comment); |
| 0 | 5834 | | statement.TryBind("@Filename" + index, attachment.FileName); |
| 0 | 5835 | | statement.TryBind("@MIMEType" + index, attachment.MimeType); |
| | 5836 | | } |
| | 5837 | |
|
| 0 | 5838 | | statement.ExecuteNonQuery(); |
| 0 | 5839 | | } |
| | 5840 | |
|
| 0 | 5841 | | insertText.Length = _mediaAttachmentInsertPrefix.Length; |
| | 5842 | | } |
| 0 | 5843 | | } |
| | 5844 | |
|
| | 5845 | | /// <summary> |
| | 5846 | | /// Gets the attachment. |
| | 5847 | | /// </summary> |
| | 5848 | | /// <param name="reader">The reader.</param> |
| | 5849 | | /// <returns>MediaAttachment.</returns> |
| | 5850 | | private MediaAttachment GetMediaAttachment(SqliteDataReader reader) |
| | 5851 | | { |
| 0 | 5852 | | var item = new MediaAttachment |
| 0 | 5853 | | { |
| 0 | 5854 | | Index = reader.GetInt32(1) |
| 0 | 5855 | | }; |
| | 5856 | |
|
| 0 | 5857 | | if (reader.TryGetString(2, out var codec)) |
| | 5858 | | { |
| 0 | 5859 | | item.Codec = codec; |
| | 5860 | | } |
| | 5861 | |
|
| 0 | 5862 | | if (reader.TryGetString(3, out var codecTag)) |
| | 5863 | | { |
| 0 | 5864 | | item.CodecTag = codecTag; |
| | 5865 | | } |
| | 5866 | |
|
| 0 | 5867 | | if (reader.TryGetString(4, out var comment)) |
| | 5868 | | { |
| 0 | 5869 | | item.Comment = comment; |
| | 5870 | | } |
| | 5871 | |
|
| 0 | 5872 | | if (reader.TryGetString(5, out var fileName)) |
| | 5873 | | { |
| 0 | 5874 | | item.FileName = fileName; |
| | 5875 | | } |
| | 5876 | |
|
| 0 | 5877 | | if (reader.TryGetString(6, out var mimeType)) |
| | 5878 | | { |
| 0 | 5879 | | item.MimeType = mimeType; |
| | 5880 | | } |
| | 5881 | |
|
| 0 | 5882 | | return item; |
| | 5883 | | } |
| | 5884 | |
|
| | 5885 | | private static string BuildMediaAttachmentInsertPrefix() |
| | 5886 | | { |
| 1 | 5887 | | var queryPrefixText = new StringBuilder(); |
| 1 | 5888 | | queryPrefixText.Append("insert into mediaattachments ("); |
| 16 | 5889 | | foreach (var column in _mediaAttachmentSaveColumns) |
| | 5890 | | { |
| 7 | 5891 | | queryPrefixText.Append(column) |
| 7 | 5892 | | .Append(','); |
| | 5893 | | } |
| | 5894 | |
|
| 1 | 5895 | | queryPrefixText.Length -= 1; |
| 1 | 5896 | | queryPrefixText.Append(") values "); |
| 1 | 5897 | | return queryPrefixText.ToString(); |
| | 5898 | | } |
| | 5899 | |
|
| | 5900 | | #nullable enable |
| | 5901 | |
|
| | 5902 | | private readonly struct QueryTimeLogger : IDisposable |
| | 5903 | | { |
| | 5904 | | private readonly ILogger _logger; |
| | 5905 | | private readonly string _commandText; |
| | 5906 | | private readonly string _methodName; |
| | 5907 | | private readonly long _startTimestamp; |
| | 5908 | |
|
| | 5909 | | public QueryTimeLogger(ILogger logger, string commandText, [CallerMemberName] string methodName = "") |
| | 5910 | | { |
| 412 | 5911 | | _logger = logger; |
| 412 | 5912 | | _commandText = commandText; |
| 412 | 5913 | | _methodName = methodName; |
| 412 | 5914 | | _startTimestamp = logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : -1; |
| 412 | 5915 | | } |
| | 5916 | |
|
| | 5917 | | public void Dispose() |
| | 5918 | | { |
| 412 | 5919 | | if (_startTimestamp == -1) |
| | 5920 | | { |
| 412 | 5921 | | return; |
| | 5922 | | } |
| | 5923 | |
|
| 0 | 5924 | | var elapsedMs = Stopwatch.GetElapsedTime(_startTimestamp).TotalMilliseconds; |
| | 5925 | |
|
| | 5926 | | #if DEBUG |
| | 5927 | | const int SlowThreshold = 100; |
| | 5928 | | #else |
| | 5929 | | const int SlowThreshold = 10; |
| | 5930 | | #endif |
| | 5931 | |
|
| 0 | 5932 | | if (elapsedMs >= SlowThreshold) |
| | 5933 | | { |
| 0 | 5934 | | _logger.LogDebug( |
| 0 | 5935 | | "{Method} query time (slow): {ElapsedMs}ms. Query: {Query}", |
| 0 | 5936 | | _methodName, |
| 0 | 5937 | | elapsedMs, |
| 0 | 5938 | | _commandText); |
| | 5939 | | } |
| 0 | 5940 | | } |
| | 5941 | | } |
| | 5942 | | } |
| | 5943 | | } |