Merge branch 'feature/v4' of https://github.com/tidusjar/ombi into feature/v4

pull/3703/head
Jamie Rees 4 years ago
commit e36b1dba3d

@ -102,12 +102,12 @@ We are planning to bring back these features in V3 but for now you can find a li
| Lidarr | Yes | No | | Lidarr | Yes | No |
# Feature Requests # Feature Requests
Feature requests are handled on FeatHub. Feature requests are handled on Feature Upvote.
Search the existing requests to see if your suggestion has already been submitted. Search the existing requests to see if your suggestion has already been submitted.
(If a similar request exists, give it a thumbs up (+1), or add additional comments to the request) (If a similar request exists, please vote, or add additional comments to the request)
#### [![Feature Requests](https://cloud.githubusercontent.com/assets/390379/10127973/045b3a96-6560-11e5-9b20-31a2032956b2.png)](http://feathub.com/tidusjar/Ombi) #### [![Feature Requests](https://cloud.githubusercontent.com/assets/390379/10127973/045b3a96-6560-11e5-9b20-31a2032956b2.png)](https://features.ombi.io)
# Preview # Preview

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

@ -111,7 +111,7 @@ namespace Ombi.Notifications.Agents
{ {
if (requestedUser.HasValue()) if (requestedUser.HasValue())
{ {
fields.Add(new DiscordField { name = "Requsted By", value = requestedUser, inline = true }); fields.Add(new DiscordField { name = "Requested By", value = requestedUser, inline = true });
} }
} }
if (model.Data.TryGetValue("DenyReason", out var denyReason)) if (model.Data.TryGetValue("DenyReason", out var denyReason))

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
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;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Schedule.Jobs.Plex.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Quartz;
namespace Ombi.Schedule.Jobs.Radarr
{
public class ArrAvailabilityChecker : IArrAvailabilityChecker
{
public ArrAvailabilityChecker(
IExternalRepository<RadarrCache> radarrRepo,
IExternalRepository<SonarrCache> sonarrRepo,
IExternalRepository<SonarrEpisodeCache> sonarrEpisodeRepo,
INotificationHelper notification, IHubContext<NotificationHub> hub,
ITvRequestRepository tvRequest, IMovieRequestRepository movies,
ILogger<ArrAvailabilityChecker> log)
{
_radarrRepo = radarrRepo;
_sonarrRepo = sonarrRepo;
_sonarrEpisodeRepo = sonarrEpisodeRepo;
_notification = notification;
_hub = hub;
_tvRequest = tvRequest;
_movies = movies;
_logger = log;
}
private readonly IExternalRepository<RadarrCache> _radarrRepo;
private readonly IExternalRepository<SonarrCache> _sonarrRepo;
private readonly ILogger<ArrAvailabilityChecker> _logger;
private readonly IExternalRepository<SonarrEpisodeCache> _sonarrEpisodeRepo;
private readonly INotificationHelper _notification;
private readonly IHubContext<NotificationHub> _hub;
private readonly ITvRequestRepository _tvRequest;
private readonly IMovieRequestRepository _movies;
public async Task Execute(IJobExecutionContext job)
{
await ProcessMovies();
await ProcessTvShows();
}
private async Task ProcessMovies()
{
var availableRadarrMovies = _radarrRepo.GetAll().Where(x => x.HasFile).ToImmutableHashSet();
var unavailableMovieRequests = _movies.GetAll().Where(x => !x.Available).ToImmutableHashSet();
var itemsForAvailability = new List<AvailabilityModel>();
foreach (var movieRequest in unavailableMovieRequests)
{
// Do we have an item in the radarr list
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
{
Id = movieRequest.Id,
RequestedUser = movieRequest.RequestedUser != null ? movieRequest.RequestedUser.Email : string.Empty
});
}
}
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)
{
await _notification.Notify(new NotificationOptions
{
DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable,
RequestId = item.Id,
RequestType = RequestType.Movie,
Recipient = item.RequestedUser
});
}
}
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
{
}
}

@ -49,29 +49,29 @@ namespace Ombi.Schedule.Jobs.Radarr
// Let's remove the old cached data // Let's remove the old cached data
using (var tran = await _ctx.Database.BeginTransactionAsync()) using (var tran = await _ctx.Database.BeginTransactionAsync())
{ {
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache"); await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM RadarrCache");
tran.Commit(); tran.Commit();
} }
var movieIds = new List<RadarrCache>(); var movieIds = new List<RadarrCache>();
foreach (var m in movies) foreach (var m in movies)
{ {
if(m.monitored) if (m.monitored || m.hasFile)
{
if (m.tmdbId > 0)
{ {
movieIds.Add(new RadarrCache if (m.tmdbId > 0)
{ {
TheMovieDbId = m.tmdbId, movieIds.Add(new RadarrCache
HasFile = m.hasFile {
}); TheMovieDbId = m.tmdbId,
} HasFile = m.hasFile
else });
{ }
Logger.LogError("TMDBId is not > 0 for movie {0}", m.title); else
{
Logger.LogError("TMDBId is not > 0 for movie {0}", m.title);
}
} }
} }
}
using (var tran = await _ctx.Database.BeginTransactionAsync()) using (var tran = await _ctx.Database.BeginTransactionAsync())
{ {
@ -81,6 +81,8 @@ namespace Ombi.Schedule.Jobs.Radarr
tran.Commit(); tran.Commit();
} }
} }
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {

@ -11,6 +11,7 @@ using Ombi.Api.Sonarr;
using Ombi.Api.Sonarr.Models; using Ombi.Api.Sonarr.Models;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Schedule.Jobs.Radarr;
using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context; using Ombi.Store.Context;
using Ombi.Store.Entities; using Ombi.Store.Entities;
@ -33,7 +34,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
private readonly ISonarrApi _api; private readonly ISonarrApi _api;
private readonly ILogger<SonarrSync> _log; private readonly ILogger<SonarrSync> _log;
private readonly ExternalContext _ctx; private readonly ExternalContext _ctx;
public async Task Execute(IJobExecutionContext job) public async Task Execute(IJobExecutionContext job)
{ {
try try
@ -50,49 +51,84 @@ namespace Ombi.Schedule.Jobs.Sonarr
var ids = sonarrSeries.Select(x => x.tvdbId); var ids = sonarrSeries.Select(x => x.tvdbId);
using (var tran = await _ctx.Database.BeginTransactionAsync()) using (var tran = await _ctx.Database.BeginTransactionAsync())
{ {
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM SonarrCache");
tran.Commit(); tran.Commit();
} }
var existingSeries = await _ctx.SonarrCache.Select(x => x.TvDbId).ToListAsync();
//var entites = ids.Except(existingSeries).Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet();
var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet(); var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet();
await _ctx.SonarrCache.AddRangeAsync(entites); await _ctx.SonarrCache.AddRangeAsync(entites);
entites.Clear(); entites.Clear();
using (var tran = await _ctx.Database.BeginTransactionAsync()) using (var tran = await _ctx.Database.BeginTransactionAsync())
{ {
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM SonarrEpisodeCache");
tran.Commit(); tran.Commit();
} }
foreach (var s in sonarrSeries) foreach (var s in sonarrSeries)
{ {
if (!s.monitored) if (!s.monitored || s.episodeFileCount == 0) // We have files
{ {
continue; continue;
} }
_log.LogDebug("Syncing series: {0}", s.title); _log.LogDebug("Syncing series: {0}", s.title);
var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri); var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri);
var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile); var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile);
//var allExistingEpisodes = await _ctx.SonarrEpisodeCache.Where(x => x.TvDbId == s.tvdbId).ToListAsync();
// Add to DB // Add to DB
_log.LogDebug("We have the episodes, adding to db transaction"); _log.LogDebug("We have the episodes, adding to db transaction");
using (var tran = await _ctx.Database.BeginTransactionAsync()) var episodesToAdd = monitoredEpisodes.Select(episode =>
{
await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode =>
new SonarrEpisodeCache new SonarrEpisodeCache
{ {
EpisodeNumber = episode.episodeNumber, EpisodeNumber = episode.episodeNumber,
SeasonNumber = episode.seasonNumber, SeasonNumber = episode.seasonNumber,
TvDbId = s.tvdbId, TvDbId = s.tvdbId,
HasFile = episode.hasFile HasFile = episode.hasFile
})); });
//var episodesToAdd = new List<SonarrEpisodeCache>();
//foreach (var monitored in monitoredEpisodes)
//{
// var existing = allExistingEpisodes.FirstOrDefault(x => x.SeasonNumber == monitored.seasonNumber && x.EpisodeNumber == monitored.episodeNumber);
// if (existing == null)
// {
// // Just add a new one
// episodesToAdd.Add(new SonarrEpisodeCache
// {
// EpisodeNumber = monitored.episodeNumber,
// SeasonNumber = monitored.seasonNumber,
// TvDbId = s.tvdbId,
// HasFile = monitored.hasFile
// });
// }
// else
// {
// // Do we need to update the availability?
// if (monitored.hasFile != existing.HasFile)
// {
// existing.HasFile = monitored.hasFile;
// }
// }
//}
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.SonarrEpisodeCache.AddRangeAsync(episodesToAdd);
_log.LogDebug("Commiting the transaction"); _log.LogDebug("Commiting the transaction");
await _ctx.SaveChangesAsync(); await _ctx.SaveChangesAsync();
tran.Commit(); tran.Commit();
} }
} }
} }
await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR");
} }
catch (Exception e) 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<ISonarrSync>(nameof(ISonarrSync), "DVR", JobSettingsHelper.Sonarr(s));
await OmbiQuartz.Instance.AddJob<IRadarrSync>(nameof(IRadarrSync), "DVR", JobSettingsHelper.Radarr(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<ICouchPotatoSync>(nameof(ICouchPotatoSync), "DVR", JobSettingsHelper.CouchPotato(s));
await OmbiQuartz.Instance.AddJob<ISickRageSync>(nameof(ISickRageSync), "DVR", JobSettingsHelper.SickRageSync(s)); await OmbiQuartz.Instance.AddJob<ISickRageSync>(nameof(ISickRageSync), "DVR", JobSettingsHelper.SickRageSync(s));
await OmbiQuartz.Instance.AddJob<ILidarrArtistSync>(nameof(ILidarrArtistSync), "DVR", JobSettingsHelper.LidarrArtistSync(s)); await OmbiQuartz.Instance.AddJob<ILidarrArtistSync>(nameof(ILidarrArtistSync), "DVR", JobSettingsHelper.LidarrArtistSync(s));

@ -29,6 +29,7 @@ export interface IUsersModel {
export interface INavBar { export interface INavBar {
icon: string; icon: string;
faIcon: string;
name: string; name: string;
link: string; link: string;
requiresAdmin: boolean; requiresAdmin: boolean;
@ -36,5 +37,5 @@ export interface INavBar {
toolTip?: boolean; toolTip?: boolean;
toolTipMessage?: string; toolTipMessage?: string;
style?: string; style?: string;
donation?: boolean; externalLink?: boolean;
} }

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

@ -5,20 +5,19 @@
<span *ngFor="let nav of navItems"> <span *ngFor="let nav of navItems">
<div *ngIf="(nav.requiresAdmin && isAdmin || !nav.requiresAdmin) && nav.enabled"> <div *ngIf="(nav.requiresAdmin && isAdmin || !nav.requiresAdmin) && nav.enabled">
<a *ngIf="nav.donation" mat-list-item [href]="nav.link" target="_blank" matTooltip="{{nav.toolTipMessage | translate}}" matTooltipPosition="right" [routerLinkActive]="getTheme()"> <a *ngIf="nav.externalLink" mat-list-item [href]="nav.link" target="_blank" matTooltip="{{nav.toolTipMessage | translate}}" matTooltipPosition="right" [routerLinkActive]="getTheme()">
<mat-icon aria-label="Side nav toggle icon" [style]="nav.style" >{{nav.icon}}</mat-icon> <mat-icon *ngIf="nav.icon" aria-label="Side nav toggle icon" [style]="nav.style" >{{nav.icon}}</mat-icon>
<i *ngIf="nav.faIcon" class="fa fa-lg {{nav.faIcon}}" style="padding-left: 5px; padding-right: 5px;" aria-hidden="true"></i>
&nbsp;{{nav.name | translate}} &nbsp;{{nav.name | translate}}
</a> </a>
<a *ngIf="!nav.donation" mat-list-item [routerLink]="nav.link" [style]="nav.color" [routerLinkActive]="getTheme()"> <a *ngIf="!nav.externalLink" mat-list-item [routerLink]="nav.link" [style]="nav.color" [routerLinkActive]="getTheme()">
<mat-icon aria-label="Side nav toggle icon">{{nav.icon}}</mat-icon> <mat-icon aria-label="Side nav toggle icon">{{nav.icon}}</mat-icon>
&nbsp;{{nav.name | translate}} &nbsp;{{nav.name | translate}}
</a> </a>
</div> </div>
</span> </span>

@ -34,7 +34,7 @@ export class MyNavComponent implements OnInit {
} }
public async ngOnInit() { public async ngOnInit() {
this.issuesEnabled = await this.settingsService.issueEnabled().toPromise(); this.issuesEnabled = await this.settingsService.issueEnabled().toPromise();
console.log("issues enabled: " + this.issuesEnabled); console.log("issues enabled: " + this.issuesEnabled);
this.theme = this.store.get("theme"); this.theme = this.store.get("theme");
@ -42,14 +42,15 @@ export class MyNavComponent implements OnInit {
this.store.save("theme","dark"); this.store.save("theme","dark");
} }
this.navItems = [ this.navItems = [
{ name: "NavigationBar.Discover", icon: "find_replace", link: "/discover", requiresAdmin: false, enabled: true }, { name: "NavigationBar.Discover", icon: "find_replace", link: "/discover", requiresAdmin: false, enabled: true, faIcon: null },
{ name: "NavigationBar.Requests", icon: "list", link: "/requests-list", requiresAdmin: false, enabled: true }, { name: "NavigationBar.Requests", icon: "list", link: "/requests-list", requiresAdmin: false, enabled: true, faIcon: null },
{ name: "NavigationBar.Issues", icon: "notification_important", link: "/issues", requiresAdmin: false, enabled: this.issuesEnabled }, { name: "NavigationBar.Issues", icon: "notification_important", link: "/issues", requiresAdmin: false, enabled: this.issuesEnabled, faIcon: null },
{ name: "NavigationBar.UserManagement", icon: "account_circle", link: "/usermanagement", requiresAdmin: true, enabled: true }, { name: "NavigationBar.UserManagement", icon: "account_circle", link: "/usermanagement", requiresAdmin: true, enabled: true, faIcon: null },
// { name: "NavigationBar.Calendar", icon: "calendar_today", link: "/calendar", requiresAdmin: false, enabled: true }, // { name: "NavigationBar.Calendar", icon: "calendar_today", link: "/calendar", requiresAdmin: false, enabled: true },
{ name: "NavigationBar.Donate", icon: "attach_money", link: "https://www.paypal.me/PlexRequestsNet", donation: true, requiresAdmin: true, enabled: true, toolTip: true, style: "color:red;", toolTipMessage: 'NavigationBar.DonateTooltip' }, { name: "NavigationBar.Donate", icon: "attach_money", link: "https://www.paypal.me/PlexRequestsNet", externalLink: true, requiresAdmin: true, enabled: true, toolTip: true, style: "color:red;", toolTipMessage: 'NavigationBar.DonateTooltip', faIcon: null },
{ name: "NavigationBar.Settings", icon: "settings", link: "/Settings/About", requiresAdmin: true, enabled: true }, { name: "NavigationBar.FeatureSuggestion", icon: null, link: "https://features.ombi.io/", externalLink: true, requiresAdmin: false, enabled: true, toolTip: true, toolTipMessage: 'NavigationBar.FeatureSuggestionTooltip', faIcon: "fa-lightbulb-o" },
{ name: "NavigationBar.UserPreferences", icon: "person", link: "/user-preferences", requiresAdmin: false, enabled: true }, { name: "NavigationBar.Settings", icon: "settings", link: "/Settings/About", requiresAdmin: true, enabled: true, faIcon: null },
{ name: "NavigationBar.UserPreferences", icon: "person", link: "/user-preferences", requiresAdmin: false, enabled: true, faIcon: null },
]; ];
} }

@ -46,4 +46,8 @@ export class JobService extends ServiceHelpers {
public runNewsletter(): Observable<boolean> { public runNewsletter(): Observable<boolean> {
return this.http.post<boolean>(`${this.url}newsletter/`, {headers: this.headers}); 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>
<div class="form-group"> <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"> <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> <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> <button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('embyContentSync')?.value)">Test</button>
</div> </div>
<div class="form-group"> <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"> <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> <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> <button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('userImporter')?.value)">Test</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="userImporter" class="control-label">Newsletter</label> <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"> <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> <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> <button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('mediaDatabaseRefresh')?.value)">Test</button>
</div> </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>
<div class="form-group"> <div class="form-group">
<div> <div>

@ -1,7 +1,7 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { NotificationService, SettingsService } from "../../services"; import { NotificationService, SettingsService, JobService } from "../../services";
@Component({ @Component({
templateUrl: "./jobs.component.html", templateUrl: "./jobs.component.html",
@ -10,13 +10,14 @@ import { NotificationService, SettingsService } from "../../services";
export class JobsComponent implements OnInit { export class JobsComponent implements OnInit {
public form: FormGroup; public form: FormGroup;
public profilesRunning: boolean; public profilesRunning: boolean;
constructor(private readonly settingsService: SettingsService, constructor(private readonly settingsService: SettingsService,
private readonly fb: FormBuilder, private readonly fb: FormBuilder,
private readonly notificationService: NotificationService) { } private readonly notificationService: NotificationService,
private readonly jobsService: JobService) { }
public ngOnInit() { public ngOnInit() {
this.settingsService.getJobSettings().subscribe(x => { this.settingsService.getJobSettings().subscribe(x => {
this.form = this.fb.group({ this.form = this.fb.group({
@ -27,27 +28,28 @@ export class JobsComponent implements OnInit {
userImporter: [x.userImporter, Validators.required], userImporter: [x.userImporter, Validators.required],
sonarrSync: [x.sonarrSync, Validators.required], sonarrSync: [x.sonarrSync, Validators.required],
radarrSync: [x.radarrSync, Validators.required], radarrSync: [x.radarrSync, Validators.required],
sickRageSync: [x.sickRageSync, Validators.required], sickRageSync: [x.sickRageSync, Validators.required],
newsletter: [x.newsletter, Validators.required], newsletter: [x.newsletter, Validators.required],
plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required], plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required],
lidarrArtistSync: [x.lidarrArtistSync, Validators.required], lidarrArtistSync: [x.lidarrArtistSync, Validators.required],
issuesPurge: [x.issuesPurge, Validators.required], issuesPurge: [x.issuesPurge, Validators.required],
retryRequests: [x.retryRequests, Validators.required], retryRequests: [x.retryRequests, Validators.required],
mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required], mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required],
}); arrAvailabilityChecker: [x.arrAvailabilityChecker, Validators.required],
});
}); });
} }
public testCron(expression: string) { public testCron(expression: string) {
this.settingsService.testCron({ expression }).subscribe(x => { this.settingsService.testCron({ expression }).subscribe(x => {
if(x.success) { if(x.success) {
this.notificationService.success("Cron is Valid"); this.notificationService.success("Cron is Valid");
} else { } else {
this.notificationService.error(x.message); this.notificationService.error(x.message);
} }
}); });
} }
public onSubmit(form: FormGroup) { public onSubmit(form: FormGroup) {
if (form.invalid) { if (form.invalid) {
this.notificationService.error("Please check your entered values"); 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.Emby;
using Ombi.Schedule.Jobs.Ombi; using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex; using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Radarr;
using Quartz; using Quartz;
namespace Ombi.Controllers.V1 namespace Ombi.Controllers.V1
@ -134,6 +135,17 @@ namespace Ombi.Controllers.V1
return true; 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> /// <summary>
/// Runs the newsletter /// Runs the newsletter
/// </summary> /// </summary>

@ -67,7 +67,9 @@
"RecentlyAdded": "Recently Added", "RecentlyAdded": "Recently Added",
"ChangeTheme": "Change Theme", "ChangeTheme": "Change Theme",
"Calendar": "Calendar", "Calendar": "Calendar",
"UserPreferences": "Preferences" "UserPreferences": "Preferences",
"FeatureSuggestion":"Feature Suggestion",
"FeatureSuggestionTooltip":"Have a great new idea? Suggest it here!"
}, },
"Search": { "Search": {
"Title": "Search", "Title": "Search",

Loading…
Cancel
Save