Răsfoiți Sursa

update recording database

Luke Pulverenti 10 ani în urmă
părinte
comite
418bb87878

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

@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public interface IHasId
+    {
+        Guid Id { get; }
+    }
+}

+ 1 - 8
MediaBrowser.Controller/Entities/IHasImages.cs

@@ -1,13 +1,12 @@
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public interface IHasImages : IHasProviderIds
+    public interface IHasImages : IHasProviderIds, IHasId
     {
         /// <summary>
         /// Gets the name.
@@ -27,12 +26,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The file name without extension.</value>
         string FileNameWithoutExtension { get; }
 
-        /// <summary>
-        /// Gets the identifier.
-        /// </summary>
-        /// <value>The identifier.</value>
-        Guid Id { get; }
-
         /// <summary>
         /// Gets the type of the location.
         /// </summary>

+ 1 - 8
MediaBrowser.Controller/Entities/IHasMediaSources.cs

@@ -1,17 +1,10 @@
 using MediaBrowser.Model.Dto;
-using System;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public interface IHasMediaSources
+    public interface IHasMediaSources : IHasId
     {
-        /// <summary>
-        /// Gets the identifier.
-        /// </summary>
-        /// <value>The identifier.</value>
-        Guid Id { get; }
-
         /// <summary>
         /// Gets the media sources.
         /// </summary>

+ 11 - 1
MediaBrowser.Controller/Entities/IHasProgramAttributes.cs

@@ -1,9 +1,19 @@
-
+using MediaBrowser.Model.LiveTv;
+using System;
+
 namespace MediaBrowser.Controller.Entities
 {
     public interface IHasProgramAttributes
     {
         bool IsMovie { get; set; }
         bool IsSports { get; set; }
+        bool IsNews { get; set; }
+        bool IsKids { get; set; }
+        bool IsRepeat { get; set; }
+        bool? IsHD { get; set; }
+        bool IsLive { get; set; }
+        bool IsPremiere { get; set; }
+        ProgramAudio? Audio { get; set; }
+        DateTime? OriginalAirDate { get; set; }
     }
 }

+ 1 - 8
MediaBrowser.Controller/Entities/IHasUserData.cs

@@ -1,19 +1,12 @@
 using MediaBrowser.Model.Dto;
-using System;
 
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
     /// Interface IHasUserData
     /// </summary>
-    public interface IHasUserData
+    public interface IHasUserData : IHasId
     {
-        /// <summary>
-        /// Gets or sets the identifier.
-        /// </summary>
-        /// <value>The identifier.</value>
-        Guid Id { get; set; }
-
         /// <summary>
         /// Gets the user data key.
         /// </summary>

+ 2 - 3
MediaBrowser.Controller/LiveTv/ILiveTvItem.cs

@@ -1,10 +1,9 @@
-using System;
+using MediaBrowser.Controller.Entities;
 
 namespace MediaBrowser.Controller.LiveTv
 {
-    public interface ILiveTvItem
+    public interface ILiveTvItem : IHasId
     {
-        Guid Id { get; }
         string ServiceName { get; set; }
     }
 }

+ 17 - 3
MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs

@@ -2,19 +2,21 @@
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Library;
+using MediaBrowser.Model.LiveTv;
+using System;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.LiveTv
 {
-    public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData, ILiveTvItem
+    public interface ILiveTvRecording : IHasImages, IHasMediaSources, IHasUserData, ILiveTvItem, IHasStartDate, IHasProgramAttributes
     {
+        string ChannelId { get; }
+        string ProgramId { get; set; }
         string MediaType { get; }
 
         string Container { get; }
 
-        RecordingInfo RecordingInfo { get; set; }
-
         long? RunTimeTicks { get; set; }
 
         string GetClientTypeName();
@@ -28,5 +30,17 @@ namespace MediaBrowser.Controller.LiveTv
         bool CanDelete();
 
         bool CanDelete(User user);
+
+        string ProviderImagePath { get; set; }
+
+        string ProviderImageUrl { get; set; }
+
+        string ExternalId { get; set; }
+        string EpisodeTitle { get; set; }
+        bool IsSeries { get; set; }
+        string SeriesTimerId { get; set; }
+        RecordingStatus Status { get; set; }
+        DateTime? EndDate { get; set; }
+        ChannelType ChannelType { get; set; }
     }
 }

+ 23 - 0
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -3,7 +3,9 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Users;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
@@ -12,6 +14,27 @@ namespace MediaBrowser.Controller.LiveTv
 {
     public class LiveTvAudioRecording : Audio, ILiveTvRecording
     {
+        public string ExternalId { get; set; }
+        public string ProviderImagePath { get; set; }
+        public string ProviderImageUrl { get; set; }
+        public string EpisodeTitle { get; set; }
+        public bool IsSeries { get; set; }
+        public string SeriesTimerId { get; set; }
+        public DateTime StartDate { get; set; }
+        public RecordingStatus Status { get; set; }
+        public bool IsSports { get; set; }
+        public bool IsNews { get; set; }
+        public bool IsKids { get; set; }
+        public bool IsRepeat { get; set; }
+        public bool IsMovie { get; set; }
+        public bool? IsHD { get; set; }
+        public bool IsLive { get; set; }
+        public bool IsPremiere { get; set; }
+        public ChannelType ChannelType { get; set; }
+        public string ProgramId { get; set; }
+        public ProgramAudio? Audio { get; set; }
+        public DateTime? OriginalAirDate { get; set; }
+
         /// <summary>
         /// Gets the user data key.
         /// </summary>

+ 23 - 0
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -2,7 +2,9 @@
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Users;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
@@ -11,6 +13,27 @@ namespace MediaBrowser.Controller.LiveTv
 {
     public class LiveTvVideoRecording : Video, ILiveTvRecording
     {
+        public string ExternalId { get; set; }
+        public string ProviderImagePath { get; set; }
+        public string ProviderImageUrl { get; set; }
+        public string EpisodeTitle { get; set; }
+        public bool IsSeries { get; set; }
+        public string SeriesTimerId { get; set; }
+        public DateTime StartDate { get; set; }
+        public RecordingStatus Status { get; set; }
+        public bool IsSports { get; set; }
+        public bool IsNews { get; set; }
+        public bool IsKids { get; set; }
+        public bool IsRepeat { get; set; }
+        public bool IsMovie { get; set; }
+        public bool? IsHD { get; set; }
+        public bool IsLive { get; set; }
+        public bool IsPremiere { get; set; }
+        public ChannelType ChannelType { get; set; }
+        public string ProgramId { get; set; }
+        public ProgramAudio? Audio { get; set; }
+        public DateTime? OriginalAirDate { get; set; }
+
         /// <summary>
         /// Gets the user data key.
         /// </summary>

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

@@ -142,6 +142,7 @@
     <Compile Include="Entities\IHasBudget.cs" />
     <Compile Include="Entities\IHasCriticRating.cs" />
     <Compile Include="Entities\IHasDisplayOrder.cs" />
+    <Compile Include="Entities\IHasId.cs" />
     <Compile Include="Entities\IHasImages.cs" />
     <Compile Include="Entities\IHasKeywords.cs" />
     <Compile Include="Entities\IHasMediaSources.cs" />

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -290,7 +290,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public Guid GetInternalRecordingId(string serviceName, string externalId)
         {
-            var name = serviceName + externalId + InternalVersionNumber;
+            var name = serviceName + externalId + InternalVersionNumber + "0";
 
             return name.ToLower().GetMBId(typeof(ILiveTvRecording));
         }

+ 134 - 73
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -53,6 +53,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         private readonly ConcurrentDictionary<string, LiveStreamData> _openStreams =
             new ConcurrentDictionary<string, LiveStreamData>();
 
+        private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
+
         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;
@@ -359,8 +361,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
                     var service = GetService(recording);
 
-                    _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
-                    info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
+                    _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
+                    info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
                     info.RequiresClosing = true;
 
                     if (info.RequiresClosing)
@@ -618,7 +620,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return item;
         }
 
-        private async Task<ILiveTvRecording> GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
+        private async Task<Guid> CreateRecordingRecord(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
         {
             var isNew = false;
 
@@ -653,14 +655,36 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 isNew = true;
             }
 
+            item.ChannelId = _tvDtoService.GetInternalChannelId(serviceName, info.ChannelId).ToString("N");
             item.CommunityRating = info.CommunityRating;
             item.OfficialRating = info.OfficialRating;
             item.Overview = info.Overview;
             item.EndDate = info.EndDate;
+            item.Genres = info.Genres;
 
             var recording = (ILiveTvRecording)item;
 
-            recording.RecordingInfo = info;
+            recording.ProgramId = _tvDtoService.GetInternalProgramId(serviceName, info.ProgramId).ToString("N");
+            recording.Audio = info.Audio;
+            recording.ChannelType = info.ChannelType;
+            recording.EndDate = info.EndDate;
+            recording.EpisodeTitle = info.EpisodeTitle;
+            recording.ProviderImagePath = info.ImagePath;
+            recording.ProviderImageUrl = info.ImageUrl;
+            recording.IsHD = info.IsHD;
+            recording.IsKids = info.IsKids;
+            recording.IsLive = info.IsLive;
+            recording.IsMovie = info.IsMovie;
+            recording.IsNews = info.IsNews;
+            recording.IsPremiere = info.IsPremiere;
+            recording.IsRepeat = info.IsRepeat;
+            recording.IsSeries = info.IsSeries;
+            recording.IsSports = info.IsSports;
+            recording.OriginalAirDate = info.OriginalAirDate;
+            recording.SeriesTimerId = info.SeriesTimerId;
+            recording.StartDate = info.StartDate;
+            recording.Status = info.Status;
+
             recording.ServiceName = serviceName;
 
             var originalPath = item.Path;
@@ -676,15 +700,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             var pathChanged = !string.Equals(originalPath, item.Path);
 
-            await item.RefreshMetadata(new MetadataRefreshOptions
+            if (isNew)
             {
-                ForceSave = isNew || pathChanged
-
-            }, cancellationToken);
+                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+            }
+            else if (pathChanged)
+            {
+                await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+            }
 
-            _libraryManager.RegisterItem(item);
+            _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions());
 
-            return recording;
+            return item.Id;
         }
 
         public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
@@ -1006,8 +1033,8 @@ 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);
+            await CleanDatabaseInternal(newChannelIdList, new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
+            await CleanDatabaseInternal(newProgramIdList, new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
 
             // Load these now which will prefetch metadata
             var dtoOptions = new DtoOptions();
@@ -1017,7 +1044,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             progress.Report(100);
         }
 
-        private async Task<Tuple<List<Guid>,List<Guid>>> 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);
 
@@ -1103,14 +1130,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             }
             progress.Report(100);
 
-            return new Tuple<List<Guid>,List<Guid>>(channels, programs);
+            return new Tuple<List<Guid>, List<Guid>>(channels, programs);
         }
 
-        private async Task CleanDatabaseInternal(List<Guid> currentIdList, string typeName, IProgress<double> progress, CancellationToken cancellationToken)
+        private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
         {
             var list = _itemRepo.GetItemIds(new InternalItemsQuery
             {
-                IncludeItemTypes = new[] { typeName }
+                IncludeItemTypes = validTypes
 
             }).Items.ToList();
 
@@ -1163,64 +1190,103 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
         }
 
-        public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
+        private DateTime _lastRecordingRefreshTime;
+        private async Task RefreshRecordings(CancellationToken cancellationToken)
         {
-            var tasks = _services.Select(async i =>
+            const int cacheMinutes = 5;
+
+            if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
             {
-                try
+                return;
+            }
+
+            await _refreshRecordingsLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
                 {
-                    var recs = await i.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
-                    return recs.Select(r => new Tuple<RecordingInfo, ILiveTvService>(r, i));
+                    return;
                 }
-                catch (Exception ex)
+
+                var tasks = _services.Select(async i =>
                 {
-                    _logger.ErrorException("Error getting recordings", ex);
-                    return new List<Tuple<RecordingInfo, ILiveTvService>>();
-                }
-            });
-            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
-            var recordings = results.SelectMany(i => i.ToList());
+                    try
+                    {
+                        var recs = await i.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+                        return recs.Select(r => new Tuple<RecordingInfo, ILiveTvService>(r, i));
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error getting recordings", ex);
+                        return new List<Tuple<RecordingInfo, ILiveTvService>>();
+                    }
+                });
 
-            var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+                var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+
+                var recordingTasks = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, cancellationToken));
+
+                var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
+
+                CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new Progress<double>(), cancellationToken).ConfigureAwait(false);
 
+                _lastRecordingRefreshTime = DateTime.UtcNow;
+            }
+            finally
+            {
+                _refreshRecordingsLock.Release();
+            }
+        }
+
+        public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
+        {
+            var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
             if (user != null && !IsLiveTvEnabled(user))
             {
-                recordings = new List<Tuple<RecordingInfo, ILiveTvService>>();
+                return new QueryResult<BaseItem>();
             }
 
-            if (!string.IsNullOrEmpty(query.ChannelId))
+            await RefreshRecordings(cancellationToken).ConfigureAwait(false);
+
+            var internalQuery = new InternalItemsQuery
             {
-                var guid = new Guid(query.ChannelId);
+                IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
+            };
 
-                recordings = recordings
-                    .Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId) == guid);
+            if (!string.IsNullOrEmpty(query.ChannelId))
+            {
+                internalQuery.ChannelIds = new[] { query.ChannelId };
             }
 
+            var queryResult = _libraryManager.GetItems(internalQuery);
+            IEnumerable<ILiveTvRecording> recordings = queryResult.Items.Cast<ILiveTvRecording>();
+
             if (!string.IsNullOrEmpty(query.Id))
             {
                 var guid = new Guid(query.Id);
 
                 recordings = recordings
-                    .Where(i => _tvDtoService.GetInternalRecordingId(i.Item2.Name, i.Item1.Id) == guid);
+                    .Where(i => i.Id == guid);
             }
 
             if (!string.IsNullOrEmpty(query.GroupId))
             {
                 var guid = new Guid(query.GroupId);
 
-                recordings = recordings.Where(i => GetRecordingGroupIds(i.Item1).Contains(guid));
+                recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid));
             }
 
             if (query.IsInProgress.HasValue)
             {
                 var val = query.IsInProgress.Value;
-                recordings = recordings.Where(i => (i.Item1.Status == RecordingStatus.InProgress) == val);
+                recordings = recordings.Where(i => (i.Status == RecordingStatus.InProgress) == val);
             }
 
             if (query.Status.HasValue)
             {
                 var val = query.Status.Value;
-                recordings = recordings.Where(i => (i.Item1.Status == val));
+                recordings = recordings.Where(i => (i.Status == val));
             }
 
             if (!string.IsNullOrEmpty(query.SeriesTimerId))
@@ -1228,21 +1294,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 var guid = new Guid(query.SeriesTimerId);
 
                 recordings = recordings
-                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item2.Name, i.Item1.SeriesTimerId) == guid);
+                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.SeriesTimerId) == guid);
             }
 
-            recordings = recordings.OrderByDescending(i => i.Item1.StartDate);
-
-            IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, cancellationToken).ConfigureAwait(false);
-
             if (user != null)
             {
                 var currentUser = user;
-                entities = entities.Where(i => i.IsParentalAllowed(currentUser));
+                recordings = recordings.Where(i => i.IsParentalAllowed(currentUser));
             }
 
-            var entityList = entities.ToList();
-            entities = entityList;
+            recordings = recordings.OrderByDescending(i => i.StartDate);
+
+            var entityList = recordings.ToList();
+            IEnumerable<ILiveTvRecording> entities = entityList;
 
             if (query.StartIndex.HasValue)
             {
@@ -1270,7 +1334,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             dto.Id = _tvDtoService.GetInternalProgramId(service.Name, program.ExternalId).ToString("N");
 
-            dto.ChannelId = channel.Id.ToString("N");
+            dto.ChannelId = item.ChannelId;
 
             dto.StartDate = program.StartDate;
             dto.IsRepeat = program.IsRepeat;
@@ -1303,16 +1367,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var recording = (ILiveTvRecording)item;
             var service = GetService(recording);
 
-            var channel = string.IsNullOrEmpty(recording.RecordingInfo.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, recording.RecordingInfo.ChannelId));
+            var channel = string.IsNullOrWhiteSpace(recording.ChannelId) ? null : GetInternalChannel(recording.ChannelId);
 
-            var info = recording.RecordingInfo;
+            var info = recording;
 
-            dto.Id = _tvDtoService.GetInternalRecordingId(service.Name, info.Id).ToString("N");
+            dto.Id = item.Id.ToString("N");
             dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
                 ? null
                 : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
 
-            dto.ChannelId = _tvDtoService.GetInternalChannelId(service.Name, info.ChannelId).ToString("N");
+            dto.ChannelId = item.ChannelId;
 
             dto.StartDate = info.StartDate;
             dto.RecordingStatus = info.Status;
@@ -1344,11 +1408,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
             }
 
-            if (info.Status == RecordingStatus.InProgress)
+            if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
             {
                 var now = DateTime.UtcNow.Ticks;
                 var start = info.StartDate.Ticks;
-                var end = info.EndDate.Ticks;
+                var end = info.EndDate.Value.Ticks;
 
                 var pct = now - start;
                 pct /= end;
@@ -1356,10 +1420,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 dto.CompletionPercentage = pct;
             }
 
-            if (!string.IsNullOrEmpty(info.ProgramId))
-            {
-                dto.ProgramId = _tvDtoService.GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
-            }
+            dto.ProgramId = info.ProgramId;
 
             if (channel != null)
             {
@@ -1394,13 +1455,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             };
         }
 
-        private Task<ILiveTvRecording[]> GetEntities(IEnumerable<Tuple<RecordingInfo, ILiveTvService>> recordings, CancellationToken cancellationToken)
-        {
-            var tasks = recordings.Select(i => GetRecording(i.Item1, i.Item2.Name, cancellationToken));
-
-            return Task.WhenAll(tasks);
-        }
-
         public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
         {
             var tasks = _services.Select(async i =>
@@ -1468,7 +1522,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             var service = GetService(recording.ServiceName);
 
-            await service.DeleteRecordingAsync(recording.RecordingInfo.Id, CancellationToken.None).ConfigureAwait(false);
+            await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
         public async Task CancelTimer(string id)
@@ -1483,6 +1538,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var service = GetService(timer.ServiceName);
 
             await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
         public async Task CancelSeriesTimer(string id)
@@ -1497,6 +1553,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var service = GetService(timer.ServiceName);
 
             await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
         public async Task<BaseItemDto> GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null)
@@ -1705,6 +1762,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             info.Priority = defaultValues.Priority;
 
             await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
         public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
@@ -1718,6 +1776,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             info.Priority = defaultValues.Priority;
 
             await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
         public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
@@ -1727,6 +1786,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var service = GetService(timer.ServiceName);
 
             await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
         public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
@@ -1736,9 +1796,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var service = GetService(timer.ServiceName);
 
             await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
+            _lastRecordingRefreshTime = DateTime.MinValue;
         }
 
-        private IEnumerable<string> GetRecordingGroupNames(RecordingInfo recording)
+        private IEnumerable<string> GetRecordingGroupNames(ILiveTvRecording recording)
         {
             var list = new List<string>();
 
@@ -1775,7 +1836,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return list;
         }
 
-        private List<Guid> GetRecordingGroupIds(RecordingInfo recording)
+        private List<Guid> GetRecordingGroupIds(ILiveTvRecording recording)
         {
             return GetRecordingGroupNames(recording).Select(i => i.ToLower()
                 .GetMD5())
@@ -1795,7 +1856,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var groups = new List<BaseItemDto>();
 
             var series = recordings
-                .Where(i => i.RecordingInfo.IsSeries)
+                .Where(i => i.IsSeries)
                 .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
                 .ToList();
 
@@ -1808,31 +1869,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             groups.Add(new BaseItemDto
             {
                 Name = "Kids",
-                RecordingCount = recordings.Count(i => i.RecordingInfo.IsKids)
+                RecordingCount = recordings.Count(i => i.IsKids)
             });
 
             groups.Add(new BaseItemDto
             {
                 Name = "Movies",
-                RecordingCount = recordings.Count(i => i.RecordingInfo.IsMovie)
+                RecordingCount = recordings.Count(i => i.IsMovie)
             });
 
             groups.Add(new BaseItemDto
             {
                 Name = "News",
-                RecordingCount = recordings.Count(i => i.RecordingInfo.IsNews)
+                RecordingCount = recordings.Count(i => i.IsNews)
             });
 
             groups.Add(new BaseItemDto
             {
                 Name = "Sports",
-                RecordingCount = recordings.Count(i => i.RecordingInfo.IsSports)
+                RecordingCount = recordings.Count(i => i.IsSports)
             });
 
             groups.Add(new BaseItemDto
             {
                 Name = "Others",
-                RecordingCount = recordings.Count(i => !i.RecordingInfo.IsSports && !i.RecordingInfo.IsNews && !i.RecordingInfo.IsMovie && !i.RecordingInfo.IsKids && !i.RecordingInfo.IsSeries)
+                RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
             });
 
             groups = groups

+ 7 - 7
MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs

@@ -36,17 +36,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             var imageResponse = new DynamicImageResponse();
 
-            if (!string.IsNullOrEmpty(liveTvItem.RecordingInfo.ImagePath))
+            if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath))
             {
-                imageResponse.Path = liveTvItem.RecordingInfo.ImagePath;
+                imageResponse.Path = liveTvItem.ProviderImagePath;
                 imageResponse.HasImage = true;
             }
-            else if (!string.IsNullOrEmpty(liveTvItem.RecordingInfo.ImageUrl))
+            else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl))
             {
                 var options = new HttpRequestOptions
                 {
                     CancellationToken = cancellationToken,
-                    Url = liveTvItem.RecordingInfo.ImageUrl
+                    Url = liveTvItem.ProviderImageUrl
                 };
 
                 var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
@@ -62,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     _logger.Error("Provider did not return an image content type.");
                 }
             }
-            else if (liveTvItem.RecordingInfo.HasImage ?? true)
+            else
             {
                 var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
 
@@ -70,7 +70,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 {
                     try
                     {
-                        var response = await service.GetRecordingImageAsync(liveTvItem.RecordingInfo.Id, cancellationToken).ConfigureAwait(false);
+                        var response = await service.GetRecordingImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false);
 
                         if (response != null)
                         {
@@ -109,7 +109,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (liveTvItem != null)
             {
-                return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.RecordingInfo.HasImage ?? true);
+                return !liveTvItem.HasImage(ImageType.Primary) && (!string.IsNullOrWhiteSpace(liveTvItem.ProviderImagePath) || !string.IsNullOrWhiteSpace(liveTvItem.ProviderImageUrl));
             }
             return false;
         }

+ 17 - 11
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -759,14 +759,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 whereClauses.Add("IsSports=@IsSports");
                 cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;
             }
-            if (query.IncludeItemTypes.Length == 1)
+
+            var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
+
+            if (includeTypes.Length == 1)
             {
                 whereClauses.Add("type=@type");
-                cmd.Parameters.Add(cmd, "@type", DbType.String).Value = MapIncludeItemType(query.IncludeItemTypes[0]);
+                cmd.Parameters.Add(cmd, "@type", DbType.String).Value = includeTypes[0];
             }
-            if (query.IncludeItemTypes.Length > 1)
+            if (includeTypes.Length > 1)
             {
-                var inClause = string.Join(",", query.IncludeItemTypes.Select(i => "'" + MapIncludeItemType(i) + "'").ToArray());
+                var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'").ToArray());
                 whereClauses.Add(string.Format("type in ({0})", inClause));
             }
             if (query.ChannelIds.Length == 1)
@@ -818,7 +821,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 {
                     whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
                     cmd.Parameters.Add(cmd, "@IsAiringDate", DbType.Date).Value = DateTime.UtcNow;
-                } 
+                }
             }
 
             if (addPaging)
@@ -839,21 +842,24 @@ namespace MediaBrowser.Server.Implementations.Persistence
         }
 
         // 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)
+        readonly Dictionary<string, string[]> _types = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
             {
-                {typeof(LiveTvProgram).Name, typeof(LiveTvProgram).FullName},
-                {typeof(LiveTvChannel).Name, typeof(LiveTvChannel).FullName}
+                {typeof(LiveTvProgram).Name, new []{typeof(LiveTvProgram).FullName}},
+                {typeof(LiveTvChannel).Name, new []{typeof(LiveTvChannel).FullName}},
+                {typeof(LiveTvVideoRecording).Name, new []{typeof(LiveTvVideoRecording).FullName}},
+                {typeof(LiveTvAudioRecording).Name, new []{typeof(LiveTvAudioRecording).FullName}},
+                {"Recording", new []{typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName}}
             };
 
-        private string MapIncludeItemType(string value)
+        private IEnumerable<string> MapIncludeItemTypes(string value)
         {
-            string result;
+            string[] result;
             if (_types.TryGetValue(value, out result))
             {
                 return result;
             }
 
-            return value;
+            return new[] { value };
         }
 
         public IEnumerable<Guid> GetItemIdsOfType(Type type)

+ 5 - 5
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1528,16 +1528,16 @@ namespace MediaBrowser.Server.Implementations.Session
             }
 
             var recording = item as ILiveTvRecording;
-            if (recording != null && recording.RecordingInfo != null)
+            if (recording != null)
             {
-                if (recording.RecordingInfo.IsSeries)
+                if (recording.IsSeries)
                 {
-                    info.Name = recording.RecordingInfo.EpisodeTitle;
-                    info.SeriesName = recording.RecordingInfo.Name;
+                    info.Name = recording.EpisodeTitle;
+                    info.SeriesName = recording.Name;
 
                     if (string.IsNullOrWhiteSpace(info.Name))
                     {
-                        info.Name = recording.RecordingInfo.Name;
+                        info.Name = recording.Name;
                     }
                 }
             }

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.622</version>
+        <version>3.0.624</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.622" />
+            <dependency id="MediaBrowser.Common" version="3.0.624" />
             <dependency id="NLog" version="3.2.1" />
             <dependency id="SimpleInjector" version="2.8.0" />
         </dependencies>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.622</version>
+        <version>3.0.624</version>
         <title>MediaBrowser.Common</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.622</version>
+        <version>3.0.624</version>
         <title>MediaBrowser.Model - Signed Edition</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.622</version>
+        <version>3.0.624</version>
         <title>Media Browser.Server.Core</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Emby Server.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.622" />
+            <dependency id="MediaBrowser.Common" version="3.0.624" />
 			<dependency id="Interfaces.IO" version="1.0.0.5" />
         </dependencies>
     </metadata>