#region Copyright // /************************************************************************ // Copyright (c) 2016 Jamie Rees // File: PlexAvailabilityChecker.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 Dapper; using NLog; using Ombi.Api.Interfaces; using Ombi.Api.Models.Plex; using Ombi.Core; using Ombi.Core.Models; using Ombi.Core.SettingModels; using Ombi.Helpers; using Ombi.Services.Interfaces; using Ombi.Services.Models; using Ombi.Store; using Ombi.Store.Models; using Ombi.Store.Models.Emby; using Ombi.Store.Models.Plex; using Ombi.Store.Repository; using Quartz; using PlexMediaType = Ombi.Api.Models.Plex.PlexMediaType; namespace Ombi.Services.Jobs { public class EmbyAvailabilityChecker : IJob, IEmbyAvailabilityChecker { public EmbyAvailabilityChecker(ISettingsService embySettings, IRequestService request, IEmbyApi emby, ICacheProvider cache, INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, IEmbyNotificationEngine e, IRepository content) { Emby = embySettings; RequestService = request; EmbyApi = emby; Cache = cache; Notification = notify; Job = rec; UserNotifyRepo = users; EpisodeRepo = repo; NotificationEngine = e; EmbyContent = content; } private ISettingsService Emby { get; } private IRepository EpisodeRepo { get; } private IRequestService RequestService { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); private IEmbyApi EmbyApi { get; } private ICacheProvider Cache { get; } private INotificationService Notification { get; } private IJobRecord Job { get; } private IRepository UserNotifyRepo { get; } private INotificationEngine NotificationEngine { get; } private IRepository EmbyContent { get; } public void CheckAndUpdateAll() { var embySettings = Emby.GetSettings(); if (!embySettings.Enable) { return; } if (!ValidateSettings(embySettings)) { Log.Debug("Validation of the Emby settings failed."); return; } var content = EmbyContent.GetAll().ToList(); var movies = GetEmbyMovies(content).ToArray(); var shows = GetEmbyTvShows(content).ToArray(); var albums = GetEmbyMusic(content).ToArray(); var requests = RequestService.GetAll(); var requestedModels = requests as RequestedModel[] ?? requests.Where(x => !x.Available).ToArray(); if (!requestedModels.Any()) { Log.Debug("There are no requests to check."); return; } var modifiedModel = new List(); foreach (var r in requestedModels) { var releaseDate = r.ReleaseDate == DateTime.MinValue ? string.Empty : r.ReleaseDate.ToString("yyyy"); bool matchResult; switch (r.Type) { case RequestType.Movie: matchResult = IsMovieAvailable(movies, r.Title, releaseDate, r.ImdbId); break; case RequestType.TvShow: if (!embySettings.EnableEpisodeSearching) { matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList); } else { matchResult = r.Episodes.Any() ? r.Episodes.All(x => IsEpisodeAvailable(r.TvDbId, x.SeasonNumber, x.EpisodeNumber)) : IsTvShowAvailable(shows, r.Title, releaseDate, r.TvDbId, r.SeasonList); } break; case RequestType.Album: //matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName); // TODO Emby matchResult = false; break; default: throw new ArgumentOutOfRangeException(); } if (matchResult) { r.Available = true; modifiedModel.Add(r); continue; } } Log.Debug("Requests that will be updated count {0}", modifiedModel.Count); if (modifiedModel.Any()) { NotificationEngine.NotifyUsers(modifiedModel, NotificationType.RequestAvailable); RequestService.BatchUpdate(modifiedModel); } } public IEnumerable GetEmbyMovies(IEnumerable content) { return content.Where(x => x.Type == EmbyMediaType.Movie); } public bool IsMovieAvailable(IEnumerable embyMovies, string title, string year, string providerId) { var movie = GetMovie(embyMovies, title, year, providerId); return movie != null; } public EmbyContent GetMovie(IEnumerable embyMovies, string title, string year, string providerId) { if (embyMovies.Count() == 0) { return null; } foreach (var movie in embyMovies) { if (string.IsNullOrEmpty(movie.Title) || movie.PremierDate == DateTime.MinValue) { continue; } if (!string.IsNullOrEmpty(movie.ProviderId) && movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { return movie; } if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && movie.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase)) { return movie; } } return null; } public IEnumerable GetEmbyTvShows(IEnumerable content) { return content.Where(x => x.Type == EmbyMediaType.Series); } public bool IsTvShowAvailable(IEnumerable embyShows, string title, string year, string providerId, int[] seasons = null) { var show = GetTvShow(embyShows, title, year, providerId, seasons); return show != null; } public EmbyContent GetTvShow(IEnumerable embyShows, string title, string year, string providerId, int[] seasons = null) { foreach (var show in embyShows) { //if (show.ProviderId == providerId && seasons != null) // TODO Emby //{ // var showSeasons = ByteConverterHelper.ReturnObject(show.Seasons); // if (seasons.Any(season => showSeasons.Contains(season))) // { // return show; // } // return null; //} if (!string.IsNullOrEmpty(show.ProviderId) && show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { return show; } if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && show.PremierDate.Year.ToString().Equals(year, StringComparison.CurrentCultureIgnoreCase)) { return show; } } return null; } public bool IsEpisodeAvailable(string theTvDbId, int season, int episode) { var ep = EpisodeRepo.Custom( connection => { connection.Open(); var result = connection.Query("select * from EmbyEpisodes where ProviderId = @ProviderId", new { ProviderId = theTvDbId }); return result; }).ToList(); if (!ep.Any()) { Log.Info("Episode cache info is not available. tvdbid: {0}, season: {1}, episode: {2}", theTvDbId, season, episode); return false; } foreach (var result in ep) { if (result.ProviderId.Equals(theTvDbId) && result.EpisodeNumber == episode && result.SeasonNumber == season) { return true; } } return false; } /// /// Gets the episode's db in the cache. /// /// public async Task> GetEpisodes() { var episodes = await EpisodeRepo.GetAllAsync(); if (episodes == null) { return new HashSet(); } return episodes; } /// /// Gets the episode's stored in the db and then filters on the TheTvDBId. /// /// The tv database identifier. /// public async Task> GetEpisodes(int theTvDbId) { var ep = await EpisodeRepo.CustomAsync(async connection => { connection.Open(); var result = await connection.QueryAsync(@"select ee.* from EmbyEpisodes ee inner join EmbyContent ec on ee.ParentId = ec.EmbyId where ec.ProviderId = @ProviderId", new { ProviderId = theTvDbId }); return result; }); var embyEpisodes = ep as EmbyEpisodes[] ?? ep.ToArray(); if (!embyEpisodes.Any()) { Log.Info("Episode db info is not available."); return new List(); } return embyEpisodes; } public IEnumerable GetEmbyMusic(IEnumerable content) { return content.Where(x => x.Type == EmbyMediaType.Music); } private bool ValidateSettings(EmbySettings emby) { if (emby.Enable) { if (string.IsNullOrEmpty(emby?.Ip) || string.IsNullOrEmpty(emby?.ApiKey) || string.IsNullOrEmpty(emby?.AdministratorId)) { Log.Warn("A setting is null, Ensure Emby is configured correctly"); return false; } } return emby.Enable; } public void Execute(IJobExecutionContext context) { Job.SetRunning(true, JobNames.EmbyChecker); try { CheckAndUpdateAll(); } catch (Exception e) { Log.Error(e); } finally { Job.Record(JobNames.EmbyChecker); Job.SetRunning(false, JobNames.EmbyChecker); } } public void Start() { Job.SetRunning(true, JobNames.EmbyChecker); try { CheckAndUpdateAll(); } catch (Exception e) { Log.Error(e); } finally { Job.Record(JobNames.EmbyChecker); Job.SetRunning(false, JobNames.EmbyChecker); } } } }