From f2abd8ba39eefd5397eb3f0e327efbca8d8ecd0f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 1 Jun 2015 10:49:23 -0400 Subject: [PATCH] update live tv database --- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- .../Playback/Hls/DynamicHlsService.cs | 11 +- .../Playback/TranscodingThrottler.cs | 13 +- .../Channels/ChannelAudioItem.cs | 1 - .../Channels/ChannelFolderItem.cs | 1 - .../Channels/ChannelVideoItem.cs | 1 - MediaBrowser.Controller/Entities/BaseItem.cs | 6 + .../Entities/IHasProgramAttributes.cs | 9 + .../Entities/IHasStartDate.cs | 9 + .../Entities/InternalItemsQuery.cs | 12 + .../Library/ILibraryManager.cs | 8 + .../LiveTv/LiveTvProgram.cs | 20 +- .../MediaBrowser.Controller.csproj | 2 + .../Persistence/IItemRepository.cs | 29 +- .../Omdb/OmdbItemProvider.cs | 30 +- .../IO/LibraryMonitor.cs | 2 +- .../Library/LibraryManager.cs | 13 + .../LiveTv/LiveTvManager.cs | 377 +++++++----------- .../LiveTv/ProgramImageProvider.cs | 4 +- .../Persistence/SqliteItemRepository.cs | 332 ++++++++++++++- 20 files changed, 583 insertions(+), 299 deletions(-) create mode 100644 MediaBrowser.Controller/Entities/IHasProgramAttributes.cs create mode 100644 MediaBrowser.Controller/Entities/IHasStartDate.cs diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index db3dbf048f..68087309bd 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -340,7 +340,7 @@ namespace MediaBrowser.Api // We can really reduce the timeout for apps that are using the newer api if (!string.IsNullOrWhiteSpace(job.PlaySessionId)) { - timerDuration = 60000; + timerDuration = 120000; } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 6ca5c57f3e..41d26417d6 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -879,7 +879,7 @@ namespace MediaBrowser.Api.Playback.Hls if (!EnableSplitTranscoding(state)) { - args += " -copyts"; + //args += " -copyts"; } return args; @@ -910,11 +910,11 @@ namespace MediaBrowser.Api.Playback.Hls //toTimeParam = " -to " + MediaEncoder.GetTimeParameter(endTime); toTimeParam = " -t " + MediaEncoder.GetTimeParameter(TimeSpan.FromSeconds(durationSeconds).Ticks); } + } - if (state.IsOutputVideo && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0) - { - timestampOffsetParam = " -output_ts_offset " + MediaEncoder.GetTimeParameter(state.Request.StartTimeTicks ?? 0).ToString(CultureInfo.InvariantCulture); - } + if (state.IsOutputVideo && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0) + { + timestampOffsetParam = " -output_ts_offset " + MediaEncoder.GetTimeParameter(state.Request.StartTimeTicks ?? 0).ToString(CultureInfo.InvariantCulture); } var mapArgs = state.IsOutputVideo ? GetMapArgs(state) : string.Empty; @@ -959,6 +959,7 @@ namespace MediaBrowser.Api.Playback.Hls private bool EnableSplitTranscoding(StreamState state) { + return false; if (string.Equals(Request.QueryString["EnableSplitTranscoding"], "false", StringComparison.OrdinalIgnoreCase)) { return false; diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index fec3dda869..f94d5d837d 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -42,7 +42,14 @@ namespace MediaBrowser.Api.Playback var options = GetOptions(); - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdInSeconds)) + var threshold = options.ThrottleThresholdInSeconds; + + if (!options.EnableThrottling) + { + threshold *= 2; + } + + if (IsThrottleAllowed(_job, threshold)) { PauseTranscoding(); } @@ -56,7 +63,7 @@ namespace MediaBrowser.Api.Playback { if (!_isPaused) { - _logger.Debug("Sending pause command to ffmpeg"); + //_logger.Debug("Sending pause command to ffmpeg"); try { @@ -74,7 +81,7 @@ namespace MediaBrowser.Api.Playback { if (_isPaused) { - _logger.Debug("Sending unpause command to ffmpeg"); + //_logger.Debug("Sending unpause command to ffmpeg"); try { diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs index 82fe66c7ba..aa4b6731c7 100644 --- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs @@ -15,7 +15,6 @@ namespace MediaBrowser.Controller.Channels { public string ExternalId { get; set; } - public string ChannelId { get; set; } public string DataVersion { get; set; } public ChannelItemType ChannelItemType { get; set; } diff --git a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs index 641d37161a..7e9da52a92 100644 --- a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs @@ -12,7 +12,6 @@ namespace MediaBrowser.Controller.Channels { public string ExternalId { get; set; } - public string ChannelId { get; set; } public string DataVersion { get; set; } public ChannelItemType ChannelItemType { get; set; } diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs index ef3cc7cbab..ca5e343f8c 100644 --- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Controller.Channels { public string ExternalId { get; set; } - public string ChannelId { get; set; } public string DataVersion { get; set; } public ChannelItemType ChannelItemType { get; set; } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 8b6cbdc931..014b3ae6a4 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -59,6 +59,12 @@ namespace MediaBrowser.Controller.Entities public List ImageInfos { get; set; } + /// + /// Gets or sets the channel identifier. + /// + /// The channel identifier. + public string ChannelId { get; set; } + [IgnoreDataMember] public virtual bool SupportsAddingToPlaylist { diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs new file mode 100644 index 0000000000..1878d8d2c1 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs @@ -0,0 +1,9 @@ + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasProgramAttributes + { + bool IsMovie { get; set; } + bool IsSports { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/IHasStartDate.cs b/MediaBrowser.Controller/Entities/IHasStartDate.cs new file mode 100644 index 0000000000..a6714fb964 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasStartDate.cs @@ -0,0 +1,9 @@ +using System; + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasStartDate + { + DateTime StartDate { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 727f756f15..245c111697 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -73,6 +73,17 @@ namespace MediaBrowser.Controller.Entities public string[] Tags { get; set; } public string[] OfficialRatings { get; set; } + public DateTime? MinStartDate { get; set; } + public DateTime? MaxStartDate { get; set; } + public DateTime? MinEndDate { get; set; } + public DateTime? MaxEndDate { get; set; } + public bool? IsAiring { get; set; } + + public bool? IsMovie { get; set; } + public bool? IsSports { get; set; } + + public string[] ChannelIds { get; set; } + public InternalItemsQuery() { Tags = new string[] { }; @@ -89,6 +100,7 @@ namespace MediaBrowser.Controller.Entities Years = new int[] { }; PersonTypes = new string[] { }; PersonIds = new string[] { }; + ChannelIds = new string[] { }; } } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index c009121154..aa8799fa6e 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Library { @@ -132,6 +133,13 @@ namespace MediaBrowser.Controller.Library /// BaseItem. BaseItem GetItemById(Guid id); + /// + /// Gets the items. + /// + /// The query. + /// QueryResult<BaseItem>. + QueryResult GetItems(InternalItemsQuery query); + /// /// Gets the memory item by identifier. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 0609df4c6b..8232c5c7ad 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.LiveTv; @@ -7,12 +6,10 @@ using MediaBrowser.Model.Users; using System; using System.Linq; using System.Runtime.Serialization; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Controller.LiveTv { - public class LiveTvProgram : BaseItem, ILiveTvItem, IHasLookupInfo + public class LiveTvProgram : BaseItem, ILiveTvItem, IHasLookupInfo, IHasStartDate, IHasProgramAttributes { /// /// Gets the user data key. @@ -28,12 +25,6 @@ namespace MediaBrowser.Controller.LiveTv /// public string ExternalId { get; set; } - /// - /// Gets or sets the channel identifier. - /// - /// The channel identifier. - public string ExternalChannelId { get; set; } - /// /// Gets or sets the original air date. /// @@ -204,15 +195,6 @@ namespace MediaBrowser.Controller.LiveTv return "Program"; } - public override Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) - { - DateLastSaved = DateTime.UtcNow; - - // Avoid library manager and keep out of in-memory cache - // Not great that this class has to know about that, but we'll improve that later. - return ItemRepository.SaveItem(this, cancellationToken); - } - protected override bool GetBlockUnratedValue(UserPolicy config) { return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram); diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index b66e6b9c8c..e1a18164ef 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -149,10 +149,12 @@ + + diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index aa5376ec3f..7c02a0ea1e 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Persistence { @@ -102,13 +103,6 @@ namespace MediaBrowser.Controller.Persistence /// IEnumerable{ChildDefinition}. IEnumerable GetChildren(Guid parentId); - /// - /// Gets the type of the items of. - /// - /// The type. - /// IEnumerable{Guid}. - IEnumerable GetItemIdsOfType(Type type); - /// /// Saves the children. /// @@ -135,11 +129,24 @@ namespace MediaBrowser.Controller.Persistence Task SaveMediaStreams(Guid id, IEnumerable streams, CancellationToken cancellationToken); /// - /// Gets the type of the items of. + /// Gets the item ids. /// - /// The type. - /// IEnumerable<BaseItem>. - IEnumerable GetItemsOfType(Type type); + /// The query. + /// IEnumerable<Guid>. + QueryResult GetItemIds(InternalItemsQuery query); + /// + /// Gets the items. + /// + /// The query. + /// QueryResult<BaseItem>. + QueryResult GetItems(InternalItemsQuery query); + + /// + /// Gets the item ids list. + /// + /// The query. + /// List<Guid>. + List GetItemIdsList(InternalItemsQuery query); } } diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs index c79d04b4f0..596b864f79 100644 --- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs @@ -1,21 +1,20 @@ -using System.Linq; -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -using MediaBrowser.Providers.Movies; -using MediaBrowser.Providers.TV; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -23,7 +22,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Providers.Omdb { public class OmdbItemProvider : IRemoteMetadataProvider, - IRemoteMetadataProvider, IRemoteMetadataProvider + IRemoteMetadataProvider, IRemoteMetadataProvider, IRemoteMetadataProvider { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; @@ -48,6 +47,16 @@ namespace MediaBrowser.Providers.Omdb return GetSearchResults(searchInfo, "movie", cancellationToken); } + public Task> GetSearchResults(LiveTvProgramLookupInfo searchInfo, CancellationToken cancellationToken) + { + if (!searchInfo.IsMovie) + { + return Task.FromResult>(new List()); + } + + return GetSearchResults(searchInfo, "movie", cancellationToken); + } + public async Task> GetSearchResults(ItemLookupInfo searchInfo, string type, CancellationToken cancellationToken) { var list = new List(); @@ -169,13 +178,22 @@ namespace MediaBrowser.Providers.Omdb return result; } + public Task> GetMetadata(LiveTvProgramLookupInfo info, CancellationToken cancellationToken) + { + if (!info.IsMovie) + { + return Task.FromResult(new MetadataResult()); + } + return GetMovieResult(info, cancellationToken); + } + public Task> GetMetadata(MovieInfo info, CancellationToken cancellationToken) { return GetMovieResult(info, cancellationToken); } private async Task> GetMovieResult(ItemLookupInfo info, CancellationToken cancellationToken) - where T : Video, new() + where T : BaseItem, new() { var result = new MetadataResult { diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 491549d64f..ef2888e4aa 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.IO // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds // But if we make this delay too high, we risk missing legitimate changes - await Task.Delay(10000).ConfigureAwait(false); + await Task.Delay(15000).ConfigureAwait(false); string val; _tempIgnoredPaths.TryRemove(path, out val); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index ee0bf354f5..ed5dde4c5a 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; using MediaBrowser.Naming.Audio; using MediaBrowser.Naming.Common; using MediaBrowser.Naming.TV; @@ -1209,6 +1210,18 @@ namespace MediaBrowser.Server.Implementations.Library return item; } + public QueryResult GetItems(InternalItemsQuery query) + { + var result = ItemRepository.GetItemIdsList(query); + + var items = result.Select(GetItemById).ToArray(); + + return new QueryResult + { + Items = items + }; + } + /// /// Gets the intros. /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index dbae6d03e8..47e862b9f8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using System; @@ -54,10 +53,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(); - private List _channelIdList = new List(); - private Dictionary _programs; - private readonly ConcurrentDictionary _refreshedPrograms = new ConcurrentDictionary(); - public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager) { _config = config; @@ -108,44 +103,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue(); } - private readonly object _programsDataLock = new object(); - private Dictionary GetProgramsDictionary() - { - if (_programs == null) - { - lock (_programsDataLock) - { - if (_programs == null) - { - var dict = new Dictionary(); - - foreach (var item in _itemRepo.GetItemsOfType(typeof(LiveTvProgram)) - .Cast() - .ToList()) - { - dict[item.Id] = item; - } - - _programs = dict; - } - } - } - - return _programs; - } - - private IEnumerable GetPrograms() - { - return GetProgramsDictionary().Values; - } - public async Task> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); - var channels = _channelIdList.Select(_libraryManager.GetItemById) - .Where(i => i != null) - .OfType(); + var channels = _libraryManager.GetItems(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(LiveTvChannel).Name } + + }).Items.Cast(); if (user != null) { @@ -258,9 +224,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv var returnList = new List(); + var now = DateTime.UtcNow; + + var programs = _libraryManager.GetItems(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + MaxStartDate = now, + MinEndDate = now + + }).Items.Cast().OrderBy(i => i.StartDate).ToList(); + foreach (var channel in internalResult.Items) { - var currentProgram = GetCurrentProgram(channel.ExternalId); + var channelIdString = channel.Id.ToString("N"); + var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString, StringComparison.OrdinalIgnoreCase)); returnList.Add(_tvDtoService.GetChannelInfoDto(channel, currentProgram, user)); } @@ -286,34 +263,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv internal LiveTvProgram GetInternalProgram(string id) { - var guid = new Guid(id); - - LiveTvProgram obj = null; - - GetProgramsDictionary().TryGetValue(guid, out obj); - - if (obj != null) - { - RefreshIfNeeded(obj); - } - return obj; - } - - private void RefreshIfNeeded(LiveTvProgram program) - { - if (!_refreshedPrograms.ContainsKey(program.Id)) - { - _refreshedPrograms.TryAdd(program.Id, true); - _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions()); - } + return _libraryManager.GetItemById(id) as LiveTvProgram; } - private void RefreshIfNeeded(IEnumerable programs) + internal LiveTvProgram GetInternalProgram(Guid id) { - foreach (var program in programs) - { - RefreshIfNeeded(program); - } + return _libraryManager.GetItemById(id) as LiveTvProgram; } public async Task GetInternalRecording(string id, CancellationToken cancellationToken) @@ -598,14 +553,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv return item; } - private async Task GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken) + private async Task GetProgram(ProgramInfo info, string channelId, ChannelType channelType, string serviceName, CancellationToken cancellationToken) { var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id); var item = _libraryManager.GetItemById(id) as LiveTvProgram; + var isNew = false; if (item == null) { + isNew = true; item = new LiveTvProgram { Name = info.Name, @@ -619,8 +576,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.ServiceName = serviceName; item.Audio = info.Audio; - item.ExternalChannelId = info.ChannelId; - item.CommunityRating = info.CommunityRating; + item.ChannelId = channelId; + item.CommunityRating = item.CommunityRating ?? info.CommunityRating; item.EndDate = info.EndDate; item.EpisodeTitle = info.EpisodeTitle; item.ExternalId = info.Id; @@ -636,8 +593,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.IsSeries = info.IsSeries; item.IsSports = info.IsSports; item.Name = info.Name; - item.OfficialRating = info.OfficialRating; - item.Overview = info.Overview; + item.OfficialRating = item.OfficialRating ?? info.OfficialRating; + item.Overview = item.Overview ?? info.Overview; item.OriginalAirDate = info.OriginalAirDate; item.ProviderImagePath = info.ImagePath; item.ProviderImageUrl = info.ImageUrl; @@ -647,7 +604,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.ProductionYear = info.ProductionYear; item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate; - await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + if (isNew) + { + await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false); + } + else + { + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + } + + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions()); return item; } @@ -721,17 +687,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv return recording; } - private LiveTvChannel GetChannel(LiveTvProgram program) - { - var programChannelId = program.ExternalChannelId; - - if (string.IsNullOrWhiteSpace(programChannelId)) return null; - - var internalProgramChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, programChannelId); - - return GetInternalChannel(internalProgramChannelId); - } - public async Task GetProgram(string id, CancellationToken cancellationToken, User user = null) { var program = GetInternalProgram(id); @@ -745,64 +700,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task> GetPrograms(ProgramQuery query, CancellationToken cancellationToken) { - IEnumerable programs = GetPrograms(); - - if (query.MinEndDate.HasValue) + var internalQuery = new InternalItemsQuery { - var val = query.MinEndDate.Value; - - programs = programs.Where(i => i.EndDate.HasValue && i.EndDate.Value >= val); - } - - if (query.MinStartDate.HasValue) - { - var val = query.MinStartDate.Value; - - programs = programs.Where(i => i.StartDate >= val); - } - - if (query.MaxEndDate.HasValue) - { - var val = query.MaxEndDate.Value; - - programs = programs.Where(i => i.EndDate.HasValue && i.EndDate.Value <= val); - } - - if (query.MaxStartDate.HasValue) - { - var val = query.MaxStartDate.Value; - - programs = programs.Where(i => i.StartDate <= val); - } + IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + MinEndDate = query.MinEndDate, + MinStartDate = query.MinStartDate, + MaxEndDate = query.MaxEndDate, + MaxStartDate = query.MaxStartDate, + ChannelIds = query.ChannelIds, + IsMovie = query.IsMovie, + IsSports = query.IsSports + }; if (query.HasAired.HasValue) { - var val = query.HasAired.Value; - programs = programs.Where(i => i.HasAired == val); - } - - if (query.ChannelIds.Length > 0) - { - var guids = query.ChannelIds.Select(i => new Guid(i)).ToList(); - - programs = programs.Where(i => + if (query.HasAired.Value) { - var programChannelId = i.ExternalChannelId; - - var service = GetService(i); - var internalProgramChannelId = _tvDtoService.GetInternalChannelId(service.Name, programChannelId); - - return guids.Contains(internalProgramChannelId); - }); + internalQuery.MaxEndDate = DateTime.UtcNow; + } + else + { + internalQuery.MinEndDate = DateTime.UtcNow; + } } - var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); - if (user != null) - { - // Avoid implicitly captured closure - var currentUser = user; - programs = programs.Where(i => i.IsVisible(currentUser)); - } + IEnumerable programs = _libraryManager.GetItems(internalQuery).Items.Cast(); // Apply genre filter if (query.Genres.Length > 0) @@ -810,14 +732,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(p => p.Genres.Any(g => query.Genres.Contains(g, StringComparer.OrdinalIgnoreCase))); } - if (query.IsMovie.HasValue) - { - programs = programs.Where(p => p.IsMovie == query.IsMovie); - } - - if (query.IsSports.HasValue) + var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); + if (user != null) { - programs = programs.Where(p => p.IsSports == query.IsSports); + // Avoid implicitly captured closure + var currentUser = user; + programs = programs.Where(i => i.IsVisible(currentUser)); } programs = _libraryManager.Sort(programs, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending) @@ -840,8 +760,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv .Select(i => _dtoService.GetBaseItemDto(i, new DtoOptions(), user)) .ToArray(); - RefreshIfNeeded(programList); - await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false); var result = new QueryResult @@ -855,35 +773,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken) { - IEnumerable programs = GetPrograms(); - - var user = _userManager.GetUserById(query.UserId); - - // Avoid implicitly captured closure - var currentUser = user; - programs = programs.Where(i => i.IsVisible(currentUser)); - - if (query.IsAiring.HasValue) + var internalQuery = new InternalItemsQuery { - var val = query.IsAiring.Value; - programs = programs.Where(i => i.IsAiring == val); - } + IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + IsAiring = query.IsAiring, + IsMovie = query.IsMovie, + IsSports = query.IsSports + }; if (query.HasAired.HasValue) { - var val = query.HasAired.Value; - programs = programs.Where(i => i.HasAired == val); + if (query.HasAired.Value) + { + internalQuery.MaxEndDate = DateTime.UtcNow; + } + else + { + internalQuery.MinEndDate = DateTime.UtcNow; + } } - if (query.IsMovie.HasValue) - { - programs = programs.Where(p => p.IsMovie == query.IsMovie.Value); - } + IEnumerable programs = _libraryManager.GetItems(internalQuery).Items.Cast(); - if (query.IsSports.HasValue) - { - programs = programs.Where(p => p.IsSports == query.IsSports.Value); - } + var user = _userManager.GetUserById(query.UserId); + + // Avoid implicitly captured closure + var currentUser = user; + programs = programs.Where(i => i.IsVisible(currentUser)); var programList = programs.ToList(); @@ -904,8 +820,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv programList = programs.ToList(); - RefreshIfNeeded(programList); - var returnArray = programList.ToArray(); var result = new QueryResult @@ -952,8 +866,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv score++; } - var internalChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, program.ExternalChannelId); - var channel = GetInternalChannel(internalChannelId); + var channel = GetInternalChannel(program.ChannelId); var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey()); @@ -1048,17 +961,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - internal async Task RefreshChannels(IProgress progress, CancellationToken cancellationToken) + internal Task RefreshChannels(IProgress progress, CancellationToken cancellationToken) { - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(p * .9)); - await RefreshChannelsInternal(innerProgress, cancellationToken).ConfigureAwait(false); - - innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(90 + (p * .1))); - await CleanDatabaseInternal(progress, cancellationToken).ConfigureAwait(false); - - RefreshIfNeeded(GetPrograms().ToList()); + return RefreshChannelsInternal(progress, cancellationToken); } private async Task RefreshChannelsInternal(IProgress progress, CancellationToken cancellationToken) @@ -1068,6 +973,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv ? 0 : 1 / _services.Count; + var newChannelIdList = new List(); + var newProgramIdList = new List(); + foreach (var service in _services) { cancellationToken.ThrowIfCancellationRequested(); @@ -1077,7 +985,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv var innerProgress = new ActionableProgress(); innerProgress.RegisterAction(p => progress.Report(p * progressPerService)); - await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false); + var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false); + + newChannelIdList.AddRange(idList.Item1); + newProgramIdList.AddRange(idList.Item2); } catch (OperationCanceledException) { @@ -1095,10 +1006,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv progress.Report(100 * percent); } + await CleanDatabaseInternal(newChannelIdList, typeof(LiveTvChannel).Name, progress, cancellationToken).ConfigureAwait(false); + await CleanDatabaseInternal(newProgramIdList, typeof(LiveTvProgram).Name, progress, cancellationToken).ConfigureAwait(false); + + // Load these now which will prefetch metadata + var dtoOptions = new DtoOptions(); + dtoOptions.Fields.Remove(ItemFields.SyncInfo); + await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false); + progress.Report(100); } - private async Task RefreshChannelsInternal(ILiveTvService service, IProgress progress, CancellationToken cancellationToken) + private async Task,List>> RefreshChannelsInternal(ILiveTvService service, IProgress progress, CancellationToken cancellationToken) { progress.Report(10); @@ -1137,23 +1056,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv progress.Report(5 * percent + 10); } - _channelIdList = list.Select(i => i.Id).ToList(); progress.Report(15); numComplete = 0; - var programs = new List(); + var programs = new List(); + var channels = new List(); var guideDays = GetGuideDays(list.Count); cancellationToken.ThrowIfCancellationRequested(); - foreach (var item in list) + foreach (var currentChannel in list) { + channels.Add(currentChannel.Id); cancellationToken.ThrowIfCancellationRequested(); - // Avoid implicitly captured closure - var currentChannel = item; - try { var start = DateTime.UtcNow.AddHours(-1); @@ -1161,9 +1078,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false); + var channelId = currentChannel.Id.ToString("N"); + foreach (var program in channelPrograms) { - programs.Add(await GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false)); + var programItem = await GetProgram(program, channelId, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false); + programs.Add(programItem.Id); } } catch (OperationCanceledException) @@ -1181,44 +1101,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv progress.Report(80 * percent + 10); } - - lock (_programsDataLock) - { - _programs = programs.ToDictionary(i => i.Id); - } - - _refreshedPrograms.Clear(); - progress.Report(90); - - // Load these now which will prefetch metadata - var dtoOptions = new DtoOptions(); - dtoOptions.Fields.Remove(ItemFields.SyncInfo); - await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false); progress.Report(100); - } - private Task CleanDatabaseInternal(IProgress progress, CancellationToken cancellationToken) - { - return DeleteOldPrograms(GetProgramsDictionary().Keys.ToList(), progress, cancellationToken); + return new Tuple,List>(channels, programs); } - private async Task DeleteOldPrograms(List currentIdList, IProgress progress, CancellationToken cancellationToken) + private async Task CleanDatabaseInternal(List currentIdList, string typeName, IProgress progress, CancellationToken cancellationToken) { - var list = _itemRepo.GetItemIdsOfType(typeof(LiveTvProgram)).ToList(); + var list = _itemRepo.GetItemIds(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeName } + + }).Items.ToList(); var numComplete = 0; - foreach (var programId in list) + foreach (var itemId in list) { cancellationToken.ThrowIfCancellationRequested(); - if (!currentIdList.Contains(programId)) + if (!currentIdList.Contains(itemId)) { - var program = _libraryManager.GetItemById(programId); + var item = _libraryManager.GetItemById(itemId); - if (program != null) + if (item != null) { - await _libraryManager.DeleteItem(program).ConfigureAwait(false); + await _libraryManager.DeleteItem(item).ConfigureAwait(false); } } @@ -1358,11 +1266,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv var program = (LiveTvProgram)item; var service = GetService(program); - var channel = string.IsNullOrEmpty(program.ExternalChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, program.ExternalChannelId)); + var channel = GetInternalChannel(program.ChannelId); dto.Id = _tvDtoService.GetInternalProgramId(service.Name, program.ExternalId).ToString("N"); - dto.ChannelId = _tvDtoService.GetInternalChannelId(service.Name, program.ExternalChannelId).ToString("N"); + dto.ChannelId = channel.Id.ToString("N"); dto.StartDate = program.StartDate; dto.IsRepeat = program.IsRepeat; @@ -1676,29 +1584,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var channel = GetInternalChannel(id); - var currentProgram = GetCurrentProgram(channel.ExternalId); - - var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user); + var now = DateTime.UtcNow; - return dto; - } + var programs = _libraryManager.GetItems(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + ChannelIds = new[] { id }, + MaxStartDate = now, + MinEndDate = now, + Limit = 1 - private LiveTvProgram GetCurrentProgram(string externalChannelId) - { - var now = DateTime.UtcNow; + }).Items.Cast(); - var program = GetPrograms() - .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase)) + var currentProgram = programs .OrderBy(i => i.StartDate) - .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue)) .FirstOrDefault(); - if (program != null) - { - RefreshIfNeeded(program); - } + var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user); - return program; + return dto; } private async Task> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null) @@ -1711,10 +1615,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (program != null) { + var channel = GetInternalChannel(program.ChannelId); + programInfo = new ProgramInfo { Audio = program.Audio, - ChannelId = program.ExternalChannelId, + ChannelId = channel.ExternalId, CommunityRating = program.CommunityRating, EndDate = program.EndDate ?? DateTime.MinValue, EpisodeTitle = program.EpisodeTitle, @@ -1990,15 +1896,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv public GuideInfo GetGuideInfo() { - var programs = GetPrograms().OrderBy(i => i.StartDate).ToList(); - - var startDate = programs.Count == 0 ? - DateTime.MinValue : - programs[0].StartDate; - - var endDate = programs.Count == 0 ? - DateTime.MinValue : - programs[programs.Count - 1].StartDate; + var startDate = DateTime.UtcNow; + var endDate = startDate.AddDays(14); return new GuideInfo { diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index b01dddb94d..134e24ef0e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -70,7 +70,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { try { - var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, liveTvItem.ExternalChannelId, cancellationToken).ConfigureAwait(false); + var channel = _liveTvManager.GetInternalChannel(liveTvItem.ChannelId); + + var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, channel.ExternalId, cancellationToken).ConfigureAwait(false); if (response != null) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index c5a9db87bf..19aca1cf9c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -1,12 +1,15 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Data; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -126,6 +129,12 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.RunQueries(queries, _logger); + _connection.AddColumn(_logger, "TypedBaseItems", "StartDate", "DATETIME"); + _connection.AddColumn(_logger, "TypedBaseItems", "EndDate", "DATETIME"); + _connection.AddColumn(_logger, "TypedBaseItems", "ChannelId", "Text"); + _connection.AddColumn(_logger, "TypedBaseItems", "IsMovie", "BIT"); + _connection.AddColumn(_logger, "TypedBaseItems", "IsSports", "BIT"); + PrepareStatements(); _mediaStreamsRepository.Initialize(); @@ -143,10 +152,15 @@ namespace MediaBrowser.Server.Implementations.Persistence private void PrepareStatements() { _saveItemCommand = _connection.CreateCommand(); - _saveItemCommand.CommandText = "replace into TypedBaseItems (guid, type, data) values (@1, @2, @3)"; + _saveItemCommand.CommandText = "replace into TypedBaseItems (guid, type, data, StartDate, EndDate, ChannelId, IsMovie, IsSports) values (@1, @2, @3, @4, @5, @6, @7, @8)"; _saveItemCommand.Parameters.Add(_saveItemCommand, "@1"); _saveItemCommand.Parameters.Add(_saveItemCommand, "@2"); _saveItemCommand.Parameters.Add(_saveItemCommand, "@3"); + _saveItemCommand.Parameters.Add(_saveItemCommand, "@4"); + _saveItemCommand.Parameters.Add(_saveItemCommand, "@5"); + _saveItemCommand.Parameters.Add(_saveItemCommand, "@6"); + _saveItemCommand.Parameters.Add(_saveItemCommand, "@7"); + _saveItemCommand.Parameters.Add(_saveItemCommand, "@8"); _deleteChildrenCommand = _connection.CreateCommand(); _deleteChildrenCommand.CommandText = "delete from ChildrenIds where ParentId=@ParentId"; @@ -155,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _deleteItemCommand = _connection.CreateCommand(); _deleteItemCommand.CommandText = "delete from TypedBaseItems where guid=@Id"; _deleteItemCommand.Parameters.Add(_deleteItemCommand, "@Id"); - + _saveChildrenCommand = _connection.CreateCommand(); _saveChildrenCommand.CommandText = "replace into ChildrenIds (ParentId, ItemId) values (@ParentId, @ItemId)"; _saveChildrenCommand.Parameters.Add(_saveChildrenCommand, "@ParentId"); @@ -200,7 +214,7 @@ namespace MediaBrowser.Server.Implementations.Persistence cancellationToken.ThrowIfCancellationRequested(); CheckDisposed(); - + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); IDbTransaction transaction = null; @@ -217,6 +231,31 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveItemCommand.GetParameter(1).Value = item.GetType().FullName; _saveItemCommand.GetParameter(2).Value = _jsonSerializer.SerializeToBytes(item); + var hasStartDate = item as IHasStartDate; + if (hasStartDate != null) + { + _saveItemCommand.GetParameter(3).Value = hasStartDate.StartDate; + } + else + { + _saveItemCommand.GetParameter(3).Value = null; + } + + _saveItemCommand.GetParameter(4).Value = item.EndDate; + _saveItemCommand.GetParameter(5).Value = item.ChannelId; + + var hasProgramAttributes = item as IHasProgramAttributes; + if (hasProgramAttributes != null) + { + _saveItemCommand.GetParameter(6).Value = hasProgramAttributes.IsMovie; + _saveItemCommand.GetParameter(7).Value = hasProgramAttributes.IsSports; + } + else + { + _saveItemCommand.GetParameter(6).Value = null; + _saveItemCommand.GetParameter(7).Value = null; + } + _saveItemCommand.Transaction = transaction; _saveItemCommand.ExecuteNonQuery(); @@ -254,7 +293,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _writeLock.Release(); } } - + /// /// Internal retrieve from items or users table /// @@ -270,7 +309,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } CheckDisposed(); - + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select type,data from TypedBaseItems where guid = @guid"; @@ -467,7 +506,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } CheckDisposed(); - + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select ItemId from ChildrenIds where ParentId = @ParentId"; @@ -492,7 +531,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } CheckDisposed(); - + using (var cmd = _connection.CreateCommand()) { cmd.CommandText = "select type,data from TypedBaseItems where guid in (select ItemId from ChildrenIds where ParentId = @ParentId)"; @@ -544,6 +583,279 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + public QueryResult GetItems(InternalItemsQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + CheckDisposed(); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = "select type,data from TypedBaseItems"; + + var whereClauses = GetWhereClauses(query, cmd, false); + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses = GetWhereClauses(query, cmd, true); + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + cmd.CommandText += whereText; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture); + } + + cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(GetItem(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + public List GetItemIdsList(InternalItemsQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + CheckDisposed(); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = "select guid from TypedBaseItems"; + + var whereClauses = GetWhereClauses(query, cmd, false); + + whereClauses = GetWhereClauses(query, cmd, true); + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + cmd.CommandText += whereText; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture); + } + + var list = new List(); + + _logger.Debug(cmd.CommandText); + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(reader.GetGuid(0)); + } + } + + return list; + } + } + + public QueryResult GetItemIds(InternalItemsQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + CheckDisposed(); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = "select guid from TypedBaseItems"; + + var whereClauses = GetWhereClauses(query, cmd, false); + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses = GetWhereClauses(query, cmd, true); + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + cmd.CommandText += whereText; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture); + } + + cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging; + + var list = new List(); + var count = 0; + + _logger.Debug(cmd.CommandText); + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(reader.GetGuid(0)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + private List GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging) + { + var whereClauses = new List(); + + if (query.IsMovie.HasValue) + { + whereClauses.Add("IsMovie=@IsMovie"); + cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie; + } + if (query.IsSports.HasValue) + { + whereClauses.Add("IsSports=@IsSports"); + cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports; + } + if (query.IncludeItemTypes.Length == 1) + { + whereClauses.Add("type=@type"); + cmd.Parameters.Add(cmd, "@type", DbType.String).Value = MapIncludeItemType(query.IncludeItemTypes[0]); + } + if (query.IncludeItemTypes.Length > 1) + { + var inClause = string.Join(",", query.IncludeItemTypes.Select(i => "'" + MapIncludeItemType(i) + "'").ToArray()); + whereClauses.Add(string.Format("type in ({0})", inClause)); + } + if (query.ChannelIds.Length == 1) + { + whereClauses.Add("ChannelId=@ChannelId"); + cmd.Parameters.Add(cmd, "@ChannelId", DbType.String).Value = query.ChannelIds[0]; + } + if (query.ChannelIds.Length > 1) + { + var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray()); + whereClauses.Add(string.Format("ChannelId in ({0})", inClause)); + } + + if (query.MinEndDate.HasValue) + { + whereClauses.Add("EndDate>=@MinEndDate"); + cmd.Parameters.Add(cmd, "@MinEndDate", DbType.Date).Value = query.MinEndDate.Value; + } + + if (query.MaxEndDate.HasValue) + { + whereClauses.Add("EndDate<=@MaxEndDate"); + cmd.Parameters.Add(cmd, "@MaxEndDate", DbType.Date).Value = query.MaxEndDate.Value; + } + + if (query.MinStartDate.HasValue) + { + whereClauses.Add("StartDate>=@MinStartDate"); + cmd.Parameters.Add(cmd, "@MinStartDate", DbType.Date).Value = query.MinStartDate.Value; + } + + if (query.MaxStartDate.HasValue) + { + whereClauses.Add("StartDate<=@MaxStartDate"); + cmd.Parameters.Add(cmd, "@MaxStartDate", DbType.Date).Value = query.MaxStartDate.Value; + } + + if (query.IsAiring.HasValue) + { + if (query.IsAiring.Value) + { + whereClauses.Add("StartDate<=@MaxStartDate"); + cmd.Parameters.Add(cmd, "@MaxStartDate", DbType.Date).Value = DateTime.UtcNow; + + whereClauses.Add("EndDate>=@MinEndDate"); + cmd.Parameters.Add(cmd, "@MinEndDate", DbType.Date).Value = DateTime.UtcNow; + } + else + { + whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)"); + cmd.Parameters.Add(cmd, "@IsAiringDate", DbType.Date).Value = DateTime.UtcNow; + } + } + + if (addPaging) + { + if (query.StartIndex.HasValue && query.StartIndex.Value > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM TypedBaseItems {0} ORDER BY DateCreated DESC LIMIT {1})", + pagingWhereText, + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture))); + } + } + + return whereClauses; + } + + // Not crazy about having this all the way down here, but at least it's in one place + readonly Dictionary _types = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {typeof(LiveTvProgram).Name, typeof(LiveTvProgram).FullName}, + {typeof(LiveTvChannel).Name, typeof(LiveTvChannel).FullName} + }; + + private string MapIncludeItemType(string value) + { + string result; + if (_types.TryGetValue(value, out result)) + { + return result; + } + + return value; + } + public IEnumerable GetItemIdsOfType(Type type) { if (type == null) @@ -577,7 +889,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } CheckDisposed(); - + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); IDbTransaction transaction = null; @@ -595,7 +907,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _deleteItemCommand.GetParameter(0).Value = id; _deleteItemCommand.Transaction = transaction; _deleteItemCommand.ExecuteNonQuery(); - + transaction.Commit(); } catch (OperationCanceledException) @@ -642,7 +954,7 @@ namespace MediaBrowser.Server.Implementations.Persistence } CheckDisposed(); - + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); IDbTransaction transaction = null;