From b8224f589541c9ef55c7a486ca437ed926dc56d9 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 2 Sep 2017 00:46:04 +0100 Subject: [PATCH] Added the emby episode cacher and the job to check if items are available on emby #1464 #865 --- src/Ombi.DependencyInjection/IocExtensions.cs | 2 + .../Jobs/Emby/EmbyAvaliabilityChecker.cs | 141 ++++++++++++++++++ .../Jobs/Emby/EmbyContentCacher.cs | 7 +- .../Jobs/Emby/EmbyEpisodeCacher.cs | 109 ++++++++++++++ .../Jobs/Emby/IEmbyAvaliabilityChecker.cs | 9 ++ .../Jobs/Emby/IEmbyEpisodeCacher.cs | 9 ++ src/Ombi.Store/Context/IOmbiContext.cs | 1 + src/Ombi.Store/Context/OmbiContext.cs | 7 + src/Ombi.Store/Entities/EmbyContent.cs | 4 + src/Ombi.Store/Entities/EmbyEpisode.cs | 47 ++++++ .../Repository/EmbyContentRepository.cs | 39 ++--- .../Repository/IEmbyContentRepository.cs | 4 + 12 files changed, 358 insertions(+), 21 deletions(-) create mode 100644 src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs create mode 100644 src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeCacher.cs create mode 100644 src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs create mode 100644 src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeCacher.cs create mode 100644 src/Ombi.Store/Entities/EmbyEpisode.cs diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 9ca43c7fd..8f8effd87 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -127,6 +127,8 @@ namespace Ombi.DependencyInjection { services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs new file mode 100644 index 000000000..ddc06b0b2 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -0,0 +1,141 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyAvaliabilityCheker.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Linq; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Notifications; +using Ombi.Helpers; +using Ombi.Notifications.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Schedule.Jobs.Emby +{ + public class EmbyAvaliabilityChecker : IEmbyAvaliabilityChecker + { + public EmbyAvaliabilityChecker(IEmbyContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m, + INotificationService n) + { + _repo = repo; + _tvRepo = t; + _movieRepo = m; + _notificationService = n; + } + + private readonly ITvRequestRepository _tvRepo; + private readonly IMovieRequestRepository _movieRepo; + private readonly IEmbyContentRepository _repo; + private readonly INotificationService _notificationService; + + public async Task Start() + { + await ProcessMovies(); + await ProcessTv(); + } + + private async Task ProcessMovies() + { + var movies = _movieRepo.Get().Where(x => !x.Available); + + foreach (var movie in movies) + { + var embyContent = await _repo.Get(movie.ImdbId); + if (embyContent == null) + { + // We don't have this yet + continue; + } + + movie.Available = true; + if (movie.Available) + { + BackgroundJob.Enqueue(() => _notificationService.Publish(new NotificationOptions + { + DateTime = DateTime.Now, + NotificationType = NotificationType.RequestAvailable, + RequestId = movie.Id, + RequestType = RequestType.Movie + })); + } + } + await _movieRepo.Save(); + } + + + + /// + /// TODO This is EXCATLY the same as the PlexAvailabilityChecker. Refactor Please future Jamie + /// + /// + private async Task ProcessTv() + { + var tv = _tvRepo.GetChild().Where(x => !x.Available); + var embyEpisodes = _repo.GetAllEpisodes().Include(x => x.Series); + + foreach (var child in tv) + { + var tvDbId = child.ParentRequest.TvDbId; + var seriesEpisodes = embyEpisodes.Where(x => x.Series.ProviderId == tvDbId.ToString()); + foreach (var season in child.SeasonRequests) + { + foreach (var episode in season.Episodes) + { + var foundEp = await seriesEpisodes.FirstOrDefaultAsync( + x => x.EpisodeNumber == episode.EpisodeNumber && + x.SeasonNumber == episode.Season.SeasonNumber); + + if (foundEp != null) + { + episode.Available = true; + } + } + } + + // Check to see if all of the episodes in all seasons are available for this request + var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available)); + if (allAvailable) + { + // We have fulfulled this request! + child.Available = true; + BackgroundJob.Enqueue(() => _notificationService.Publish(new NotificationOptions + { + DateTime = DateTime.Now, + NotificationType = NotificationType.RequestAvailable, + RequestId = child.ParentRequestId, + RequestType = RequestType.TvShow + })); + } + } + + await _tvRepo.Save(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentCacher.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentCacher.cs index d6dd16780..26c5672b2 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentCacher.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentCacher.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Hangfire; using Microsoft.Extensions.Logging; using Ombi.Api.Emby; using Ombi.Api.Emby.Models.Movie; @@ -18,18 +19,20 @@ namespace Ombi.Schedule.Jobs.Emby public class EmbyContentCacher : IEmbyContentCacher { public EmbyContentCacher(ISettingsService settings, IEmbyApi api, ILogger logger, - IEmbyContentRepository repo) + IEmbyContentRepository repo, IEmbyEpisodeCacher epCacher) { _logger = logger; _settings = settings; _api = api; _repo = repo; + _episodeCacher = epCacher; } private readonly ILogger _logger; private readonly ISettingsService _settings; private readonly IEmbyApi _api; private readonly IEmbyContentRepository _repo; + private readonly IEmbyEpisodeCacher _episodeCacher; public async Task Start() @@ -42,7 +45,7 @@ namespace Ombi.Schedule.Jobs.Emby await StartServerCache(server); // Episodes - //BackgroundJob.Enqueue(() => ); + BackgroundJob.Enqueue(() => _episodeCacher.Start()); } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeCacher.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeCacher.cs new file mode 100644 index 000000000..9031993ff --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeCacher.cs @@ -0,0 +1,109 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyEpisodeCacher.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.Extensions.Logging; +using Ombi.Api.Emby; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Schedule.Jobs.Emby +{ + public class EmbyEpisodeCacher : IEmbyEpisodeCacher + { + public EmbyEpisodeCacher(ISettingsService s, IEmbyApi api, ILogger l, IEmbyContentRepository repo, + IEmbyAvaliabilityChecker checker) + { + _api = api; + _logger = l; + _settings = s; + _repo = repo; + _avaliabilityChecker = checker; + } + + private readonly ISettingsService _settings; + private readonly IEmbyApi _api; + private readonly ILogger _logger; + private readonly IEmbyContentRepository _repo; + private readonly IEmbyAvaliabilityChecker _avaliabilityChecker; + + + public async Task Start() + { + var settings = await _settings.GetSettingsAsync(); + + foreach (var server in settings.Servers) + { + await CacheEpisodes(server); + } + + BackgroundJob.Enqueue(() => _avaliabilityChecker.Start()); + } + + private async Task CacheEpisodes(EmbyServers server) + { + var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, server.AdministratorId, server.FullUri); + var epToAdd = new List(); + + foreach (var ep in allEpisodes.Items) + { + var epInfo = await _api.GetEpisodeInformation(ep.Id, server.ApiKey, server.AdministratorId, server.FullUri); + if (epInfo?.ProviderIds?.Tvdb == null) + { + continue; + } + + var existingEpisode = await _repo.GetByEmbyId(ep.Id); + if (existingEpisode == null) + { + // add it + epToAdd.Add(new EmbyEpisode + { + EmbyId = ep.Id, + EpisodeNumber = ep.IndexNumber, + SeasonNumber = ep.ParentIndexNumber, + ParentId = ep.SeriesId, + ProviderId = epInfo.ProviderIds.Tvdb, + Title = ep.Name, + AddedAt = DateTime.UtcNow + }); + } + } + + if (epToAdd.Any()) + { + await _repo.AddRange(epToAdd); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs new file mode 100644 index 000000000..db9c58079 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Emby/IEmbyAvaliabilityChecker.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Emby +{ + public interface IEmbyAvaliabilityChecker + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeCacher.cs b/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeCacher.cs new file mode 100644 index 000000000..84eff723f --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Emby/IEmbyEpisodeCacher.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Emby +{ + public interface IEmbyEpisodeCacher + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 3757e26fd..f4a7910e1 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -18,6 +18,7 @@ namespace Ombi.Store.Context DbSet PlexEpisode { get; set; } DbSet RadarrCache { get; set; } DbSet EmbyContent { get; set; } + DbSet EmbyEpisode { get; set; } DatabaseFacade Database { get; } EntityEntry Entry(T entry) where T : class; EntityEntry Attach(TEntity entity) where TEntity : class; diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 9eb0764a7..4ec9716da 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -29,6 +29,7 @@ namespace Ombi.Store.Context public DbSet PlexEpisode { get; set; } public DbSet RadarrCache { get; set; } public DbSet EmbyContent { get; set; } + public DbSet EmbyEpisode { get; set; } public DbSet MovieRequests { get; set; } public DbSet TvRequests { get; set; } @@ -53,6 +54,12 @@ namespace Ombi.Store.Context .WithMany(b => b.Episodes) .HasPrincipalKey(x => x.Key) .HasForeignKey(p => p.GrandparentKey); + + builder.Entity() + .HasOne(p => p.Series) + .WithMany(b => b.Episodes) + .HasPrincipalKey(x => x.EmbyId) + .HasForeignKey(p => p.ParentId); base.OnModelCreating(builder); } diff --git a/src/Ombi.Store/Entities/EmbyContent.cs b/src/Ombi.Store/Entities/EmbyContent.cs index 340f8dc26..4d98c2d2b 100644 --- a/src/Ombi.Store/Entities/EmbyContent.cs +++ b/src/Ombi.Store/Entities/EmbyContent.cs @@ -26,6 +26,7 @@ #endregion using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace Ombi.Store.Entities @@ -38,6 +39,9 @@ namespace Ombi.Store.Entities public string EmbyId { get; set; } public EmbyMediaType Type { get; set; } public DateTime AddedAt { get; set; } + + + public ICollection Episodes { get; set; } } public enum EmbyMediaType diff --git a/src/Ombi.Store/Entities/EmbyEpisode.cs b/src/Ombi.Store/Entities/EmbyEpisode.cs new file mode 100644 index 000000000..150829240 --- /dev/null +++ b/src/Ombi.Store/Entities/EmbyEpisode.cs @@ -0,0 +1,47 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbyEpisode.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Ombi.Store.Entities +{ + [Table("EmbyEpisode")] + public class EmbyEpisode : Entity + { + public string Title { get; set; } + public string EmbyId { get; set; } + public int EpisodeNumber { get; set; } + public int SeasonNumber { get; set; } + public string ParentId { get; set; } + public string ProviderId { get; set; } + public DateTime AddedAt { get; set; } + + public EmbyContent Series { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index d32f80498..314fa0042 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -88,25 +88,26 @@ namespace Ombi.Store.Repository await Db.SaveChangesAsync(); } - //public IQueryable GetAllEpisodes() - //{ - // return Db.PlexEpisode.AsQueryable(); - //} + public IQueryable GetAllEpisodes() + { + return Db.EmbyEpisode.AsQueryable(); + } + + public async Task Add(EmbyEpisode content) + { + await Db.EmbyEpisode.AddAsync(content); + await Db.SaveChangesAsync(); + return content; + } + public async Task GetEpisodeByEmbyId(string key) + { + return await Db.EmbyEpisode.FirstOrDefaultAsync(x => x.EmbyId == key); + } - //public async Task Add(PlexEpisode content) - //{ - // await Db.PlexEpisode.AddAsync(content); - // await Db.SaveChangesAsync(); - // return content; - //} - //public async Task GetEpisodeByKey(int key) - //{ - // return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key); - //} - //public async Task AddRange(IEnumerable content) - //{ - // Db.PlexEpisode.AddRange(content); - // await Db.SaveChangesAsync(); - //} + public async Task AddRange(IEnumerable content) + { + Db.EmbyEpisode.AddRange(content); + await Db.SaveChangesAsync(); + } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IEmbyContentRepository.cs b/src/Ombi.Store/Repository/IEmbyContentRepository.cs index 2e6eb0384..bc10a6273 100644 --- a/src/Ombi.Store/Repository/IEmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/IEmbyContentRepository.cs @@ -15,5 +15,9 @@ namespace Ombi.Store.Repository Task> GetAll(); Task GetByEmbyId(string embyId); Task Update(EmbyContent existingContent); + IQueryable GetAllEpisodes(); + Task Add(EmbyEpisode content); + Task GetEpisodeByEmbyId(string key); + Task AddRange(IEnumerable content); } } \ No newline at end of file