diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index a9d8cfe3d..e342e623a 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -205,7 +205,7 @@ public List GetPlexTvShows() foreach (var lib in tvLibs) { - shows.AddRange(lib.Directory.Select(x => new PlexTvShow() // shows are in the directory list + shows.AddRange(lib.Directory.Select(x => new PlexTvShow // shows are in the directory list { Title = x.Title, ReleaseYear = x.Year, diff --git a/PlexRequests.Store/RequestedModel.cs b/PlexRequests.Store/RequestedModel.cs index b203db7d3..65b1b31cf 100644 --- a/PlexRequests.Store/RequestedModel.cs +++ b/PlexRequests.Store/RequestedModel.cs @@ -42,6 +42,7 @@ public RequestedModel() public string ArtistName { get; set; } public string ArtistId { get; set; } public int IssueId { get; set; } + public EpisodesModel[] Episodes { get; set; } [JsonIgnore] public List AllUsers @@ -105,4 +106,10 @@ public enum IssueState PlaybackIssues = 3, Other = 4, // Provide a message } + + public class EpisodesModel + { + public int SeasonNumber { get; set; } + public int EpisodeNumber { get; set; } + } } diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs index 1c980e650..9240009f8 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.UI/Helpers/TvSender.cs @@ -24,6 +24,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion +using System; +using System.Collections.Generic; +using System.Diagnostics; + using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.SickRage; @@ -32,6 +36,10 @@ using PlexRequests.Helpers; using PlexRequests.Store; using System.Linq; +using System.Threading.Tasks; + +using PlexRequests.Helpers.Exceptions; +using PlexRequests.UI.Models; namespace PlexRequests.UI.Helpers { @@ -46,15 +54,15 @@ public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi) private ISickRageApi SickrageApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); - public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) + public async Task SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) { - return SendToSonarr(sonarrSettings, model, string.Empty); + return await SendToSonarr(sonarrSettings, model, string.Empty); } - public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId) + public async Task SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId) { var qualityProfile = 0; - + var episodeRequest = model.Episodes.Length > 0; if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality { int.TryParse(qualityId, out qualityProfile); @@ -65,10 +73,57 @@ public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedMode int.TryParse(sonarrSettings.QualityProfile, out qualityProfile); } + // Does series exist? + var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + + + // Series Exists + if (episodeRequest) + { + if (series != null) + { + // Request the episodes in the existing series + RequestEpisodesWithExistingSeries(model, series, sonarrSettings); + } + else + { + // Series doesn't exist, need to add it as unmonitored. + var addResult = await Task.Run(() => SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, + sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, new int[0], sonarrSettings.ApiKey, + sonarrSettings.FullUri)); + + if (string.IsNullOrEmpty(addResult?.title)) + { + var sw = new Stopwatch(); + sw.Start(); + while (series == null) + { + await Task.Delay(TimeSpan.FromSeconds(5)); + series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + + // Check how long we have been doing this for + if (sw.Elapsed > TimeSpan.FromSeconds(30)) + { + // 30 seconds is a long time, it's not going to work. + throw new ApiRequestException("Sonarr still didn't have the series added after 30 seconds."); + } + } + sw.Stop(); + } + + // We now have the series in Sonarr + RequestEpisodesWithExistingSeries(model, series, sonarrSettings); + + return addResult; + } + } + + + var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, sonarrSettings.FullUri); - + return result; } @@ -93,5 +148,41 @@ public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, Requested return result; } + + private bool RequestEpisodesWithExistingSeries(RequestedModel model, Series selectedSeries, SonarrSettings sonarrSettings) + { + // Show Exists + // Look up all episodes + var episodes = SonarrApi.GetEpisodes(selectedSeries.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri).ToList(); + var internalEpisodeIds = new List(); + var tasks = new List(); + foreach (var r in model.Episodes) + { + var episode = + episodes.FirstOrDefault( + x => x.episodeNumber == r.EpisodeNumber && x.seasonNumber == r.SeasonNumber); + if (episode == null) + { + continue; + } + var episodeInfo = SonarrApi.GetEpisode(episode.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri); + episodeInfo.monitored = true; // Set the episode to monitored + tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey, + sonarrSettings.FullUri))); + internalEpisodeIds.Add(episode.id); + } + Task.WaitAll(tasks.ToArray()); + + SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri); + + return true; + } + private async Task GetSonarrSeries(SonarrSettings sonarrSettings, int showId) + { + var task = await Task.Run(() => SonarrApi.GetSeries(sonarrSettings.ApiKey, sonarrSettings.FullUri)).ConfigureAwait(false); + var selectedSeries = task.FirstOrDefault(series => series.tvdbId == showId); + + return selectedSeries; + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index ac0ae28f6..810624ecb 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -458,7 +458,7 @@ private async Task UpdateRequestsAsync(RequestedModel[] requestedModel else if (sonarr.Enabled) { - var res = sender.SendToSonarr(sonarr, r); + var res = await sender.SendToSonarr(sonarr, r); if (!string.IsNullOrEmpty(res?.title)) { r.Approved = true; diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 6fcae6fd2..9152f9206 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -117,7 +117,7 @@ public SearchModule(ICacheProvider cache, ISettingsService Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); - Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0,"episode"); + Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode"); Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); @@ -219,7 +219,7 @@ private async Task ProcessMovies(MovieSearchType searchType, string se apiMovies = new List(); break; } - + var allResults = await RequestService.GetAllAsync(); allResults = allResults.Where(x => x.Type == RequestType.Movie); @@ -530,7 +530,6 @@ private async Task RequestTvShow(int showId, string seasons) var json = req.FirstOrDefault()?.ToString(); var episodeModel = JsonConvert.DeserializeObject(json); // Convert it into the object - var episodeResult = false; var episodeRequest = false; var settings = await PrService.GetSettingsAsync(); @@ -540,46 +539,46 @@ private async Task RequestTvShow(int showId, string seasons) } Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var sonarrSettings = SonarrService.GetSettingsAsync(); + // This means we are requesting an episode rather than a whole series or season if (episodeModel != null) { episodeRequest = true; - var sonarrSettings = await SonarrService.GetSettingsAsync(); - if (!sonarrSettings.Enabled) + var s = await sonarrSettings; + if (!s.Enabled) { return Response.AsJson("This is currently only supported with Sonarr"); } - - var series = await GetSonarrSeries(sonarrSettings, episodeModel.ShowId); - if (series != null) - { - // The series already exists in Sonarr - episodeResult = RequestEpisodesWithExistingSeries(episodeModel, series, sonarrSettings); - } } var showInfo = TvApi.ShowLookupByTheTvDbId(showId); DateTime firstAir; DateTime.TryParse(showInfo.premiered, out firstAir); string fullShowName = $"{showInfo.name} ({firstAir.Year})"; - //#if !DEBUG if (showInfo.externals?.thetvdb == null) { return Response.AsJson(new JsonResponseModel { Result = false, Message = "Our TV Provider (TVMaze) doesn't have a TheTVDBId for this item. Please report this to TVMaze. We cannot add the series sorry." }); } - // check if the show has already been requested + // check if the show/episodes have already been requested var existingRequest = await RequestService.CheckRequestAsync(showId); if (existingRequest != null) { - // check if the current user is already marked as a requester for this show, if not, add them - if (!existingRequest.UserHasRequested(Username)) + if (episodeRequest) { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); + var difference = GetListDifferences(existingRequest.Episodes, episodeModel.Episodes).ToList(); + if (!difference.Any()) + { + return await AddUserToRequest(existingRequest, settings, fullShowName); + } + // We have an episode that has not yet been requested, let's continue + } + else + { + return await AddUserToRequest(existingRequest, settings, fullShowName); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" : $"{fullShowName} {Resources.UI.Search_AlreadyRequested}" }); } try @@ -601,7 +600,6 @@ private async Task RequestTvShow(int showId, string seasons) { return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) }); } - //#endif var model = new RequestedModel @@ -622,8 +620,8 @@ private async Task RequestTvShow(int showId, string seasons) TvDbId = showId.ToString() }; + var seasonsList = new List(); - //TODO something here for the episodes switch (seasons) { case "first": @@ -638,7 +636,12 @@ private async Task RequestTvShow(int showId, string seasons) model.SeasonsRequested = "All"; break; case "episode": - + model.Episodes = new Store.EpisodesModel[episodeModel.Episodes.Length]; + for (var i = 0; i < episodeModel.Episodes.Length; i++) + { + model.Episodes[i] = new Store.EpisodesModel { EpisodeNumber = episodeModel.Episodes[i].EpisodeNumber, SeasonNumber = episodeModel.Episodes[i].SeasonNumber }; + } + break; default: model.SeasonsRequested = seasons; var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); @@ -658,11 +661,11 @@ private async Task RequestTvShow(int showId, string seasons) if (ShouldAutoApprove(RequestType.TvShow, settings)) { model.Approved = true; - var sonarrSettings = await SonarrService.GetSettingsAsync(); + var s = await sonarrSettings; var sender = new TvSender(SonarrApi, SickrageApi); - if (sonarrSettings.Enabled) + if (s.Enabled) { - var result = sender.SendToSonarr(sonarrSettings, model); + var result = await sender.SendToSonarr(s, model); if (!string.IsNullOrEmpty(result?.title)) { return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); @@ -682,7 +685,7 @@ private async Task RequestTvShow(int showId, string seasons) return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message ?? Resources.UI.Search_SickrageError }); } - if (!srSettings.Enabled && !sonarrSettings.Enabled) + if (!srSettings.Enabled && !s.Enabled) { return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } @@ -693,6 +696,26 @@ private async Task RequestTvShow(int showId, string seasons) return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } + private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, string fullShowName) + { + // check if the current user is already marked as a requester for this show, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return + Response.AsJson( + new JsonResponseModel + { + Result = true, + Message = + settings.UsersCanViewOnlyOwnRequests + ? $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + : $"{fullShowName} {Resources.UI.Search_AlreadyRequested}" + }); + } + private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) { var sendNotification = ShouldAutoApprove(type, prSettings) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true; @@ -986,42 +1009,17 @@ await RequestLimitRepo.InsertAsync(new RequestLimit return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); } - private bool RequestEpisodesWithExistingSeries(EpisodeRequestModel model, Series selectedSeries, SonarrSettings sonarrSettings) + private IEnumerable GetListDifferences(IEnumerable model, IEnumerable request) { - // Show Exists - // Look up all episodes - var episodes = SonarrApi.GetEpisodes(selectedSeries.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri).ToList(); - var internalEpisodeIds = new List(); - var tasks = new List(); - foreach (var r in model.Episodes) - { - var episode = - episodes.FirstOrDefault( - x => x.episodeNumber == r.EpisodeNumber && x.seasonNumber == r.SeasonNumber); - if (episode == null) - { - continue; - } - var episodeInfo = SonarrApi.GetEpisode(episode.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri); - episodeInfo.monitored = true; // Set the episode to monitored - tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey, - sonarrSettings.FullUri))); - internalEpisodeIds.Add(episode.id); - } - Task.WaitAll(tasks.ToArray()); - - SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri); - - return true; - } - - private async Task GetSonarrSeries(SonarrSettings sonarrSettings, int showId) - { - var task = await Task.Run(() => SonarrApi.GetSeries(sonarrSettings.ApiKey, sonarrSettings.FullUri)).ConfigureAwait(false); - var selectedSeries = task.FirstOrDefault(series => series.tvdbId == showId); + var newRequest = request + .Select(r => + new Store.EpisodesModel + { + SeasonNumber = r.SeasonNumber, + EpisodeNumber = r.EpisodeNumber + }).ToList(); - return selectedSeries; + return newRequest.Except(model); } - } }