diff options
| author | Eric Reed <ebr@mediabrowser3.com> | 2013-12-04 15:07:56 -0500 |
|---|---|---|
| committer | Eric Reed <ebr@mediabrowser3.com> | 2013-12-04 15:07:56 -0500 |
| commit | 6819be81601f6a95a60ce2735474ae0015d19bff (patch) | |
| tree | 7e2743455e53d4a028fae789f2fc74a7c5ae87b9 /MediaBrowser.Server.Implementations/LiveTv | |
| parent | 190be6311fbdf3a73f9c8e330f44edafe7764284 (diff) | |
| parent | cb882a4b48e9cf03cd363c54d93338ad62153e7e (diff) | |
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv')
3 files changed, 702 insertions, 5 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs new file mode 100644 index 0000000000..322948bade --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -0,0 +1,95 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + public class ChannelImageProvider : BaseMetadataProvider + { + private readonly ILiveTvManager _liveTvManager; + private readonly IProviderManager _providerManager; + + public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager) + : base(logManager, configurationManager) + { + _liveTvManager = liveTvManager; + _providerManager = providerManager; + } + + public override bool Supports(BaseItem item) + { + return item is Channel; + } + + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + return !item.HasImage(ImageType.Primary); + } + + public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + if (item.HasImage(ImageType.Primary)) + { + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + + try + { + await DownloadImage(item, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + // Don't fail the provider on a 404 + if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) + { + throw; + } + } + + + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + + private async Task DownloadImage(BaseItem item, CancellationToken cancellationToken) + { + var channel = (Channel)item; + + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, channel.ServiceName, StringComparison.OrdinalIgnoreCase)); + + if (service != null) + { + var response = await service.GetChannelImageAsync(channel.ChannelId, cancellationToken).ConfigureAwait(false); + + // Dummy up the original url + var url = channel.ServiceName + channel.ChannelId; + + await _providerManager.SaveImage(channel, response.Stream, response.MimeType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); + } + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 05bac17c3b..4fd1f0e43b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,6 +1,23 @@ -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -9,7 +26,35 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// </summary> public class LiveTvManager : ILiveTvManager { + private readonly IServerApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; + private readonly IImageProcessor _imageProcessor; + + private readonly IUserManager _userManager; + private readonly ILocalizationManager _localization; + private readonly IUserDataManager _userDataManager; + private readonly IDtoService _dtoService; + private readonly List<ILiveTvService> _services = new List<ILiveTvService>(); + + private List<Channel> _channels = new List<Channel>(); + private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>(); + + public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserManager userManager, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService) + { + _appPaths = appPaths; + _fileSystem = fileSystem; + _logger = logger; + _itemRepo = itemRepo; + _imageProcessor = imageProcessor; + _userManager = userManager; + _localization = localization; + _userDataManager = userDataManager; + _dtoService = dtoService; + } + /// <summary> /// Gets the services. /// </summary> @@ -32,17 +77,515 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// Gets the channel info dto. /// </summary> /// <param name="info">The info.</param> + /// <param name="user">The user.</param> /// <returns>ChannelInfoDto.</returns> - public ChannelInfoDto GetChannelInfoDto(ChannelInfo info) + public ChannelInfoDto GetChannelInfoDto(Channel info, User user) { - return new ChannelInfoDto + var dto = new ChannelInfoDto { Name = info.Name, ServiceName = info.ServiceName, ChannelType = info.ChannelType, - Id = info.Id, - Number = info.Number + Number = info.ChannelNumber, + Type = info.GetType().Name, + Id = info.Id.ToString("N"), + MediaType = info.MediaType + }; + + if (user != null) + { + dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey())); + } + + var imageTag = GetLogoImageTag(info); + + if (imageTag.HasValue) + { + dto.ImageTags[ImageType.Primary] = imageTag.Value; + } + + return dto; + } + + private Guid? GetLogoImageTag(Channel info) + { + var path = info.PrimaryImagePath; + + if (string.IsNullOrEmpty(path)) + { + return null; + } + + try + { + return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name); + } + + return null; + } + + public QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query) + { + var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); + + IEnumerable<Channel> channels = _channels; + + if (user != null) + { + channels = channels.Where(i => i.IsParentalAllowed(user, _localization)) + .OrderBy(i => + { + double number = 0; + + if (!string.IsNullOrEmpty(i.ChannelNumber)) + { + double.TryParse(i.ChannelNumber, out number); + } + + return number; + + }); + } + + var returnChannels = channels.OrderBy(i => + { + double number = 0; + + if (!string.IsNullOrEmpty(i.ChannelNumber)) + { + double.TryParse(i.ChannelNumber, out number); + } + + return number; + + }).ThenBy(i => i.Name) + .Select(i => GetChannelInfoDto(i, user)) + .ToArray(); + + return new QueryResult<ChannelInfoDto> + { + Items = returnChannels, + TotalRecordCount = returnChannels.Length + }; + } + + public Channel GetChannel(string id) + { + var guid = new Guid(id); + + return _channels.FirstOrDefault(i => i.Id == guid); + } + + public ChannelInfoDto GetChannelInfoDto(string id, string userId) + { + var channel = GetChannel(id); + + var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(new Guid(userId)); + + return channel == null ? null : GetChannelInfoDto(channel, user); + } + + private ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel) + { + var id = GetInternalProgramIdId(channel.ServiceName, program.Id).ToString("N"); + + return new ProgramInfoDto + { + ChannelId = channel.Id.ToString("N"), + Description = program.Description, + EndDate = program.EndDate, + Genres = program.Genres, + ExternalId = program.Id, + Id = id, + Name = program.Name, + ServiceName = channel.ServiceName, + StartDate = program.StartDate, + OfficialRating = program.OfficialRating, + Quality = program.Quality, + OriginalAirDate = program.OriginalAirDate, + Audio = program.Audio, + CommunityRating = program.CommunityRating, + AspectRatio = program.AspectRatio, + IsRepeat = program.IsRepeat, + EpisodeTitle = program.EpisodeTitle + }; + } + + private Guid GetInternalChannelId(string serviceName, string externalChannelId, string channelName) + { + var name = serviceName + externalChannelId + channelName; + + return name.ToLower().GetMBId(typeof(Channel)); + } + + private Guid GetInternalProgramIdId(string serviceName, string externalProgramId) + { + var name = serviceName + externalProgramId; + + return name.ToLower().GetMD5(); + } + + private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken) + { + var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name)); + + var fileInfo = new DirectoryInfo(path); + + var isNew = false; + + if (!fileInfo.Exists) + { + Directory.CreateDirectory(path); + fileInfo = new DirectoryInfo(path); + + if (!fileInfo.Exists) + { + throw new IOException("Path not created: " + path); + } + + isNew = true; + } + + var id = GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name); + + var item = _itemRepo.RetrieveItem(id) as Channel; + + if (item == null) + { + item = new Channel + { + Name = channelInfo.Name, + Id = id, + DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo), + DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo), + Path = path, + ChannelId = channelInfo.Id, + ChannelNumber = channelInfo.Number, + ServiceName = serviceName + }; + + isNew = true; + } + + // Set this now so we don't cause additional file system access during provider executions + item.ResetResolveArgs(fileInfo); + + await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); + + return item; + } + + public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken) + { + IEnumerable<ProgramInfoDto> programs = _programs + .OrderBy(i => i.StartDate) + .ThenBy(i => i.EndDate); + + if (!string.IsNullOrEmpty(query.ServiceName)) + { + programs = programs.Where(i => string.Equals(i.ServiceName, query.ServiceName, StringComparison.OrdinalIgnoreCase)); + } + + if (query.ChannelIdList.Length > 0) + { + var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList(); + + programs = programs.Where(i => guids.Contains(new Guid(i.ChannelId))); + } + + var returnArray = programs.ToArray(); + + return new QueryResult<ProgramInfoDto> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + + internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken) + { + // Avoid implicitly captured closure + var currentCancellationToken = cancellationToken; + + var channelTasks = _services.Select(i => GetChannels(i, currentCancellationToken)); + + progress.Report(10); + + var results = await Task.WhenAll(channelTasks).ConfigureAwait(false); + + var allChannels = results.SelectMany(i => i).ToList(); + + var list = new List<Channel>(); + var programs = new List<ProgramInfoDto>(); + + var numComplete = 0; + + foreach (var channelInfo in allChannels) + { + try + { + var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false); + + var service = _services.First(i => string.Equals(channelInfo.Item1, i.Name, StringComparison.OrdinalIgnoreCase)); + + var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false); + + programs.AddRange(channelPrograms.Select(program => GetProgramInfoDto(program, item))); + + list.Add(item); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name); + } + + numComplete++; + double percent = numComplete; + percent /= allChannels.Count; + + progress.Report(90 * percent + 10); + } + + _programs = programs; + _channels = list; + } + + private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken) + { + var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false); + + return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i)); + } + + private async Task<IEnumerable<RecordingInfoDto>> GetRecordings(ILiveTvService service, CancellationToken cancellationToken) + { + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + + return recordings.Select(i => GetRecordingInfoDto(i, service)); + } + + private RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service) + { + var id = service.Name + info.ChannelId + info.Id; + id = id.GetMD5().ToString("N"); + + var dto = new RecordingInfoDto + { + ChannelName = info.ChannelName, + Description = info.Description, + EndDate = info.EndDate, + Name = info.Name, + StartDate = info.StartDate, + Id = id, + ExternalId = info.Id, + ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"), + Status = info.Status, + Path = info.Path, + Genres = info.Genres, + IsRepeat = info.IsRepeat, + EpisodeTitle = info.EpisodeTitle, + ChannelType = info.ChannelType, + MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video, + CommunityRating = info.CommunityRating, + OfficialRating = info.OfficialRating }; + + var duration = info.EndDate - info.StartDate; + dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds); + + if (!string.IsNullOrEmpty(info.ProgramId)) + { + dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N"); + } + + return dto; + } + + public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken) + { + var list = new List<RecordingInfoDto>(); + + foreach (var service in GetServices(query.ServiceName, query.ChannelId)) + { + var recordings = await GetRecordings(service, cancellationToken).ConfigureAwait(false); + + list.AddRange(recordings); + } + + if (!string.IsNullOrEmpty(query.ChannelId)) + { + list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId)) + .ToList(); + } + + var returnArray = list.OrderByDescending(i => i.StartDate) + .ToArray(); + + return new QueryResult<RecordingInfoDto> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + + private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId) + { + IEnumerable<ILiveTvService> services = _services; + + if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId)) + { + var channelIdGuid = new Guid(channelId); + + serviceName = _channels.Where(i => i.Id == channelIdGuid) + .Select(i => i.ServiceName) + .FirstOrDefault(); + } + + if (!string.IsNullOrEmpty(serviceName)) + { + services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase)); + } + + return services; + } + + public Task ScheduleRecording(string programId) + { + throw new NotImplementedException(); + } + + public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken) + { + var list = new List<TimerInfoDto>(); + + foreach (var service in GetServices(query.ServiceName, query.ChannelId)) + { + var timers = await GetTimers(service, cancellationToken).ConfigureAwait(false); + + list.AddRange(timers); + } + + if (!string.IsNullOrEmpty(query.ChannelId)) + { + list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId)) + .ToList(); + } + + var returnArray = list.OrderByDescending(i => i.StartDate) + .ToArray(); + + return new QueryResult<TimerInfoDto> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + + private async Task<IEnumerable<TimerInfoDto>> GetTimers(ILiveTvService service, CancellationToken cancellationToken) + { + var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false); + + return timers.Select(i => GetTimerInfoDto(i, service)); + } + + private TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service) + { + var id = service.Name + info.ChannelId + info.Id; + id = id.GetMD5().ToString("N"); + + var dto = new TimerInfoDto + { + ChannelName = info.ChannelName, + Description = info.Description, + EndDate = info.EndDate, + Name = info.Name, + StartDate = info.StartDate, + Id = id, + ExternalId = info.Id, + ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"), + Status = info.Status, + SeriesTimerId = info.SeriesTimerId, + PrePaddingSeconds = info.PrePaddingSeconds, + PostPaddingSeconds = info.PostPaddingSeconds + }; + + var duration = info.EndDate - info.StartDate; + dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds); + + if (!string.IsNullOrEmpty(info.ProgramId)) + { + dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N"); + } + + return dto; + } + + public async Task DeleteRecording(string recordingId) + { + var recordings = await GetRecordings(new RecordingQuery + { + + }, CancellationToken.None).ConfigureAwait(false); + + var recording = recordings.Items + .FirstOrDefault(i => string.Equals(recordingId, i.Id, StringComparison.OrdinalIgnoreCase)); + + if (recording == null) + { + throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId)); + } + + var channel = GetChannel(recording.ChannelId); + + var service = GetServices(channel.ServiceName, null) + .First(); + + await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false); + } + + public async Task CancelTimer(string id) + { + var timers = await GetTimers(new TimerQuery + { + + }, CancellationToken.None).ConfigureAwait(false); + + var timer = timers.Items + .FirstOrDefault(i => string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)); + + if (timer == null) + { + throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id)); + } + + var channel = GetChannel(timer.ChannelId); + + var service = GetServices(channel.ServiceName, null) + .First(); + + await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); + } + + public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken) + { + var results = await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false); + + return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture)); + } + + public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken) + { + var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false); + + return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture)); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs new file mode 100644 index 0000000000..00bf9e55ba --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -0,0 +1,59 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILiveTvManager _liveTvManager; + + public RefreshChannelsScheduledTask(ILiveTvManager liveTvManager) + { + _liveTvManager = liveTvManager; + } + + public string Name + { + get { return "Refresh Guide"; } + } + + public string Description + { + get { return "Downloads channel information from live tv services."; } + } + + public string Category + { + get { return "Live TV"; } + } + + public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress) + { + var manager = (LiveTvManager)_liveTvManager; + + return manager.RefreshChannels(progress, cancellationToken); + } + + public IEnumerable<ITaskTrigger> GetDefaultTriggers() + { + return new ITaskTrigger[] + { + + new StartupTrigger(), + + new SystemEventTrigger{ SystemEvent = SystemEvent.WakeFromSleep}, + + new IntervalTrigger{ Interval = TimeSpan.FromHours(2)} + }; + } + + public bool IsHidden + { + get { return _liveTvManager.Services.Count == 0; } + } + } +} |
