Merge pull request #3049 from tidusjar/develop

Develop
pull/3061/head
Jamie 5 years ago committed by GitHub
commit 3b46f14c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,7 +15,7 @@ ___
Follow me developing Ombi!
[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://twitch.tv/tiusjar)
[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://www.twitch.tv/tidusjar)
___
@ -68,6 +68,7 @@ We integrate with the following applications:
Supported notifications:
* SMTP Notifications (Email)
* Discord
* Gotify
* Slack
* Pushbullet
* Pushover

@ -3,7 +3,7 @@
#addin "Cake.Gulp"
#addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator"
#addin "Cake.Incubator&version=3.1.0"
#addin "Cake.Yarn"
//////////////////////////////////////////////////////////////////////
@ -81,9 +81,9 @@ Task("SetVersionInfo")
versionInfo = GitVersion(settings);
Information("GitResults -> {0}", versionInfo.Dump());
// Information("GitResults -> {0}", versionInfo.Dump());
Information(@"Build:{0}",AppVeyor.Environment.Build.Dump());
//Information(@"Build:{0}",AppVeyor.Environment.Build.Dump());
var buildVersion = string.Empty;
if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version))

@ -16,6 +16,7 @@ namespace Ombi.Api.Emby.Models.Media.Tv
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public int IndexNumber { get; set; }
public int? IndexNumberEnd { get; set; }
public int ParentIndexNumber { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }

@ -0,0 +1,36 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public class GotifyApi : IGotifyApi
{
public GotifyApi(IApi api)
{
_api = api;
}
private readonly IApi _api;
public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority)
{
var request = new Request("/message", baseUrl, HttpMethod.Post);
request.AddQueryString("token", accessToken);
request.AddHeader("Access-Token", accessToken);
request.ApplicationJsonContentType();
var jsonBody = new
{
message = body,
title = subject,
priority = priority
};
request.AddJsonBody(jsonBody);
await _api.Request(request);
}
}
}

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public interface IGotifyApi
{
Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority);
}
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
<PackageVersion></PackageVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -23,5 +23,6 @@ namespace Ombi.Api.Lidarr
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
Task<LidarrStatus> Status(string apiKey, string baseUrl);
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
Task<AlbumByForeignId> AlbumInformation(string albumId, string apiKey, string baseUrl);
}
}

@ -84,7 +84,7 @@ namespace Ombi.Api.Lidarr
public Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId)
{
var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}",
var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.4/artist/{foreignArtistId}",
HttpMethod.Get) {IgnoreBaseUrlAppend = true};
return Api.Request<AlbumByArtistResponse>(request);
}
@ -105,6 +105,31 @@ namespace Ombi.Api.Lidarr
return Api.Request<List<AlbumResponse>>(request);
}
public async Task<AlbumByForeignId> AlbumInformation(string albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("foreignAlbumId", albumId);
AddHeaders(request, apiKey);
var albums = await Api.Request<List<AlbumByForeignId>>(request);
return albums.FirstOrDefault();
}
/// <summary>
/// THIS ONLY SUPPORTS ALBUMS THAT THE ARTIST IS IN LIDARR
/// </summary>
/// <param name="albumId"></param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public Task<List<LidarrTrack>> GetTracksForAlbum(int albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("albumId", albumId.ToString());
AddHeaders(request, apiKey);
return Api.Request<List<LidarrTrack>>(request);
}
public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post);

@ -0,0 +1,31 @@
using System;
using System.Net.Mime;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumByForeignId
{
public string title { get; set; }
public string disambiguation { get; set; }
public string overview { get; set; }
public int artistId { get; set; }
public string foreignAlbumId { get; set; }
public bool monitored { get; set; }
public bool anyReleaseOk { get; set; }
public int profileId { get; set; }
public int duration { get; set; }
public string albumType { get; set; }
public object[] secondaryTypes { get; set; }
public int mediumCount { get; set; }
public Ratings ratings { get; set; }
public DateTime releaseDate { get; set; }
public Release[] releases { get; set; }
public object[] genres { get; set; }
public Medium[] media { get; set; }
public Artist artist { get; set; }
public Image[] images { get; set; }
public Link[] links { get; set; }
public Statistics statistics { get; set; }
public int id { get; set; }
}
}

@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumLookup
{
public string title { get; set; }
public string status { get; set; }
public string artistType { get; set; }
public string disambiguation { get; set; }
public List<LidarrLinks> links { get; set; }
public int artistId { get; set; }
public string foreignAlbumId { get; set; }
public bool monitored { get; set; }

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrLinks
{
public string url { get; set; }
public string name { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class LidarrRatings
{
public int votes { get; set; }
public decimal value { get; set; }
}
}

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrTrack
{
public int artistId { get; set; }
public int trackFileId { get; set; }
public int albumId { get; set; }
public bool _explicit { get; set; }
public int absoluteTrackNumber { get; set; }
public string trackNumber { get; set; }
public string title { get; set; }
public int duration { get; set; }
public int mediumNumber { get; set; }
public bool hasFile { get; set; }
public bool monitored { get; set; }
public int id { get; set; }
}
}

@ -2,6 +2,7 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using Ombi.Api.Pushover.Models;
namespace Ombi.Api.Pushover
@ -18,11 +19,7 @@ namespace Ombi.Api.Pushover
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound)
{
if (message.Contains("'"))
{
message = message.Replace("'", "&#39;");
}
var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.UrlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
var result = await _api.Request<PushoverResponse>(request);
return result;

@ -72,6 +72,7 @@ namespace Ombi.Api
// do something with the response
var receivedString = await httpResponseMessage.Content.ReadAsStringAsync();
LogDebugContent(receivedString);
if (request.ContentType == ContentType.Json)
{
request.OnBeforeDeserialization?.Invoke(receivedString);
@ -110,7 +111,7 @@ namespace Ombi.Api
}
// do something with the response
var data = httpResponseMessage.Content;
await LogDebugContent(httpResponseMessage);
return await data.ReadAsStringAsync();
}
@ -122,6 +123,7 @@ namespace Ombi.Api
{
AddHeadersBody(request, httpRequestMessage);
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
await LogDebugContent(httpResponseMessage);
if (!httpResponseMessage.IsSuccessStatusCode)
{
if (!request.IgnoreErrors)
@ -132,11 +134,12 @@ namespace Ombi.Api
}
}
private static void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage)
private void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage)
{
// Add the Json Body
if (request.JsonBody != null)
{
LogDebugContent("REQUEST: " + request.JsonBody);
httpRequestMessage.Content = new JsonContent(request.JsonBody);
httpRequestMessage.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header
@ -153,11 +156,24 @@ namespace Ombi.Api
{
Logger.LogError(LoggingEvents.Api,
$"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}");
await LogDebugContent(httpResponseMessage);
}
private async Task LogDebugContent(HttpResponseMessage message)
{
if (Logger.IsEnabled(LogLevel.Debug))
{
var content = await httpResponseMessage.Content.ReadAsStringAsync();
var content = await message.Content.ReadAsStringAsync();
Logger.LogDebug(content);
}
}
private void LogDebugContent(string message)
{
if (Logger.IsEnabled(LogLevel.Debug))
{
Logger.LogDebug(message);
}
}
}
}

@ -11,7 +11,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Polly" Version="6.1.0" />
<PackageReference Include="Polly" Version="7.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>

@ -4,6 +4,7 @@ using Moq;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Store.Entities.Requests;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Helpers;
namespace Ombi.Core.Tests.Rule.Request
@ -16,7 +17,7 @@ namespace Ombi.Core.Tests.Rule.Request
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new AutoApproveRule(PrincipalMock.Object);
Rule = new AutoApproveRule(PrincipalMock.Object, null);
}

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers;
using Ombi.Store.Entities.Requests;
@ -15,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Request
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new CanRequestRule(PrincipalMock.Object);
Rule = new CanRequestRule(PrincipalMock.Object, null);
}

@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IEmbyContentRepository>();
Rule = new EmbyAvailabilityRule(ContextMock.Object);
Rule = new EmbyAvailabilityRule(ContextMock.Object, null);
}
private EmbyAvailabilityRule Rule { get; set; }

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Ombi.Core.Models.Search;
@ -14,7 +15,7 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IPlexContentRepository>();
Rule = new PlexAvailabilityRule(ContextMock.Object);
Rule = new PlexAvailabilityRule(ContextMock.Object, new Mock<ILogger<PlexAvailabilityRule>>().Object);
}
private PlexAvailabilityRule Rule { get; set; }

@ -12,5 +12,6 @@ namespace Ombi.Core.Engine
Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId);
Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search);
Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search);
Task<SearchAlbumViewModel> GetAlbumInformation(string foreignAlbumId);
}
}

@ -227,7 +227,7 @@ namespace Ombi.Core.Engine
}
var request = await RequestService.MovieRequestService.GetAll()
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
if (request)
if (request || viewModel.Available)
{
viewModel.ShowSubscribe = false;
}

@ -60,6 +60,18 @@ namespace Ombi.Core.Engine
return vm;
}
public async Task<SearchAlbumViewModel> GetAlbumInformation(string foreignAlbumId)
{
var settings = await GetSettings();
var result = await _lidarrApi.AlbumInformation(foreignAlbumId, settings.ApiKey, settings.FullUri);
var vm = await MapIntoAlbumVm(result, settings);
return vm;
}
/// <summary>
/// Searches the specified artist
/// </summary>
@ -143,6 +155,47 @@ namespace Ombi.Core.Engine
return vm;
}
// TODO
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumByForeignId a, LidarrSettings settings)
{
var vm = new SearchAlbumViewModel
{
ForeignAlbumId = a.foreignAlbumId,
Monitored = a.monitored,
Rating = a.ratings?.value ?? 0m,
ReleaseDate = a.releaseDate,
Title = a.title,
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"),
Genres = a.genres,
AlbumType = a.albumType,
ArtistName = a.artist.artistName,
ForeignArtistId = a.artist.foreignArtistId,
};
if (a.artistId > 0)
{
//TODO THEY HAVE FIXED THIS IN DEV
// The JSON is different for some stupid reason
// Need to lookup the artist now and all the images -.-"
var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri);
vm.ArtistName = artist.artistName;
vm.ForeignArtistId = artist.foreignArtistId;
}
else
{
//vm.ForeignArtistId = a.artistId?.foreignArtistId;
//vm.ArtistName = a.artist?.artistName;
}
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https");
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
await RunSearchRules(vm);
return vm;
}
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings)
{
var vm = new SearchAlbumViewModel
@ -152,7 +205,8 @@ namespace Ombi.Core.Engine
Rating = a.ratings?.value ?? 0m,
ReleaseDate = a.releaseDate,
Title = a.title,
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.Replace("http", "https"),
Genres = a.genres
};
if (a.artistId > 0)
{
@ -169,7 +223,7 @@ namespace Ombi.Core.Engine
vm.ArtistName = a.artist?.artistName;
}
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url;
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.Replace("http", "https");
if (vm.Cover.IsNullOrEmpty())
{
vm.Cover = a.remoteCover;

@ -31,14 +31,13 @@ namespace Ombi.Core.Engine
{
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
ITvSender sender, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{
TvApi = tvApi;
MovieDbApi = movApi;
NotificationHelper = helper;
TvSender = sender;
Audit = audit;
_requestLog = rl;
}
@ -46,7 +45,6 @@ namespace Ombi.Core.Engine
private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; }
private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
@ -84,8 +82,6 @@ namespace Ombi.Core.Engine
}
}
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null)
{
@ -351,7 +347,6 @@ namespace Ombi.Core.Engine
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
@ -394,7 +389,6 @@ namespace Ombi.Core.Engine
if (request.Approved)
{
NotificationHelper.Notify(request, NotificationType.RequestApproved);
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
// Autosend
await TvSender.Send(request);
}
@ -426,9 +420,7 @@ namespace Ombi.Core.Engine
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
await TvRepository.UpdateChild(request);
await TvRepository.UpdateChild(request);
return request;
}
@ -446,7 +438,6 @@ namespace Ombi.Core.Engine
// Delete the parent
TvRepository.Db.TvRequests.Remove(parent);
}
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Db.SaveChangesAsync();
}
@ -454,8 +445,7 @@ namespace Ombi.Core.Engine
public async Task RemoveTvRequest(int requestId)
{
var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId);
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Delete(request);
await TvRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)

@ -16,8 +16,13 @@ namespace Ombi.Core.Models.Search
public string Cover { get; set; }
public string Disk { get; set; }
public decimal PercentOfTracks { get; set; }
public object[] Genres { get; set; }
public override RequestType Type => RequestType.Album;
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
public bool FullyAvailable => PercentOfTracks == 100;
// Below is from the INFO call NEED A SEPERATE VM FOR THIS IN V4 TODO
// TODO ADD TRACK COUNT
}
}

@ -0,0 +1,23 @@

using System.Collections.Generic;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
/// <summary>
/// The view model for the notification settings page
/// </summary>
/// <seealso cref="GotifyNotificationSettings" />
public class GotifyNotificationViewModel : GotifySettings
{
/// <summary>
/// Gets or sets the notification templates.
/// </summary>
/// <value>
/// The notification templates.
/// </value>
public List<NotificationTemplates> NotificationTemplates { get; set; }
}
}

@ -11,9 +11,9 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.21" />
<PackageReference Include="Hangfire" Version="1.6.22" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.2" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />

@ -1,5 +1,7 @@
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request
{
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
{
public AutoApproveRule(IPrincipal principal)
public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
{
User = principal;
_manager = um;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
{
obj.Approved = true;
return Task.FromResult(Success());
return Success();
}
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie))
if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
obj.Approved = true;
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
obj.Approved = true;
if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic))
if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
obj.Approved = true;
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
return Success(); // We don't really care, we just don't set the obj to approve
}
}
}

@ -1,46 +1,62 @@
using Ombi.Store.Entities;
using Ombi.Store.Entities;
using System.IO;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Rule.Rules
namespace Ombi.Core.Rule.Rules.Request
{
public class CanRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public CanRequestRule(IPrincipal principal)
public CanRequestRule(IPrincipal principal, OmbiUserManager manager)
{
User = principal;
_manager = manager;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
return Task.FromResult(Success());
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
return Success();
if (obj.RequestType == RequestType.Movie)
{
if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie))
return Task.FromResult(Success());
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
return Success();
return Fail("You do not have permissions to Request a Movie");
}
if (obj.RequestType == RequestType.TvShow)
{
if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
{
return Success();
}
return Fail("You do not have permissions to Request a TV Show");
}
if (obj.RequestType == RequestType.Album)
{
if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
{
return Success();
}
return Fail("You do not have permissions to Request an Album");
}
return Task.FromResult(Fail("You do not have permissions to Request a TV Show"));
throw new InvalidDataException("Permission check failed: unknown RequestType");
}
}
}

@ -2,7 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
@ -24,7 +24,7 @@ namespace Ombi.Core.Rule.Rules.Search
if (!airedButNotAvailable)
{
var unairedEpisodes = search.SeasonRequests.Any(x =>
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date));
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date || c.AirDate != DateTime.MinValue));
if (unairedEpisodes)
{
search.FullyAvailable = true;
@ -34,28 +34,36 @@ namespace Ombi.Core.Rule.Rules.Search
}
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<PlexEpisode> allEpisodes, EpisodeRequests episode,
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb)
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log)
{
PlexEpisode epExists = null;
if (useImdb)
try
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId.ToString());
}
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
}
if (useImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId);
}
if (useTvDb)
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId);
}
}
catch (Exception e)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
log.LogError(e, "Exception thrown when attempting to check if something is available");
}
if (epExists != null)
@ -71,21 +79,21 @@ namespace Ombi.Core.Rule.Rules.Search
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ImdbId == item.ImdbId.ToString());
x.Series.ImdbId == item.ImdbId);
}
if (useTheMovieDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
x.Series.TheMovieDbId == item.TheMovieDbId);
}
if (useTvDb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.TvDbId == item.TvDbId.ToString());
x.Series.TvDbId == item.TvDbId);
}
if (epExists != null)

@ -3,6 +3,8 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -11,12 +13,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public EmbyAvailabilityRule(IEmbyContentRepository repo)
public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
{
EmbyContentRepository = repo;
EmbySettings = s;
}
private IEmbyContentRepository EmbyContentRepository { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -60,7 +64,16 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null)
{
obj.Available = true;
obj.EmbyUrl = item.Url;
var s = await EmbySettings.GetSettingsAsync();
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
}
else
{
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
}
if (obj.Type == RequestType.TvShow)
{

@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,12 +11,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class PlexAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public PlexAvailabilityRule(IPlexContentRepository repo)
public PlexAvailabilityRule(IPlexContentRepository repo, ILogger<PlexAvailabilityRule> log)
{
PlexContentRepository = repo;
Log = log;
}
private IPlexContentRepository PlexContentRepository { get; }
private ILogger Log { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -72,7 +75,7 @@ namespace Ombi.Core.Rule.Rules.Search
{
foreach (var episode in season.Episodes)
{
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log);
}
}

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EnsureThat;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
@ -87,6 +88,11 @@ namespace Ombi.Core.Senders
if (artist == null || artist.id <= 0)
{
EnsureArg.IsNotNullOrEmpty(model.ForeignArtistId, nameof(model.ForeignArtistId));
EnsureArg.IsNotNullOrEmpty(model.ForeignAlbumId, nameof(model.ForeignAlbumId));
EnsureArg.IsNotNullOrEmpty(model.ArtistName, nameof(model.ArtistName));
EnsureArg.IsNotNullOrEmpty(rootFolderPath, nameof(rootFolderPath));
// Create artist
var newArtist = new ArtistAdd
{

@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato;
using Ombi.Api.DogNzb;
using Ombi.Api.FanartTv;
using Ombi.Api.Github;
using Ombi.Api.Gotify;
using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost;
using Ombi.Api.Notifications;
@ -60,6 +61,7 @@ using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.SickRage;
using Ombi.Schedule.Processor;
using Ombi.Store.Entities;
using Quartz.Spi;
namespace Ombi.DependencyInjection
{
@ -120,6 +122,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IOmbiService, OmbiService>();
services.AddTransient<IFanartTvApi, FanartTvApi>();
services.AddTransient<IPushoverApi, PushoverApi>();
services.AddTransient<IGotifyApi, GotifyApi>();
services.AddTransient<IMattermostApi, MattermostApi>();
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
services.AddTransient<IDogNzbApi, DogNzbApi>();
@ -132,28 +135,28 @@ namespace Ombi.DependencyInjection
}
public static void RegisterStore(this IServiceCollection services) {
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
services.AddEntityFrameworkSqlite().AddDbContext<SettingsContext>();
services.AddEntityFrameworkSqlite().AddDbContext<ExternalContext>();
services.AddDbContext<OmbiContext>();
services.AddDbContext<SettingsContext>();
services.AddDbContext<ExternalContext>();
services.AddScoped<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<ISettingsContext, SettingsContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<IExternalContext, ExternalContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexServerContentRepository>();
services.AddTransient<IEmbyContentRepository, EmbyContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddScoped<ISettingsRepository, SettingsJsonRepository>();
services.AddScoped<ISettingsResolver, SettingsResolver>();
services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>();
services.AddTransient<IAuditRepository, AuditRepository>();
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddTransient<ITokenRepository, TokenRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
services.AddScoped<IMusicRequestRepository, MusicRequestRepository>();
services.AddScoped<IAuditRepository, AuditRepository>();
services.AddScoped<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddScoped<ITokenRepository, TokenRepository>();
services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
}
public static void RegisterServices(this IServiceCollection services)
{
@ -161,7 +164,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<IEmailProvider, GenericEmailProvider>();
services.AddTransient<INotificationHelper, NotificationHelper>();
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ICacheService, CacheService>();
services.AddTransient<IDiscordNotification, DiscordNotification>();
services.AddTransient<IEmailNotification, EmailNotification>();
@ -170,6 +173,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISlackNotification, SlackNotification>();
services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<IGotifyNotification, GotifyNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
@ -177,6 +181,7 @@ namespace Ombi.DependencyInjection
public static void RegisterJobs(this IServiceCollection services)
{
services.AddSingleton<IJobFactory, IoCJobFactory>(provider => new IoCJobFactory(provider));
services.AddTransient<IBackgroundJobClient, BackgroundJobClient>();
services.AddTransient<IPlexContentSync, PlexContentSync>();
@ -197,7 +202,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISickRageSync, SickRageSync>();
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
services.AddTransient<INewsletterJob, NewsletterJob>();
services.AddTransient<IPlexRecentlyAddedSync, PlexRecentlyAddedSync>();
//services.AddTransient<IPlexRecentlyAddedSync, PlexRecentlyAddedSync>();
services.AddTransient<ILidarrAlbumSync, LidarrAlbumSync>();
services.AddTransient<ILidarrArtistSync, LidarrArtistSync>();
services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>();

@ -28,18 +28,15 @@ namespace Ombi.Helpers
return result;
}
using (await _mutex.LockAsync())
if (_memoryCache.TryGetValue(cacheKey, out result))
{
if (_memoryCache.TryGetValue(cacheKey, out result))
{
return result;
}
result = await factory();
_memoryCache.Set(cacheKey, result, absoluteExpiration);
return result;
}
result = await factory();
_memoryCache.Set(cacheKey, result, absoluteExpiration);
return result;
}
public void Remove(string key)
@ -49,32 +46,32 @@ namespace Ombi.Helpers
public T GetOrAdd<T>(string cacheKey, Func<T> factory, DateTime absoluteExpiration)
public T GetOrAdd<T>(string cacheKey, Func<T> factory, DateTime absoluteExpiration)
{
// locks get and set internally
if (_memoryCache.TryGetValue<T>(cacheKey, out var result))
{
return result;
}
lock (TypeLock<T>.Lock)
{
// locks get and set internally
if (_memoryCache.TryGetValue<T>(cacheKey, out var result))
if (_memoryCache.TryGetValue(cacheKey, out result))
{
return result;
}
lock (TypeLock<T>.Lock)
{
if (_memoryCache.TryGetValue(cacheKey, out result))
{
return result;
}
result = factory();
_memoryCache.Set(cacheKey, result, absoluteExpiration);
result = factory();
_memoryCache.Set(cacheKey, result, absoluteExpiration);
return result;
}
return result;
}
}
private static class TypeLock<T>
{
public static object Lock { get; } = new object();
}
private static class TypeLock<T>
{
public static object Lock { get; } = new object();
}
}
}

@ -16,7 +16,6 @@
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
/// <summary>
/// Helper class that provides common values for the cron expressions.
/// </summary>
@ -44,7 +43,7 @@
/// <param name="minute">The minute in which the schedule will be activated (0-59).</param>
public static string Hourly(int minute)
{
return $"{minute} * * * *";
return $"0 {minute} 0/1 1/1 * ? *";
}
/// <summary>
@ -73,7 +72,7 @@
/// <param name="minute">The minute in which the schedule will be activated (0-59).</param>
public static string Daily(int hour, int minute)
{
return $"{minute} {hour} * * *";
return $"0 {minute} {hour} 1/1 * ? *";
}
/// <summary>
@ -114,7 +113,7 @@
/// <param name="minute">The minute in which the schedule will be activated (0-59).</param>
public static string Weekly(DayOfWeek dayOfWeek, int hour, int minute)
{
return $"{minute} {hour} * * {(int)dayOfWeek}";
return $"0 {minute} {hour} ? * {(int)dayOfWeek} *";
}
/// <summary>
@ -219,7 +218,7 @@
/// <param name="interval">The number of minutes to wait between every activation.</param>
public static string MinuteInterval(int interval)
{
return $"*/{interval} * * * *";
return $"0 0/{interval} * 1/1 * ? *";
}
/// <summary>
@ -228,7 +227,7 @@
/// <param name="interval">The number of hours to wait between every activation.</param>
public static string HourInterval(int interval)
{
return $"0 */{interval} * * *";
return $"0 0 0/{interval} 1/1 * ? *";
}
/// <summary>
@ -237,7 +236,7 @@
/// <param name="interval">The number of days to wait between every activation.</param>
public static string DayInterval(int interval)
{
return $"0 0 */{interval} * *";
return $"0 0 12 1/{interval} * ? *";
}
/// <summary>
@ -249,4 +248,39 @@
return $"0 0 1 */{interval} *";
}
}
//
// Summary:
// Specifies the day of the week.
public enum DayOfWeek
{
//
// Summary:
// Indicates Sunday.
Sunday = 1,
//
// Summary:
// Indicates Monday.
Monday = 2,
//
// Summary:
// Indicates Tuesday.
Tuesday = 3,
//
// Summary:
// Indicates Wednesday.
Wednesday = 4,
//
// Summary:
// Indicates Thursday.
Thursday = 5,
//
// Summary:
// Indicates Friday.
Friday = 6,
//
// Summary:
// Indicates Saturday.
Saturday = 7
}
}

@ -32,6 +32,7 @@ namespace Ombi.Helpers
public static EventId MattermostNotification => new EventId(4004);
public static EventId PushoverNotification => new EventId(4005);
public static EventId TelegramNotifcation => new EventId(4006);
public static EventId GotifyNotification => new EventId(4007);
public static EventId TvSender => new EventId(5000);
public static EventId SonarrSender => new EventId(5001);

@ -10,5 +10,6 @@
Slack = 5,
Mattermost = 6,
Mobile = 7,
Gotify = 8,
}
}

@ -19,6 +19,7 @@ namespace Ombi.Mapping.Profiles
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
}
}
}

@ -0,0 +1,116 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Gotify;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications.Agents
{
public class GotifyNotification : BaseNotification<GotifySettings>, IGotifyNotification
{
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
{
Api = api;
Logger = log;
}
public override string NotificationName => "GotifyNotification";
private IGotifyApi Api { get; }
private ILogger<GotifyNotification> Logger { get; }
protected override bool ValidateConfiguration(GotifySettings settings)
{
return settings.Enabled && !string.IsNullOrEmpty(settings.BaseUrl) && !string.IsNullOrEmpty(settings.ApplicationToken);
}
protected override async Task NewRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.NewRequest);
}
protected override async Task NewIssue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.Issue);
}
protected override async Task IssueComment(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueComment);
}
protected override async Task IssueResolved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueResolved);
}
protected override async Task AddedToRequestQueue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
}
protected override async Task RequestDeclined(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestDeclined);
}
protected override async Task RequestApproved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestApproved);
}
protected override async Task AvailableRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestAvailable);
}
protected override async Task Send(NotificationMessage model, GotifySettings settings)
{
try
{
await Api.PushAsync(settings.BaseUrl, settings.ApplicationToken, model.Subject, model.Message, settings.Priority);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.GotifyNotification, e, "Failed to send Gotify notification");
}
}
protected override async Task Test(NotificationOptions model, GotifySettings settings)
{
var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
private async Task Run(NotificationOptions model, GotifySettings settings, NotificationType type)
{
var parsed = await LoadTemplate(NotificationAgent.Gotify, type, model);
if (parsed.Disabled)
{
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Gotify}");
return;
}
var notification = new NotificationMessage
{
Message = parsed.Message,
};
await Send(notification, settings);
}
}
}

@ -0,0 +1,6 @@
namespace Ombi.Notifications.Agents
{
public interface IGotifyNotification : INotification
{
}
}

@ -52,7 +52,7 @@ namespace Ombi.Notifications.Agents
private void AddOtherInformation(NotificationOptions model, NotificationMessage notification,
NotificationMessageContent parsed)
{
notification.Other.Add("image", parsed.Image);
notification.Other.Add("image", parsed?.Image ?? string.Empty);
notification.Other.Add("title", model.RequestType == RequestType.Movie ? MovieRequest.Title : TvRequest.Title);
}

@ -30,6 +30,7 @@ namespace Ombi.Notifications
_log = log;
AlbumRepository = album;
UserNotificationPreferences = notificationUserPreferences;
Settings.ClearCache();
}
protected ISettingsService<T> Settings { get; }

@ -4,6 +4,7 @@ using EnsureThat;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Logging;
using MimeKit;
using MimeKit.Utils;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
@ -38,6 +39,15 @@ namespace Ombi.Notifications
var customization = await CustomizationSettings.GetSettingsAsync();
var html = email.LoadTemplate(model.Subject, model.Message, null, customization.Logo);
var messageId = MimeUtils.GenerateMessageId();
if (customization.ApplicationUrl.HasValue())
{
if (Uri.TryCreate(customization.ApplicationUrl, UriKind.RelativeOrAbsolute, out var url))
{
messageId = MimeUtils.GenerateMessageId(url.IdnHost);
}
}
var textBody = string.Empty;
model.Other.TryGetValue("PlainTextBody", out textBody);
@ -50,7 +60,8 @@ namespace Ombi.Notifications
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = model.Subject
Subject = model.Subject,
MessageId = messageId
};
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
message.To.Add(new MailboxAddress(model.To, model.To));

@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Api.Gotify\Ombi.Api.Gotify.csproj" />
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />

@ -32,7 +32,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task DoesNotRun_WhenDisabled()
{
await Job.Start();
await Job.Execute(null);
Repo.Verify(x => x.GetAll(),Times.Never);
}
@ -50,7 +50,7 @@ namespace Ombi.Schedule.Tests
Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 });
Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Issues>(issues));
await Job.Start();
await Job.Execute(null);
Assert.That(issues.First().Status, Is.EqualTo(IssueStatus.Deleted));
Repo.Verify(x => x.SaveChangesAsync(), Times.Once);
@ -75,7 +75,7 @@ namespace Ombi.Schedule.Tests
Settings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new IssueSettings { DeleteIssues = true, DaysAfterResolvedToDelete = 5 });
Repo.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Issues>(issues));
await Job.Start();
await Job.Execute(null);
Assert.That(issues[0].Status, Is.Not.EqualTo(IssueStatus.Deleted));
Assert.That(issues[1].Status, Is.EqualTo(IssueStatus.Deleted));

@ -0,0 +1,36 @@
using Moq;
using NUnit.Framework;
using Quartz;
using System.Threading;
using System.Threading.Tasks;
namespace Ombi.Schedule.Tests
{
[TestFixture]
public class OmbiQuartzTests
{
[Test]
[Ignore("Cannot get this to work")]
public async Task Test()
{
var scheduleMock = new Mock<IScheduler>();
scheduleMock.Setup(x => x.TriggerJob(It.IsAny<JobKey>(),
It.IsAny<CancellationToken>()));
var sut = new QuartzMock(scheduleMock);
//await QuartzMock.TriggerJob("ABC");
scheduleMock.Verify(x => x.TriggerJob(It.Is<JobKey>(j => j.Name == "ABC"),
default(CancellationToken)), Times.Once);
}
}
public class QuartzMock : OmbiQuartz
{
public QuartzMock(Mock<IScheduler> mock)
{
_instance = this;
_scheduler = mock.Object;
}
}
}

@ -46,7 +46,7 @@ namespace Ombi.Schedule.Tests
_movie.Setup(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
_repo.Setup(x => x.Get("test")).ReturnsAsync(new PlexServerContent());
await Checker.Start();
await Checker.Execute(null);
_movie.Verify(x => x.Save(), Times.Once);
@ -62,7 +62,7 @@ namespace Ombi.Schedule.Tests
};
_movie.Setup(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
await Checker.Start();
await Checker.Execute(null);
Assert.False(request.Available);
}
@ -107,7 +107,7 @@ namespace Ombi.Schedule.Tests
}.AsQueryable);
_repo.Setup(x => x.Include(It.IsAny<IQueryable<PlexEpisode>>(),It.IsAny<Expression<Func<PlexEpisode, PlexServerContent>>>()));
await Checker.Start();
await Checker.Execute(null);
_tv.Verify(x => x.Save(), Times.Once);

@ -0,0 +1,32 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
namespace Ombi.Schedule
{
public class IoCJobFactory : IJobFactory
{
private readonly IServiceProvider _factory;
public IoCJobFactory(IServiceProvider factory)
{
_factory = factory;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var scopeFactory = _factory.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
var scopedContainer = scope.ServiceProvider;
var implementation = scopedContainer.GetRequiredService(bundle.JobDetail.JobType) as IJob;
return implementation;
}
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
}

@ -65,25 +65,24 @@ namespace Ombi.Schedule
{
var s = _jobSettings.GetSettings();
RecurringJob.AddOrUpdate(() => _embyContentSync.Start(), JobSettingsHelper.EmbyContent(s));
RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s));
RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(false), JobSettingsHelper.PlexContent(s));
RecurringJob.AddOrUpdate(() => _plexRecentlyAddedSync.Start(), JobSettingsHelper.PlexRecentlyAdded(s));
RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s));
RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s));
RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s));
RecurringJob.AddOrUpdate(() => _lidarrArtistSync.CacheContent(), JobSettingsHelper.LidarrArtistSync(s));
RecurringJob.AddOrUpdate(() => _issuesPurge.Start(), JobSettingsHelper.IssuePurge(s));
// RecurringJob.AddOrUpdate(() => _embyContentSync.Start(), JobSettingsHelper.EmbyContent(s));
// RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s));
// RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
// //RecurringJob.AddOrUpdate(() => _plexContentSync.Execute(null), JobSettingsHelper.PlexContent(s));
// //RecurringJob.AddOrUpdate(() => _plexRecentlyAddedSync.Start(), JobSettingsHelper.PlexRecentlyAdded(s));
// RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s));
// RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s));
// RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s));
// RecurringJob.AddOrUpdate(() => _lidarrArtistSync.CacheContent(), JobSettingsHelper.LidarrArtistSync(s));
// RecurringJob.AddOrUpdate(() => _issuesPurge.Start(), JobSettingsHelper.IssuePurge(s));
RecurringJob.AddOrUpdate(() => _updater.Update(null), JobSettingsHelper.Updater(s));
// RecurringJob.AddOrUpdate(() => _updater.Update(null), JobSettingsHelper.Updater(s));
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s));
RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s));
// RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
// RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
// RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
//// RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s));
// RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s));
}
private bool _disposed;
@ -94,7 +93,6 @@ namespace Ombi.Schedule
if (disposing)
{
_plexContentSync?.Dispose();
_radarrSync?.Dispose();
_updater?.Dispose();
_plexUserImporter?.Dispose();

@ -36,6 +36,7 @@ using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Quartz;
namespace Ombi.Schedule.Jobs.Couchpotato
{
@ -56,7 +57,7 @@ namespace Ombi.Schedule.Jobs.Couchpotato
private readonly ILogger<CouchPotatoSync> _log;
private readonly IExternalContext _ctx;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var settings = await _settings.GetSettingsAsync();
if (!settings.Enabled)
@ -71,7 +72,11 @@ namespace Ombi.Schedule.Jobs.Couchpotato
if (movies != null)
{
// Let's remove the old cached data
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM CouchPotatoCache");
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM CouchPotatoCache");
tran.Commit();
}
// Save
var movieIds = new List<CouchPotatoCache>();
@ -91,9 +96,14 @@ namespace Ombi.Schedule.Jobs.Couchpotato
_log.LogError("TMDBId is not > 0 for movie {0}", m.title);
}
}
await _ctx.CouchPotatoCache.AddRangeAsync(movieIds);
await _ctx.SaveChangesAsync();
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.CouchPotatoCache.AddRangeAsync(movieIds);
await _ctx.SaveChangesAsync();
tran.Commit();
}
}
}
catch (Exception e)

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Couchpotato
{
public interface ICouchPotatoSync : IBaseJob
{
Task Start();
}
}

@ -37,6 +37,7 @@ using Ombi.Notifications.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Quartz;
namespace Ombi.Schedule.Jobs.Emby
{
@ -58,7 +59,7 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly INotificationService _notificationService;
private readonly ILogger<EmbyAvaliabilityChecker> _log;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
await ProcessMovies();
await ProcessTv();

@ -12,6 +12,7 @@ using Ombi.Helpers;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using Serilog;
using EmbyMediaType = Ombi.Store.Entities.EmbyMediaType;
@ -20,25 +21,21 @@ namespace Ombi.Schedule.Jobs.Emby
public class EmbyContentSync : IEmbyContentSync
{
public EmbyContentSync(ISettingsService<EmbySettings> settings, IEmbyApi api, ILogger<EmbyContentSync> logger,
IEmbyContentRepository repo, IEmbyEpisodeSync epSync, IRefreshMetadata metadata)
IEmbyContentRepository repo)
{
_logger = logger;
_settings = settings;
_api = api;
_repo = repo;
_episodeSync = epSync;
_metadata = metadata;
}
private readonly ILogger<EmbyContentSync> _logger;
private readonly ISettingsService<EmbySettings> _settings;
private readonly IEmbyApi _api;
private readonly IEmbyContentRepository _repo;
private readonly IEmbyEpisodeSync _episodeSync;
private readonly IRefreshMetadata _metadata;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var embySettings = await _settings.GetSettingsAsync();
if (!embySettings.Enable)
@ -57,8 +54,9 @@ namespace Ombi.Schedule.Jobs.Emby
}
// Episodes
BackgroundJob.Enqueue(() => _episodeSync.Start());
BackgroundJob.Enqueue(() => _metadata.Start());
await OmbiQuartz.TriggerJob(nameof(IEmbyEpisodeSync), "Emby");
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
}

@ -36,29 +36,27 @@ using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Schedule.Jobs.Emby
{
public class EmbyEpisodeSync : IEmbyEpisodeSync
{
public EmbyEpisodeSync(ISettingsService<EmbySettings> s, IEmbyApi api, ILogger<EmbyEpisodeSync> l, IEmbyContentRepository repo,
IEmbyAvaliabilityChecker checker)
public EmbyEpisodeSync(ISettingsService<EmbySettings> s, IEmbyApi api, ILogger<EmbyEpisodeSync> l, IEmbyContentRepository repo)
{
_api = api;
_logger = l;
_settings = s;
_repo = repo;
_avaliabilityChecker = checker;
}
private readonly ISettingsService<EmbySettings> _settings;
private readonly IEmbyApi _api;
private readonly ILogger<EmbyEpisodeSync> _logger;
private readonly IEmbyContentRepository _repo;
private readonly IEmbyAvaliabilityChecker _avaliabilityChecker;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var settings = await _settings.GetSettingsAsync();
@ -67,7 +65,8 @@ namespace Ombi.Schedule.Jobs.Emby
await CacheEpisodes(server);
}
BackgroundJob.Enqueue(() => _avaliabilityChecker.Start());
await OmbiQuartz.TriggerJob(nameof(IEmbyAvaliabilityChecker), "Emby");
}
private async Task CacheEpisodes(EmbyServers server)
@ -118,6 +117,22 @@ namespace Ombi.Schedule.Jobs.Emby
Title = ep.Name,
AddedAt = DateTime.UtcNow
});
if (ep.IndexNumberEnd.HasValue && ep.IndexNumberEnd.Value != ep.IndexNumber)
{
epToAdd.Add(new EmbyEpisode
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumberEnd.Value,
SeasonNumber = ep.ParentIndexNumber,
ParentId = ep.SeriesId,
TvDbId = ep.ProviderIds.Tvdb,
TheMovieDbId = ep.ProviderIds.Tmdb,
ImdbId = ep.ProviderIds.Imdb,
Title = ep.Name,
AddedAt = DateTime.UtcNow
});
}
}
}
@ -142,7 +157,6 @@ namespace Ombi.Schedule.Jobs.Emby
{
_settings?.Dispose();
_repo?.Dispose();
_avaliabilityChecker?.Dispose();
}
_disposed = true;
}

@ -37,6 +37,7 @@ using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Quartz;
namespace Ombi.Schedule.Jobs.Emby
{
@ -58,7 +59,7 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly ISettingsService<EmbySettings> _embySettings;
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
if (!userManagementSettings.ImportEmbyUsers)

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby
{
public interface IEmbyAvaliabilityChecker : IBaseJob
{
Task Start();
}
}

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby
{
public interface IEmbyContentSync : IBaseJob
{
Task Start();
}
}

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby
{
public interface IEmbyEpisodeSync : IBaseJob
{
Task Start();
}
}

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Emby
{
public interface IEmbyUserImporter : IBaseJob
{
Task Start();
}
}

@ -25,11 +25,12 @@
// ************************************************************************/
#endregion
using Quartz;
using System;
namespace Ombi.Schedule
{
public interface IBaseJob : IDisposable
public interface IBaseJob : IJob, IDisposable
{
}

@ -1,12 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Entities;
using Quartz;
namespace Ombi.Schedule.Jobs.Lidarr
{
public interface ILidarrArtistSync
public interface ILidarrArtistSync : IJob
{
Task CacheContent();
void Dispose();
Task<IEnumerable<LidarrArtistCache>> GetCachedContent();
}

@ -48,7 +48,11 @@ namespace Ombi.Schedule.Jobs.Lidarr
if (albums != null && albums.Any())
{
// Let's remove the old cached data
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache");
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache");
tran.Commit();
}
var albumCache = new List<LidarrAlbumCache>();
foreach (var a in albums)
@ -60,7 +64,7 @@ namespace Ombi.Schedule.Jobs.Lidarr
ArtistId = a.artistId,
ForeignAlbumId = a.foreignAlbumId,
ReleaseDate = a.releaseDate,
TrackCount = a.currentRelease.trackCount,
TrackCount = a.currentRelease?.trackCount ?? 0,
Monitored = a.monitored,
Title = a.title,
PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m,
@ -68,9 +72,14 @@ namespace Ombi.Schedule.Jobs.Lidarr
});
}
}
await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache);
await _ctx.SaveChangesAsync();
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache);
await _ctx.SaveChangesAsync();
tran.Commit();
}
}
}
catch (System.Exception ex)

@ -11,6 +11,7 @@ using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Quartz;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Schedule.Jobs.Lidarr
@ -35,7 +36,7 @@ namespace Ombi.Schedule.Jobs.Lidarr
private readonly IBackgroundJobClient _job;
private readonly ILidarrAlbumSync _albumSync;
public async Task CacheContent()
public async Task Execute(IJobExecutionContext job)
{
try
{
@ -48,7 +49,11 @@ namespace Ombi.Schedule.Jobs.Lidarr
if (artists != null && artists.Any())
{
// Let's remove the old cached data
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrArtistCache");
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrArtistCache");
tran.Commit();
}
var artistCache = new List<LidarrArtistCache>();
foreach (var a in artists)
@ -64,9 +69,14 @@ namespace Ombi.Schedule.Jobs.Lidarr
});
}
}
await _ctx.LidarrArtistCache.AddRangeAsync(artistCache);
await _ctx.SaveChangesAsync();
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.LidarrArtistCache.AddRangeAsync(artistCache);
await _ctx.SaveChangesAsync();
tran.Commit();
}
}
}
catch (Exception ex)

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public interface IIssuesPurge : IBaseJob
{
Task Start();
}
}

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Plex.Interfaces
{
public interface IMediaDatabaseRefresh : IBaseJob
{
Task Start();
}
}

@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public interface INewsletterJob : IBaseJob
{
Task Start();
Task Start(NewsletterSettings settings, bool test);
}
}

@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public interface IOmbiAutomaticUpdater : IBaseJob
{
Task Update(PerformContext context);
string[] GetVersion();
Task<bool> UpdateAvailable(string branch, string currentVersion);
}

@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public interface IRefreshMetadata : IBaseJob
{
Task Start();
Task ProcessPlexServerContent(IEnumerable<int> contentIds);
}
}

@ -1,9 +1,9 @@
using System.Threading.Tasks;
using Quartz;
using System.Threading.Tasks;
namespace Ombi.Schedule.Jobs.Ombi
{
public interface IResendFailedRequests
public interface IResendFailedRequests : IJob
{
Task Start();
}
}

@ -3,7 +3,7 @@ using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.Ombi
{
public interface IWelcomeEmail : IBaseJob
public interface IWelcomeEmail
{
Task SendEmail(OmbiUser user);
}

@ -5,6 +5,7 @@ using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Schedule.Jobs.Ombi
{
@ -20,7 +21,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly IRepository<Issues> _issuesRepository;
private readonly ISettingsService<IssueSettings> _settings;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var settings = await _settings.GetSettingsAsync();
if (!settings.DeleteIssues)

@ -9,28 +9,28 @@ using Ombi.Helpers;
using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Schedule.Jobs.Ombi
{
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
{
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log,
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IEmbyContentSync embySync)
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo)
{
_settings = s;
_log = log;
_plexRepo = plexRepo;
_embyRepo = embyRepo;
_embyContentSync = embySync;
_settings.ClearCache();
}
private readonly ISettingsService<PlexSettings> _settings;
private readonly ILogger _log;
private readonly IPlexContentRepository _plexRepo;
private readonly IEmbyContentRepository _embyRepo;
private readonly IEmbyContentSync _embyContentSync;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
try
{
@ -53,13 +53,12 @@ namespace Ombi.Schedule.Jobs.Ombi
{
return;
}
const string episodeSQL = "DELETE FROM EmbyEpisode";
const string mainSql = "DELETE FROM EmbyContent";
await _embyRepo.ExecuteSql(episodeSQL);
await _embyRepo.ExecuteSql(mainSql);
BackgroundJob.Enqueue(() => _embyContentSync.Start());
await OmbiQuartz.TriggerJob(nameof(IEmbyContentSync), "Emby");
}
catch (Exception e)
{

@ -26,6 +26,7 @@ using Ombi.Settings.Settings.Models.External;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using ContentType = Ombi.Store.Entities.ContentType;
namespace Ombi.Schedule.Jobs.Ombi
@ -57,6 +58,10 @@ namespace Ombi.Schedule.Jobs.Ombi
_ombiSettings = ombiSettings;
_plexSettings = plexSettings;
_embySettings = embySettings;
_ombiSettings.ClearCache();
_plexSettings.ClearCache();
_emailSettings.ClearCache();
_customizationSettings.ClearCache();
}
private readonly IPlexContentRepository _plex;
@ -284,7 +289,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
await Start(newsletterSettings, false);
@ -359,7 +364,7 @@ namespace Ombi.Schedule.Jobs.Ombi
if (embySettings.Enable)
{
await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode);
await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty);
}
sb.Append("</tr>");
@ -385,7 +390,7 @@ namespace Ombi.Schedule.Jobs.Ombi
if (embySettings.Enable)
{
await ProcessEmbyTv(embyEp, sb);
await ProcessEmbyTv(embyEp, sb, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty);
}
sb.Append("</tr>");
@ -490,7 +495,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb, string defaultLangaugeCode)
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb, string defaultLangaugeCode, string customUrl)
{
int count = 0;
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
@ -511,6 +516,10 @@ namespace Ombi.Schedule.Jobs.Ombi
}
var mediaurl = content.Url;
if (customUrl.HasValue())
{
mediaurl = customUrl;
}
var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId), defaultLangaugeCode);
if (info == null)
{
@ -754,7 +763,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb, string serverUrl)
{
var series = new List<EmbyContent>();
foreach (var episode in embyContent)
@ -809,7 +818,7 @@ namespace Ombi.Schedule.Jobs.Ombi
AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/");
}
AddPosterInsideTable(sb, banner);
AddMediaServerUrl(sb, t.Url, banner);
AddMediaServerUrl(sb, serverUrl.HasValue() ? serverUrl : t.Url, banner);
AddInfoTable(sb);
var title = "";

@ -20,6 +20,7 @@ using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Updater;
using Quartz;
using SharpCompress.Readers;
using SharpCompress.Readers.Tar;
@ -41,7 +42,6 @@ namespace Ombi.Schedule.Jobs.Ombi
private IChangeLogProcessor Processor { get; }
private ISettingsService<UpdateSettings> Settings { get; }
private readonly IProcessProvider _processProvider;
private static PerformContext Ctx { get; set; }
private readonly IApplicationConfigRepository _appConfig;
public string[] GetVersion()
@ -59,10 +59,8 @@ namespace Ombi.Schedule.Jobs.Ombi
}
[AutomaticRetry(Attempts = 1)]
public async Task Update(PerformContext c)
public async Task Execute(IJobExecutionContext job)
{
Ctx = c;
Logger.LogDebug(LoggingEvents.Updater, "Starting Update job");
var settings = await Settings.GetSettingsAsync();
@ -182,7 +180,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
Logger.LogDebug(LoggingEvents.Updater, "Starting Download");
await DownloadAsync(download.Url, zipDir, c);
await DownloadAsync(download.Url, zipDir);
Logger.LogDebug(LoggingEvents.Updater, "Finished Download");
}
catch (Exception e)
@ -321,7 +319,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
public async Task DownloadAsync(string requestUri, string filename, PerformContext ctx)
public async Task DownloadAsync(string requestUri, string filename)
{
Logger.LogDebug(LoggingEvents.Updater, "Starting the DownloadAsync");
using (var client = new WebClient())

@ -15,6 +15,7 @@ using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Schedule.Jobs.Ombi
{
@ -22,8 +23,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo,
ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings,
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability,
IEmbyApi embyApi)
IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings, IEmbyApi embyApi)
{
_plexRepo = plexRepo;
_embyRepo = embyRepo;
@ -32,15 +32,11 @@ namespace Ombi.Schedule.Jobs.Ombi
_tvApi = tvApi;
_plexSettings = plexSettings;
_embySettings = embySettings;
_plexAvailabilityChecker = plexAvailability;
_embyAvaliabilityChecker = embyAvaliability;
_embyApi = embyApi;
}
private readonly IPlexContentRepository _plexRepo;
private readonly IEmbyContentRepository _embyRepo;
private readonly IPlexAvailabilityChecker _plexAvailabilityChecker;
private readonly IEmbyAvaliabilityChecker _embyAvaliabilityChecker;
private readonly ILogger _log;
private readonly IMovieDbApi _movieApi;
private readonly ITvMazeApi _tvApi;
@ -48,7 +44,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ISettingsService<EmbySettings> _embySettings;
private readonly IEmbyApi _embyApi;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
_log.LogInformation("Starting the Metadata refresh");
try
@ -93,12 +89,12 @@ namespace Ombi.Schedule.Jobs.Ombi
{
if (plexSettings.Enable)
{
BackgroundJob.Enqueue(() => _plexAvailabilityChecker.Start());
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
}
if (embySettings.Enable)
{
BackgroundJob.Enqueue(() => _embyAvaliabilityChecker.Start());
await OmbiQuartz.TriggerJob(nameof(IEmbyAvaliabilityChecker), "Emby");
}
}

@ -7,6 +7,7 @@ using Ombi.Core.Senders;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Quartz;
namespace Ombi.Schedule.Jobs.Ombi
{
@ -32,7 +33,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ITvRequestRepository _tvRequestRepository;
private readonly IMusicRequestRepository _musicRequestRepository;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
// Get all the failed ones!
var failedRequests = _requestQueue.GetAll().Where(x => !x.Completed.HasValue);

@ -5,6 +5,5 @@ namespace Ombi.Schedule.Jobs.Plex
{
public interface IPlexAvailabilityChecker : IBaseJob
{
Task Start();
}
}

@ -1,9 +1,9 @@
using System.Threading.Tasks;
using Quartz;
namespace Ombi.Schedule.Jobs
{
public interface IPlexContentSync : IBaseJob
public interface IPlexContentSync : IJob
{
Task CacheContent(bool recentlyAddedSearch = false);
}
}

@ -9,7 +9,6 @@ namespace Ombi.Schedule.Jobs.Plex.Interfaces
{
public interface IPlexEpisodeSync : IBaseJob
{
Task Start();
Task<HashSet<PlexEpisode>> ProcessEpsiodes(Metadata[] episodes, IQueryable<PlexEpisode> currentEpisodes);
}
}

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Plex
{
public interface IPlexUserImporter : IBaseJob
{
Task Start();
}
}

@ -12,6 +12,7 @@ using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Quartz;
namespace Ombi.Schedule.Jobs.Plex
{
@ -35,7 +36,7 @@ namespace Ombi.Schedule.Jobs.Plex
private readonly IBackgroundJobClient _backgroundJobClient;
private readonly ILogger _log;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
try
{

@ -42,13 +42,14 @@ using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.Plex.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Schedule.Jobs.Plex
{
public class PlexContentSync : IPlexContentSync
{
public PlexContentSync(ISettingsService<PlexSettings> plex, IPlexApi plexApi, ILogger<PlexContentSync> logger, IPlexContentRepository repo,
IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh, IPlexAvailabilityChecker checker)
IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh)
{
Plex = plex;
PlexApi = plexApi;
@ -56,7 +57,7 @@ namespace Ombi.Schedule.Jobs.Plex
Repo = repo;
EpisodeSync = epsiodeSync;
Metadata = metadataRefresh;
Checker = checker;
Plex.ClearCache();
}
private ISettingsService<PlexSettings> Plex { get; }
@ -65,10 +66,12 @@ namespace Ombi.Schedule.Jobs.Plex
private IPlexContentRepository Repo { get; }
private IPlexEpisodeSync EpisodeSync { get; }
private IRefreshMetadata Metadata { get; }
private IPlexAvailabilityChecker Checker { get; }
public async Task CacheContent(bool recentlyAddedSearch = false)
public async Task Execute(IJobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
var recentlyAddedSearch = dataMap.GetBooleanValueFromString("recentlyAddedSearch");
var plexSettings = await Plex.GetSettingsAsync();
if (!plexSettings.Enable)
{
@ -100,19 +103,23 @@ namespace Ombi.Schedule.Jobs.Plex
if (!recentlyAddedSearch)
{
Logger.LogInformation("Starting EP Cacher");
BackgroundJob.Enqueue(() => EpisodeSync.Start());
await OmbiQuartz.TriggerJob(nameof(IPlexEpisodeSync), "Plex");
}
if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch)
{
// Just check what we send it
BackgroundJob.Enqueue(() => Metadata.ProcessPlexServerContent(processedContent.Content));
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
}
if ((processedContent?.HasProcessedEpisodes ?? false) && recentlyAddedSearch)
if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch)
{
BackgroundJob.Enqueue(() => Checker.Start());
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
}
Logger.LogInformation("Finished Plex Content Cacher, with processed content: {0}, episodes: {0}", processedContent.Content.Count(), processedContent.Episodes.Count());
}
private async Task<ProcessedContent> StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
@ -190,10 +197,10 @@ namespace Ombi.Schedule.Jobs.Plex
}
contentToAdd.Clear();
}
if (count > 200)
if (count > 30)
{
await Repo.SaveChangesAsync();
count = 0;
}
}
@ -227,7 +234,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
contentToAdd.Clear();
}
if (count > 200)
if (count > 30)
{
await Repo.SaveChangesAsync();
}

@ -13,28 +13,28 @@ using Ombi.Helpers;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
namespace Ombi.Schedule.Jobs.Plex
{
public class PlexEpisodeSync : IPlexEpisodeSync
{
public PlexEpisodeSync(ISettingsService<PlexSettings> s, ILogger<PlexEpisodeSync> log, IPlexApi plexApi,
IPlexContentRepository repo, IPlexAvailabilityChecker a)
IPlexContentRepository repo)
{
_settings = s;
_log = log;
_api = plexApi;
_repo = repo;
_availabilityChecker = a;
_settings.ClearCache();
}
private readonly ISettingsService<PlexSettings> _settings;
private readonly ILogger<PlexEpisodeSync> _log;
private readonly IPlexApi _api;
private readonly IPlexContentRepository _repo;
private readonly IPlexAvailabilityChecker _availabilityChecker;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
try
{
@ -55,7 +55,8 @@ namespace Ombi.Schedule.Jobs.Plex
_log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed");
}
BackgroundJob.Enqueue(() => _availabilityChecker.Start());
await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex");
}
private async Task Cache(PlexServers settings)

@ -1,40 +1,40 @@
using System;
using System.Threading.Tasks;
using Hangfire;
//using System;
//using System.Threading.Tasks;
//using Hangfire;
namespace Ombi.Schedule.Jobs.Plex
{
public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync
{
public PlexRecentlyAddedSync(IPlexContentSync sync)
{
_sync = sync;
}
//namespace Ombi.Schedule.Jobs.Plex
//{
// public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync
// {
// public PlexRecentlyAddedSync(IPlexContentSync sync)
// {
// _sync = sync;
// }
private readonly IPlexContentSync _sync;
// private readonly IPlexContentSync _sync;
public void Start()
{
BackgroundJob.Enqueue(() => _sync.CacheContent(true));
}
// public void Start()
// {
// BackgroundJob.Enqueue(() => _sync.CacheContent(true));
// }
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
// private bool _disposed;
// protected virtual void Dispose(bool disposing)
// {
// if (_disposed)
// return;
if (disposing)
{
_sync?.Dispose();
}
_disposed = true;
}
// if (disposing)
// {
// _sync?.Dispose();
// }
// _disposed = true;
// }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
// public void Dispose()
// {
// Dispose(true);
// GC.SuppressFinalize(this);
// }
// }
//}

@ -11,6 +11,7 @@ using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Quartz;
namespace Ombi.Schedule.Jobs.Plex
{
@ -24,6 +25,8 @@ namespace Ombi.Schedule.Jobs.Plex
_log = log;
_plexSettings = plexSettings;
_userManagementSettings = ums;
_plexSettings.ClearCache();
_userManagementSettings.ClearCache();
}
private readonly IPlexApi _api;
@ -33,7 +36,7 @@ namespace Ombi.Schedule.Jobs.Plex
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
var userManagementSettings = await _userManagementSettings.GetSettingsAsync();
if (!userManagementSettings.ImportPlexUsers)

@ -6,7 +6,6 @@ namespace Ombi.Schedule.Jobs.Radarr
{
public interface IRadarrSync : IBaseJob
{
Task CacheContent();
Task<IEnumerable<RadarrCache>> GetCachedContent();
}
}

@ -10,6 +10,7 @@ using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Quartz;
using Serilog;
namespace Ombi.Schedule.Jobs.Radarr
@ -22,6 +23,7 @@ namespace Ombi.Schedule.Jobs.Radarr
RadarrApi = radarrApi;
Logger = log;
_ctx = ctx;
RadarrSettings.ClearCache();
}
private ISettingsService<RadarrSettings> RadarrSettings { get; }
@ -31,7 +33,7 @@ namespace Ombi.Schedule.Jobs.Radarr
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
public async Task CacheContent()
public async Task Execute(IJobExecutionContext job)
{
await SemaphoreSlim.WaitAsync();
try
@ -45,12 +47,16 @@ namespace Ombi.Schedule.Jobs.Radarr
if (movies != null)
{
// Let's remove the old cached data
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache");
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache");
tran.Commit();
}
var movieIds = new List<RadarrCache>();
foreach (var m in movies)
{
if (m.tmdbId > 0)
if (m.tmdbId > 0 && m.monitored)
{
movieIds.Add(new RadarrCache
{
@ -63,9 +69,14 @@ namespace Ombi.Schedule.Jobs.Radarr
Logger.LogError("TMDBId is not > 0 for movie {0}", m.title);
}
}
await _ctx.RadarrCache.AddRangeAsync(movieIds);
await _ctx.SaveChangesAsync();
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.RadarrCache.AddRangeAsync(movieIds);
await _ctx.SaveChangesAsync();
tran.Commit();
}
}
}
catch (System.Exception ex)

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.SickRage
{
public interface ISickRageSync : IBaseJob
{
Task Start();
}
}

@ -11,6 +11,7 @@ using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Quartz;
namespace Ombi.Schedule.Jobs.SickRage
{
@ -22,6 +23,7 @@ namespace Ombi.Schedule.Jobs.SickRage
_api = api;
_log = l;
_ctx = ctx;
_settings.ClearCache();
}
private readonly ISettingsService<SickRageSettings> _settings;
@ -29,7 +31,7 @@ namespace Ombi.Schedule.Jobs.SickRage
private readonly ILogger<SickRageSync> _log;
private readonly IExternalContext _ctx;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
try
{
@ -44,8 +46,12 @@ namespace Ombi.Schedule.Jobs.SickRage
{
var srShows = shows.data.Values;
var ids = srShows.Select(x => x.tvdbid);
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache");
tran.Commit();
}
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache");
var entites = ids.Select(id => new SickRageCache { TvDbId = id }).ToList();
await _ctx.SickRageCache.AddRangeAsync(entites);
@ -72,8 +78,12 @@ namespace Ombi.Schedule.Jobs.SickRage
}
await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd);
await _ctx.SaveChangesAsync();
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd);
await _ctx.SaveChangesAsync();
tran.Commit();
}
}
}
catch (Exception e)

@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Sonarr
{
public interface ISonarrSync : IBaseJob
{
Task Start();
}
}

@ -14,6 +14,7 @@ using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Quartz;
namespace Ombi.Schedule.Jobs.Sonarr
{
@ -25,6 +26,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
_api = api;
_log = l;
_ctx = ctx;
_settings.ClearCache();
}
private readonly ISettingsService<SonarrSettings> _settings;
@ -32,7 +34,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
private readonly ILogger<SonarrSync> _log;
private readonly IExternalContext _ctx;
public async Task Start()
public async Task Execute(IJobExecutionContext job)
{
try
{
@ -46,14 +48,22 @@ namespace Ombi.Schedule.Jobs.Sonarr
{
var sonarrSeries = series as ImmutableHashSet<SonarrSeries> ?? series.ToImmutableHashSet();
var ids = sonarrSeries.Select(x => x.tvdbId);
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache");
tran.Commit();
}
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache");
var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet();
await _ctx.SonarrCache.AddRangeAsync(entites);
entites.Clear();
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache");
tran.Commit();
}
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache");
foreach (var s in sonarrSeries)
{
if (!s.monitored)
@ -66,15 +76,20 @@ namespace Ombi.Schedule.Jobs.Sonarr
// Add to DB
_log.LogDebug("We have the episodes, adding to db transaction");
await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode => new SonarrEpisodeCache
using (var tran = await _ctx.Database.BeginTransactionAsync())
{
EpisodeNumber = episode.episodeNumber,
SeasonNumber = episode.seasonNumber,
TvDbId = s.tvdbId,
HasFile = episode.hasFile
}));
_log.LogDebug("Commiting the transaction");
await _ctx.SaveChangesAsync();
await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode =>
new SonarrEpisodeCache
{
EpisodeNumber = episode.episodeNumber,
SeasonNumber = episode.seasonNumber,
TvDbId = s.tvdbId,
HasFile = episode.hasFile
}));
_log.LogDebug("Commiting the transaction");
await _ctx.SaveChangesAsync();
tran.Commit();
}
}
}

@ -10,13 +10,14 @@
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Hangfire" Version="1.6.21" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.21" />
<PackageReference Include="Hangfire" Version="1.6.22" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.22" />
<PackageReference Include="Hangfire.Console" Version="1.3.10" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" />
<PackageReference Include="Serilog" Version="2.7.1" />
<PackageReference Include="Quartz" Version="3.0.7" />
<PackageReference Include="SharpCompress" Version="0.18.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.6.13" />

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Helpers;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
namespace Ombi.Schedule
{
public class OmbiQuartz
{
protected IScheduler _scheduler { get; set; }
public static IScheduler Scheduler => Instance._scheduler;
// Singleton
protected static OmbiQuartz _instance;
/// <summary>
/// Singleton
/// </summary>
public static OmbiQuartz Instance => _instance ?? (_instance = new OmbiQuartz());
protected OmbiQuartz()
{
Init();
}
private async void Init()
{
_scheduler = await new StdSchedulerFactory().GetScheduler();
}
public IScheduler UseJobFactory(IJobFactory jobFactory)
{
Scheduler.JobFactory = jobFactory;
return Scheduler;
}
public async Task AddJob<T>(string name, string group, string cronExpression, Dictionary<string, string> jobData = null)
where T : IJob
{
var jobBuilder = JobBuilder.Create<T>()
.WithIdentity(new JobKey(name, group));
if (jobData != null)
{
foreach (var o in jobData)
{
jobBuilder.UsingJobData(o.Key, o.Value);
}
}
if(!cronExpression.HasValue())
{
jobBuilder.StoreDurably(true);
}
var job = jobBuilder.Build();
if (cronExpression.HasValue())
{
ITrigger jobTrigger = TriggerBuilder.Create()
.WithIdentity(name + "Trigger", group)
.WithCronSchedule(cronExpression,
x => x.WithMisfireHandlingInstructionFireAndProceed())
.ForJob(name, group)
.StartNow()
.Build();
await Scheduler.ScheduleJob(job, jobTrigger);
}
else
{
await Scheduler.AddJob(job, true);
}
}
public static async Task TriggerJob(string jobName, string group)
{
await Scheduler.TriggerJob(new JobKey(jobName, group));
}
public static async Task Start()
{
await Scheduler.Start();
}
}
}

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Ombi.Core.Settings;
using Ombi.Schedule.Jobs;
using Ombi.Schedule.Jobs.Couchpotato;
using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Lidarr;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.Radarr;
using Ombi.Schedule.Jobs.SickRage;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Settings.Settings.Models;
using Quartz;
using Quartz.Spi;
namespace Ombi.Schedule
{
public static class OmbiScheduler
{
//public void Setup()
//{
// CreateJobDefinitions();
//}
//public void CreateJobDefinitions()
//{
// var contentSync = JobBuilder.Create<PlexContentSync>()
// .UsingJobData("recentlyAddedSearch", false)
// .WithIdentity(nameof(PlexContentSync), "Plex")
// .Build();
// var recentlyAdded = JobBuilder.Create<PlexContentSync>()
// .UsingJobData("recentlyAddedSearch", true)
// .WithIdentity("PlexRecentlyAdded", "Plex")
// .Build();
//}
public static async Task UseQuartz(this IApplicationBuilder app)
{
// Job Factory through IOC container
var jobFactory = (IJobFactory)app.ApplicationServices.GetService(typeof(IJobFactory));
var service = (ISettingsService<JobSettings>)app.ApplicationServices.GetService(typeof(ISettingsService<JobSettings>));
var s = service.GetSettings();
// Set job factory
OmbiQuartz.Instance.UseJobFactory(jobFactory);
// Run configuration
await AddPlex(s);
await AddEmby(s);
await AddDvrApps(s);
await AddSystem(s);
// Run Quartz
await OmbiQuartz.Start();
}
private static async Task AddSystem(JobSettings s)
{
await OmbiQuartz.Instance.AddJob<IRefreshMetadata>(nameof(IRefreshMetadata), "System", JobSettingsHelper.RefreshMetadata(s));
await OmbiQuartz.Instance.AddJob<IIssuesPurge>(nameof(IIssuesPurge), "System", JobSettingsHelper.IssuePurge(s));
//OmbiQuartz.Instance.AddJob<IOmbiAutomaticUpdater>(nameof(IOmbiAutomaticUpdater), "System", JobSettingsHelper.Updater(s));
await OmbiQuartz.Instance.AddJob<INewsletterJob>(nameof(INewsletterJob), "System", JobSettingsHelper.Newsletter(s));
await OmbiQuartz.Instance.AddJob<IResendFailedRequests>(nameof(IResendFailedRequests), "System", JobSettingsHelper.ResendFailedRequests(s));
await OmbiQuartz.Instance.AddJob<IMediaDatabaseRefresh>(nameof(IMediaDatabaseRefresh), "System", JobSettingsHelper.MediaDatabaseRefresh(s));
}
private static async Task AddDvrApps(JobSettings 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<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));
}
private static async Task AddPlex(JobSettings s)
{
await OmbiQuartz.Instance.AddJob<IPlexContentSync>(nameof(IPlexContentSync), "Plex", JobSettingsHelper.PlexContent(s), new Dictionary<string, string> { { "recentlyAddedSearch", "false" } });
await OmbiQuartz.Instance.AddJob<IPlexContentSync>(nameof(IPlexContentSync) + "RecentlyAdded", "Plex", JobSettingsHelper.PlexRecentlyAdded(s), new Dictionary<string, string> { { "recentlyAddedSearch", "true" } });
await OmbiQuartz.Instance.AddJob<IPlexUserImporter>(nameof(IPlexUserImporter), "Plex", JobSettingsHelper.UserImporter(s));
await OmbiQuartz.Instance.AddJob<IPlexEpisodeSync>(nameof(IPlexEpisodeSync), "Plex", null);
await OmbiQuartz.Instance.AddJob<IPlexAvailabilityChecker>(nameof(IPlexAvailabilityChecker), "Plex", null);
}
private static async Task AddEmby(JobSettings s)
{
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s));
await OmbiQuartz.Instance.AddJob<IEmbyEpisodeSync>(nameof(IEmbyEpisodeSync), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyAvaliabilityChecker>(nameof(IEmbyAvaliabilityChecker), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyUserImporter>(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s));
}
}
}

@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Quartz" Version="3.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup>

@ -1,5 +1,6 @@
using System;
using Ombi.Helpers;
using Quartz;
namespace Ombi.Settings.Settings.Models
{
@ -7,71 +8,93 @@ namespace Ombi.Settings.Settings.Models
{
public static string Radarr(JobSettings s)
{
return Get(s.RadarrSync, Cron.Hourly(15));
return ValidateCron(Get(s.RadarrSync, Cron.Hourly(15)));
}
public static string Sonarr(JobSettings s)
{
return Get(s.SonarrSync, Cron.Hourly(10));
return ValidateCron(Get(s.SonarrSync, Cron.Hourly(10)));
}
public static string EmbyContent(JobSettings s)
{
return Get(s.EmbyContentSync, Cron.Hourly(5));
return ValidateCron(Get(s.EmbyContentSync, Cron.Hourly(5)));
}
public static string PlexContent(JobSettings s)
{
return Get(s.PlexContentSync, Cron.Daily(2));
return ValidateCron(Get(s.PlexContentSync, Cron.Daily(2)));
}
public static string PlexRecentlyAdded(JobSettings s)
{
return Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30));
return ValidateCron(Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30)));
}
public static string CouchPotato(JobSettings s)
{
return Get(s.CouchPotatoSync, Cron.Hourly(30));
return ValidateCron(Get(s.CouchPotatoSync, Cron.Hourly(30)));
}
public static string Updater(JobSettings s)
{
return Get(s.AutomaticUpdater, Cron.HourInterval(6));
return ValidateCron(Get(s.AutomaticUpdater, Cron.HourInterval(6)));
}
public static string UserImporter(JobSettings s)
{
return Get(s.UserImporter, Cron.Daily());
return ValidateCron(Get(s.UserImporter, Cron.Daily()));
}
public static string Newsletter(JobSettings s)
{
return Get(s.Newsletter, Cron.Weekly(DayOfWeek.Friday, 12));
return ValidateCron(Get(s.Newsletter, Cron.Weekly(Helpers.DayOfWeek.Friday, 12)));
}
public static string SickRageSync(JobSettings s)
{
return Get(s.SickRageSync, Cron.Hourly(35));
return ValidateCron(Get(s.SickRageSync, Cron.Hourly(35)));
}
public static string RefreshMetadata(JobSettings s)
{
return Get(s.RefreshMetadata, Cron.DayInterval(3));
return ValidateCron(Get(s.RefreshMetadata, Cron.DayInterval(3)));
}
public static string LidarrArtistSync(JobSettings s)
{
return Get(s.LidarrArtistSync, Cron.Hourly(40));
return ValidateCron(Get(s.LidarrArtistSync, Cron.Hourly(40)));
}
public static string IssuePurge(JobSettings s)
{
return Get(s.IssuesPurge, Cron.Daily());
return ValidateCron(Get(s.IssuesPurge, Cron.Daily()));
}
public static string ResendFailedRequests(JobSettings s)
{
return Get(s.RetryRequests, Cron.Daily(6));
return ValidateCron(Get(s.RetryRequests, Cron.Daily(6)));
}
public static string MediaDatabaseRefresh(JobSettings s)
{
return Get(s.MediaDatabaseRefresh, Cron.DayInterval(5));
return ValidateCron(Get(s.MediaDatabaseRefresh, Cron.DayInterval(5)));
}
private static string Get(string settings, string defaultCron)
{
return settings.HasValue() ? settings : defaultCron;
}
private const string _defaultCron = "0 0 12 1/1 * ? *";
private static string ValidateCron(string cron)
{
if (CronExpression.IsValidExpression(cron))
{
return cron;
}
return _defaultCron;
}
}
}

@ -0,0 +1,10 @@
namespace Ombi.Settings.Settings.Models.Notifications
{
public class GotifySettings : Settings
{
public bool Enabled { get; set; }
public string BaseUrl { get; set; }
public string ApplicationToken { get; set; }
public sbyte Priority { get; set; } = 4;
}
}

@ -13,6 +13,7 @@ namespace Ombi.Store.Context
if (_created) return;
_created = true;
Database.SetCommandTimeout(60);
Database.Migrate();
}
@ -62,7 +63,12 @@ namespace Ombi.Store.Context
{
// VACUUM;
Database.ExecuteSqlCommand("VACUUM;");
SaveChanges();
using (var tran = Database.BeginTransaction())
{
SaveChanges();
tran.Commit();
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save