Added the availability checker for Radarr/Sonarr

pull/3700/head
tidusjar 4 years ago
parent 63fd43b1d8
commit dc00a87da8

@ -230,6 +230,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IIssuesPurge, IssuesPurge>();
services.AddTransient<IResendFailedRequests, ResendFailedRequests>();
services.AddTransient<IMediaDatabaseRefresh, MediaDatabaseRefresh>();
services.AddTransient<IArrAvailabilityChecker, ArrAvailabilityChecker>();
}
}
}

@ -4,6 +4,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core;
using Ombi.Helpers;
@ -17,7 +18,7 @@ using Quartz;
namespace Ombi.Schedule.Jobs.Radarr
{
public class ArrAvailabilityChecker
public class ArrAvailabilityChecker : IArrAvailabilityChecker
{
public ArrAvailabilityChecker(
IExternalRepository<RadarrCache> radarrRepo,
@ -48,10 +49,8 @@ namespace Ombi.Schedule.Jobs.Radarr
public async Task Execute(IJobExecutionContext job)
{
await ProcessMovies();
await ProcessTvShows();
}
private async Task ProcessMovies()
@ -66,6 +65,7 @@ namespace Ombi.Schedule.Jobs.Radarr
var available = availableRadarrMovies.Any(x => x.TheMovieDbId == movieRequest.TheMovieDbId);
if (available)
{
_logger.LogInformation($"Found move '{movieRequest.Title}' available in Radarr");
movieRequest.Available = true;
movieRequest.MarkedAsAvailable = DateTime.UtcNow;
itemsForAvailability.Add(new AvailabilityModel
@ -78,6 +78,8 @@ namespace Ombi.Schedule.Jobs.Radarr
if (itemsForAvailability.Any())
{
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Radarr Availability Checker found some new available movies!");
await _movies.SaveChangesAsync();
}
foreach (var item in itemsForAvailability)
@ -96,8 +98,104 @@ namespace Ombi.Schedule.Jobs.Radarr
public async Task ProcessTvShows()
{
var tv = await _tvRequest.GetChild().Where(x => !x.Available).ToListAsync();
var sonarrEpisodes = _sonarrEpisodeRepo.GetAll().Where(x => x.HasFile);
foreach (var child in tv)
{
var tvDbId = child.ParentRequest.TvDbId;
IQueryable<SonarrEpisodeCache> seriesEpisodes = sonarrEpisodes.Where(x => x.TvDbId == tvDbId);
if (seriesEpisodes == null || !seriesEpisodes.Any())
{
continue;
}
//if (!seriesEpisodes.Any())
//{
// // Let's try and match the series by name
// seriesEpisodes = sonarrEpisodes.Where(x =>
// x.EpisodeNumber == child.Title &&
// x.Series.ReleaseYear == child.ParentRequest.ReleaseDate.Year.ToString());
//}
var availableEpisode = new List<AvailabilityModel>();
foreach (var season in child.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
if (episode.Available)
{
continue;
}
var foundEp = await seriesEpisodes.AnyAsync(
x => x.EpisodeNumber == episode.EpisodeNumber &&
x.SeasonNumber == episode.Season.SeasonNumber);
if (foundEp)
{
availableEpisode.Add(new AvailabilityModel
{
Id = episode.Id
});
episode.Available = true;
}
}
}
//TODO Partial avilability notifications here
if (availableEpisode.Any())
{
//await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
// .SendAsync(NotificationHub.NotificationEvent, "Sonarr Availability Checker found some new available episodes!");
await _tvRequest.Save();
}
//foreach(var c in availableEpisode)
//{
// await _tvRepo.MarkEpisodeAsAvailable(c.Id);
//}
// 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)
{
await _hub.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Sonarr Availability Checker found some new available Shows!");
child.Available = true;
child.MarkedAsAvailable = DateTime.UtcNow;
_logger.LogInformation("[ARR_AC] - Child request {0} is now available, sending notification", $"{child.Title} - {child.Id}");
// We have ful-fulled this request!
await _tvRequest.Save();
await _notification.Notify(new NotificationOptions
{
DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable,
RequestId = child.Id,
RequestType = RequestType.TvShow,
Recipient = child.RequestedUser.Email
});
}
}
await _tvRequest.Save();
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

@ -0,0 +1,6 @@
namespace Ombi.Schedule.Jobs.Radarr
{
public interface IArrAvailabilityChecker : IBaseJob
{
}
}

@ -81,6 +81,8 @@ namespace Ombi.Schedule.Jobs.Radarr
tran.Commit();
}
}
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
}
catch (System.Exception ex)
{

@ -11,6 +11,7 @@ using Ombi.Api.Sonarr;
using Ombi.Api.Sonarr.Models;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Schedule.Jobs.Radarr;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
@ -69,7 +70,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
foreach (var s in sonarrSeries)
{
if (!s.monitored || s.episodeFileCount > 0) // We have files
if (!s.monitored || s.episodeFileCount == 0) // We have files
{
continue;
}
@ -78,7 +79,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri);
var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile);
var allExistingEpisodes = await _ctx.SonarrEpisodeCache.Where(x => x.TvDbId == s.tvdbId).ToListAsync();
//var allExistingEpisodes = await _ctx.SonarrEpisodeCache.Where(x => x.TvDbId == s.tvdbId).ToListAsync();
// Add to DB
_log.LogDebug("We have the episodes, adding to db transaction");
var episodesToAdd = monitoredEpisodes.Select(episode =>
@ -126,6 +127,8 @@ namespace Ombi.Schedule.Jobs.Sonarr
}
}
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
}
catch (Exception e)
{

@ -73,6 +73,7 @@ namespace Ombi.Schedule
{
await OmbiQuartz.Instance.AddJob<ISonarrSync>(nameof(ISonarrSync), "DVR", JobSettingsHelper.Sonarr(s));
await OmbiQuartz.Instance.AddJob<IRadarrSync>(nameof(IRadarrSync), "DVR", JobSettingsHelper.Radarr(s));
await OmbiQuartz.Instance.AddJob<IArrAvailabilityChecker>(nameof(IArrAvailabilityChecker), "DVR", null);
await OmbiQuartz.Instance.AddJob<ICouchPotatoSync>(nameof(ICouchPotatoSync), "DVR", JobSettingsHelper.CouchPotato(s));
await OmbiQuartz.Instance.AddJob<ISickRageSync>(nameof(ISickRageSync), "DVR", JobSettingsHelper.SickRageSync(s));
await OmbiQuartz.Instance.AddJob<ILidarrArtistSync>(nameof(ILidarrArtistSync), "DVR", JobSettingsHelper.LidarrArtistSync(s));

@ -146,6 +146,7 @@ export interface IJobSettings {
issuesPurge: string;
retryRequests: string;
mediaDatabaseRefresh: string;
arrAvailabilityChecker: string;
}
export interface IIssueSettings extends ISettings {

@ -46,4 +46,8 @@ export class JobService extends ServiceHelpers {
public runNewsletter(): Observable<boolean> {
return this.http.post<boolean>(`${this.url}newsletter/`, {headers: this.headers});
}
public runArrAvailabilityChecker(): Observable<boolean> {
return this.http.post<boolean>(`${this.url}arrAvailability/`, {headers: this.headers});
}
}

@ -72,19 +72,19 @@
</div>
<div class="form-group">
<label for="embyContentSync" class="control-label">Emby Sync</label>
<label for="embyContentSync" class="control-label">Emby Sync</label>
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('embyContentSync').hasError('required')}" id="embyContentSync" name="embyContentSync" formControlName="embyContentSync">
<small *ngIf="form.get('embyContentSync').hasError('required')" class="error-text">The Emby Sync is required</small>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('embyContentSync')?.value)">Test</button>
</div>
<div class="form-group">
<label for="userImporter" class="control-label">User Importer</label>
<label for="userImporter" class="control-label">User Importer</label>
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('userImporter').hasError('required')}" id="userImporter" name="userImporter" formControlName="userImporter">
<small *ngIf="form.get('userImporter').hasError('required')" class="error-text">The User Importer is required</small>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('userImporter')?.value)">Test</button>
</div>
<div class="form-group">
<label for="userImporter" class="control-label">Newsletter</label>
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('newsletter').hasError('required')}" id="newsletter" name="newsletter" formControlName="newsletter">
@ -105,6 +105,14 @@
<small *ngIf="form.get('mediaDatabaseRefresh').hasError('required')" class="error-text">The Media Database Refresh is required</small>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('mediaDatabaseRefresh')?.value)">Test</button>
</div>
<div class="form-group">
<label for="userImporter" class="control-label">Radarr/Sonarr Availability Checker</label>
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('arrAvailabilityChecker').hasError('required')}" id="arrAvailabilityChecker" name="arrAvailabilityChecker" formControlName="mediaDatabaseRefresh">
<small *ngIf="form.get('arrAvailabilityChecker').hasError('required')" class="error-text">The Radarr/Sonarr Availability Checker is required</small>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('arrAvailabilityChecker')?.value)">Test</button>
<button type="button" class="btn btn-sm btn-primary-outline" (click)="runArrAvailabilityChecker()">Run</button>
</div>
</div>
<div class="form-group">
<div>

@ -1,7 +1,7 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { NotificationService, SettingsService } from "../../services";
import { NotificationService, SettingsService, JobService } from "../../services";
@Component({
templateUrl: "./jobs.component.html",
@ -10,13 +10,14 @@ import { NotificationService, SettingsService } from "../../services";
export class JobsComponent implements OnInit {
public form: FormGroup;
public profilesRunning: boolean;
constructor(private readonly settingsService: SettingsService,
private readonly fb: FormBuilder,
private readonly notificationService: NotificationService) { }
private readonly notificationService: NotificationService,
private readonly jobsService: JobService) { }
public ngOnInit() {
this.settingsService.getJobSettings().subscribe(x => {
this.form = this.fb.group({
@ -27,27 +28,28 @@ export class JobsComponent implements OnInit {
userImporter: [x.userImporter, Validators.required],
sonarrSync: [x.sonarrSync, Validators.required],
radarrSync: [x.radarrSync, Validators.required],
sickRageSync: [x.sickRageSync, Validators.required],
sickRageSync: [x.sickRageSync, Validators.required],
newsletter: [x.newsletter, Validators.required],
plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required],
lidarrArtistSync: [x.lidarrArtistSync, Validators.required],
issuesPurge: [x.issuesPurge, Validators.required],
retryRequests: [x.retryRequests, Validators.required],
mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required],
});
arrAvailabilityChecker: [x.arrAvailabilityChecker, Validators.required],
});
});
}
public testCron(expression: string) {
this.settingsService.testCron({ expression }).subscribe(x => {
if(x.success) {
this.notificationService.success("Cron is Valid");
if(x.success) {
this.notificationService.success("Cron is Valid");
} else {
this.notificationService.error(x.message);
}
});
}
public onSubmit(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
@ -62,4 +64,8 @@ export class JobsComponent implements OnInit {
}
});
}
public runArrAvailabilityChecker() {
this.jobsService.runArrAvailabilityChecker().subscribe();
}
}

@ -8,6 +8,7 @@ using Ombi.Schedule.Jobs;
using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Radarr;
using Quartz;
namespace Ombi.Controllers.V1
@ -134,6 +135,17 @@ namespace Ombi.Controllers.V1
return true;
}
/// <summary>
/// Runs the Arr Availability Checker
/// </summary>
/// <returns></returns>
[HttpPost("arrAvailability")]
public async Task<bool> StartArrAvailabiltityChecker()
{
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
return true;
}
/// <summary>
/// Runs the newsletter
/// </summary>

Loading…
Cancel
Save