Got most of the requesting stuff done! !wip #2313

This commit is contained in:
Jamie 2018-08-26 23:12:21 +01:00
parent cda7c0fe4c
commit b72905ab4a
26 changed files with 1061 additions and 61 deletions

View file

@ -16,5 +16,10 @@ namespace Ombi.Api.Lidarr
Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl); Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl);
Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl); Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl);
Task<List<AlbumResponse>> GetAllAlbums(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);
} }
} }

View file

@ -63,13 +63,13 @@ namespace Ombi.Api.Lidarr
return Api.Request<ArtistResult>(request); return Api.Request<ArtistResult>(request);
} }
public Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl) public async Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl)
{ {
var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get); var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", $"lidarr:{foreignArtistId}"); request.AddQueryString("term", $"lidarr:{foreignArtistId}");
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
return Api.Request<ArtistResult>(request); return (await Api.Request<List<ArtistResult>>(request)).FirstOrDefault();
} }
public async Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl) public async Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl)
@ -105,6 +105,48 @@ namespace Ombi.Api.Lidarr
return Api.Request<List<AlbumResponse>>(request); 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 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 Api.Request<AlbumResponse>(request);
}
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);
}
private void AddHeaders(Request request, string key) private void AddHeaders(Request request, string key)
{ {
request.AddHeader("X-Api-Key", key); request.AddHeader("X-Api-Key", key);

View file

@ -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
{
/// <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; }
}
}

View file

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

View file

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

View file

@ -15,6 +15,7 @@ using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.UI; using Ombi.Core.Models.UI;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Senders;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.External;
@ -29,11 +30,11 @@ namespace Ombi.Core.Engine
INotificationHelper helper, IRuleEvaluator r, ILogger<MusicRequestEngine> log, INotificationHelper helper, IRuleEvaluator r, ILogger<MusicRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache, OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, ILidarrApi lidarr, ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, ILidarrApi lidarr,
ISettingsService<LidarrSettings> lidarrSettings) ISettingsService<LidarrSettings> lidarrSettings, IMusicSender sender)
: base(user, requestService, r, manager, cache, ombiSettings, sub) : base(user, requestService, r, manager, cache, ombiSettings, sub)
{ {
NotificationHelper = helper; NotificationHelper = helper;
//Sender = sender; _musicSender = sender;
Logger = log; Logger = log;
_requestLog = rl; _requestLog = rl;
_lidarrApi = lidarr; _lidarrApi = lidarr;
@ -46,6 +47,7 @@ namespace Ombi.Core.Engine
private readonly IRepository<RequestLog> _requestLog; private readonly IRepository<RequestLog> _requestLog;
private readonly ISettingsService<LidarrSettings> _lidarrSettings; private readonly ISettingsService<LidarrSettings> _lidarrSettings;
private readonly ILidarrApi _lidarrApi; private readonly ILidarrApi _lidarrApi;
private readonly IMusicSender _musicSender;
/// <summary> /// <summary>
/// Requests the Album. /// Requests the Album.
@ -79,7 +81,8 @@ namespace Ombi.Core.Engine
RequestedUserId = userDetails.Id, RequestedUserId = userDetails.Id,
Title = album.title, Title = album.title,
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, 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()) if (requestModel.Cover.IsNullOrEmpty())
{ {
@ -341,26 +344,25 @@ namespace Ombi.Core.Engine
if (request.Approved) if (request.Approved)
{ {
//TODO var result = await _musicSender.Send(request);
//var result = await Sender.Send(request); if (result.Success && result.Sent)
//if (result.Success && result.Sent) {
//{ return new RequestEngineResult
// return new RequestEngineResult {
// { Result = true
// Result = true };
// }; }
//}
//if (!result.Success) if (!result.Success)
//{ {
// Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message); Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message);
// return new RequestEngineResult return new RequestEngineResult
// { {
// Message = result.Message, Message = result.Message,
// ErrorMessage = result.Message, ErrorMessage = result.Message,
// Result = false Result = false
// }; };
//} }
// If there are no providers then it's successful but movie has not been sent // If there are no providers then it's successful but movie has not been sent
} }

View file

@ -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);
}
}

View file

@ -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<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
},
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<SenderResult> 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 };
}
}
}

View file

@ -88,6 +88,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IMusicSearchEngine, MusicSearchEngine>(); services.AddTransient<IMusicSearchEngine, MusicSearchEngine>();
services.AddTransient<IMusicRequestEngine, MusicRequestEngine>(); services.AddTransient<IMusicRequestEngine, MusicRequestEngine>();
services.AddTransient<ITvSender, TvSender>(); services.AddTransient<ITvSender, TvSender>();
services.AddTransient<IMusicSender, MusicSender>();
services.AddTransient<IMassEmailSender, MassEmailSender>(); services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>(); services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
} }

View file

@ -75,5 +75,10 @@ namespace Ombi.Helpers
return -1; return -1;
} }
public static string RemoveSpaces(this string str)
{
return str.Replace(" ", "");
}
} }
} }

View file

@ -8,5 +8,8 @@ namespace Ombi.Settings.Settings.Models.External
public string ApiKey { get; set; } public string ApiKey { get; set; }
public string DefaultQualityProfile { get; set; } public string DefaultQualityProfile { get; set; }
public string DefaultRootPath { get; set; } public string DefaultRootPath { get; set; }
public bool AlbumFolder { get; set; }
public int LanguageProfileId { get; set; }
public int MetadataProfileId { get; set; }
} }
} }

View file

@ -8,6 +8,11 @@ export interface IRadarrProfile {
id: number; id: number;
} }
export interface IProfiles {
name: string;
id: number;
}
export interface IMinimumAvailability { export interface IMinimumAvailability {
value: string; value: string;
name: string; name: string;

View file

@ -23,13 +23,14 @@ export interface IMovieRequests extends IFullBaseRequest {
export interface IAlbumRequest extends IBaseRequest { export interface IAlbumRequest extends IBaseRequest {
foreignAlbumId: string; foreignAlbumId: string;
foreignArtistId: string; foreignArtistId: string;
Disk: string; disk: string;
cover: string; cover: string;
releaseDate: Date; releaseDate: Date;
artistName: string; artistName: string;
subscribed: boolean; subscribed: boolean;
showSubscribe: boolean; showSubscribe: boolean;
background: any;
} }
export interface IAlbumRequestModel { export interface IAlbumRequestModel {

View file

@ -90,6 +90,9 @@ export interface ILidarrSettings extends IExternalSettings {
defaultQualityProfile: string; defaultQualityProfile: string;
defaultRootPath: string; defaultRootPath: string;
fullRootPath: string; fullRootPath: string;
metadataProfileId: number;
languageProfileId: number;
albumFolder: boolean;
} }
export interface ILandingPageSettings extends ISettings { export interface ILandingPageSettings extends ISettings {

View file

@ -0,0 +1,270 @@
<div class="form-group">
<div class="input-group">
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
<span class="input-group-btn">
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
</button>
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
<li>
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }}
</a>
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }}
</a>
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}}
</a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}}
</a>
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}}
</a>
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}}
</a>
</li>
</ul>
</span>
</div>
</div>
<br />
<div>
<div *ngFor="let request of albumRequests" class="col-md-4">
<div class="row">
<div class="album-bg backdrop" [style.background-image]="request.background"></div>
<div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<div class="col-sm-12 small-padding">
<img *ngIf="request.disk" class="img-responsive poster album-cover" src="{{request.disk}}" alt="poster">
</div>
<div class="col-sm-12 small-padding">
<div>
<h4>
<a href="" target="_blank">
{{request.title}}
</a>
</h4>
<h5>
<a href="">
{{request.artistName}}
</a>
</h5>
</div>
<div class="request-info">
<div class="request-by">
<span>{{ 'Requests.RequestedBy' | translate }} </span>
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
</div>
<div class="request-status">
<span>{{ 'Requests.Status' | translate }} </span>
<span class="label label-success" id="requestedStatusLabel">{{request.status}}</span>
</div>
<div class="requested-status">
<span>{{ 'Requests.RequestStatus' | translate }} </span>
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
<i class="fa fa-info-circle"></i>
</span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
[translate]="'Common.PendingApproval'"></span>
</div>
<div *ngIf="request.denied" id="requestDenied">
{{ 'Requests.Denied' | translate }}
<i style="color:red;" class="fa fa-check"></i>
</div>
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}</div>
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}</div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}</div>
<br />
</div>
<div *ngIf="isAdmin">
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
<span>{{request.qualityOverrideTitle}} </span>
</div>
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
<span>{{request.rootPathOverrideTitle}} </span>
</div>
</div>
</div>
<div class="col-sm-12 small-padding">
<!-- <div class="row">
<div class="col-md-2 col-md-push-6">
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications">
<i class="fa fa-rss"></i>
</a>
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)" pTooltip="Unsubscribe notification">
<i class="fa fa-rss"></i>
</a>
</div>
</div> -->
<div *ngIf="isAdmin">
<div *ngIf="!request.approved" id="approveBtn">
<form>
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
</button>
</form>
<!--Radarr Root Folder-->
<!-- <div *ngIf="radarrRootFolders" class="btn-group btn-split" id="rootFolderBtn">
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fa fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let folder of radarrRootFolders">
<a href="#" (click)="selectRootFolder(request, folder, $event)">{{folder.path}}</a>
</li>
</ul>
</div> -->
<!--Radarr Quality Profiles -->
<!-- <div *ngIf="radarrProfiles" class="btn-group btn-split" id="changeQualityBtn">
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fa fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let profile of radarrProfiles">
<a href="#" (click)="selectQualityProfile(request, profile, $event)">{{profile.name}}</a>
</li>
</ul>
</div> -->
<div *ngIf="!request.denied" id="denyBtn">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny">
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}
</button>
</div>
</div>
<form id="removeBtn">
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete">
<i class="fa fa-minus"></i> {{ 'Requests.Remove' | translate }}
</button>
</form>
<form id="markBtnGroup">
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right"
value="false" class="btn btn-sm btn-info-outline change">
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
</button>
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right"
value="true" class="btn btn-sm btn-success-outline change">
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
</button>
</form>
</div>
<!-- <div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a>
</li>
</ul>
</div> -->
</div>
</div>
<br/>
<br/>
</div>
<p-paginator [rows]="10" [totalRecords]="totalMovies" (onPageChange)="paginate($event)"></p-paginator>
</div>
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report>
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
<h3>{{ 'Requests.Filter' | translate }}</h3>
<hr>
<div>
<h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4>
<div class="form-group">
<div class="radio">
<input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available, $event)">
<label for="Available">{{ 'Common.Available' | translate }}</label>
</div>
</div>
<div class="form-group">
<div class="radio">
<input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable, $event)">
<label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label>
</div>
</div>
</div>
<div>
<h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4>
<div class="form-group">
<div class="radio">
<input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved, $event)">
<label for="approved">{{ 'Filter.Approved' | translate }}</label>
</div>
</div>
<div class="form-group">
<div class="radio">
<input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing, $event)">
<label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label>
</div>
</div>
<div class="form-group">
<div class="radio">
<input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)">
<label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label>
</div>
</div>
</div>
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)">
<i class="fa fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}</button>
</p-sidebar>

View file

@ -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<string> = new Subject<string>();
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 + ")");
}
}

View file

@ -9,6 +9,10 @@
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a> <a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a>
</li> </li>
<li role="presentation">
<a id="albumTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()"><i class="fa fa-music"></i> {{ 'Requests.MusicTab' | translate }}</a>
</li>
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
@ -19,5 +23,8 @@
<div [hidden]="!showTv"> <div [hidden]="!showTv">
<tv-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></tv-requests> <tv-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></tv-requests>
</div> </div>
<div [hidden]="!showAlbums">
<music-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></music-requests>
</div>
</div> </div>

View file

@ -11,6 +11,7 @@ export class RequestComponent implements OnInit {
public showMovie = true; public showMovie = true;
public showTv = false; public showTv = false;
public showAlbums = false;
public issueCategories: IIssueCategory[]; public issueCategories: IIssueCategory[];
public issuesEnabled = false; public issuesEnabled = false;
@ -28,10 +29,18 @@ export class RequestComponent implements OnInit {
public selectMovieTab() { public selectMovieTab() {
this.showMovie = true; this.showMovie = true;
this.showTv = false; this.showTv = false;
this.showAlbums = false;
} }
public selectTvTab() { public selectTvTab() {
this.showMovie = false; this.showMovie = false;
this.showTv = true; this.showTv = true;
this.showAlbums = false;
}
public selectMusicTab() {
this.showMovie = false;
this.showTv = false;
this.showAlbums = true;
} }
} }

View file

@ -8,6 +8,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng"; import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng";
import { MovieRequestsComponent } from "./movierequests.component"; import { MovieRequestsComponent } from "./movierequests.component";
import { MusicRequestsComponent } from "./music/musicrequests.component";
// Request // Request
import { RequestComponent } from "./request.component"; import { RequestComponent } from "./request.component";
import { TvRequestChildrenComponent } from "./tvrequest-children.component"; import { TvRequestChildrenComponent } from "./tvrequest-children.component";
@ -23,7 +24,6 @@ import { SharedModule } from "../shared/shared.module";
const routes: Routes = [ const routes: Routes = [
{ path: "", component: RequestComponent, canActivate: [AuthGuard] }, { path: "", component: RequestComponent, canActivate: [AuthGuard] },
{ path: ":id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
@ -44,6 +44,7 @@ const routes: Routes = [
MovieRequestsComponent, MovieRequestsComponent,
TvRequestsComponent, TvRequestsComponent,
TvRequestChildrenComponent, TvRequestChildrenComponent,
MusicRequestsComponent,
], ],
exports: [ exports: [
RouterModule, RouterModule,

View file

@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { ILidarrProfile, ILidarrRootFolder } from "../../interfaces"; import { ILidarrProfile, ILidarrRootFolder, IProfiles } from "../../interfaces";
import { ILidarrSettings } from "../../interfaces"; import { ILidarrSettings } from "../../interfaces";
import { ServiceHelpers } from "../service.helpers"; import { ServiceHelpers } from "../service.helpers";
@ -26,4 +26,11 @@ export class LidarrService extends ServiceHelpers {
public getQualityProfilesFromSettings(): Observable<ILidarrProfile[]> { public getQualityProfilesFromSettings(): Observable<ILidarrProfile[]> {
return this.http.get<ILidarrProfile[]>(`${this.url}/Profiles/`, {headers: this.headers}); return this.http.get<ILidarrProfile[]>(`${this.url}/Profiles/`, {headers: this.headers});
} }
public getMetadataProfiles(settings: ILidarrSettings): Observable<IProfiles[]> {
return this.http.post<IProfiles[]>(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers});
}
public getLanguages(settings: ILidarrSettings): Observable<IProfiles[]> {
return this.http.post<IProfiles[]>(`${this.url}/Langauges/`,JSON.stringify(settings), {headers: this.headers});
}
} }

View file

@ -159,8 +159,8 @@ export class RequestService extends ServiceHelpers {
return this.http.post<IRequestEngineResult>(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers}); return this.http.post<IRequestEngineResult>(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers});
} }
public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable<IRequestsViewModel<IMovieRequests>> { public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable<IRequestsViewModel<IAlbumRequest>> {
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers});
} }
public searchAlbumRequests(search: string): Observable<IAlbumRequest[]> { public searchAlbumRequests(search: string): Observable<IAlbumRequest[]> {

View file

@ -50,38 +50,59 @@
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group">
<div>
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="select" class="control-label">Quality Profiles</label> <label for="select" class="control-label">Quality Profiles</label>
<div id="profiles"> <div id="profiles">
<select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}"> <select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option> <option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
</select> </select>
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
</div> </div>
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small> <small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
</div> </div>
<div class="form-group">
<div>
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label> <label for="rootFolders" class="control-label">Default Root Folders</label>
<div id="rootFolders"> <div id="rootFolders">
<select formControlName="defaultRootPath" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}"> <select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
<option *ngFor="let folder of rootFolders" value="{{folder.path}}" >{{folder.path}}</option> <option *ngFor="let folder of rootFolders" value="{{folder.path}}" >{{folder.path}}</option>
</select> </select>
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small> <small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
</div> </div>
<div class="form-group">
<label for="languageProfileId" class="control-label">Language Profile</label>
<div id="languageProfileId">
<select formControlName="languageProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('languageProfileId').hasError('required')}">
<option *ngFor="let folder of languageProfiles" value="{{folder.id}}" >{{folder.name}}</option>
</select>
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Languages <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div>
<small *ngIf="form.get('languageProfileId').hasError('required')" class="error-text">A Language profile is required</small>
</div>
<div class="form-group">
<label for="metadataProfileId" class="control-label">Metadata Profile</label>
<div id="metadataProfileId">
<select formControlName="metadataProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('metadataProfileId').hasError('required')}">
<option *ngFor="let folder of metadataProfiles" value="{{folder.id}}" >{{folder.name}}</option>
</select>
<button (click)="getMetadataProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Metadata <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div>
<small *ngIf="form.get('metadataProfileId').hasError('required')" class="error-text">A Metadata profile is required</small>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>

View file

@ -1,7 +1,7 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ILidarrSettings, IMinimumAvailability, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
import { IRadarrSettings } from "../../interfaces"; import { IRadarrSettings } from "../../interfaces";
import { LidarrService, TesterService } from "../../services"; import { LidarrService, TesterService } from "../../services";
import { NotificationService } from "../../services"; import { NotificationService } from "../../services";
@ -13,10 +13,14 @@ import { SettingsService } from "../../services";
export class LidarrComponent implements OnInit { export class LidarrComponent implements OnInit {
public qualities: IRadarrProfile[]; public qualities: IRadarrProfile[];
public languageProfiles: IProfiles[];
public metadataProfiles: IProfiles[];
public rootFolders: IRadarrRootFolder[]; public rootFolders: IRadarrRootFolder[];
public minimumAvailabilityOptions: IMinimumAvailability[]; public minimumAvailabilityOptions: IMinimumAvailability[];
public profilesRunning: boolean; public profilesRunning: boolean;
public rootFoldersRunning: boolean; public rootFoldersRunning: boolean;
public metadataRunning: boolean;
public languageRunning: boolean;
public advanced = false; public advanced = false;
public form: FormGroup; public form: FormGroup;
@ -39,6 +43,9 @@ export class LidarrComponent implements OnInit {
subDir: [x.subDir], subDir: [x.subDir],
ip: [x.ip, [Validators.required]], ip: [x.ip, [Validators.required]],
port: [x.port, [Validators.required]], port: [x.port, [Validators.required]],
albumFolder: [x.albumFolder],
languageProfileId: [x.languageProfileId, [Validators.required]],
metadataProfileId: [x.metadataProfileId, [Validators.required]],
}); });
if (x.defaultQualityProfile) { if (x.defaultQualityProfile) {
@ -47,35 +54,69 @@ export class LidarrComponent implements OnInit {
if (x.defaultRootPath) { if (x.defaultRootPath) {
this.getRootFolders(this.form); this.getRootFolders(this.form);
} }
if (x.languageProfileId) {
this.getLanguageProfiles(this.form);
}
if (x.metadataProfileId) {
this.getMetadataProfiles(this.form);
}
}); });
this.qualities = []; this.qualities = [];
this.qualities.push({ name: "Please Select", id: -1 }); this.qualities.push({ name: "Please Select", id: -1 });
this.rootFolders = []; this.rootFolders = [];
this.rootFolders.push({ path: "Please Select", id: -1 }); 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) { public getProfiles(form: FormGroup) {
this.profilesRunning = true; this.profilesRunning = true;
this.lidarrService.getQualityProfiles(form.value).subscribe(x => { this.lidarrService.getQualityProfiles(form.value).subscribe(x => {
this.qualities = x; this.qualities = x;
this.qualities.unshift({ name: "Please Select", id: -1 }); this.qualities.unshift({ name: "Please Select", id: -1 });
this.profilesRunning = false; this.profilesRunning = false;
this.notificationService.success("Successfully retrieved the Quality Profiles"); this.notificationService.success("Successfully retrieved the Quality Profiles");
}); });
} }
public getRootFolders(form: FormGroup) { public getRootFolders(form: FormGroup) {
this.rootFoldersRunning = true; this.rootFoldersRunning = true;
this.lidarrService.getRootFolders(form.value).subscribe(x => { this.lidarrService.getRootFolders(form.value).subscribe(x => {
this.rootFolders = x; this.rootFolders = x;
this.rootFolders.unshift({ path: "Please Select", id: -1 }); this.rootFolders.unshift({ path: "Please Select", id: -1 });
this.rootFoldersRunning = false; this.rootFoldersRunning = false;
this.notificationService.success("Successfully retrieved the Root Folders"); 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) { public test(form: FormGroup) {
@ -93,12 +134,12 @@ export class LidarrComponent implements OnInit {
}); });
} }
public onSubmit(form: FormGroup) { public onSubmit(form: FormGroup) {
if (form.invalid) { if (form.invalid) {
this.notificationService.error("Please check your entered values"); this.notificationService.error("Please check your entered values");
return; 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"); this.notificationService.error("Please check your entered values");
return; return;
} }

View file

@ -1003,4 +1003,25 @@ a > h4:hover {
white-space: normal; white-space: normal;
vertical-align: baseline; vertical-align: baseline;
border-radius: .25em; 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;
} }

View file

@ -50,6 +50,27 @@ namespace Ombi.Controllers.External
return await _lidarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); return await _lidarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
} }
/// <summary>
/// Gets the Lidarr metadata profiles.
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns></returns>
[HttpPost("Metadata")]
public async Task<IEnumerable<MetadataProfile>> GetMetadataProfiles([FromBody] LidarrSettings settings)
{
return await _lidarrApi.GetMetadataProfile(settings.ApiKey, settings.FullUri);
}
/// <summary>
/// Gets the Lidarr Langauge profiles.
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns></returns>
[HttpPost("Langauges")]
public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles([FromBody] LidarrSettings settings)
{
return await _lidarrApi.GetLanguageProfile(settings.ApiKey, settings.FullUri);
}
/// <summary> /// <summary>
/// Gets the Lidarr profiles using the saved settings /// Gets the Lidarr profiles using the saved settings
/// <remarks>The data is cached for an hour</remarks> /// <remarks>The data is cached for an hour</remarks>

View file

@ -119,6 +119,7 @@
"Below you can see yours and all other requests, as well as their download and approval status.", "Below you can see yours and all other requests, as well as their download and approval status.",
"MoviesTab": "Movies", "MoviesTab": "Movies",
"TvTab": "TV Shows", "TvTab": "TV Shows",
"MusicTab":"Music",
"RequestedBy": "Requested By:", "RequestedBy": "Requested By:",
"Status": "Status:", "Status": "Status:",
"RequestStatus": "Request status:", "RequestStatus": "Request status:",