From b72905ab4aecc1af30d124806bfad240db3fa3f3 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 23:12:21 +0100 Subject: [PATCH] Got most of the requesting stuff done! !wip #2313 --- src/Ombi.Api.Lidarr/ILidarrApi.cs | 5 + src/Ombi.Api.Lidarr/LidarrApi.cs | 46 ++- src/Ombi.Api.Lidarr/Models/ArtistAdd.cs | 48 +++ .../Models/LanguageProfiles.cs | 8 + src/Ombi.Api.Lidarr/Models/MetadataProfile.cs | 8 + src/Ombi.Core/Engine/MusicRequestEngine.cs | 48 +-- src/Ombi.Core/Senders/IMusicSender.cs | 10 + src/Ombi.Core/Senders/MusicSender.cs | 110 ++++++ src/Ombi.DependencyInjection/IocExtensions.cs | 1 + src/Ombi.Helpers/StringHelper.cs | 5 + .../Models/External/LidarrSettings.cs | 3 + src/Ombi/ClientApp/app/interfaces/IRadarr.ts | 5 + .../ClientApp/app/interfaces/IRequestModel.ts | 3 +- .../ClientApp/app/interfaces/ISettings.ts | 3 + .../music/musicrequests.component.html | 270 ++++++++++++++ .../requests/music/musicrequests.component.ts | 350 ++++++++++++++++++ .../app/requests/request.component.html | 7 + .../app/requests/request.component.ts | 9 + .../ClientApp/app/requests/requests.module.ts | 3 +- .../services/applications/lidarr.service.ts | 9 +- .../ClientApp/app/services/request.service.ts | 4 +- .../app/settings/lidarr/lidarr.component.html | 49 ++- .../app/settings/lidarr/lidarr.component.ts | 81 +++- src/Ombi/ClientApp/styles/base.scss | 21 ++ .../Controllers/External/LidarrController.cs | 21 ++ src/Ombi/wwwroot/translations/en.json | 1 + 26 files changed, 1064 insertions(+), 64 deletions(-) create mode 100644 src/Ombi.Api.Lidarr/Models/ArtistAdd.cs create mode 100644 src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs create mode 100644 src/Ombi.Api.Lidarr/Models/MetadataProfile.cs create mode 100644 src/Ombi.Core/Senders/IMusicSender.cs create mode 100644 src/Ombi.Core/Senders/MusicSender.cs create mode 100644 src/Ombi/ClientApp/app/requests/music/musicrequests.component.html create mode 100644 src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs index d66f9716c..38724f668 100644 --- a/src/Ombi.Api.Lidarr/ILidarrApi.cs +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -16,5 +16,10 @@ namespace Ombi.Api.Lidarr Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl); Task> GetArtists(string apiKey, string baseUrl); Task> GetAllAlbums(string apiKey, string baseUrl); + Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl); + Task MontiorAlbum(int albumId, string apiKey, string baseUrl); + Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl); + Task> GetMetadataProfile(string apiKey, string baseUrl); + Task> GetLanguageProfile(string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index 84e0d090a..572a2b55c 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -63,13 +63,13 @@ namespace Ombi.Api.Lidarr return Api.Request(request); } - public Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl) + public async Task 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 Api.Request(request); + return (await Api.Request>(request)).FirstOrDefault(); } public async Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl) @@ -105,6 +105,48 @@ namespace Ombi.Api.Lidarr return Api.Request>(request); } + public Task 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(request); + } + + public Task 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 Api.Request(request); + } + + public Task> 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>(request); + } + + public Task> GetLanguageProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetMetadataProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + private void AddHeaders(Request request, string key) { request.AddHeader("X-Api-Key", key); diff --git a/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs new file mode 100644 index 000000000..e77a239e6 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs @@ -0,0 +1,48 @@ +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 + { + /// + /// Future = 1 + /// Missing = 2 + /// Existing = 3 + /// First = 5 + /// Latest = 4 + /// None = 6 + /// + public int selectedOption { get; set; } + public bool monitored { get; set; } + public bool searchForMissingAlbums { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs b/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs new file mode 100644 index 000000000..f503fe33f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LanguageProfiles + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs new file mode 100644 index 000000000..bda3333f1 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class MetadataProfile + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index bcf9e8c02..a27b17c38 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -15,6 +15,7 @@ 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; @@ -29,11 +30,11 @@ namespace Ombi.Core.Engine INotificationHelper helper, IRuleEvaluator r, ILogger log, OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings, IRepository sub, ILidarrApi lidarr, - ISettingsService lidarrSettings) + ISettingsService lidarrSettings, IMusicSender sender) : base(user, requestService, r, manager, cache, ombiSettings, sub) { NotificationHelper = helper; - //Sender = sender; + _musicSender = sender; Logger = log; _requestLog = rl; _lidarrApi = lidarr; @@ -46,6 +47,7 @@ namespace Ombi.Core.Engine private readonly IRepository _requestLog; private readonly ISettingsService _lidarrSettings; private readonly ILidarrApi _lidarrApi; + private readonly IMusicSender _musicSender; /// /// Requests the Album. @@ -79,7 +81,8 @@ namespace Ombi.Core.Engine 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 + Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url, + ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty }; if (requestModel.Cover.IsNullOrEmpty()) { @@ -341,26 +344,25 @@ namespace Ombi.Core.Engine if (request.Approved) { - //TODO - //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 - // }; - //} + 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 } diff --git a/src/Ombi.Core/Senders/IMusicSender.cs b/src/Ombi.Core/Senders/IMusicSender.cs new file mode 100644 index 000000000..abeec5c29 --- /dev/null +++ b/src/Ombi.Core/Senders/IMusicSender.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Core.Senders +{ + public interface IMusicSender + { + Task Send(AlbumRequest model); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs new file mode 100644 index 000000000..3baa26003 --- /dev/null +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -0,0 +1,110 @@ +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 lidarr, ILidarrApi lidarrApi) + { + _lidarrSettings = lidarr; + _lidarrApi = lidarrApi; + } + + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + + public async Task 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 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 + }, + 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 + await SetupAlbum(model, result, settings); + } + } + else + { + await SetupAlbum(model, artist, settings); + } + + return new SenderResult { Success = false, Sent = false, Message = "Album is already monitored" }; + } + + private async Task SetupAlbum(AlbumRequest model, ArtistResult artist, LidarrSettings settings) + { + // Get the album id + var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + // Get the album we want. + var album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + 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 }; + } + } +} \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index c4b291936..334b94675 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -88,6 +88,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); } diff --git a/src/Ombi.Helpers/StringHelper.cs b/src/Ombi.Helpers/StringHelper.cs index aba120c65..fe26d0751 100644 --- a/src/Ombi.Helpers/StringHelper.cs +++ b/src/Ombi.Helpers/StringHelper.cs @@ -75,5 +75,10 @@ namespace Ombi.Helpers return -1; } + + public static string RemoveSpaces(this string str) + { + return str.Replace(" ", ""); + } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs index 560a6e2d2..e0bdbdc43 100644 --- a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs @@ -8,5 +8,8 @@ namespace Ombi.Settings.Settings.Models.External 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; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/IRadarr.ts b/src/Ombi/ClientApp/app/interfaces/IRadarr.ts index ebc92507c..b643993f4 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRadarr.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRadarr.ts @@ -8,6 +8,11 @@ export interface IRadarrProfile { id: number; } +export interface IProfiles { + name: string; + id: number; +} + export interface IMinimumAvailability { value: string; name: string; diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index 736f60aa2..f7fe3df87 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts @@ -23,13 +23,14 @@ export interface IMovieRequests extends IFullBaseRequest { export interface IAlbumRequest extends IBaseRequest { foreignAlbumId: string; foreignArtistId: string; - Disk: string; + disk: string; cover: string; releaseDate: Date; artistName: string; subscribed: boolean; showSubscribe: boolean; + background: any; } export interface IAlbumRequestModel { diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 202dbf2c2..2fb46a2b7 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -90,6 +90,9 @@ export interface ILidarrSettings extends IExternalSettings { defaultQualityProfile: string; defaultRootPath: string; fullRootPath: string; + metadataProfileId: number; + languageProfileId: number; + albumFolder: boolean; } export interface ILandingPageSettings extends ISettings { diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html new file mode 100644 index 000000000..221d829a0 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -0,0 +1,270 @@ + +
+ + +
+
+
+
+
+ +
+ poster +
+ + +
+ + +
+
+ {{ 'Requests.RequestedBy' | translate }} + {{request.requestedUser.userName}} + {{request.requestedUser.alias}} + {{request.requestedUser.userName}} +
+
+ {{ 'Requests.Status' | translate }} + {{request.status}} +
+ +
+ {{ 'Requests.RequestStatus' | translate }} + + + + + + + + +
+
+ {{ 'Requests.Denied' | translate }} + + +
+ + +
{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
+
{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
+
{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}
+
+
+
+
{{ 'Requests.QualityOverride' | translate }} + {{request.qualityOverrideTitle}} +
+
{{ 'Requests.RootFolderOverride' | translate }} + {{request.rootPathOverrideTitle}} +
+
+ +
+
+ +
+
+
+ +
+ + + + + + + +
+ +
+
+
+ +
+ +
+ + +
+ + + +
+ + +
+
+
+
+ + + + +
+ + +
+ + + + + + +

{{ 'Requests.Filter' | translate }}

+
+
+

{{ 'Filter.FilterHeaderAvailability' | translate }}

+
+
+ + +
+
+
+
+ + +
+
+
+
+

{{ 'Filter.FilterHeaderRequestStatus' | translate }}

+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts new file mode 100644 index 000000000..f004b4019 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts @@ -0,0 +1,350 @@ +import { PlatformLocation } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; + +import { AuthService } from "../../auth/auth.service"; +import { FilterType, IAlbumRequest, IFilter, IIssueCategory, IPagenator, OrderType } from "../../interfaces"; +import { NotificationService, RequestService } from "../../services"; + +@Component({ + selector: "music-requests", + templateUrl: "./musicrequests.component.html", +}) +export class MusicRequestsComponent implements OnInit { + public albumRequests: IAlbumRequest[]; + public defaultPoster: string; + + public searchChanged: Subject = new Subject(); + public searchText: string; + + public isAdmin: boolean; // Also PowerUser + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequest: IAlbumRequest; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + + public filterDisplay: boolean; + public filter: IFilter; + public filterType = FilterType; + + public orderType: OrderType = OrderType.RequestedDateDesc; + public OrderType = OrderType; + + public totalAlbums: number = 100; + private currentlyLoaded: number; + private amountToLoad: number; + + constructor( + private requestService: RequestService, + private auth: AuthService, + private notificationService: NotificationService, + private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation) { + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.resetSearch(); + return; + } + this.requestService.searchAlbumRequests(this.searchText) + .subscribe(m => { + this.setOverrides(m); + this.albumRequests = m; + }); + }); + this.defaultPoster = "../../../images/default-music-placeholder.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png"; + } + } + + public ngOnInit() { + this.amountToLoad = 10; + this.currentlyLoaded = 10; + this.filter = { + availabilityFilter: FilterType.None, + statusFilter: FilterType.None, + }; + this.loadInit(); + this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + } + + public paginate(event: IPagenator) { + const skipAmount = event.first; + this.loadRequests(this.amountToLoad, skipAmount); + } + + public search(text: any) { + this.searchChanged.next(text.target.value); + } + + public removeRequest(request: IAlbumRequest) { + this.requestService.removeAlbumRequest(request); + this.removeRequestFromUi(request); + this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); + } + + public changeAvailability(request: IAlbumRequest, available: boolean) { + request.available = available; + + if (available) { + this.requestService.markAlbumAvailable({ id: request.id }).subscribe(x => { + if (x.result) { + this.notificationService.success( + `${request.title} Is now available`); + } else { + this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } else { + this.requestService.markAlbumUnavailable({ id: request.id }).subscribe(x => { + if (x.result) { + this.notificationService.success( + `${request.title} Is now unavailable`); + } else { + this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } + } + + public approve(request: IAlbumRequest) { + request.approved = true; + this.approveRequest(request); + } + + public deny(request: IAlbumRequest) { + request.denied = true; + this.denyRequest(request); + } + + // public selectRootFolder(searchResult: IAlbumRequest, rootFolderSelected: IRadarrRootFolder, event: any) { + // event.preventDefault(); + // // searchResult.rootPathOverride = rootFolderSelected.id; + // this.setOverride(searchResult); + // this.updateRequest(searchResult); + // } + + // public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile, event: any) { + // event.preventDefault(); + // searchResult.qualityOverride = profileSelected.id; + // this.setOverride(searchResult); + // this.updateRequest(searchResult); + // } + + public reportIssue(catId: IIssueCategory, req: IAlbumRequest) { + this.issueRequest = req; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.foreignAlbumId; + } + + public ignore(event: any): void { + event.preventDefault(); + } + + public clearFilter(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; + el = el.querySelectorAll("INPUT"); + for (el of el) { + el.checked = false; + el.parentElement.classList.remove("active"); + } + + this.filterDisplay = false; + this.filter.availabilityFilter = FilterType.None; + this.filter.statusFilter = FilterType.None; + + this.resetSearch(); + } + + public filterAvailability(filter: FilterType, el: any) { + this.filterActiveStyle(el); + this.filter.availabilityFilter = filter; + this.loadInit(); + } + + public filterStatus(filter: FilterType, el: any) { + this.filterActiveStyle(el); + this.filter.statusFilter = filter; + this.loadInit(); + } + + public setOrder(value: OrderType, el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + const parent = el.parentElement; + const previousFilter = parent.querySelector(".active"); + + previousFilter.className = ""; + el.className = "active"; + + this.orderType = value; + + this.loadInit(); + } + + // public subscribe(request: IAlbumRequest) { + // request.subscribed = true; + // this.requestService.subscribeToMovie(request.id) + // .subscribe(x => { + // this.notificationService.success("Subscribed To Movie!"); + // }); + // } + + // public unSubscribe(request: IMovieRequests) { + // request.subscribed = false; + // this.requestService.unSubscribeToMovie(request.id) + // .subscribe(x => { + // this.notificationService.success("Unsubscribed Movie!"); + // }); + // } + + private filterActiveStyle(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; //gets radio div + el = el.parentElement; //gets form group div + el = el.parentElement; //gets status filter div + el = el.querySelectorAll("INPUT"); + for (el of el) { + if (el.checked) { + if (!el.parentElement.classList.contains("active")) { + el.parentElement.className += " active"; + } + } else { + el.parentElement.classList.remove("active"); + } + } + } + + private loadRequests(amountToLoad: number, currentlyLoaded: number) { + this.requestService.getAlbumRequests(amountToLoad, currentlyLoaded, this.orderType, this.filter) + .subscribe(x => { + this.setOverrides(x.collection); + if (!this.albumRequests) { + this.albumRequests = []; + } + this.albumRequests = x.collection; + this.totalAlbums = x.total; + this.currentlyLoaded = currentlyLoaded + amountToLoad; + }); + } + + private approveRequest(request: IAlbumRequest) { + this.requestService.approveAlbum({ id: request.id }) + .subscribe(x => { + request.approved = true; + if (x.result) { + this.notificationService.success( + `Request for ${request.title} has been approved successfully`); + } else { + this.notificationService.warning("Request Approved", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } + + private denyRequest(request: IAlbumRequest) { + this.requestService.denyAlbum({ id: request.id }) + .subscribe(x => { + if (x.result) { + this.notificationService.success( + `Request for ${request.title} has been denied successfully`); + } else { + this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage); + request.denied = false; + } + }); + } + + private loadInit() { + this.requestService.getAlbumRequests(this.amountToLoad, 0, this.orderType, this.filter) + .subscribe(x => { + this.albumRequests = x.collection; + this.totalAlbums = x.total; + + this.setOverrides(this.albumRequests); + + if (this.isAdmin) { + // this.radarrService.getQualityProfilesFromSettings().subscribe(c => { + // this.radarrProfiles = c; + // this.albumRequests.forEach((req) => this.setQualityOverrides(req)); + // }); + // this.radarrService.getRootFoldersFromSettings().subscribe(c => { + // this.radarrRootFolders = c; + // this.albumRequests.forEach((req) => this.setRootFolderOverrides(req)); + // }); + } + }); + } + + private resetSearch() { + this.currentlyLoaded = 5; + this.loadInit(); + } + + private removeRequestFromUi(key: IAlbumRequest) { + const index = this.albumRequests.indexOf(key, 0); + if (index > -1) { + this.albumRequests.splice(index, 1); + } + } + + private setOverrides(requests: IAlbumRequest[]): void { + requests.forEach((req) => { + this.setOverride(req); + }); + } + + // private setQualityOverrides(req: IMovieRequests): void { + // if (this.radarrProfiles) { + // const profile = this.radarrProfiles.filter((p) => { + // return p.id === req.qualityOverride; + // }); + // if (profile.length > 0) { + // req.qualityOverrideTitle = profile[0].name; + // } + // } + // } + // private setRootFolderOverrides(req: IMovieRequests): void { + // if (this.radarrRootFolders) { + // const path = this.radarrRootFolders.filter((folder) => { + // return folder.id === req.rootPathOverride; + // }); + // if (path.length > 0) { + // req.rootPathOverrideTitle = path[0].path; + // } + // } + // } + + private setOverride(req: IAlbumRequest): void { + this.setAlbumBackground(req); + // this.setQualityOverrides(req); + // this.setRootFolderOverrides(req); + } + private setAlbumBackground(req: IAlbumRequest) { + if (req.disk === null) { + if(req.cover === null) { + req.disk = this.defaultPoster; + } else { + req.disk = req.cover; + } + } + req.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + req.cover + ")"); + } +} diff --git a/src/Ombi/ClientApp/app/requests/request.component.html b/src/Ombi/ClientApp/app/requests/request.component.html index 916d3b774..45509fba3 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.html +++ b/src/Ombi/ClientApp/app/requests/request.component.html @@ -9,6 +9,10 @@ {{ 'Requests.TvTab' | translate }} +
  • + {{ 'Requests.MusicTab' | translate }} + +
  • @@ -19,5 +23,8 @@
    +
    + +
    diff --git a/src/Ombi/ClientApp/app/requests/request.component.ts b/src/Ombi/ClientApp/app/requests/request.component.ts index 557bb7a07..d0fa9d0b1 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.ts +++ b/src/Ombi/ClientApp/app/requests/request.component.ts @@ -11,6 +11,7 @@ export class RequestComponent implements OnInit { public showMovie = true; public showTv = false; + public showAlbums = false; public issueCategories: IIssueCategory[]; public issuesEnabled = false; @@ -28,10 +29,18 @@ export class RequestComponent implements OnInit { public selectMovieTab() { this.showMovie = true; this.showTv = false; + this.showAlbums = false; } public selectTvTab() { this.showMovie = false; this.showTv = true; + this.showAlbums = false; + } + + public selectMusicTab() { + this.showMovie = false; + this.showTv = false; + this.showAlbums = true; } } diff --git a/src/Ombi/ClientApp/app/requests/requests.module.ts b/src/Ombi/ClientApp/app/requests/requests.module.ts index 31a45e07a..63d7117f5 100644 --- a/src/Ombi/ClientApp/app/requests/requests.module.ts +++ b/src/Ombi/ClientApp/app/requests/requests.module.ts @@ -8,6 +8,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng"; import { MovieRequestsComponent } from "./movierequests.component"; +import { MusicRequestsComponent } from "./music/musicrequests.component"; // Request import { RequestComponent } from "./request.component"; import { TvRequestChildrenComponent } from "./tvrequest-children.component"; @@ -23,7 +24,6 @@ import { SharedModule } from "../shared/shared.module"; const routes: Routes = [ { path: "", component: RequestComponent, canActivate: [AuthGuard] }, - { path: ":id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] }, ]; @NgModule({ imports: [ @@ -44,6 +44,7 @@ const routes: Routes = [ MovieRequestsComponent, TvRequestsComponent, TvRequestChildrenComponent, + MusicRequestsComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts index ddd7e48ec..9ef36357a 100644 --- a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts @@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { Observable } from "rxjs"; -import { ILidarrProfile, ILidarrRootFolder } from "../../interfaces"; +import { ILidarrProfile, ILidarrRootFolder, IProfiles } from "../../interfaces"; import { ILidarrSettings } from "../../interfaces"; import { ServiceHelpers } from "../service.helpers"; @@ -26,4 +26,11 @@ export class LidarrService extends ServiceHelpers { public getQualityProfilesFromSettings(): Observable { return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); } + + public getMetadataProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers}); + } + public getLanguages(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Langauges/`,JSON.stringify(settings), {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 2a9de8b2a..045a73310 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -159,8 +159,8 @@ export class RequestService extends ServiceHelpers { return this.http.post(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers}); } - public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable> { - return this.http.get>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); + public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable> { + return this.http.get>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); } public searchAlbumRequests(search: string): Observable { diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html index d36295038..aa3dbaa66 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html @@ -50,38 +50,59 @@
    -
    -
    - -
    -
    +
    - + +
    A Default Quality Profile is required
    -
    -
    - - -
    - -
    - + +
    A Default Root Path is required
    + +
    + +
    + + + +
    + A Language profile is required +
    + + +
    + +
    + + + + +
    + + A Metadata profile is required +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 91d7ead80..8100c0194 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { ILidarrSettings, IMinimumAvailability, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; +import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; import { IRadarrSettings } from "../../interfaces"; import { LidarrService, TesterService } from "../../services"; import { NotificationService } from "../../services"; @@ -13,10 +13,14 @@ import { SettingsService } from "../../services"; export class LidarrComponent implements OnInit { public qualities: IRadarrProfile[]; + public languageProfiles: IProfiles[]; + public metadataProfiles: IProfiles[]; public rootFolders: IRadarrRootFolder[]; public minimumAvailabilityOptions: IMinimumAvailability[]; public profilesRunning: boolean; public rootFoldersRunning: boolean; + public metadataRunning: boolean; + public languageRunning: boolean; public advanced = false; public form: FormGroup; @@ -39,6 +43,9 @@ export class LidarrComponent implements OnInit { subDir: [x.subDir], ip: [x.ip, [Validators.required]], port: [x.port, [Validators.required]], + albumFolder: [x.albumFolder], + languageProfileId: [x.languageProfileId, [Validators.required]], + metadataProfileId: [x.metadataProfileId, [Validators.required]], }); if (x.defaultQualityProfile) { @@ -47,35 +54,69 @@ export class LidarrComponent implements OnInit { if (x.defaultRootPath) { this.getRootFolders(this.form); } + if (x.languageProfileId) { + this.getLanguageProfiles(this.form); + } + if (x.metadataProfileId) { + this.getMetadataProfiles(this.form); + } }); this.qualities = []; this.qualities.push({ name: "Please Select", id: -1 }); - + this.rootFolders = []; this.rootFolders.push({ path: "Please Select", id: -1 }); + + this.languageProfiles = []; + this.languageProfiles.push({ name: "Please Select", id: -1 }); + + this.metadataProfiles = []; + this.metadataProfiles.push({ name: "Please Select", id: -1 }); } public getProfiles(form: FormGroup) { - this.profilesRunning = true; - this.lidarrService.getQualityProfiles(form.value).subscribe(x => { - this.qualities = x; - this.qualities.unshift({ name: "Please Select", id: -1 }); - - this.profilesRunning = false; - this.notificationService.success("Successfully retrieved the Quality Profiles"); - }); + this.profilesRunning = true; + this.lidarrService.getQualityProfiles(form.value).subscribe(x => { + this.qualities = x; + this.qualities.unshift({ name: "Please Select", id: -1 }); + + this.profilesRunning = false; + this.notificationService.success("Successfully retrieved the Quality Profiles"); + }); } public getRootFolders(form: FormGroup) { - this.rootFoldersRunning = true; - this.lidarrService.getRootFolders(form.value).subscribe(x => { - this.rootFolders = x; - this.rootFolders.unshift({ path: "Please Select", id: -1 }); - - this.rootFoldersRunning = false; - this.notificationService.success("Successfully retrieved the Root Folders"); - }); + this.rootFoldersRunning = true; + this.lidarrService.getRootFolders(form.value).subscribe(x => { + this.rootFolders = x; + this.rootFolders.unshift({ path: "Please Select", id: -1 }); + + this.rootFoldersRunning = false; + this.notificationService.success("Successfully retrieved the Root Folders"); + }); + } + + public getMetadataProfiles(form: FormGroup) { + this.metadataRunning = true; + this.lidarrService.getMetadataProfiles(form.value).subscribe(x => { + this.metadataProfiles = x; + this.metadataProfiles.unshift({ name: "Please Select", id: -1 }); + + this.metadataRunning = false; + this.notificationService.success("Successfully retrieved the Metadata profiles"); + }); + } + + public getLanguageProfiles(form: FormGroup) { + this.languageRunning = true; + this.lidarrService.getLanguages(form.value).subscribe(x => { + this.languageProfiles = x; + this.languageProfiles.unshift({ name: "Please Select", id: -1 }); + + this.languageRunning = false; + this.notificationService.success("Successfully retrieved the Language profiles"); + }); } public test(form: FormGroup) { @@ -93,12 +134,12 @@ export class LidarrComponent implements OnInit { }); } -public onSubmit(form: FormGroup) { + public onSubmit(form: FormGroup) { if (form.invalid) { this.notificationService.error("Please check your entered values"); return; } - if(form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { + if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { this.notificationService.error("Please check your entered values"); return; } diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index 593511316..1afad391c 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -1003,4 +1003,25 @@ a > h4:hover { white-space: normal; vertical-align: baseline; border-radius: .25em; +} + +.form-control-grid { + display: block; + width: inherit !important; + height: 39px; + color: #fefefe; + border-radius: 5px; + padding: 8px 16px; + font-size: 15px; + line-height: 1.42857143; + color: #2b3e50; + background-color: #ffffff; + background-image: none; + border: 1px solid transparent; + border-radius: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; } \ No newline at end of file diff --git a/src/Ombi/Controllers/External/LidarrController.cs b/src/Ombi/Controllers/External/LidarrController.cs index 084d4f258..076d68241 100644 --- a/src/Ombi/Controllers/External/LidarrController.cs +++ b/src/Ombi/Controllers/External/LidarrController.cs @@ -50,6 +50,27 @@ namespace Ombi.Controllers.External return await _lidarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); } + /// + /// Gets the Lidarr metadata profiles. + /// + /// The settings. + /// + [HttpPost("Metadata")] + public async Task> GetMetadataProfiles([FromBody] LidarrSettings settings) + { + return await _lidarrApi.GetMetadataProfile(settings.ApiKey, settings.FullUri); + } + /// + /// Gets the Lidarr Langauge profiles. + /// + /// The settings. + /// + [HttpPost("Langauges")] + public async Task> GetLanguageProfiles([FromBody] LidarrSettings settings) + { + return await _lidarrApi.GetLanguageProfile(settings.ApiKey, settings.FullUri); + } + /// /// Gets the Lidarr profiles using the saved settings /// The data is cached for an hour diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 4fd3ccb1b..74a6fc7cc 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -119,6 +119,7 @@ "Below you can see yours and all other requests, as well as their download and approval status.", "MoviesTab": "Movies", "TvTab": "TV Shows", + "MusicTab":"Music", "RequestedBy": "Requested By:", "Status": "Status:", "RequestStatus": "Request status:",