Prechádzať zdrojové kódy

update live tv database

Luke Pulverenti 10 rokov pred
rodič
commit
f2abd8ba39

+ 1 - 1
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;
                 }
             }
 

+ 6 - 5
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;

+ 10 - 3
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
                 {

+ 0 - 1
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; }

+ 0 - 1
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; }

+ 0 - 1
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; }

+ 6 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -59,6 +59,12 @@ namespace MediaBrowser.Controller.Entities
 
         public List<ItemImageInfo> ImageInfos { get; set; }
 
+        /// <summary>
+        /// Gets or sets the channel identifier.
+        /// </summary>
+        /// <value>The channel identifier.</value>
+        public string ChannelId { get; set; }
+
         [IgnoreDataMember]
         public virtual bool SupportsAddingToPlaylist
         {

+ 9 - 0
MediaBrowser.Controller/Entities/IHasProgramAttributes.cs

@@ -0,0 +1,9 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+    public interface IHasProgramAttributes
+    {
+        bool IsMovie { get; set; }
+        bool IsSports { get; set; }
+    }
+}

+ 9 - 0
MediaBrowser.Controller/Entities/IHasStartDate.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public interface IHasStartDate
+    {
+        DateTime StartDate { get; set; }
+    }
+}

+ 12 - 0
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[] { };
         }
     }
 }

+ 8 - 0
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
         /// <returns>BaseItem.</returns>
         BaseItem GetItemById(Guid id);
 
+        /// <summary>
+        /// Gets the items.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> GetItems(InternalItemsQuery query);
+
         /// <summary>
         /// Gets the memory item by identifier.
         /// </summary>

+ 1 - 19
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<LiveTvProgramLookupInfo>
+    public class LiveTvProgram : BaseItem, ILiveTvItem, IHasLookupInfo<LiveTvProgramLookupInfo>, IHasStartDate, IHasProgramAttributes
     {
         /// <summary>
         /// Gets the user data key.
@@ -28,12 +25,6 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         public string ExternalId { get; set; }
 
-        /// <summary>
-        /// Gets or sets the channel identifier.
-        /// </summary>
-        /// <value>The channel identifier.</value>
-        public string ExternalChannelId { get; set; }
-
         /// <summary>
         /// Gets or sets the original air date.
         /// </summary>
@@ -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);

+ 2 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -149,10 +149,12 @@
     <Compile Include="Entities\IHasOriginalTitle.cs" />
     <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" />
     <Compile Include="Entities\IHasProductionLocations.cs" />
+    <Compile Include="Entities\IHasProgramAttributes.cs" />
     <Compile Include="Entities\IHasScreenshots.cs" />
     <Compile Include="Entities\IHasSeries.cs" />
     <Compile Include="Entities\IHasShortOverview.cs" />
     <Compile Include="Entities\IHasSpecialFeatures.cs" />
+    <Compile Include="Entities\IHasStartDate.cs" />
     <Compile Include="Entities\IHasTaglines.cs" />
     <Compile Include="Entities\IHasTags.cs" />
     <Compile Include="Entities\IHasThemeMedia.cs" />

+ 18 - 11
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
         /// <returns>IEnumerable{ChildDefinition}.</returns>
         IEnumerable<Guid> GetChildren(Guid parentId);
 
-        /// <summary>
-        /// Gets the type of the items of.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <returns>IEnumerable{Guid}.</returns>
-        IEnumerable<Guid> GetItemIdsOfType(Type type);
-
         /// <summary>
         /// Saves the children.
         /// </summary>
@@ -135,11 +129,24 @@ namespace MediaBrowser.Controller.Persistence
         Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
 
         /// <summary>
-        /// Gets the type of the items of.
+        /// Gets the item ids.
         /// </summary>
-        /// <param name="type">The type.</param>
-        /// <returns>IEnumerable&lt;BaseItem&gt;.</returns>
-        IEnumerable<BaseItem> GetItemsOfType(Type type);
+        /// <param name="query">The query.</param>
+        /// <returns>IEnumerable&lt;Guid&gt;.</returns>
+        QueryResult<Guid> GetItemIds(InternalItemsQuery query);
+        /// <summary>
+        /// Gets the items.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> GetItems(InternalItemsQuery query);
+
+        /// <summary>
+        /// Gets the item ids list.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>List&lt;Guid&gt;.</returns>
+        List<Guid> GetItemIdsList(InternalItemsQuery query);
     }
 }
 

+ 24 - 6
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<Series, SeriesInfo>,
-        IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<ChannelVideoItem, ChannelItemLookupInfo>
+        IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<ChannelVideoItem, ChannelItemLookupInfo>, IRemoteMetadataProvider<LiveTvProgram, LiveTvProgramLookupInfo>
     {
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClient _httpClient;
@@ -48,6 +47,16 @@ namespace MediaBrowser.Providers.Omdb
             return GetSearchResults(searchInfo, "movie", cancellationToken);
         }
 
+        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(LiveTvProgramLookupInfo searchInfo, CancellationToken cancellationToken)
+        {
+            if (!searchInfo.IsMovie)
+            {
+                return Task.FromResult<IEnumerable<RemoteSearchResult>>(new List<RemoteSearchResult>());
+            }
+
+            return GetSearchResults(searchInfo, "movie", cancellationToken);
+        }
+
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ItemLookupInfo searchInfo, string type, CancellationToken cancellationToken)
         {
             var list = new List<RemoteSearchResult>();
@@ -169,13 +178,22 @@ namespace MediaBrowser.Providers.Omdb
             return result;
         }
 
+        public Task<MetadataResult<LiveTvProgram>> GetMetadata(LiveTvProgramLookupInfo info, CancellationToken cancellationToken)
+        {
+            if (!info.IsMovie)
+            {
+                return Task.FromResult(new MetadataResult<LiveTvProgram>());
+            }
+            return GetMovieResult<LiveTvProgram>(info, cancellationToken);
+        }
+
         public Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
         {
             return GetMovieResult<Movie>(info, cancellationToken);
         }
 
         private async Task<MetadataResult<T>> GetMovieResult<T>(ItemLookupInfo info, CancellationToken cancellationToken)
-            where T : Video, new()
+            where T : BaseItem, new()
         {
             var result = new MetadataResult<T>
             {

+ 1 - 1
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);

+ 13 - 0
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<BaseItem> GetItems(InternalItemsQuery query)
+        {
+            var result = ItemRepository.GetItemIdsList(query);
+
+            var items = result.Select(GetItemById).ToArray();
+
+            return new QueryResult<BaseItem>
+            {
+                Items = items
+            };
+        }
+
         /// <summary>
         /// Gets the intros.
         /// </summary>

+ 138 - 239
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<string, LiveStreamData> _openStreams =
             new ConcurrentDictionary<string, LiveStreamData>();
 
-        private List<Guid> _channelIdList = new List<Guid>();
-        private Dictionary<Guid, LiveTvProgram> _programs;
-        private readonly ConcurrentDictionary<Guid, bool> _refreshedPrograms = new ConcurrentDictionary<Guid, bool>();
-
         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<RefreshChannelsScheduledTask>();
         }
 
-        private readonly object _programsDataLock = new object();
-        private Dictionary<Guid, LiveTvProgram> GetProgramsDictionary()
-        {
-            if (_programs == null)
-            {
-                lock (_programsDataLock)
-                {
-                    if (_programs == null)
-                    {
-                        var dict = new Dictionary<Guid, LiveTvProgram>();
-
-                        foreach (var item in _itemRepo.GetItemsOfType(typeof(LiveTvProgram))
-                            .Cast<LiveTvProgram>()
-                            .ToList())
-                        {
-                            dict[item.Id] = item;
-                        }
-
-                        _programs = dict;
-                    }
-                }
-            }
-
-            return _programs;
-        }
-
-        private IEnumerable<LiveTvProgram> GetPrograms()
-        {
-            return GetProgramsDictionary().Values;
-        }
-
         public async Task<QueryResult<LiveTvChannel>> 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<LiveTvChannel>();
+            var channels = _libraryManager.GetItems(new InternalItemsQuery
+            {
+                IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }
+
+            }).Items.Cast<LiveTvChannel>();
 
             if (user != null)
             {
@@ -258,9 +224,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             var returnList = new List<ChannelInfoDto>();
 
+            var now = DateTime.UtcNow;
+
+            var programs = _libraryManager.GetItems(new InternalItemsQuery
+            {
+                IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
+                MaxStartDate = now,
+                MinEndDate = now
+
+            }).Items.Cast<LiveTvProgram>().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<LiveTvProgram> programs)
+        internal LiveTvProgram GetInternalProgram(Guid id)
         {
-            foreach (var program in programs)
-            {
-                RefreshIfNeeded(program);
-            }
+            return _libraryManager.GetItemById(id) as LiveTvProgram;
         }
 
         public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
@@ -598,14 +553,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return item;
         }
 
-        private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
+        private async Task<LiveTvProgram> 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<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
         {
             var program = GetInternalProgram(id);
@@ -745,64 +700,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public async Task<QueryResult<BaseItemDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
         {
-            IEnumerable<LiveTvProgram> 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<LiveTvProgram> programs = _libraryManager.GetItems(internalQuery).Items.Cast<LiveTvProgram>();
 
             // 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<BaseItemDto>
@@ -855,35 +773,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
         {
-            IEnumerable<LiveTvProgram> 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<LiveTvProgram> programs = _libraryManager.GetItems(internalQuery).Items.Cast<LiveTvProgram>();
 
-            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<LiveTvProgram>
@@ -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<double> progress, CancellationToken cancellationToken)
+        internal Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            var innerProgress = new ActionableProgress<double>();
-            innerProgress.RegisterAction(p => progress.Report(p * .9));
-            await RefreshChannelsInternal(innerProgress, cancellationToken).ConfigureAwait(false);
-
-            innerProgress = new ActionableProgress<double>();
-            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<double> progress, CancellationToken cancellationToken)
@@ -1068,6 +973,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 ? 0
                 : 1 / _services.Count;
 
+            var newChannelIdList = new List<Guid>();
+            var newProgramIdList = new List<Guid>();
+
             foreach (var service in _services)
             {
                 cancellationToken.ThrowIfCancellationRequested();
@@ -1077,7 +985,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     var innerProgress = new ActionableProgress<double>();
                     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<double> progress, CancellationToken cancellationToken)
+        private async Task<Tuple<List<Guid>,List<Guid>>> RefreshChannelsInternal(ILiveTvService service, IProgress<double> 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<LiveTvProgram>();
+            var programs = new List<Guid>();
+            var channels = new List<Guid>();
 
             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<double> progress, CancellationToken cancellationToken)
-        {
-            return DeleteOldPrograms(GetProgramsDictionary().Keys.ToList(), progress, cancellationToken);
+            return new Tuple<List<Guid>,List<Guid>>(channels, programs);
         }
 
-        private async Task DeleteOldPrograms(List<Guid> currentIdList, IProgress<double> progress, CancellationToken cancellationToken)
+        private async Task CleanDatabaseInternal(List<Guid> currentIdList, string typeName, IProgress<double> 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<LiveTvProgram>();
 
-            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<Tuple<SeriesTimerInfo, ILiveTvService>> 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
             {

+ 3 - 1
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)
                         {

+ 322 - 10
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();
             }
         }
-        
+
         /// <summary>
         /// Internal retrieve from items or users table
         /// </summary>
@@ -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<BaseItem> 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<BaseItem>();
+                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<BaseItem>()
+                {
+                    Items = list.ToArray(),
+                    TotalRecordCount = count
+                };
+            }
+        }
+
+        public List<Guid> 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<Guid>();
+
+                _logger.Debug(cmd.CommandText);
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
+                {
+                    while (reader.Read())
+                    {
+                        list.Add(reader.GetGuid(0));
+                    }
+                }
+
+                return list;
+            }
+        }
+
+        public QueryResult<Guid> 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<Guid>();
+                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<Guid>()
+                {
+                    Items = list.ToArray(),
+                    TotalRecordCount = count
+                };
+            }
+        }
+
+        private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging)
+        {
+            var whereClauses = new List<string>();
+
+            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<string, string> _types = new Dictionary<string, string>(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<Guid> 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;