Merge remote-tracking branch 'upstream/develop' into request-counter-fixed

pull/2467/head
Kenton Royal 6 years ago
commit 5725015789

Binary file not shown.

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Lidarr.Models;
namespace Ombi.Api.Lidarr
{
public interface ILidarrApi
{
Task<List<AlbumLookup>> AlbumLookup(string searchTerm, string apiKey, string baseUrl);
Task<List<ArtistLookup>> ArtistLookup(string searchTerm, string apiKey, string baseUrl);
Task<List<LidarrProfile>> GetProfiles(string apiKey, string baseUrl);
Task<List<LidarrRootFolder>> GetRootFolders(string apiKey, string baseUrl);
Task<ArtistResult> GetArtist(int artistId, string apiKey, string baseUrl);
Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl);
Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId);
Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl);
Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl);
Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl);
Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl);
Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl);
Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl);
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
Task<LidarrStatus> Status(string apiKey, string baseUrl);
}
}

@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr.Models;
namespace Ombi.Api.Lidarr
{
public class LidarrApi : ILidarrApi
{
public LidarrApi(ILogger<LidarrApi> logger, IApi api)
{
Api = api;
Logger = logger;
}
private IApi Api { get; }
private ILogger Logger { get; }
private const string ApiVersion = "/api/v1";
public Task<List<LidarrProfile>> GetProfiles(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/qualityprofile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<LidarrProfile>>(request);
}
public Task<List<LidarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/rootfolder", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<LidarrRootFolder>>(request);
}
public async Task<List<ArtistLookup>> ArtistLookup(string searchTerm, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/Artist/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", searchTerm);
AddHeaders(request, apiKey);
return await Api.Request<List<ArtistLookup>>(request);
}
public Task<List<AlbumLookup>> AlbumLookup(string searchTerm, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/Album/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", searchTerm);
AddHeaders(request, apiKey);
return Api.Request<List<AlbumLookup>>(request);
}
public Task<ArtistResult> GetArtist(int artistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist/{artistId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<ArtistResult>(request);
}
public async Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
AddHeaders(request, apiKey);
return (await Api.Request<List<ArtistResult>>(request)).FirstOrDefault();
}
public async Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
AddHeaders(request, apiKey);
var albums = await Api.Request<List<AlbumLookup>>(request);
return albums.FirstOrDefault();
}
public Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId)
{
var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}",
HttpMethod.Get) {IgnoreBaseUrlAppend = true};
return Api.Request<AlbumByArtistResponse>(request);
}
public Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<ArtistResult>>(request);
}
public Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<AlbumResponse>>(request);
}
public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post);
request.AddJsonBody(artist);
AddHeaders(request, apiKey);
return Api.Request<ArtistResult>(request);
}
public async Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put);
request.AddJsonBody(new
{
albumIds = new[] { albumId },
monitored = true
});
AddHeaders(request, apiKey);
return (await Api.Request<List<AlbumResponse>>(request)).FirstOrDefault();
}
public Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("artistId", artistId.ToString());
AddHeaders(request, apiKey);
return Api.Request<List<AlbumResponse>>(request);
}
public Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<LanguageProfiles>>(request);
}
public Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<MetadataProfile>>(request);
}
public Task<LidarrStatus> Status(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/system/status", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<LidarrStatus>(request);
}
private void AddHeaders(Request request, string key)
{
request.AddHeader("X-Api-Key", key);
}
}
}

@ -0,0 +1,34 @@
namespace Ombi.Api.Lidarr.Models
{
public class AlbumByArtistResponse
{
public Album[] Albums { get; set; }
public string ArtistName { get; set; }
public string Disambiguation { get; set; }
public string Id { get; set; }
public Image[] Images { get; set; }
public Link[] Links { get; set; }
public string Overview { get; set; }
public Rating Rating { get; set; }
public string SortName { get; set; }
public string Status { get; set; }
public string Type { get; set; }
}
public class Rating
{
public int Count { get; set; }
public decimal Value { get; set; }
}
public class Album
{
public string Disambiguation { get; set; }
public string Id { get; set; }
public string ReleaseDate { get; set; }
public string[] ReleaseStatuses { get; set; }
public string[] SecondaryTypes { get; set; }
public string Title { get; set; }
public string Type { get; set; }
}
}

@ -0,0 +1,25 @@
using System;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumLookup
{
public string title { get; set; }
public int artistId { get; set; }
public string foreignAlbumId { get; set; }
public bool monitored { get; set; }
public int profileId { get; set; }
public int duration { get; set; }
public string albumType { get; set; }
public string[] secondaryTypes { get; set; }
public int mediumCount { get; set; }
public Ratings ratings { get; set; }
public DateTime releaseDate { get; set; }
//public object[] releases { get; set; }
public object[] genres { get; set; }
//public object[] media { get; set; }
public Artist artist { get; set; }
public Image[] images { get; set; }
public string remoteCover { get; set; }
}
}

@ -0,0 +1,27 @@
using System;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumResponse
{
public string title { get; set; }
public string disambiguation { get; set; }
public int artistId { get; set; }
public string foreignAlbumId { get; set; }
public bool monitored { 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 Currentrelease currentRelease { get; set; }
public Release[] releases { get; set; }
public object[] genres { get; set; }
public Medium[] media { get; set; }
public Image[] images { get; set; }
public Statistics statistics { get; set; }
public int id { get; set; }
}
}

@ -0,0 +1,25 @@
using System;
namespace Ombi.Api.Lidarr.Models
{
public class Artist
{
public string status { get; set; }
public bool ended { get; set; }
public string artistName { get; set; }
public string foreignArtistId { get; set; }
public int tadbId { get; set; }
public int discogsId { get; set; }
public object[] links { get; set; }
public object[] images { get; set; }
public int qualityProfileId { get; set; }
public int languageProfileId { get; set; }
public int metadataProfileId { get; set; }
public bool albumFolder { get; set; }
public bool monitored { get; set; }
public object[] genres { get; set; }
public object[] tags { get; set; }
public DateTime added { get; set; }
public Statistics statistics { get; set; }
}
}

@ -0,0 +1,49 @@
using System;
using System.Net.Mime;
namespace Ombi.Api.Lidarr.Models
{
public class ArtistAdd
{
public string status { get; set; }
public bool ended { get; set; }
public string artistName { get; set; }
public string foreignArtistId { get; set; }
public int tadbId { get; set; }
public int discogsId { get; set; }
public string overview { get; set; }
public string disambiguation { get; set; }
public Link[] links { get; set; }
public Image[] images { get; set; }
public string remotePoster { get; set; }
public int qualityProfileId { get; set; }
public int languageProfileId { get; set; }
public int metadataProfileId { get; set; }
public bool albumFolder { get; set; }
public bool monitored { get; set; }
public string cleanName { get; set; }
public string sortName { get; set; }
public object[] tags { get; set; }
public DateTime added { get; set; }
public Ratings ratings { get; set; }
public Statistics statistics { get; set; }
public Addoptions addOptions { get; set; }
public string rootFolderPath { get; set; }
}
public class Addoptions
{
/// <summary>
/// Future = 1
/// Missing = 2
/// Existing = 3
/// First = 5
/// Latest = 4
/// None = 6
/// </summary>
public int selectedOption { get; set; }
public bool monitored { get; set; }
public bool searchForMissingAlbums { get; set; }
public string[] AlbumsToMonitor { get; set; } // Uses the MusicBrainzAlbumId!
}
}

@ -0,0 +1,32 @@
using System;
using System.Net.Mime;
namespace Ombi.Api.Lidarr.Models
{
public class ArtistLookup
{
public string status { get; set; }
public bool ended { get; set; }
public string artistName { get; set; }
public string foreignArtistId { get; set; }
public int tadbId { get; set; }
public int discogsId { get; set; }
public string overview { get; set; }
public string artistType { get; set; }
public string disambiguation { get; set; }
public Link[] links { get; set; }
public Image[] images { get; set; }
public string remotePoster { get; set; }
public int qualityProfileId { get; set; }
public int languageProfileId { get; set; }
public int metadataProfileId { get; set; }
public bool albumFolder { get; set; }
public bool monitored { get; set; }
public string cleanName { get; set; }
public string sortName { get; set; }
public object[] tags { get; set; }
public DateTime added { get; set; }
public Ratings ratings { get; set; }
public Statistics statistics { get; set; }
}
}

@ -0,0 +1,93 @@
using System;
namespace Ombi.Api.Lidarr.Models
{
public class ArtistResult
{
public string status { get; set; }
public bool ended { get; set; }
public DateTime lastInfoSync { get; set; }
public string artistName { get; set; }
public string foreignArtistId { get; set; }
public int tadbId { get; set; }
public int discogsId { get; set; }
public string overview { get; set; }
public string artistType { get; set; }
public string disambiguation { get; set; }
public Link[] links { get; set; }
public Nextalbum nextAlbum { get; set; }
public Image[] images { get; set; }
public string path { get; set; }
public int qualityProfileId { get; set; }
public int languageProfileId { get; set; }
public int metadataProfileId { get; set; }
public bool albumFolder { get; set; }
public bool monitored { get; set; }
public object[] genres { get; set; }
public string cleanName { get; set; }
public string sortName { get; set; }
public object[] tags { get; set; }
public DateTime added { get; set; }
public Ratings ratings { get; set; }
public Statistics statistics { get; set; }
public int id { get; set; }
}
public class Nextalbum
{
public string foreignAlbumId { get; set; }
public int artistId { get; set; }
public string title { get; set; }
public string disambiguation { get; set; }
public string cleanTitle { get; set; }
public DateTime releaseDate { get; set; }
public int profileId { get; set; }
public int duration { get; set; }
public bool monitored { get; set; }
public object[] images { get; set; }
public object[] genres { get; set; }
public Medium[] media { get; set; }
public DateTime lastInfoSync { get; set; }
public DateTime added { get; set; }
public string albumType { get; set; }
public object[] secondaryTypes { get; set; }
public Ratings ratings { get; set; }
public Release[] releases { get; set; }
public Currentrelease currentRelease { get; set; }
public int id { get; set; }
}
public class Currentrelease
{
public string id { get; set; }
public string title { get; set; }
public DateTime releaseDate { get; set; }
public int trackCount { get; set; }
public int mediaCount { get; set; }
public string disambiguation { get; set; }
public string[] country { get; set; }
public string format { get; set; }
public string[] label { get; set; }
}
public class Medium
{
public int number { get; set; }
public string name { get; set; }
public string format { get; set; }
}
public class Release
{
public string id { get; set; }
public string title { get; set; }
public DateTime releaseDate { get; set; }
public int trackCount { get; set; }
public int mediaCount { get; set; }
public string disambiguation { get; set; }
public string[] country { get; set; }
public string format { get; set; }
public string[] label { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class Image
{
public string coverType { get; set; }
public string url { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class LanguageProfiles
{
public string name { get; set; }
public int id { get; set; }
}
}

@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace Ombi.Api.Lidarr.Models
{
public class Quality
{
public int id { get; set; }
public string name { get; set; }
}
public class Item
{
public Quality quality { get; set; }
public bool allowed { get; set; }
}
public class LidarrProfile
{
public string name { get; set; }
public List<Item> items { get; set; }
public int id { get; set; }
}
}

@ -0,0 +1,11 @@
namespace Ombi.Api.Lidarr.Models
{
public class LidarrRootFolder
{
public string path { get; set; }
public long freeSpace { get; set; }
public object[] unmappedFolders { get; set; }
public int id { get; set; }
}
}

@ -0,0 +1,31 @@
using System;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrStatus
{
public string version { get; set; }
public DateTime buildTime { get; set; }
public bool isDebug { get; set; }
public bool isProduction { get; set; }
public bool isAdmin { get; set; }
public bool isUserInteractive { get; set; }
public string startupPath { get; set; }
public string appData { get; set; }
public string osName { get; set; }
public string osVersion { get; set; }
public bool isMonoRuntime { get; set; }
public bool isMono { get; set; }
public bool isLinux { get; set; }
public bool isOsx { get; set; }
public bool isWindows { get; set; }
public string mode { get; set; }
public string branch { get; set; }
public string authentication { get; set; }
public string sqliteVersion { get; set; }
public int migrationVersion { get; set; }
public string urlBase { get; set; }
public string runtimeVersion { get; set; }
public string runtimeName { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class Link
{
public string url { get; set; }
public string name { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class MetadataProfile
{
public string name { get; set; }
public int id { get; set; }
}
}

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

@ -0,0 +1,12 @@
namespace Ombi.Api.Lidarr.Models
{
public class Statistics
{
public int albumCount { get; set; }
public int trackFileCount { get; set; }
public int trackCount { get; set; }
public int totalTrackCount { get; set; }
public int sizeOnDisk { get; set; }
public decimal percentOfEpisodes { get; set; }
}
}

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -5,6 +5,6 @@ namespace Ombi.Api.Pushover
{
public interface IPushoverApi
{
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken);
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound);
}
}

@ -16,13 +16,13 @@ namespace Ombi.Api.Pushover
private readonly IApi _api;
private const string PushoverEndpoint = "https://api.pushover.net/1";
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken)
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}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
var result = await _api.Request<PushoverResponse>(request);
return result;

@ -28,6 +28,7 @@ namespace Ombi.Api
public bool IgnoreErrors { get; set; }
public bool Retry { get; set; }
public List<HttpStatusCode> StatusCodeToRetry { get; set; } = new List<HttpStatusCode>();
public bool IgnoreBaseUrlAppend { get; set; }
public Action<string> OnBeforeDeserialization { get; set; }
@ -38,7 +39,7 @@ namespace Ombi.Api
var sb = new StringBuilder();
if (!string.IsNullOrEmpty(BaseUrl))
{
sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl);
sb.Append(!BaseUrl.EndsWith("/") && !IgnoreBaseUrlAppend ? string.Format("{0}/", BaseUrl) : BaseUrl);
}
sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint);
return sb.ToString();

@ -19,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search
MovieMock = new Mock<IMovieRequestRepository>();
TvMock = new Mock<ITvRequestRepository>();
Rule = new ExistingRule(MovieMock.Object, TvMock.Object);
MusicMock = new Mock<IMusicRequestRepository>();
Rule = new ExistingRule(MovieMock.Object, TvMock.Object, MusicMock.Object);
}
private ExistingRule Rule { get; set; }
private Mock<IMovieRequestRepository> MovieMock { get; set; }
private Mock<ITvRequestRepository> TvMock { get; set; }
private Mock<IMusicRequestRepository> MusicMock { get; set; }
[Test]

@ -36,6 +36,7 @@ namespace Ombi.Core.Engine
protected IRequestServiceMain RequestService { get; }
protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService;
protected ITvRequestRepository TvRepository => RequestService.TvRequestService;
protected IMusicRequestRepository MusicRepository => RequestService.MusicRequestRepository;
protected readonly ICacheService Cache;
protected readonly ISettingsService<OmbiSettings> OmbiSettings;
protected readonly IRepository<RequestSubscription> _subscriptionRepository;

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Engine
{
public interface IMusicRequestEngine
{
Task<RequestEngineResult>ApproveAlbum(AlbumRequest request);
Task<RequestEngineResult> ApproveAlbumById(int requestId);
Task<RequestEngineResult> DenyAlbumById(int modelId);
Task<IEnumerable<AlbumRequest>> GetRequests();
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, OrderFilterModel orderFilter);
Task<int> GetTotal();
Task<RequestEngineResult> MarkAvailable(int modelId);
Task<RequestEngineResult> MarkUnavailable(int modelId);
Task RemoveAlbumRequest(int requestId);
Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model);
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
Task<bool> UserHasRequest(string userId);
}
}

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Lidarr.Models;
using Ombi.Core.Models.Search;
namespace Ombi.Core.Engine
{
public interface IMusicSearchEngine
{
Task<ArtistResult> GetAlbumArtist(string foreignArtistId);
Task<ArtistResult> GetArtist(int artistId);
Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId);
Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search);
Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search);
}
}

@ -337,6 +337,7 @@ namespace Ombi.Core.Engine
};
}
request.MarkedAsApproved = DateTime.Now;
request.Approved = true;
request.Denied = false;
await MovieRepository.Update(request);

@ -0,0 +1,457 @@
using Ombi.Api.TheMovieDb;
using Ombi.Core.Models.Requests;
using Ombi.Helpers;
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.UI;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Senders;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine
{
public class MusicRequestEngine : BaseMediaEngine, IMusicRequestEngine
{
public MusicRequestEngine(IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator r, ILogger<MusicRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, ILidarrApi lidarr,
ISettingsService<LidarrSettings> lidarrSettings, IMusicSender sender)
: base(user, requestService, r, manager, cache, ombiSettings, sub)
{
NotificationHelper = helper;
_musicSender = sender;
Logger = log;
_requestLog = rl;
_lidarrApi = lidarr;
_lidarrSettings = lidarrSettings;
}
private INotificationHelper NotificationHelper { get; }
//private IMovieSender Sender { get; }
private ILogger Logger { get; }
private readonly IRepository<RequestLog> _requestLog;
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ILidarrApi _lidarrApi;
private readonly IMusicSender _musicSender;
/// <summary>
/// Requests the Album.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
public async Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model)
{
var s = await _lidarrSettings.GetSettingsAsync();
var album = await _lidarrApi.GetAlbumByForeignId(model.ForeignAlbumId, s.ApiKey, s.FullUri);
if (album == null)
{
return new RequestEngineResult
{
Result = false,
Message = "There was an issue adding this album!",
ErrorMessage = "Please try again later"
};
}
var userDetails = await GetUser();
var requestModel = new AlbumRequest
{
ForeignAlbumId = model.ForeignAlbumId,
ArtistName = album.artist?.artistName,
ReleaseDate = album.releaseDate,
RequestedDate = DateTime.Now,
RequestType = RequestType.Album,
Rating = album.ratings?.value ?? 0m,
RequestedUserId = userDetails.Id,
Title = album.title,
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty
};
if (requestModel.Cover.IsNullOrEmpty())
{
requestModel.Cover = album.remoteCover;
}
var ruleResults = (await RunRequestRules(requestModel)).ToList();
if (ruleResults.Any(x => !x.Success))
{
return new RequestEngineResult
{
ErrorMessage = ruleResults.FirstOrDefault(x => x.Message.HasValue()).Message
};
}
if (requestModel.Approved) // The rules have auto approved this
{
var requestEngineResult = await AddAlbumRequest(requestModel);
if (requestEngineResult.Result)
{
var result = await ApproveAlbum(requestModel);
if (result.IsError)
{
Logger.LogWarning("Tried auto sending Album but failed. Message: {0}", result.Message);
return new RequestEngineResult
{
Message = result.Message,
ErrorMessage = result.Message,
Result = false
};
}
return requestEngineResult;
}
// If there are no providers then it's successful but album has not been sent
}
return await AddAlbumRequest(requestModel);
}
/// <summary>
/// Gets the requests.
/// </summary>
/// <param name="count">The count.</param>
/// <param name="position">The position.</param>
/// <param name="orderFilter">The order/filter type.</param>
/// <returns></returns>
public async Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position,
OrderFilterModel orderFilter)
{
var shouldHide = await HideFromOtherUsers();
IQueryable<AlbumRequest> allRequests;
if (shouldHide.Hide)
{
allRequests =
MusicRepository.GetWithUser(shouldHide
.UserId); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
else
{
allRequests =
MusicRepository
.GetWithUser(); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
switch (orderFilter.AvailabilityFilter)
{
case FilterType.None:
break;
case FilterType.Available:
allRequests = allRequests.Where(x => x.Available);
break;
case FilterType.NotAvailable:
allRequests = allRequests.Where(x => !x.Available);
break;
default:
throw new ArgumentOutOfRangeException();
}
switch (orderFilter.StatusFilter)
{
case FilterType.None:
break;
case FilterType.Approved:
allRequests = allRequests.Where(x => x.Approved);
break;
case FilterType.Processing:
allRequests = allRequests.Where(x => x.Approved && !x.Available);
break;
case FilterType.PendingApproval:
allRequests = allRequests.Where(x => !x.Approved && !x.Available && !(x.Denied ?? false));
break;
default:
throw new ArgumentOutOfRangeException();
}
var total = allRequests.Count();
var requests = await (OrderAlbums(allRequests, orderFilter.OrderType)).Skip(position).Take(count)
.ToListAsync();
requests.ForEach(async x =>
{
await CheckForSubscription(shouldHide, x);
});
return new RequestsViewModel<AlbumRequest>
{
Collection = requests,
Total = total
};
}
private IQueryable<AlbumRequest> OrderAlbums(IQueryable<AlbumRequest> allRequests, OrderType type)
{
switch (type)
{
case OrderType.RequestedDateAsc:
return allRequests.OrderBy(x => x.RequestedDate);
case OrderType.RequestedDateDesc:
return allRequests.OrderByDescending(x => x.RequestedDate);
case OrderType.TitleAsc:
return allRequests.OrderBy(x => x.Title);
case OrderType.TitleDesc:
return allRequests.OrderByDescending(x => x.Title);
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public async Task<int> GetTotal()
{
var shouldHide = await HideFromOtherUsers();
if (shouldHide.Hide)
{
return await MusicRepository.GetWithUser(shouldHide.UserId).CountAsync();
}
else
{
return await MusicRepository.GetWithUser().CountAsync();
}
}
/// <summary>
/// Gets the requests.
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<AlbumRequest>> GetRequests()
{
var shouldHide = await HideFromOtherUsers();
List<AlbumRequest> allRequests;
if (shouldHide.Hide)
{
allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync();
}
else
{
allRequests = await MusicRepository.GetWithUser().ToListAsync();
}
allRequests.ForEach(async x =>
{
await CheckForSubscription(shouldHide, x);
});
return allRequests;
}
private async Task CheckForSubscription(HideResult shouldHide, AlbumRequest x)
{
if (shouldHide.UserId == x.RequestedUserId)
{
x.ShowSubscribe = false;
}
else
{
x.ShowSubscribe = true;
var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s =>
s.UserId == shouldHide.UserId && s.RequestId == x.Id && s.RequestType == RequestType.Album);
x.Subscribed = sub != null;
}
}
/// <summary>
/// Searches the album request.
/// </summary>
/// <param name="search">The search.</param>
/// <returns></returns>
public async Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search)
{
var shouldHide = await HideFromOtherUsers();
List<AlbumRequest> allRequests;
if (shouldHide.Hide)
{
allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync();
}
else
{
allRequests = await MusicRepository.GetWithUser().ToListAsync();
}
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList();
results.ForEach(async x =>
{
await CheckForSubscription(shouldHide, x);
});
return results;
}
public async Task<RequestEngineResult> ApproveAlbumById(int requestId)
{
var request = await MusicRepository.Find(requestId);
return await ApproveAlbum(request);
}
public async Task<RequestEngineResult> DenyAlbumById(int modelId)
{
var request = await MusicRepository.Find(modelId);
if (request == null)
{
return new RequestEngineResult
{
ErrorMessage = "Request does not exist"
};
}
request.Denied = true;
// We are denying a request
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
await MusicRepository.Update(request);
return new RequestEngineResult
{
Message = "Request successfully deleted",
};
}
public async Task<RequestEngineResult> ApproveAlbum(AlbumRequest request)
{
if (request == null)
{
return new RequestEngineResult
{
ErrorMessage = "Request does not exist"
};
}
request.MarkedAsApproved = DateTime.Now;
request.Approved = true;
request.Denied = false;
await MusicRepository.Update(request);
var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification);
if (canNotify.Success)
{
NotificationHelper.Notify(request, NotificationType.RequestApproved);
}
if (request.Approved)
{
var result = await _musicSender.Send(request);
if (result.Success && result.Sent)
{
return new RequestEngineResult
{
Result = true
};
}
if (!result.Success)
{
Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message);
return new RequestEngineResult
{
Message = result.Message,
ErrorMessage = result.Message,
Result = false
};
}
// If there are no providers then it's successful but movie has not been sent
}
return new RequestEngineResult
{
Result = true
};
}
/// <summary>
/// Removes the Album request.
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <returns></returns>
public async Task RemoveAlbumRequest(int requestId)
{
var request = await MusicRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId);
await MusicRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)
{
return await MusicRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
}
public async Task<RequestEngineResult> MarkUnavailable(int modelId)
{
var request = await MusicRepository.Find(modelId);
if (request == null)
{
return new RequestEngineResult
{
ErrorMessage = "Request does not exist"
};
}
request.Available = false;
await MusicRepository.Update(request);
return new RequestEngineResult
{
Message = "Request is now unavailable",
Result = true
};
}
public async Task<RequestEngineResult> MarkAvailable(int modelId)
{
var request = await MusicRepository.Find(modelId);
if (request == null)
{
return new RequestEngineResult
{
ErrorMessage = "Request does not exist"
};
}
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
NotificationHelper.Notify(request, NotificationType.RequestAvailable);
await MusicRepository.Update(request);
return new RequestEngineResult
{
Message = "Request is now available",
Result = true
};
}
private async Task<RequestEngineResult> AddAlbumRequest(AlbumRequest model)
{
await MusicRepository.Add(model);
var result = await RunSpecificRule(model, SpecificRules.CanSendNotification);
if (result.Success)
{
NotificationHelper.NewRequest(model);
}
await _requestLog.Add(new RequestLog
{
UserId = (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.Album,
});
return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!" };
}
}
}

@ -0,0 +1,219 @@
using System;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine
{
public class MusicSearchEngine : BaseMediaEngine, IMusicSearchEngine
{
public MusicSearchEngine(IPrincipal identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper,
ILogger<MusicSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub,
ISettingsService<LidarrSettings> lidarrSettings)
: base(identity, service, r, um, mem, s, sub)
{
_lidarrApi = lidarrApi;
_lidarrSettings = lidarrSettings;
Mapper = mapper;
Logger = logger;
}
private readonly ILidarrApi _lidarrApi;
private IMapper Mapper { get; }
private ILogger Logger { get; }
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
/// <summary>
/// Searches the specified album.
/// </summary>
/// <param name="search">The search.</param>
/// <returns></returns>
public async Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search)
{
var settings = await GetSettings();
var result = await _lidarrApi.AlbumLookup(search, settings.ApiKey, settings.FullUri);
var vm = new List<SearchAlbumViewModel>();
foreach (var r in result)
{
vm.Add(await MapIntoAlbumVm(r, settings));
}
return vm;
}
/// <summary>
/// Searches the specified artist
/// </summary>
/// <param name="search">The search.</param>
/// <returns></returns>
public async Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search)
{
var settings = await GetSettings();
var result = await _lidarrApi.ArtistLookup(search, settings.ApiKey, settings.FullUri);
var vm = new List<SearchArtistViewModel>();
foreach (var r in result)
{
vm.Add(await MapIntoArtistVm(r));
}
return vm;
}
/// <summary>
/// Returns all albums by the specified artist
/// </summary>
/// <param name="foreignArtistId"></param>
/// <returns></returns>
public async Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId)
{
var settings = await GetSettings();
var result = await _lidarrApi.GetAlbumsByArtist(foreignArtistId);
// We do not want any Singles (This will include EP's)
var albumsOnly =
result.Albums.Where(x => !x.Type.Equals("Single", StringComparison.InvariantCultureIgnoreCase));
var vm = new List<SearchAlbumViewModel>();
foreach (var album in albumsOnly)
{
vm.Add(await MapIntoAlbumVm(album, result.Id, result.ArtistName, settings));
}
return vm;
}
/// <summary>
/// Returns the artist that produced the album
/// </summary>
/// <param name="foreignArtistId"></param>
/// <returns></returns>
public async Task<ArtistResult> GetAlbumArtist(string foreignArtistId)
{
var settings = await GetSettings();
return await _lidarrApi.GetArtistByForeignId(foreignArtistId, settings.ApiKey, settings.FullUri);
}
public async Task<ArtistResult> GetArtist(int artistId)
{
var settings = await GetSettings();
return await _lidarrApi.GetArtist(artistId, settings.ApiKey, settings.FullUri);
}
private async Task<SearchArtistViewModel> MapIntoArtistVm(ArtistLookup a)
{
var vm = new SearchArtistViewModel
{
ArtistName = a.artistName,
ArtistType = a.artistType,
Banner = a.images?.FirstOrDefault(x => x.coverType.Equals("banner"))?.url,
Logo = a.images?.FirstOrDefault(x => x.coverType.Equals("logo"))?.url,
CleanName = a.cleanName,
Disambiguation = a.disambiguation,
ForignArtistId = a.foreignArtistId,
Links = a.links,
Overview = a.overview,
};
var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster"));
if (poster == null)
{
vm.Poster = a.remotePoster;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist);
return vm;
}
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumLookup 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
};
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.artist?.foreignArtistId;
vm.ArtistName = a.artist?.artistName;
}
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url;
if (vm.Cover.IsNullOrEmpty())
{
vm.Cover = a.remoteCover;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
await RunSearchRules(vm);
return vm;
}
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(Album a, string artistId, string artistName, LidarrSettings settings)
{
var fullAlbum = await _lidarrApi.GetAlbumByForeignId(a.Id, settings.ApiKey, settings.FullUri);
var vm = new SearchAlbumViewModel
{
ForeignAlbumId = a.Id,
Monitored = fullAlbum.monitored,
Rating = fullAlbum.ratings?.value ?? 0m,
ReleaseDate = fullAlbum.releaseDate,
Title = a.Title,
Disk = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
ForeignArtistId = artistId,
ArtistName = artistName,
Cover = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url
};
if (vm.Cover.IsNullOrEmpty())
{
vm.Cover = fullAlbum.remoteCover;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
await RunSearchRules(vm);
return vm;
}
private LidarrSettings _settings;
private async Task<LidarrSettings> GetSettings()
{
return _settings ?? (_settings = await _lidarrSettings.GetSettingsAsync());
}
}
}

@ -54,7 +54,16 @@ namespace Ombi.Core.Engine
if (searchResult != null)
{
return await ProcessResults(searchResult);
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in searchResult)
{
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
{
continue;
}
retVal.Add(await ProcessResult(tvMazeSearch));
}
return retVal;
}
return null;
}
@ -145,12 +154,16 @@ namespace Ombi.Core.Engine
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
var viewT = Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
retVal.Add(await ProcessResult(viewT));
retVal.Add(await ProcessResult(tvMazeSearch));
}
return retVal;
}
private async Task<SearchTvShowViewModel> ProcessResult<T>(T tvMazeSearch)
{
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item)
{
item.TheTvDbId = item.Id.ToString();

@ -40,6 +40,18 @@ namespace Ombi.Core
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
public void NewRequest(AlbumRequest model)
{
var notificationModel = new NotificationOptions
{
RequestId = model.Id,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = model.RequestType
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
public void Notify(MovieRequests model, NotificationType type)
{
@ -66,5 +78,19 @@ namespace Ombi.Core
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
public void Notify(AlbumRequest model, NotificationType type)
{
var notificationModel = new NotificationOptions
{
RequestId = model.Id,
DateTime = DateTime.Now,
NotificationType = type,
RequestType = model.RequestType,
Recipient = model.RequestedUser?.Email ?? string.Empty
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
}
}

@ -7,5 +7,6 @@ namespace Ombi.Core.Models.Requests
{
IMovieRequestRepository MovieRequestService { get; }
ITvRequestRepository TvRequestService { get; }
IMusicRequestRepository MusicRequestRepository { get; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Core.Models.Requests
{
public class MusicAlbumRequestViewModel
{
public string ForeignAlbumId { get; set; }
}
}

@ -5,13 +5,15 @@ namespace Ombi.Core.Models.Requests
{
public class RequestService : IRequestServiceMain
{
public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie)
public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie, IMusicRequestRepository music)
{
TvRequestService = tv;
MovieRequestService = movie;
MusicRequestRepository = music;
}
public ITvRequestRepository TvRequestService { get; }
public IMusicRequestRepository MusicRequestRepository { get; }
public IMovieRequestRepository MovieRequestService { get; }
}
}

@ -0,0 +1,23 @@
using System;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search
{
public class SearchAlbumViewModel : SearchViewModel
{
public string Title { get; set; }
public string ForeignAlbumId { get; set; }
public bool Monitored { get; set; }
public string AlbumType { get; set; }
public decimal Rating { get; set; }
public DateTime ReleaseDate { get; set; }
public string ArtistName { get; set; }
public string ForeignArtistId { get; set; }
public string Cover { get; set; }
public string Disk { get; set; }
public decimal PercentOfTracks { get; set; }
public override RequestType Type => RequestType.Album;
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
public bool FullyAvailable => PercentOfTracks == 100;
}
}

@ -0,0 +1,19 @@
using Ombi.Api.Lidarr.Models;
namespace Ombi.Core.Models.Search
{
public class SearchArtistViewModel
{
public string ArtistName { get; set; }
public string ForignArtistId { get; set; }
public string Overview { get; set; }
public string Disambiguation { get; set; }
public string Banner { get; set; }
public string Poster { get; set; }
public string Logo { get; set; }
public bool Monitored { get; set; }
public string ArtistType { get; set; }
public string CleanName { get; set; }
public Link[] Links { get; set; } // Couldn't be bothered to map it
}
}

@ -14,7 +14,6 @@
<PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
@ -22,6 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.DogNzb\Ombi.Api.DogNzb.csproj" />
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />

@ -3,5 +3,7 @@
public enum SpecificRules
{
CanSendNotification,
LidarrArtist,
LidarrAlbum,
}
}

@ -29,6 +29,8 @@ namespace Ombi.Core.Rule.Rules.Request
obj.Approved = true;
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
obj.Approved = true;
if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic))
obj.Approved = true;
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
}
}

@ -23,13 +23,23 @@ namespace Ombi.Core.Rule.Rules
if (obj.RequestType == RequestType.Movie)
{
if (User.IsInRole(OmbiRoles.RequestMovie))
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 (User.IsInRole(OmbiRoles.RequestTv))
if (obj.RequestType == RequestType.TvShow)
{
if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv))
return Task.FromResult(Success());
}
if (obj.RequestType == RequestType.Album)
{
if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic))
return Task.FromResult(Success());
}
return Task.FromResult(Fail("You do not have permissions to Request a TV Show"));
}
}

@ -54,6 +54,7 @@ namespace Ombi.Core.Rule.Rules.Request
var movieLimit = user.MovieRequestLimit;
var episodeLimit = user.EpisodeRequestLimit;
var musicLimit = user.MusicRequestLimit;
var requestLog = _requestLog.GetAll().Where(x => x.UserId == obj.RequestedUserId);
if (obj.RequestType == RequestType.Movie)
@ -71,7 +72,7 @@ namespace Ombi.Core.Rule.Rules.Request
return Fail("You have exceeded your Movie request quota!");
}
}
else
else if (obj.RequestType == RequestType.TvShow)
{
if (episodeLimit <= 0)
return Success();
@ -99,6 +100,20 @@ namespace Ombi.Core.Rule.Rules.Request
{
return Fail("You have exceeded your Episode request quota!");
}
} else if (obj.RequestType == RequestType.Album)
{
if (musicLimit <= 0)
return Success();
var albumLogs = requestLog.Where(x => x.RequestType == RequestType.Album);
// Count how many requests in the past 7 days
var count = await albumLogs.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
count += 1; // Since we are including this request
if (count > musicLimit)
{
return Fail("You have exceeded your Album request quota!");
}
}
return Success();
}

@ -11,20 +11,22 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class ExistingRule : BaseSearchRule, IRules<SearchViewModel>
{
public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv)
public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv, IMusicRequestRepository music)
{
Movie = movie;
Tv = tv;
Music = music;
}
private IMovieRequestRepository Movie { get; }
private IMusicRequestRepository Music { get; }
private ITvRequestRepository Tv { get; }
public Task<RuleResult> Execute(SearchViewModel obj)
public async Task<RuleResult> Execute(SearchViewModel obj)
{
if (obj.Type == RequestType.Movie)
{
var movieRequests = Movie.GetRequest(obj.Id);
var movieRequests = await Movie.GetRequestAsync(obj.Id);
if (movieRequests != null) // Do we already have a request for this?
{
@ -33,11 +35,11 @@ namespace Ombi.Core.Rule.Rules.Search
obj.Approved = movieRequests.Approved;
obj.Available = movieRequests.Available;
return Task.FromResult(Success());
return Success();
}
return Task.FromResult(Success());
return Success();
}
else
if (obj.Type == RequestType.TvShow)
{
//var tvRequests = Tv.GetRequest(obj.Id);
//if (tvRequests != null) // Do we already have a request for this?
@ -94,8 +96,24 @@ namespace Ombi.Core.Rule.Rules.Search
request.PartlyAvailable = true;
}
return Task.FromResult(Success());
return Success();
}
if (obj.Type == RequestType.Album)
{
var album = (SearchAlbumViewModel) obj;
var albumRequest = await Music.GetRequestAsync(album.ForeignAlbumId);
if (albumRequest != null) // Do we already have a request for this?
{
obj.Requested = true;
obj.RequestId = albumRequest.Id;
obj.Approved = albumRequest.Approved;
obj.Available = albumRequest.Available;
return Success();
}
return Success();
}
return Success();
}
}
}

@ -0,0 +1,36 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Rule.Rules.Search
{
public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule<object>
{
public LidarrAlbumCacheRule(IRepository<LidarrAlbumCache> db)
{
_db = db;
}
private readonly IRepository<LidarrAlbumCache> _db;
public Task<RuleResult> Execute(object objec)
{
var obj = (SearchAlbumViewModel) objec;
// Check if it's in Lidarr
var result = _db.GetAll().FirstOrDefault(x => x.ForeignAlbumId.Equals(obj.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase));
if (result != null)
{
obj.PercentOfTracks = result.PercentOfTracks;
obj.Monitored = true; // It's in Lidarr so it's monitored
}
return Task.FromResult(Success());
}
public override SpecificRules Rule => SpecificRules.LidarrAlbum;
}
}

@ -0,0 +1,35 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Rule.Rules.Search
{
public class LidarrArtistCacheRule : SpecificRule, ISpecificRule<object>
{
public LidarrArtistCacheRule(IRepository<LidarrArtistCache> db)
{
_db = db;
}
private readonly IRepository<LidarrArtistCache> _db;
public Task<RuleResult> Execute(object objec)
{
var obj = (SearchArtistViewModel) objec;
// Check if it's in Lidarr
var result = _db.GetAll().FirstOrDefault(x => x.ForeignArtistId.Equals(obj.ForignArtistId, StringComparison.InvariantCultureIgnoreCase));
if (result != null)
{
obj.Monitored = true; // It's in Lidarr so it's monitored
}
return Task.FromResult(Success());
}
public override SpecificRules Rule => SpecificRules.LidarrArtist;
}
}

@ -42,6 +42,13 @@ namespace Ombi.Core.Rule.Rules.Specific
sendNotification = !await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.AutoApproveTv);
}
}
else if (req.RequestType == RequestType.Album)
{
if (settings.DoNotSendNotificationsForAutoApprove)
{
sendNotification = !await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.AutoApproveMusic);
}
}
if (await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.Admin))
{

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Senders
{
public interface IMusicSender
{
Task<SenderResult> Send(AlbumRequest model);
}
}

@ -8,7 +8,9 @@ namespace Ombi.Core
{
void NewRequest(FullBaseRequest model);
void NewRequest(ChildRequests model);
void NewRequest(AlbumRequest model);
void Notify(MovieRequests model, NotificationType type);
void Notify(ChildRequests model, NotificationType type);
void Notify(AlbumRequest model, NotificationType type);
}
}

@ -0,0 +1,126 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
using Ombi.Api.Radarr;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities.Requests;
using Serilog;
namespace Ombi.Core.Senders
{
public class MusicSender : IMusicSender
{
public MusicSender(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi)
{
_lidarrSettings = lidarr;
_lidarrApi = lidarrApi;
}
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ILidarrApi _lidarrApi;
public async Task<SenderResult> Send(AlbumRequest model)
{
var settings = await _lidarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
return await SendToLidarr(model, settings);
}
return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" };
}
private async Task<SenderResult> SendToLidarr(AlbumRequest model, LidarrSettings settings)
{
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
//if (model.QualityOverride > 0)
//{
// qualityToUse = model.QualityOverride;
//}
var rootFolderPath = /*model.RootPathOverride <= 0 ?*/ settings.DefaultRootPath /*: await RadarrRootPath(model.RootPathOverride, settings)*/;
// Need to get the artist
var artist = await _lidarrApi.GetArtistByForeignId(model.ForeignArtistId, settings.ApiKey, settings.FullUri);
if (artist == null || artist.id <= 0)
{
// Create artist
var newArtist = new ArtistAdd
{
foreignArtistId = model.ForeignArtistId,
addOptions = new Addoptions
{
monitored = true,
searchForMissingAlbums = false,
selectedOption = 6, // None
AlbumsToMonitor = new[] {model.ForeignAlbumId}
},
added = DateTime.Now,
monitored = true,
albumFolder = settings.AlbumFolder,
artistName = model.ArtistName,
cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(),
images = new Image[] { },
languageProfileId = settings.LanguageProfileId,
links = new Link[] {},
metadataProfileId = settings.MetadataProfileId,
qualityProfileId = qualityToUse,
rootFolderPath = rootFolderPath,
};
var result = await _lidarrApi.AddArtist(newArtist, settings.ApiKey, settings.FullUri);
if (result != null && result.id > 0)
{
// Setup the albums
return new SenderResult { Message = "Album has been requested!", Sent = true, Success = true };
}
}
else
{
await SetupAlbum(model, artist, settings);
}
return new SenderResult { Success = false, Sent = false, Message = "Album is already monitored" };
}
private async Task<SenderResult> SetupAlbum(AlbumRequest model, ArtistResult artist, LidarrSettings settings)
{
// Get the album id
var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri);
var album = albums.FirstOrDefault(x =>
x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase));
var maxRetryCount = 10; // 5 seconds
var currentRetry = 0;
while (!albums.Any() || album == null)
{
if (currentRetry >= maxRetryCount)
{
break;
}
currentRetry++;
await Task.Delay(500);
albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri);
album = albums.FirstOrDefault(x =>
x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase));
}
// Get the album we want.
if (album == null)
{
return new SenderResult { Message = "Could not find album in Lidarr", Sent = false, Success = false };
}
var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri);
if (result.monitored)
{
return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true};
}
return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false };
}
}
}

@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato;
using Ombi.Api.DogNzb;
using Ombi.Api.FanartTv;
using Ombi.Api.Github;
using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost;
using Ombi.Api.Notifications;
using Ombi.Api.Pushbullet;
@ -53,9 +54,11 @@ using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
using Ombi.Api.Telegram;
using Ombi.Core.Authentication;
using Ombi.Core.Processor;
using Ombi.Schedule.Jobs.Lidarr;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.SickRage;
using Ombi.Schedule.Processor;
using Ombi.Store.Entities;
namespace Ombi.DependencyInjection
{
@ -82,7 +85,10 @@ namespace Ombi.DependencyInjection
services.AddTransient<IUserStatsEngine, UserStatsEngine>();
services.AddTransient<IMovieSender, MovieSender>();
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
services.AddTransient<IMusicSearchEngine, MusicSearchEngine>();
services.AddTransient<IMusicRequestEngine, MusicRequestEngine>();
services.AddTransient<ITvSender, TvSender>();
services.AddTransient<IMusicSender, MusicSender>();
services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
}
@ -117,6 +123,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISickRageApi, SickRageApi>();
services.AddTransient<IAppVeyorApi, AppVeyorApi>();
services.AddTransient<IOneSignalApi, OneSignalApi>();
services.AddTransient<ILidarrApi, LidarrApi>();
}
public static void RegisterStore(this IServiceCollection services) {
@ -131,6 +138,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>();
services.AddTransient<IAuditRepository, AuditRepository>();
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddTransient<ITokenRepository, TokenRepository>();
@ -180,6 +188,9 @@ namespace Ombi.DependencyInjection
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
services.AddTransient<INewsletterJob, NewsletterJob>();
services.AddTransient<IPlexRecentlyAddedSync, PlexRecentlyAddedSync>();
services.AddTransient<ILidarrAlbumSync, LidarrAlbumSync>();
services.AddTransient<ILidarrArtistSync, LidarrArtistSync>();
services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>();
}
}
}

@ -21,6 +21,7 @@
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
<ProjectReference Include="..\Ombi.Api.FanartTv\Ombi.Api.FanartTv.csproj" />
<ProjectReference Include="..\Ombi.Api.Github\Ombi.Api.Github.csproj" />
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />

@ -18,6 +18,8 @@ namespace Ombi.Helpers
public const string NowPlayingMovies = nameof(NowPlayingMovies);
public const string RadarrRootProfiles = nameof(RadarrRootProfiles);
public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles);
public const string LidarrRootFolders = nameof(LidarrRootFolders);
public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles);
public const string FanartTv = nameof(FanartTv);
}
}

@ -20,6 +20,7 @@ namespace Ombi.Helpers
public static EventId CouchPotatoCacher => new EventId(2007);
public static EventId PlexContentCacher => new EventId(2008);
public static EventId SickRageCacher => new EventId(2009);
public static EventId LidarrArtistCache => new EventId(2010);
public static EventId MovieSender => new EventId(3000);

@ -7,9 +7,11 @@
public const string Admin = nameof(Admin);
public const string AutoApproveMovie = nameof(AutoApproveMovie);
public const string AutoApproveTv = nameof(AutoApproveTv);
public const string AutoApproveMusic = nameof(AutoApproveMusic);
public const string PowerUser = nameof(PowerUser);
public const string RequestTv = nameof(RequestTv);
public const string RequestMovie = nameof(RequestMovie);
public const string RequestMusic = nameof(RequestMusic);
public const string Disabled = nameof(Disabled);
public const string ReceivesNewsletter = nameof(ReceivesNewsletter);
}

@ -76,6 +76,10 @@ namespace Ombi.Helpers
return -1;
}
public static string RemoveSpaces(this string str)
{
return str.Replace(" ", "");
}
public static string StripCharacters(this string str, params char[] chars)
{
return string.Concat(str.Where(c => !chars.Contains(c)));

@ -20,8 +20,8 @@ namespace Ombi.Notifications.Agents
{
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn,
ILogger<DiscordNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub)
: base(sn, r, m, t,s,log, sub)
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music)
: base(sn, r, m, t, s, log, sub, music)
{
Api = api;
Logger = log;
@ -130,12 +130,18 @@ namespace Ombi.Notifications.Agents
title = MovieRequest.Title;
image = MovieRequest.PosterPath;
}
else
else if (model.RequestType == RequestType.TvShow)
{
user = TvRequest.RequestedUser.UserAlias;
title = TvRequest.ParentRequest.Title;
image = TvRequest.ParentRequest.PosterPath;
}
else if (model.RequestType == RequestType.Album)
{
user = AlbumRequest.RequestedUser.UserAlias;
title = AlbumRequest.Title;
image = AlbumRequest.Cover;
}
var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying";
var notification = new NotificationMessage
{

@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents
public class EmailNotification : BaseNotification<EmailNotificationSettings>, IEmailNotification
{
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService<CustomizationSettings> c,
ILogger<EmailNotification> log, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub) : base(settings, r, m, t, c, log, sub)
ILogger<EmailNotification> log, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(settings, r, m, t, c, log, sub, music)
{
EmailProvider = prov;
Logger = log;

@ -21,7 +21,7 @@ namespace Ombi.Notifications.Agents
public class MattermostNotification : BaseNotification<MattermostNotificationSettings>, IMattermostNotification
{
public MattermostNotification(IMattermostApi api, ISettingsService<MattermostNotificationSettings> sn, ILogger<MattermostNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t,s,log, sub)
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music)
{
Api = api;
Logger = log;

@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents
{
public MobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification,
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub) : base(sn, r, m, t, s,log, sub)
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music)
{
_api = api;
_logger = log;

@ -17,7 +17,7 @@ namespace Ombi.Notifications.Agents
public class PushbulletNotification : BaseNotification<PushbulletSettings>, IPushbulletNotification
{
public PushbulletNotification(IPushbulletApi api, ISettingsService<PushbulletSettings> sn, ILogger<PushbulletNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t,s,log, sub)
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music)
{
Api = api;
Logger = log;

@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents
public class PushoverNotification : BaseNotification<PushoverSettings>, IPushoverNotification
{
public PushoverNotification(IPushoverApi api, ISettingsService<PushoverSettings> sn, ILogger<PushoverNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t, s, log, sub)
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music)
{
Api = api;
Logger = log;
@ -178,7 +178,7 @@ namespace Ombi.Notifications.Agents
try
{
//&+' < >
await Api.PushAsync(settings.AccessToken, model.Message.StripCharacters('&','+','<','>'), settings.UserToken);
await Api.PushAsync(settings.AccessToken, model.Message.StripCharacters('&','+','<','>'), settings.UserToken, settings.Priority, settings.Sound);
}
catch (Exception e)
{

@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents
public class SlackNotification : BaseNotification<SlackNotificationSettings>, ISlackNotification
{
public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn, ILogger<SlackNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t, s, log, sub)
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music)
{
Api = api;
Logger = log;

@ -19,7 +19,7 @@ namespace Ombi.Notifications.Agents
public TelegramNotification(ITelegramApi api, ISettingsService<TelegramSettings> sn, ILogger<TelegramNotification> log,
INotificationTemplatesRepository r, IMovieRequestRepository m,
ITvRequestRepository t, ISettingsService<CustomizationSettings> s
, IRepository<RequestSubscription> sub) : base(sn, r, m, t,s,log, sub)
, IRepository<RequestSubscription> sub, IMusicRequestRepository music) : base(sn, r, m, t,s,log, sub, music)
{
Api = api;
Logger = log;

@ -19,7 +19,7 @@ namespace Ombi.Notifications.Interfaces
public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new()
{
protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv,
ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log, IRepository<RequestSubscription> sub)
ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log, IRepository<RequestSubscription> sub, IMusicRequestRepository album)
{
Settings = settings;
TemplateRepository = templateRepo;
@ -30,12 +30,14 @@ namespace Ombi.Notifications.Interfaces
CustomizationSettings.ClearCache();
RequestSubscription = sub;
_log = log;
AlbumRepository = album;
}
protected ISettingsService<T> Settings { get; }
protected INotificationTemplatesRepository TemplateRepository { get; }
protected IMovieRequestRepository MovieRepository { get; }
protected ITvRequestRepository TvRepository { get; }
protected IMusicRequestRepository AlbumRepository { get; }
protected CustomizationSettings Customization { get; set; }
protected IRepository<RequestSubscription> RequestSubscription { get; set; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
@ -43,6 +45,7 @@ namespace Ombi.Notifications.Interfaces
protected ChildRequests TvRequest { get; set; }
protected AlbumRequest AlbumRequest { get; set; }
protected MovieRequests MovieRequest { get; set; }
protected IQueryable<OmbiUser> SubsribedUsers { get; private set; }
@ -130,10 +133,14 @@ namespace Ombi.Notifications.Interfaces
{
MovieRequest = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId);
}
else
else if (type == RequestType.TvShow)
{
TvRequest = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
}
else if (type == RequestType.Album)
{
AlbumRequest = await AlbumRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId);
}
}
private async Task<T> GetConfiguration()
@ -181,11 +188,16 @@ namespace Ombi.Notifications.Interfaces
curlys.Setup(model, MovieRequest, Customization);
}
else
else if (model.RequestType == RequestType.TvShow)
{
_log.LogDebug("Notification options: {@model}, Req: {@TvRequest}, Settings: {@Customization}", model, TvRequest, Customization);
curlys.Setup(model, TvRequest, Customization);
}
else if (model.RequestType == RequestType.Album)
{
_log.LogDebug("Notification options: {@model}, Req: {@AlbumRequest}, Settings: {@Customization}", model, AlbumRequest, Customization);
curlys.Setup(model, AlbumRequest, Customization);
}
var parsed = resolver.ParseMessage(template, curlys);
return parsed;

@ -58,6 +58,40 @@ namespace Ombi.Notifications
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
}
public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s)
{
LoadIssues(opts);
string title;
if (req == null)
{
opts.Substitutes.TryGetValue("Title", out title);
}
else
{
title = req?.Title;
}
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = req?.RequestedUser?.UserName;
if (UserName.IsNullOrEmpty())
{
// Can be set if it's an issue
UserName = req?.RequestedUser?.UserName;
}
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
Title = title;
RequestedDate = req?.RequestedDate.ToString("D");
if (Type.IsNullOrEmpty())
{
Type = req?.RequestType.Humanize();
}
Year = req?.ReleaseDate.Year.ToString();
PosterImage = (req?.Cover.HasValue() ?? false) ? req.Cover : req?.Disk ?? string.Empty;
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
}
public void SetupNewsletter(CustomizationSettings s, OmbiUser username)
{
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;

@ -4,6 +4,7 @@ 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.Radarr;
@ -19,7 +20,7 @@ namespace Ombi.Schedule
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex)
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex, ILidarrArtistSync artist)
{
_plexContentSync = plexContentSync;
_radarrSync = radarrSync;
@ -34,6 +35,7 @@ namespace Ombi.Schedule
_refreshMetadata = refresh;
_newsletter = newsletter;
_plexRecentlyAddedSync = recentlyAddedPlex;
_lidarrArtistSync = artist;
}
private readonly IPlexContentSync _plexContentSync;
@ -49,6 +51,7 @@ namespace Ombi.Schedule
private readonly ISettingsService<JobSettings> _jobSettings;
private readonly IRefreshMetadata _refreshMetadata;
private readonly INewsletterJob _newsletter;
private readonly ILidarrArtistSync _lidarrArtistSync;
public void Setup()
{
@ -62,6 +65,7 @@ namespace Ombi.Schedule
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(() => _updater.Update(null), JobSettingsHelper.Updater(s));

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.Lidarr
{
public interface ILidarrAlbumSync
{
Task CacheContent();
void Dispose();
Task<IEnumerable<LidarrAlbumCache>> GetCachedContent();
}
}

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

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Schedule.Jobs.Lidarr
{
public interface ILidarrAvailabilityChecker
{
Task Start();
}
}

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Radarr;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Schedule.Jobs.Lidarr
{
public class LidarrAlbumSync : ILidarrAlbumSync
{
public LidarrAlbumSync(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi, ILogger<LidarrAlbumSync> log, IOmbiContext ctx,
IBackgroundJobClient job, ILidarrAvailabilityChecker availability)
{
_lidarrSettings = lidarr;
_lidarrApi = lidarrApi;
_logger = log;
_ctx = ctx;
_job = job;
_availability = availability;
_lidarrSettings.ClearCache();
}
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ILidarrApi _lidarrApi;
private readonly ILogger _logger;
private readonly IOmbiContext _ctx;
private readonly IBackgroundJobClient _job;
private readonly ILidarrAvailabilityChecker _availability;
public async Task CacheContent()
{
try
{
var settings = await _lidarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
try
{
var albums = await _lidarrApi.GetAllAlbums(settings.ApiKey, settings.FullUri);
if (albums != null && albums.Any())
{
// Let's remove the old cached data
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache");
var albumCache = new List<LidarrAlbumCache>();
foreach (var a in albums)
{
if (a.id > 0)
{
albumCache.Add(new LidarrAlbumCache
{
ArtistId = a.artistId,
ForeignAlbumId = a.foreignAlbumId,
ReleaseDate = a.releaseDate,
TrackCount = a.currentRelease.trackCount,
Monitored = a.monitored,
Title = a.title,
PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m,
AddedAt = DateTime.Now,
});
}
}
await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache);
await _ctx.SaveChangesAsync();
}
}
catch (System.Exception ex)
{
_logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Lidarr Album");
}
_job.Enqueue(() => _availability.Start());
}
}
catch (Exception)
{
_logger.LogInformation(LoggingEvents.LidarrArtistCache, "Lidarr is not setup, cannot cache Album");
}
}
public async Task<IEnumerable<LidarrAlbumCache>> GetCachedContent()
{
return await _ctx.LidarrAlbumCache.ToListAsync();
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_ctx?.Dispose();
_lidarrSettings?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Radarr;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Ombi.Schedule.Jobs.Lidarr
{
public class LidarrArtistSync : ILidarrArtistSync
{
public LidarrArtistSync(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi, ILogger<LidarrArtistSync> log, IOmbiContext ctx,
IBackgroundJobClient background, ILidarrAlbumSync album)
{
_lidarrSettings = lidarr;
_lidarrApi = lidarrApi;
_logger = log;
_ctx = ctx;
_job = background;
_albumSync = album;
_lidarrSettings.ClearCache();
}
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ILidarrApi _lidarrApi;
private readonly ILogger _logger;
private readonly IOmbiContext _ctx;
private readonly IBackgroundJobClient _job;
private readonly ILidarrAlbumSync _albumSync;
public async Task CacheContent()
{
try
{
var settings = await _lidarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
try
{
var artists = await _lidarrApi.GetArtists(settings.ApiKey, settings.FullUri);
if (artists != null && artists.Any())
{
// Let's remove the old cached data
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrArtistCache");
var artistCache = new List<LidarrArtistCache>();
foreach (var a in artists)
{
if (a.id > 0)
{
artistCache.Add(new LidarrArtistCache
{
ArtistId = a.id,
ArtistName = a.artistName,
ForeignArtistId = a.foreignArtistId,
Monitored = a.monitored
});
}
}
await _ctx.LidarrArtistCache.AddRangeAsync(artistCache);
await _ctx.SaveChangesAsync();
}
}
catch (Exception ex)
{
_logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Lidarr");
}
_job.Enqueue(() => _albumSync.CacheContent());
}
}
catch (Exception)
{
_logger.LogInformation(LoggingEvents.LidarrArtistCache, "Lidarr is not setup, cannot cache Artist");
}
}
public async Task<IEnumerable<LidarrArtistCache>> GetCachedContent()
{
return await _ctx.LidarrArtistCache.ToListAsync();
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_ctx?.Dispose();
_lidarrSettings?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core.Notifications;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Schedule.Jobs.Lidarr
{
public class LidarrAvailabilityChecker : ILidarrAvailabilityChecker
{
public LidarrAvailabilityChecker(IMusicRequestRepository requests, IRepository<LidarrAlbumCache> albums, ILogger<LidarrAvailabilityChecker> log,
IBackgroundJobClient job, INotificationService notification)
{
_cachedAlbums = albums;
_requestRepository = requests;
_logger = log;
_job = job;
_notificationService = notification;
}
private readonly IMusicRequestRepository _requestRepository;
private readonly IRepository<LidarrAlbumCache> _cachedAlbums;
private readonly ILogger _logger;
private readonly IBackgroundJobClient _job;
private readonly INotificationService _notificationService;
public async Task Start()
{
var allAlbumRequests = _requestRepository.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available);
var albumsToUpdate = new List<AlbumRequest>();
foreach (var request in allAlbumRequests)
{
// Check if we have it cached
var cachedAlbum = await _cachedAlbums.FirstOrDefaultAsync(x => x.ForeignAlbumId.Equals(request.ForeignAlbumId));
if (cachedAlbum != null)
{
if (cachedAlbum.Monitored && cachedAlbum.FullyAvailable)
{
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
albumsToUpdate.Add(request);
}
}
}
foreach (var albumRequest in albumsToUpdate)
{
await _requestRepository.Update(albumRequest);
var recipient = albumRequest.RequestedUser.Email.HasValue() ? albumRequest.RequestedUser.Email : string.Empty;
_logger.LogDebug("AlbumId: {0}, RequestUser: {1}", albumRequest.Id, recipient);
_job.Enqueue(() => _notificationService.Publish(new NotificationOptions
{
DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable,
RequestId = albumRequest.Id,
RequestType = RequestType.Album,
Recipient = recipient,
}));
}
}
}
}

@ -1,4 +1,5 @@
using System.Text;
using Ombi.Helpers;
namespace Ombi.Schedule.Jobs.Ombi
{
@ -21,14 +22,21 @@ namespace Ombi.Schedule.Jobs.Ombi
}
protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url)
{
if (url.HasValue())
{
sb.Append("<tr>");
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append(
"<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", mediaurl);
sb.AppendFormat("<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none;-ms-interpolation-mode: bicubic; max-width: 100%;display: block; visibility: hidden; \">", url);
sb.AppendFormat(
"<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none;-ms-interpolation-mode: bicubic; max-width: 100%;display: block; visibility: hidden; \">",
url);
sb.Append("</a>");
sb.Append("</td>");
sb.Append("</tr>");
}
sb.Append("</table>");
sb.Append("</td>");
}
@ -44,9 +52,9 @@ namespace Ombi.Schedule.Jobs.Ombi
{
sb.Append("<tr>");
sb.Append("<td class=\"title\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.9rem; vertical-align: top; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 1.2rem; padding: 5px; \">");
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", url);
if(url.HasValue()) sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", url);
sb.AppendFormat("<h1 style=\"white-space: normal; line-height: 1;\" >{0}</h1>", title);
sb.Append("</a>");
if (url.HasValue()) sb.Append("</a>");
sb.Append("</td>");
sb.Append("</tr>");
}

@ -9,6 +9,8 @@ using MailKit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze;
@ -18,6 +20,7 @@ using Ombi.Notifications;
using Ombi.Notifications.Models;
using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -29,7 +32,8 @@ namespace Ombi.Schedule.Jobs.Ombi
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log)
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log,
ILidarrApi lidarrApi, IRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings)
{
_plex = plex;
_emby = emby;
@ -46,6 +50,10 @@ namespace Ombi.Schedule.Jobs.Ombi
_customizationSettings.ClearCache();
_newsletterSettings.ClearCache();
_log = log;
_lidarrApi = lidarrApi;
_lidarrAlbumRepository = albumCache;
_lidarrSettings = lidarrSettings;
_lidarrSettings.ClearCache();
}
private readonly IPlexContentRepository _plex;
@ -60,6 +68,9 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
private readonly UserManager<OmbiUser> _userManager;
private readonly ILogger _log;
private readonly ILidarrApi _lidarrApi;
private readonly IRepository<LidarrAlbumCache> _lidarrAlbumRepository;
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
public async Task Start(NewsletterSettings settings, bool test)
{
@ -87,21 +98,26 @@ namespace Ombi.Schedule.Jobs.Ombi
// Get the Content
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
var lidarrContent = _lidarrAlbumRepository.GetAll().AsNoTracking();
var addedLog = _recentlyAddedLog.GetAll();
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId);
var addedPlexEpisodesLogIds =
addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode);
var addedEmbyEpisodesLogIds =
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
// Filter out the ones that we haven't sent yet
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId)));
var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet();
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
_log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count());
var plexEpisodesToSend =
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds);
@ -117,11 +133,12 @@ namespace Ombi.Schedule.Jobs.Ombi
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10);
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
body = await BuildHtml(plexm, embym, plext, embyt, settings);
var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings);
}
else
{
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings);
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings);
if (body.IsNullOrEmpty())
{
return;
@ -298,7 +315,8 @@ namespace Ombi.Schedule.Jobs.Ombi
return resolver.ParseMessage(template, curlys);
}
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, NewsletterSettings settings)
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend,
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings)
{
var sb = new StringBuilder();
@ -340,6 +358,24 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("</table>");
}
if (albums.Any() && !settings.DisableMusic)
{
sb.Append("<h1 style=\"text-align: center; max-width: 1042px;\">New Albums</h1><br /><br />");
sb.Append(
"<table class=\"movies-table\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
sb.Append("<tr>");
await ProcessAlbums(albums, sb);
sb.Append("</tr>");
sb.Append("</table>");
sb.Append("</td>");
sb.Append("</tr>");
sb.Append("</table>");
}
return sb.ToString();
}
@ -382,6 +418,40 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
}
private async Task ProcessAlbums(HashSet<LidarrAlbumCache> albumsToSend, StringBuilder sb)
{
var settings = await _lidarrSettings.GetSettingsAsync();
int count = 0;
var ordered = albumsToSend.OrderByDescending(x => x.AddedAt);
foreach (var content in ordered)
{
var info = await _lidarrApi.GetAlbumByForeignId(content.ForeignAlbumId, settings.ApiKey, settings.FullUri);
if (info == null)
{
continue;
}
try
{
CreateAlbumHtmlContent(sb, info);
count += 1;
}
catch (Exception e)
{
_log.LogError(e, "Error when Processing Lidarr Album {0}", info.title);
}
finally
{
EndLoopHtml(sb);
}
if (count == 2)
{
count = 0;
sb.Append("</tr>");
sb.Append("<tr>");
}
}
}
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
{
@ -467,6 +537,41 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
private void CreateAlbumHtmlContent(StringBuilder sb, AlbumLookup info)
{
var cover = info.images
.FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url;
if (cover.IsNullOrEmpty())
{
cover = info.remoteCover;
}
AddBackgroundInsideTable(sb, cover);
var disk = info.images
.FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url;
if (disk.IsNullOrEmpty())
{
disk = info.remoteCover;
}
AddPosterInsideTable(sb, disk);
AddMediaServerUrl(sb, string.Empty, string.Empty);
AddInfoTable(sb);
var releaseDate = $"({info.releaseDate.Year})";
AddTitle(sb, string.Empty, $"{info.title} {releaseDate}");
var summary = info.artist?.artistName ?? string.Empty;
if (summary.Length > 280)
{
summary = summary.Remove(280);
summary = summary + "...</p>";
}
AddParagraph(sb, summary);
AddGenres(sb, $"Type: {info.albumType}");
}
private async Task ProcessPlexTv(HashSet<PlexEpisode> plexContent, StringBuilder sb)
{
var series = new List<PlexServerContent>();

@ -27,6 +27,7 @@
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Service\Ombi.Api.Service.csproj" />

@ -0,0 +1,15 @@
using Ombi.Core.Settings.Models.External;
namespace Ombi.Settings.Settings.Models.External
{
public class LidarrSettings : ExternalSettings
{
public bool Enabled { get; set; }
public string ApiKey { get; set; }
public string DefaultQualityProfile { get; set; }
public string DefaultRootPath { get; set; }
public bool AlbumFolder { get; set; }
public int LanguageProfileId { get; set; }
public int MetadataProfileId { get; set; }
}
}

@ -13,5 +13,6 @@
public string SickRageSync { get; set; }
public string RefreshMetadata { get; set; }
public string Newsletter { get; set; }
public string LidarrArtistSync { get; set; }
}
}

@ -52,6 +52,10 @@ namespace Ombi.Settings.Settings.Models
{
return Get(s.RefreshMetadata, Cron.DayInterval(3));
}
public static string LidarrArtistSync(JobSettings s)
{
return Get(s.LidarrArtistSync, Cron.Hourly(40));
}
private static string Get(string settings, string defaultCron)
{

@ -6,6 +6,7 @@ namespace Ombi.Settings.Settings.Models.Notifications
{
public bool DisableTv { get; set; }
public bool DisableMovies { get; set; }
public bool DisableMusic { get; set; }
public bool Enabled { get; set; }
public List<string> ExternalEmails { get; set; } = new List<string>();
}

@ -8,5 +8,7 @@ namespace Ombi.Settings.Settings.Models.Notifications
public bool Enabled { get; set; }
public string AccessToken { get; set; }
public string UserToken { get; set; }
public sbyte Priority { get; set; } = 0;
public string Sound { get; set; } = "pushover";
}
}

@ -28,6 +28,7 @@ namespace Ombi.Store.Context
void Seed();
DbSet<Audit> Audit { get; set; }
DbSet<MovieRequests> MovieRequests { get; set; }
DbSet<AlbumRequest> AlbumRequests { get; set; }
DbSet<TvRequests> TvRequests { get; set; }
DbSet<ChildRequests> ChildRequests { get; set; }
DbSet<Issues> Issues { get; set; }
@ -39,6 +40,8 @@ namespace Ombi.Store.Context
EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class;
DbSet<CouchPotatoCache> CouchPotatoCache { get; set; }
DbSet<SickRageCache> SickRageCache { get; set; }
DbSet<LidarrArtistCache> LidarrArtistCache { get; set; }
DbSet<LidarrAlbumCache> LidarrAlbumCache { get; set; }
DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
DbSet<RequestLog> RequestLogs { get; set; }
DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }

@ -31,6 +31,7 @@ namespace Ombi.Store.Context
public DbSet<EmbyEpisode> EmbyEpisode { get; set; }
public DbSet<MovieRequests> MovieRequests { get; set; }
public DbSet<AlbumRequest> AlbumRequests { get; set; }
public DbSet<TvRequests> TvRequests { get; set; }
public DbSet<ChildRequests> ChildRequests { get; set; }
@ -44,6 +45,8 @@ namespace Ombi.Store.Context
public DbSet<Audit> Audit { get; set; }
public DbSet<Tokens> Tokens { get; set; }
public DbSet<SonarrCache> SonarrCache { get; set; }
public DbSet<LidarrArtistCache> LidarrArtistCache { get; set; }
public DbSet<LidarrAlbumCache> LidarrAlbumCache { get; set; }
public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
public DbSet<SickRageCache> SickRageCache { get; set; }
public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
@ -117,8 +120,8 @@ namespace Ombi.Store.Context
Database.ExecuteSqlCommand("VACUUM;");
// Make sure we have the roles
var roles = Roles.Where(x => x.Name == OmbiRoles.ReceivesNewsletter);
if (!roles.Any())
var newsletterRole = Roles.Where(x => x.Name == OmbiRoles.ReceivesNewsletter);
if (!newsletterRole.Any())
{
Roles.Add(new IdentityRole(OmbiRoles.ReceivesNewsletter)
{
@ -126,6 +129,19 @@ namespace Ombi.Store.Context
});
SaveChanges();
}
var requestMusicRole = Roles.Where(x => x.Name == OmbiRoles.RequestMusic);
if (!requestMusicRole.Any())
{
Roles.Add(new IdentityRole(OmbiRoles.RequestMusic)
{
NormalizedName = OmbiRoles.RequestMusic.ToUpper()
});
Roles.Add(new IdentityRole(OmbiRoles.AutoApproveMusic)
{
NormalizedName = OmbiRoles.AutoApproveMusic.ToUpper()
});
SaveChanges();
}
// Make sure we have the API User
var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase));

@ -0,0 +1,23 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("LidarrAlbumCache")]
public class LidarrAlbumCache : Entity
{
public int ArtistId { get; set; }
public string ForeignAlbumId { get; set; }
public int TrackCount { get; set; }
public DateTime ReleaseDate { get; set; }
public bool Monitored { get; set; }
public string Title { get; set; }
public decimal PercentOfTracks { get; set; }
public DateTime AddedAt { get; set; }
[NotMapped]
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
[NotMapped]
public bool FullyAvailable => PercentOfTracks == 100;
}
}

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("LidarrArtistCache")]
public class LidarrArtistCache : Entity
{
public int ArtistId { get; set; }
public string ArtistName { get; set; }
public string ForeignArtistId { get; set; }
public bool Monitored { get; set; }
}
}

@ -23,6 +23,7 @@ namespace Ombi.Store.Entities
public int? MovieRequestLimit { get; set; }
public int? EpisodeRequestLimit { get; set; }
public int? MusicRequestLimit { get; set; }
public string UserAccessToken { get; set; }
@ -59,5 +60,6 @@ namespace Ombi.Store.Entities
get => base.ConcurrencyStamp;
set => base.ConcurrencyStamp = value;
}
}
}

@ -11,18 +11,21 @@ namespace Ombi.Store.Entities
public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID
public int? EpisodeNumber { get; set; }
public int? SeasonNumber { get; set; }
public string AlbumId { get; set; }
public DateTime AddedAt { get; set; }
}
public enum RecentlyAddedType
{
Plex = 0,
Emby = 1
Emby = 1,
Lidarr = 2
}
public enum ContentType
{
Parent = 0,
Episode = 1
Episode = 1,
Album = 2,
}
}

@ -7,6 +7,7 @@ namespace Ombi.Store.Entities
public enum RequestType
{
TvShow = 0,
Movie = 1
Movie = 1,
Album = 2,
}
}

@ -0,0 +1,21 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities.Requests
{
[Table("AlbumRequests")]
public class AlbumRequest : BaseRequest
{
public string ForeignAlbumId { get; set; }
public string ForeignArtistId { get; set; }
public string Disk { get; set; }
public string Cover { get; set; }
public decimal Rating { get; set; }
public DateTime ReleaseDate { get; set; }
public string ArtistName { get; set; }
[NotMapped]
public bool Subscribed { get; set; }
[NotMapped]
public bool ShowSubscribe { get; set; }
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,67 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations
{
public partial class MusicRequests : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "MusicRequestLimit",
table: "AspNetUsers",
nullable: true);
migrationBuilder.CreateTable(
name: "AlbumRequests",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Title = table.Column<string>(nullable: true),
Approved = table.Column<bool>(nullable: false),
MarkedAsApproved = table.Column<DateTime>(nullable: false),
RequestedDate = table.Column<DateTime>(nullable: false),
Available = table.Column<bool>(nullable: false),
MarkedAsAvailable = table.Column<DateTime>(nullable: true),
RequestedUserId = table.Column<string>(nullable: true),
Denied = table.Column<bool>(nullable: true),
MarkedAsDenied = table.Column<DateTime>(nullable: false),
DeniedReason = table.Column<string>(nullable: true),
RequestType = table.Column<int>(nullable: false),
ForeignAlbumId = table.Column<string>(nullable: true),
ForeignArtistId = table.Column<string>(nullable: true),
Disk = table.Column<string>(nullable: true),
Cover = table.Column<string>(nullable: true),
Rating = table.Column<decimal>(nullable: false),
ReleaseDate = table.Column<DateTime>(nullable: false),
ArtistName = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AlbumRequests", x => x.Id);
table.ForeignKey(
name: "FK_AlbumRequests_AspNetUsers_RequestedUserId",
column: x => x.RequestedUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_AlbumRequests_RequestedUserId",
table: "AlbumRequests",
column: "RequestedUserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AlbumRequests");
migrationBuilder.DropColumn(
name: "MusicRequestLimit",
table: "AspNetUsers");
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,55 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations
{
public partial class LidarrSyncJobs : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "LidarrAlbumCache",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ArtistId = table.Column<int>(nullable: false),
ForeignAlbumId = table.Column<string>(nullable: true),
TrackCount = table.Column<int>(nullable: false),
ReleaseDate = table.Column<DateTime>(nullable: false),
Monitored = table.Column<bool>(nullable: false),
Title = table.Column<string>(nullable: true),
PercentOfTracks = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id);
});
migrationBuilder.CreateTable(
name: "LidarrArtistCache",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ArtistId = table.Column<int>(nullable: false),
ArtistName = table.Column<string>(nullable: true),
ForeignArtistId = table.Column<string>(nullable: true),
Monitored = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LidarrArtistCache", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "LidarrAlbumCache");
migrationBuilder.DropTable(
name: "LidarrArtistCache");
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations
{
public partial class MusicIssues : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "AlbumId",
table: "RecentlyAddedLog",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "AddedAt",
table: "LidarrAlbumCache",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AlbumId",
table: "RecentlyAddedLog");
migrationBuilder.DropColumn(
name: "AddedAt",
table: "LidarrAlbumCache");
}
}
}

@ -244,6 +244,50 @@ namespace Ombi.Store.Migrations
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ArtistId");
b.Property<string>("ForeignAlbumId");
b.Property<bool>("Monitored");
b.Property<decimal>("PercentOfTracks");
b.Property<DateTime>("ReleaseDate");
b.Property<string>("Title");
b.Property<int>("TrackCount");
b.HasKey("Id");
b.ToTable("LidarrAlbumCache");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ArtistId");
b.Property<string>("ArtistName");
b.Property<string>("ForeignArtistId");
b.Property<bool>("Monitored");
b.HasKey("Id");
b.ToTable("LidarrArtistCache");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
@ -311,6 +355,8 @@ namespace Ombi.Store.Migrations
b.Property<int?>("MovieRequestLimit");
b.Property<int?>("MusicRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
@ -445,6 +491,8 @@ namespace Ombi.Store.Migrations
b.Property<DateTime>("AddedAt");
b.Property<string>("AlbumId");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
@ -460,6 +508,54 @@ namespace Ombi.Store.Migrations
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<string>("ArtistName");
b.Property<bool>("Available");
b.Property<string>("Cover");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<string>("Disk");
b.Property<string>("ForeignAlbumId");
b.Property<string>("ForeignArtistId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<decimal>("Rating");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("AlbumRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
@ -894,6 +990,13 @@ namespace Ombi.Store.Migrations
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")

@ -0,0 +1,17 @@
using System.Linq;
using System.Threading.Tasks;
using Ombi.Store.Entities.Requests;
namespace Ombi.Store.Repository.Requests
{
public interface IMusicRequestRepository : IRepository<AlbumRequest>
{
IQueryable<AlbumRequest> GetAll(string userId);
AlbumRequest GetRequest(string foreignAlbumId);
Task<AlbumRequest> GetRequestAsync(string foreignAlbumId);
IQueryable<AlbumRequest> GetWithUser();
IQueryable<AlbumRequest> GetWithUser(string userId);
Task Save();
Task Update(AlbumRequest request);
}
}

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Store.Context;
using Ombi.Store.Entities.Requests;
namespace Ombi.Store.Repository.Requests
{
public class MusicRequestRepository : Repository<AlbumRequest>, IMusicRequestRepository
{
public MusicRequestRepository(IOmbiContext ctx) : base(ctx)
{
Db = ctx;
}
private IOmbiContext Db { get; }
public Task<AlbumRequest> GetRequestAsync(string foreignAlbumId)
{
return Db.AlbumRequests.Where(x => x.ForeignAlbumId == foreignAlbumId)
.Include(x => x.RequestedUser)
.FirstOrDefaultAsync();
}
public IQueryable<AlbumRequest> GetAll(string userId)
{
return GetWithUser().Where(x => x.RequestedUserId == userId);
}
public AlbumRequest GetRequest(string foreignAlbumId)
{
return Db.AlbumRequests.Where(x => x.ForeignAlbumId == foreignAlbumId)
.Include(x => x.RequestedUser)
.FirstOrDefault();
}
public IQueryable<AlbumRequest> GetWithUser()
{
return Db.AlbumRequests
.Include(x => x.RequestedUser)
.ThenInclude(x => x.NotificationUserIds)
.AsQueryable();
}
public IQueryable<AlbumRequest> GetWithUser(string userId)
{
return Db.AlbumRequests
.Where(x => x.RequestedUserId == userId)
.Include(x => x.RequestedUser)
.ThenInclude(x => x.NotificationUserIds)
.AsQueryable();
}
public async Task Update(AlbumRequest request)
{
if (Db.Entry(request).State == EntityState.Detached)
{
Db.AlbumRequests.Attach(request);
Db.Update(request);
}
await Db.SaveChangesAsync();
}
public async Task Save()
{
await Db.SaveChangesAsync();
}
}
}

@ -50,6 +50,7 @@ namespace Ombi.Updater
private void StartOmbi(StartupOptions options)
{
var startupArgsBuilder = new StringBuilder();
_log.LogDebug("Starting ombi");
var fileName = "Ombi.exe";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -73,7 +74,6 @@ namespace Ombi.Updater
}
else
{
var startupArgsBuilder = new StringBuilder();
if (!string.IsNullOrEmpty(options.Host))
{
startupArgsBuilder.Append($"--host {options.Host} ");
@ -96,7 +96,10 @@ namespace Ombi.Updater
}
}
_log.LogDebug("Ombi started, now exiting");
_log.LogDebug($"Ombi started, now exiting");
_log.LogDebug($"Working dir: {options.ApplicationPath} (Application Path)");
_log.LogDebug($"Filename: {Path.Combine(options.ApplicationPath, fileName)}");
_log.LogDebug($"Startup Args: {startupArgsBuilder.ToString()}");
Environment.Exit(0);
}

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

Loading…
Cancel
Save