diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs new file mode 100644 index 000000000..a99b98535 --- /dev/null +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -0,0 +1,492 @@ +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.Api.TheMovieDb.Models; +using Ombi.Core.Authentication; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models.UI; +using Ombi.Core.Rule.Interfaces; +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 + { + public MusicRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, + INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, + OmbiUserManager manager, IRepository rl, ICacheService cache, + ISettingsService ombiSettings, IRepository sub, ILidarrApi lidarr, + ISettingsService lidarrSettings) + : base(user, requestService, r, manager, cache, ombiSettings, sub) + { + MovieApi = movieApi; + NotificationHelper = helper; + Sender = sender; + Logger = log; + _requestLog = rl; + _lidarrApi = lidarr; + _lidarrSettings = lidarrSettings; + } + + private IMovieDbApi MovieApi { get; } + private INotificationHelper NotificationHelper { get; } + private IMovieSender Sender { get; } + private ILogger Logger { get; } + private readonly IRepository _requestLog; + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + + /// + /// Requests the movie. + /// + /// The model. + /// + public async Task RequestArtist(MusicArtistRequestViewModel model) + { + var s = await _lidarrSettings.GetSettingsAsync(); + var artist = await _lidarrApi.GetArtistByForignId(model.ForeignArtistId, s.ApiKey, s.FullUri); + if (artist == null) + { + return new RequestEngineResult + { + Result = false, + Message = "There was an issue adding this Artist!", + ErrorMessage = "Please try again later" + }; + } + + var userDetails = await GetUser(); + + var requestModel = new MovieRequests + { + TheMovieDbId = movieInfo.Id, + RequestType = RequestType.Movie, + Overview = movieInfo.Overview, + ImdbId = movieInfo.ImdbId, + PosterPath = PosterPathHelper.FixPosterPath(movieInfo.PosterPath), + Title = movieInfo.Title, + ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate) + ? DateTime.Parse(movieInfo.ReleaseDate) + : DateTime.MinValue, + Status = movieInfo.Status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUserId = userDetails.Id, + Background = movieInfo.BackdropPath + }; + + var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); + requestModel.DigitalReleaseDate = usDates?.ReleaseDate + ?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; + + 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 AddMovieRequest(requestModel, fullMovieName); + if (requestEngineResult.Result) + { + var result = await ApproveMovie(requestModel); + if (result.IsError) + { + Logger.LogWarning("Tried auto sending movie 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 movie has not been sent + } + + return await AddMovieRequest(requestModel, fullMovieName); + } + + + /// + /// Gets the requests. + /// + /// The count. + /// The position. + /// The order/filter type. + /// + public async Task> GetRequests(int count, int position, + OrderFilterModel orderFilter) + { + var shouldHide = await HideFromOtherUsers(); + IQueryable allRequests; + if (shouldHide.Hide) + { + allRequests = + MovieRepository.GetWithUser(shouldHide + .UserId); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); + } + else + { + allRequests = + MovieRepository + .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 (OrderMovies(allRequests, orderFilter.OrderType)).Skip(position).Take(count) + .ToListAsync(); + + requests.ForEach(async x => + { + x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); + await CheckForSubscription(shouldHide, x); + }); + return new RequestsViewModel + { + Collection = requests, + Total = total + }; + } + + private IQueryable OrderMovies(IQueryable 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); + case OrderType.StatusAsc: + return allRequests.OrderBy(x => x.Status); + case OrderType.StatusDesc: + return allRequests.OrderByDescending(x => x.Status); + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + public async Task GetTotal() + { + var shouldHide = await HideFromOtherUsers(); + if (shouldHide.Hide) + { + return await MovieRepository.GetWithUser(shouldHide.UserId).CountAsync(); + } + else + { + return await MovieRepository.GetWithUser().CountAsync(); + } + } + + /// + /// Gets the requests. + /// + /// + public async Task> GetRequests() + { + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MovieRepository.GetWithUser().ToListAsync(); + } + + allRequests.ForEach(async x => + { + x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); + await CheckForSubscription(shouldHide, x); + }); + return allRequests; + } + + private async Task CheckForSubscription(HideResult shouldHide, MovieRequests 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.Movie); + x.Subscribed = sub != null; + } + } + + /// + /// Searches the movie request. + /// + /// The search. + /// + public async Task> SearchMovieRequest(string search) + { + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MovieRepository.GetWithUser().ToListAsync(); + } + + var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); + results.ForEach(async x => + { + x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); + await CheckForSubscription(shouldHide, x); + }); + return results; + } + + public async Task ApproveMovieById(int requestId) + { + var request = await MovieRepository.Find(requestId); + return await ApproveMovie(request); + } + + public async Task DenyMovieById(int modelId) + { + var request = await MovieRepository.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 MovieRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request successfully deleted", + }; + } + + public async Task ApproveMovie(MovieRequests request) + { + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Approved = true; + request.Denied = false; + await MovieRepository.Update(request); + + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + if (canNotify.Success) + { + NotificationHelper.Notify(request, NotificationType.RequestApproved); + } + + if (request.Approved) + { + var result = await Sender.Send(request); + if (result.Success && result.Sent) + { + return new RequestEngineResult + { + Result = true + }; + } + + if (!result.Success) + { + Logger.LogWarning("Tried auto sending movie 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 + }; + } + + /// + /// Updates the movie request. + /// + /// The request. + /// + public async Task UpdateMovieRequest(MovieRequests request) + { + var allRequests = await MovieRepository.GetWithUser().ToListAsync(); + var results = allRequests.FirstOrDefault(x => x.Id == request.Id); + + results.Approved = request.Approved; + results.Available = request.Available; + results.Denied = request.Denied; + results.DeniedReason = request.DeniedReason; + results.ImdbId = request.ImdbId; + results.IssueId = request.IssueId; + results.Issues = request.Issues; + results.Overview = request.Overview; + results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath); + results.QualityOverride = request.QualityOverride; + results.RootPathOverride = request.RootPathOverride; + + await MovieRepository.Update(results); + return results; + } + + /// + /// Removes the movie request. + /// + /// The request identifier. + /// + public async Task RemoveMovieRequest(int requestId) + { + var request = await MovieRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId); + await MovieRepository.Delete(request); + } + + public async Task UserHasRequest(string userId) + { + return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); + } + + public async Task MarkUnavailable(int modelId) + { + var request = await MovieRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Available = false; + await MovieRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request is now unavailable", + Result = true + }; + } + + public async Task MarkAvailable(int modelId) + { + var request = await MovieRepository.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 MovieRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request is now available", + Result = true + }; + } + + private async Task AddMovieRequest(MovieRequests model, string movieName) + { + await MovieRepository.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.Movie, + }); + + return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"}; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs new file mode 100644 index 000000000..d4b857153 --- /dev/null +++ b/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs @@ -0,0 +1,15 @@ +namespace Ombi.Core.Models.Requests +{ + public class MusicArtistRequestViewModel + { + public string ForeignArtistId { get; set; } + public ArtistRequestOption RequestOption { get; set; } + } + + public enum ArtistRequestOption + { + AllAlbums = 0, + LatestAlbum = 1, + FirstAlbum = 2 + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/ArtistRequest.cs b/src/Ombi.Store/Entities/Requests/ArtistRequest.cs new file mode 100644 index 000000000..84f82694f --- /dev/null +++ b/src/Ombi.Store/Entities/Requests/ArtistRequest.cs @@ -0,0 +1,16 @@ +namespace Ombi.Store.Entities.Requests +{ + public class ArtistRequest : BaseRequest + { + 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; } + } +} \ No newline at end of file