diff --git a/Ombi.Api.Interfaces/ISonarrApi.cs b/Ombi.Api.Interfaces/ISonarrApi.cs index 203020737..bce750901 100644 --- a/Ombi.Api.Interfaces/ISonarrApi.cs +++ b/Ombi.Api.Interfaces/ISonarrApi.cs @@ -40,10 +40,11 @@ namespace Ombi.Api.Interfaces bool searchForMissingEpisodes = false); SonarrAddSeries AddSeriesNew(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, - int[] seasons, string apiKey, Uri baseUrl, bool monitor = true, + int[] seasons, string apiKey, Uri baseUrl, bool monitor = true, bool searchForMissingEpisodes = false); SystemStatus SystemStatus(string apiKey, Uri baseUrl); + List GetRootFolders(string apiKey, Uri baseUrl); List GetSeries(string apiKey, Uri baseUrl); Series GetSeries(string seriesId, string apiKey, Uri baseUrl); diff --git a/Ombi.Api.Models/Ombi.Api.Models.csproj b/Ombi.Api.Models/Ombi.Api.Models.csproj index 003b6d465..c180f5624 100644 --- a/Ombi.Api.Models/Ombi.Api.Models.csproj +++ b/Ombi.Api.Models/Ombi.Api.Models.csproj @@ -101,6 +101,7 @@ + diff --git a/Ombi.Api.Models/Sonarr/SonarrRootFolder.cs b/Ombi.Api.Models/Sonarr/SonarrRootFolder.cs new file mode 100644 index 000000000..d506feba6 --- /dev/null +++ b/Ombi.Api.Models/Sonarr/SonarrRootFolder.cs @@ -0,0 +1,35 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: SonarrRootFolder.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace Ombi.Api.Models.Sonarr +{ + public class SonarrRootFolder + { + public int id { get; set; } + public string path { get; set; } + public long freespace { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api/SonarrApi.cs b/Ombi.Api/SonarrApi.cs index 7c80883dc..2485150b8 100644 --- a/Ombi.Api/SonarrApi.cs +++ b/Ombi.Api/SonarrApi.cs @@ -62,6 +62,22 @@ namespace Ombi.Api return obj; } + public List GetRootFolders(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/rootfolder", Method = Method.GET }; + + request.AddHeader("X-Api-Key", apiKey); + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetRootFolders for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + + return obj; + } + public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, string apiKey, Uri baseUrl, bool monitor = true, bool searchForMissingEpisodes = false) { Log.Debug("Adding series {0}", title); diff --git a/Ombi.Core/CacheKeys.cs b/Ombi.Core/CacheKeys.cs index 889ac6df7..39ed7a71b 100644 --- a/Ombi.Core/CacheKeys.cs +++ b/Ombi.Core/CacheKeys.cs @@ -47,5 +47,6 @@ namespace Ombi.Core public const string WatcherQueued = nameof(WatcherQueued); public const string GetPlexRequestSettings = nameof(GetPlexRequestSettings); public const string LastestProductVersion = nameof(LastestProductVersion); + public const string SonarrRootFolders = nameof(SonarrRootFolders); } } \ No newline at end of file diff --git a/Ombi.Core/SettingModels/SonarrSettings.cs b/Ombi.Core/SettingModels/SonarrSettings.cs index 31f96b3e6..ec2edb49a 100644 --- a/Ombi.Core/SettingModels/SonarrSettings.cs +++ b/Ombi.Core/SettingModels/SonarrSettings.cs @@ -33,6 +33,5 @@ namespace Ombi.Core.SettingModels public string QualityProfile { get; set; } public bool SeasonFolders { get; set; } public string RootPath { get; set; } - } } \ No newline at end of file diff --git a/Ombi.Core/TvSender.cs b/Ombi.Core/TvSender.cs index 1e97e3550..14146c17f 100644 --- a/Ombi.Core/TvSender.cs +++ b/Ombi.Core/TvSender.cs @@ -34,19 +34,22 @@ using Ombi.Api.Interfaces; using Ombi.Api.Models.SickRage; using Ombi.Api.Models.Sonarr; using Ombi.Core.SettingModels; +using Ombi.Helpers; using Ombi.Store; namespace Ombi.Core { public class TvSender { - public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi) + public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi, ICacheProvider cache) { SonarrApi = sonarrApi; SickrageApi = srApi; + Cache = cache; } private ISonarrApi SonarrApi { get; } private ISickRageApi SickrageApi { get; } + private ICacheProvider Cache { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public async Task SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) @@ -82,6 +85,8 @@ namespace Ombi.Core var latest = model.SeasonsRequested?.Equals("Latest", StringComparison.CurrentCultureIgnoreCase); var specificSeasonRequest = model.SeasonList?.Any(); + var rootFolderPath = model.RootFolderSelected <= 0 ? sonarrSettings.RootPath : await GetRootPath(model.RootFolderSelected, sonarrSettings); + if (episodeRequest) { // Does series exist? @@ -96,7 +101,7 @@ namespace Ombi.Core // Series doesn't exist, need to add it as unmonitored. var addResult = await Task.Run(() => SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, - sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, new int[0], sonarrSettings.ApiKey, + sonarrSettings.SeasonFolders, rootFolderPath, 0, new int[0], sonarrSettings.ApiKey, sonarrSettings.FullUri, false)); @@ -125,7 +130,7 @@ namespace Ombi.Core { // Set the series as monitored with a season count as 0 so it doesn't search for anything SonarrApi.AddSeriesNew(model.ProviderId, model.Title, qualityProfile, - sonarrSettings.SeasonFolders, sonarrSettings.RootPath, new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13}, sonarrSettings.ApiKey, + sonarrSettings.SeasonFolders, rootFolderPath, new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13}, sonarrSettings.ApiKey, sonarrSettings.FullUri); await Task.Delay(TimeSpan.FromSeconds(1)); @@ -372,5 +377,20 @@ namespace Ombi.Core return selectedSeries; } + + private async Task GetRootPath(int pathId, SonarrSettings sonarrSettings) + { + var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () => + { + return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + + foreach (var r in rootFoldersResult.Where(r => r.id == pathId)) + { + return r.path; + } + return string.Empty; + } + } } \ No newline at end of file diff --git a/Ombi.Core/TvSenderOld.cs b/Ombi.Core/TvSenderOld.cs index 6594fe5b8..6d819753e 100644 --- a/Ombi.Core/TvSenderOld.cs +++ b/Ombi.Core/TvSenderOld.cs @@ -34,19 +34,22 @@ using Ombi.Api.Interfaces; using Ombi.Api.Models.SickRage; using Ombi.Api.Models.Sonarr; using Ombi.Core.SettingModels; +using Ombi.Helpers; using Ombi.Store; namespace Ombi.Core { public class TvSenderOld { - public TvSenderOld(ISonarrApi sonarrApi, ISickRageApi srApi) + public TvSenderOld(ISonarrApi sonarrApi, ISickRageApi srApi, ICacheProvider cache) { SonarrApi = sonarrApi; SickrageApi = srApi; + Cache = cache; } private ISonarrApi SonarrApi { get; } private ISickRageApi SickrageApi { get; } + private ICacheProvider Cache { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public async Task SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) @@ -67,6 +70,8 @@ namespace Ombi.Core { int.TryParse(sonarrSettings.QualityProfile, out qualityProfile); } + var rootFolderPath = model.RootFolderSelected <= 0 ? sonarrSettings.RootPath : await GetRootPath(model.RootFolderSelected, sonarrSettings); + var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); @@ -84,7 +89,7 @@ namespace Ombi.Core // Series doesn't exist, need to add it as unmonitored. var addResult = await Task.Run(() => SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, - sonarrSettings.SeasonFolders, sonarrSettings.RootPath, 0, new int[0], sonarrSettings.ApiKey, + sonarrSettings.SeasonFolders, rootFolderPath, 0, new int[0], sonarrSettings.ApiKey, sonarrSettings.FullUri, false)); @@ -156,7 +161,7 @@ namespace Ombi.Core var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, - sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, + sonarrSettings.SeasonFolders, rootFolderPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, sonarrSettings.FullUri, true, true); return result; @@ -298,5 +303,20 @@ namespace Ombi.Core return selectedSeries; } + + + private async Task GetRootPath(int pathId, SonarrSettings sonarrSettings) + { + var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () => + { + return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + + foreach (var r in rootFoldersResult.Where(r => r.id == pathId)) + { + return r.path; + } + return string.Empty; + } } } \ No newline at end of file diff --git a/Ombi.Services/Jobs/FaultQueueHandler.cs b/Ombi.Services/Jobs/FaultQueueHandler.cs index 61d3a4674..09c311225 100644 --- a/Ombi.Services/Jobs/FaultQueueHandler.cs +++ b/Ombi.Services/Jobs/FaultQueueHandler.cs @@ -53,7 +53,7 @@ namespace Ombi.Services.Jobs ISickRageApi srApi, ISettingsService sonarrSettings, ISettingsService srSettings, ICouchPotatoApi cpApi, ISettingsService cpsettings, IRequestService requestService, ISettingsService hpSettings, IHeadphonesApi headphonesApi, ISettingsService prSettings, - ISecurityExtensions security, IMovieSender movieSender) + ISecurityExtensions security, IMovieSender movieSender, ICacheProvider cache) { Record = record; Repo = repo; @@ -71,6 +71,8 @@ namespace Ombi.Services.Jobs Security = security; PrSettings = prSettings.GetSettings(); MovieSender = movieSender; + + Cache = cache; } private IMovieSender MovieSender { get; } @@ -78,6 +80,7 @@ namespace Ombi.Services.Jobs private IJobRecord Record { get; } private ISonarrApi SonarrApi { get; } private ISickRageApi SrApi { get; } + private ICacheProvider Cache { get; } private ICouchPotatoApi CpApi { get; } private IHeadphonesApi HpApi { get; } private IRequestService RequestService { get; } @@ -163,7 +166,7 @@ namespace Ombi.Services.Jobs try { - var sender = new TvSenderOld(SonarrApi, SrApi); + var sender = new TvSenderOld(SonarrApi, SrApi, Cache); if (sonarr.Enabled) { var task = sender.SendToSonarr(sonarr, tvModel, sonarr.QualityProfile); diff --git a/Ombi.Services/Jobs/PlexAvailabilityChecker.cs b/Ombi.Services/Jobs/PlexAvailabilityChecker.cs index a58b5841c..936b128dc 100644 --- a/Ombi.Services/Jobs/PlexAvailabilityChecker.cs +++ b/Ombi.Services/Jobs/PlexAvailabilityChecker.cs @@ -79,6 +79,7 @@ namespace Ombi.Services.Jobs public void CheckAndUpdateAll() { + var plexSettings = Plex.GetSettings(); if (!ValidateSettings(plexSettings)) diff --git a/Ombi.Store/RequestedModel.cs b/Ombi.Store/RequestedModel.cs index ffd416966..89c30e21e 100644 --- a/Ombi.Store/RequestedModel.cs +++ b/Ombi.Store/RequestedModel.cs @@ -46,6 +46,13 @@ namespace Ombi.Store public List Episodes { get; set; } public bool Denied { get; set; } public string DeniedReason { get; set; } + /// + /// For TV Shows with a custom root folder + /// + /// + /// The root folder selected. + /// + public int RootFolderSelected { get; set; } [JsonIgnore] public List AllUsers diff --git a/Ombi.UI.Tests/TvSenderTests.cs b/Ombi.UI.Tests/TvSenderTests.cs index 1f6d337da..1a766ad67 100644 --- a/Ombi.UI.Tests/TvSenderTests.cs +++ b/Ombi.UI.Tests/TvSenderTests.cs @@ -35,6 +35,7 @@ using Ombi.Api.Interfaces; using Ombi.Api.Models.Sonarr; using Ombi.Core; using Ombi.Core.SettingModels; +using Ombi.Helpers; using Ombi.Store; using Ploeh.AutoFixture; @@ -49,6 +50,7 @@ namespace Ombi.UI.Tests private TvSender Sender { get; set; } private Fixture F { get; set; } + private Mock Cache { get; set; } [SetUp] public void Setup() @@ -56,7 +58,8 @@ namespace Ombi.UI.Tests F = new Fixture(); SonarrMock = new Mock(); SickrageMock = new Mock(); - Sender = new TvSender(SonarrMock.Object, SickrageMock.Object); + Cache = new Mock(); + Sender = new TvSender(SonarrMock.Object, SickrageMock.Object, Cache.Object); } [Test] @@ -66,7 +69,7 @@ namespace Ombi.UI.Tests var seriesResult = new SonarrAddSeries() { title = "ABC"}; SonarrMock.Setup(x => x.GetSeries(It.IsAny(), It.IsAny())).Returns(F.Build().With(x => x.tvdbId, 1).With(x => x.title, "ABC").CreateMany().ToList()); - Sender = new TvSender(SonarrMock.Object, SickrageMock.Object); + Sender = new TvSender(SonarrMock.Object, SickrageMock.Object, Cache.Object); var request = new RequestedModel {SeasonsRequested = "All", ProviderId = 1, Title = "ABC"}; @@ -116,7 +119,7 @@ namespace Ombi.UI.Tests SonarrMock.Setup(x => x.GetEpisodes(It.IsAny(), It.IsAny(), It.IsAny())).Returns(F.CreateMany()); - Sender = new TvSender(SonarrMock.Object, SickrageMock.Object); + Sender = new TvSender(SonarrMock.Object, SickrageMock.Object, Cache.Object); var episodes = new List { new EpisodesModel diff --git a/Ombi.UI/Content/requests.js b/Ombi.UI/Content/requests.js index 7928f39aa..1d2ad987d 100644 --- a/Ombi.UI/Content/requests.js +++ b/Ombi.UI/Content/requests.js @@ -559,6 +559,25 @@ $(document).on("click", ".approve-with-quality", function (e) { }); +// Change root folder +$(document).on("click", ".change-root-folder", function (e) { + e.preventDefault(); + var $this = $(this); + var $button = $this.parents('.btn-split').children('.change').first(); + var rootFolderId = e.target.id + var $form = $this.parents('form').first(); + + if ($button.text() === " Loading...") { + return; + } + + loadingButton($button.attr('id'), "success"); + + changeRootFolder($form, rootFolderId, function () { + }); + +}); + // Change Availability $(document).on("click", ".change", function (e) { @@ -638,6 +657,37 @@ function approveRequest($form, qualityId, successCallback) { }); } +function changeRootFolder($form, rootFolderId, successCallback) { + + var formData = $form.serialize(); + if (rootFolderId) formData += ("&rootFolderId=" + rootFolderId); + + $.ajax({ + type: $form.prop('method'), + url: $form.prop('action'), + data: formData, + dataType: "json", + success: function (response) { + + if (checkJsonResponse(response)) { + if (response.message) { + generateNotify(response.message, "success"); + } else { + generateNotify("Success! Changed Root Path.", "success"); + } + + if (successCallback) { + successCallback(); + } + } + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); +} + function denyRequest($form, successCallback) { var formData = $form.serialize(); @@ -808,6 +858,9 @@ function buildRequestContext(result, type) { musicBrainzId: result.musicBrainzId, denied: result.denied, deniedReason: result.deniedReason, + hasRootFolders: result.hasRootFolders, + rootFolders: result.rootFolders, + currentRootPath : result.currentRootPath }; return context; diff --git a/Ombi.UI/ModelDataProviders/SonarrSettingsDataProvider.cs b/Ombi.UI/ModelDataProviders/SonarrSettingsDataProvider.cs index 694973e6a..7f3474ff8 100644 --- a/Ombi.UI/ModelDataProviders/SonarrSettingsDataProvider.cs +++ b/Ombi.UI/ModelDataProviders/SonarrSettingsDataProvider.cs @@ -52,7 +52,7 @@ namespace Ombi.UI.ModelDataProviders with.Property(x => x.QualityProfile).Description("Sonarr's quality profile").Required(true); with.Property(x => x.SeasonFolders).Description("Sonarr's season folders").Required(false); - + with.Property(x => x.RootPath).Description("Sonarr's root path").Required(false); }); } diff --git a/Ombi.UI/Models/RequestViewModel.cs b/Ombi.UI/Models/RequestViewModel.cs index b6db27bca..b31237bdc 100644 --- a/Ombi.UI/Models/RequestViewModel.cs +++ b/Ombi.UI/Models/RequestViewModel.cs @@ -58,5 +58,8 @@ namespace Ombi.UI.Models public Store.EpisodesModel[] Episodes { get; set; } public bool Denied { get; set; } public string DeniedReason { get; set; } + public RootFolderModel[] RootFolders { get; set; } + public bool HasRootFolders { get; set; } + public string CurrentRootPath { get; set; } } } diff --git a/Ombi.UI/Models/RootFolderModel.cs b/Ombi.UI/Models/RootFolderModel.cs new file mode 100644 index 000000000..9d15024fe --- /dev/null +++ b/Ombi.UI/Models/RootFolderModel.cs @@ -0,0 +1,36 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: RootFolderModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace Ombi.UI.Models +{ + + public class RootFolderModel + { + public string Id { get; set; } + public string Path { get; set; } + public long FreeSpace { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.UI/Modules/Admin/IntegrationModule.cs b/Ombi.UI/Modules/Admin/IntegrationModule.cs index 1322e7c64..36ec5fcc9 100644 --- a/Ombi.UI/Modules/Admin/IntegrationModule.cs +++ b/Ombi.UI/Modules/Admin/IntegrationModule.cs @@ -54,7 +54,7 @@ namespace Ombi.UI.Modules.Admin { public IntegrationModule(ISettingsService settingsService, ISettingsService watcher, ISettingsService cp,ISecurityExtensions security, IAnalytics a, ISettingsService radarrSettings, - ICacheProvider cache, IRadarrApi radarrApi) : base("admin", settingsService, security) + ICacheProvider cache, IRadarrApi radarrApi, ISonarrApi sonarrApi) : base("admin", settingsService, security) { WatcherSettings = watcher; @@ -63,9 +63,13 @@ namespace Ombi.UI.Modules.Admin Cache = cache; RadarrApi = radarrApi; RadarrSettings = radarrSettings; + SonarrApi = sonarrApi; Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); + + Post["/sonarrrootfolders"] = _ => GetSonarrRootFolders(); + Get["/watcher", true] = async (x, ct) => await Watcher(); Post["/watcher", true] = async (x, ct) => await SaveWatcher(); @@ -82,6 +86,7 @@ namespace Ombi.UI.Modules.Admin private IRadarrApi RadarrApi { get; } private ICacheProvider Cache { get; } private IAnalytics Analytics { get; } + private ISonarrApi SonarrApi { get; } private async Task Watcher() { @@ -182,5 +187,20 @@ namespace Ombi.UI.Modules.Admin return Response.AsJson(profiles); } + private Response GetSonarrRootFolders() + { + var settings = this.Bind(); + + var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); + + // set the cache + if (rootFolders != null) + { + Cache.Set(CacheKeys.SonarrRootFolders, rootFolders); + } + + return Response.AsJson(rootFolders); + } + } } \ No newline at end of file diff --git a/Ombi.UI/Modules/ApprovalModule.cs b/Ombi.UI/Modules/ApprovalModule.cs index 9781b79a9..667b3e843 100644 --- a/Ombi.UI/Modules/ApprovalModule.cs +++ b/Ombi.UI/Modules/ApprovalModule.cs @@ -50,7 +50,7 @@ namespace Ombi.UI.Modules public ApprovalModule(IRequestService service, ISonarrApi sonarrApi, ISettingsService sonarrSettings, ISickRageApi srApi, ISettingsService srSettings, ISettingsService hpSettings, IHeadphonesApi hpApi, ISettingsService pr, ITransientFaultQueue faultQueue - , ISecurityExtensions security, IMovieSender movieSender) : base("approval", pr, security) + , ISecurityExtensions security, IMovieSender movieSender, ICacheProvider cache) : base("approval", pr, security) { Before += (ctx) => Security.AdminLoginRedirect(ctx, Permissions.Administrator,Permissions.ManageRequests); @@ -64,6 +64,7 @@ namespace Ombi.UI.Modules HeadphoneApi = hpApi; FaultQueue = faultQueue; MovieSender = movieSender; + Cache = cache; Post["/approve", true] = async (x, ct) => await Approve((int)Request.Form.requestid, (string)Request.Form.qualityId); Post["/deny", true] = async (x, ct) => await DenyRequest((int)Request.Form.requestid, (string)Request.Form.reason); @@ -86,6 +87,7 @@ namespace Ombi.UI.Modules private ISickRageApi SickRageApi { get; } private IHeadphonesApi HeadphoneApi { get; } private ITransientFaultQueue FaultQueue { get; } + private ICacheProvider Cache { get; } /// /// Approves the specified request identifier. @@ -120,7 +122,7 @@ namespace Ombi.UI.Modules private async Task RequestTvAndUpdateStatus(RequestedModel request, string qualityId) { - var sender = new TvSenderOld(SonarrApi, SickRageApi); // TODO put back + var sender = new TvSenderOld(SonarrApi, SickRageApi, Cache); // TODO put back var sonarrSettings = await SonarrSettings.GetSettingsAsync(); if (sonarrSettings.Enabled) @@ -435,7 +437,7 @@ namespace Ombi.UI.Modules } if (r.Type == RequestType.TvShow) { - var sender = new TvSenderOld(SonarrApi, SickRageApi); // TODO put back + var sender = new TvSenderOld(SonarrApi, SickRageApi, Cache); // TODO put back var sr = await SickRageSettings.GetSettingsAsync(); var sonarr = await SonarrSettings.GetSettingsAsync(); if (sr.Enabled) diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 4faefa206..72fbba0f9 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -96,6 +96,8 @@ namespace Ombi.UI.Modules Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available); + Post["/changeRootFolder", true] = async (x, ct) => await ChangeRootFolder((int) Request.Form.requestId, (int) Request.Form.rootFolderId); + Get["/UpdateFilters", true] = async (x, ct) => await GetFilterAndSortSettings(); } @@ -160,7 +162,7 @@ namespace Ombi.UI.Modules } } - + var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests); var viewModel = dbMovies.Select(movie => new RequestViewModel { @@ -185,7 +187,7 @@ namespace Ombi.UI.Modules IssueId = movie.IssueId, Denied = movie.Denied, DeniedReason = movie.DeniedReason, - Qualities = qualities.ToArray() + Qualities = qualities.ToArray(), }).ToList(); return Response.AsJson(viewModel); @@ -193,32 +195,39 @@ namespace Ombi.UI.Modules private async Task GetTvShows() { - var settingsTask = PrSettings.GetSettingsAsync(); - var requests = await Service.GetAllAsync(); requests = requests.Where(x => x.Type == RequestType.TvShow); var dbTv = requests; - var settings = await settingsTask; if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) && !IsAdmin) { dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList(); } IEnumerable qualities = new List(); + IEnumerable rootFolders = new List(); + + var sonarrSettings = await SonarrSettings.GetSettingsAsync(); if (IsAdmin) { try { - var sonarrSettings = await SonarrSettings.GetSettingsAsync(); if (sonarrSettings.Enabled) { - var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () => + var result = await Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () => { return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri)); }); - qualities = result.Result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList(); - } + qualities = result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList(); + + + var rootFoldersResult =await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () => + { + return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + + rootFolders = rootFoldersResult.Select(x => new RootFolderModel { Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace}).ToList(); + } else { var sickRageSettings = await SickRageSettings.GetSettingsAsync(); @@ -235,7 +244,7 @@ namespace Ombi.UI.Modules } - + var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests); var viewModel = dbTv.Select(tv => new RequestViewModel @@ -264,11 +273,28 @@ namespace Ombi.UI.Modules TvSeriesRequestType = tv.SeasonsRequested, Qualities = qualities.ToArray(), Episodes = tv.Episodes.ToArray(), + RootFolders = rootFolders.ToArray(), + HasRootFolders = rootFolders.Any(), + CurrentRootPath = GetRootPath(tv.RootFolderSelected, sonarrSettings).Result }).ToList(); return Response.AsJson(viewModel); } + private async Task GetRootPath(int pathId, SonarrSettings sonarrSettings) + { + var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () => + { + return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + + foreach (var r in rootFoldersResult.Where(r => r.id == pathId)) + { + return r.path; + } + return string.Empty; + } + private async Task GetAlbumRequests() { var settings = PrSettings.GetSettings(); @@ -432,5 +458,34 @@ namespace Ombi.UI.Modules return Response.AsJson(vm); } - } + + private async Task ChangeRootFolder(int id, int rootFolderId) + { + // Get all root folders + var settings = await SonarrSettings.GetSettingsAsync(); + var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); + + // Get Request + var allRequests = await Service.GetAllAsync(); + var request = allRequests.FirstOrDefault(x => x.Id == id); + + if (request == null) + { + return Response.AsJson(new JsonResponseModel {Result = false}); + } + + foreach (var folder in rootFolders) + { + if (folder.id.Equals(rootFolderId)) + { + request.RootFolderSelected = folder.id; + break; + } + } + + await Service.UpdateRequestAsync(request); + + return Response.AsJson(new JsonResponseModel {Result = true}); + } + } } diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 63cc4c03c..75c6352e4 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -1107,7 +1107,7 @@ namespace Ombi.UI.Modules { model.Approved = true; var s = await sonarrSettings; - var sender = new TvSenderOld(SonarrApi, SickrageApi); // TODO put back + var sender = new TvSenderOld(SonarrApi, SickrageApi, Cache); // TODO put back if (s.Enabled) { var result = await sender.SendToSonarr(s, model); diff --git a/Ombi.UI/Ombi.UI.csproj b/Ombi.UI/Ombi.UI.csproj index 22dd6175d..25745546a 100644 --- a/Ombi.UI/Ombi.UI.csproj +++ b/Ombi.UI/Ombi.UI.csproj @@ -250,6 +250,7 @@ + @@ -797,7 +798,7 @@ Always - + Always diff --git a/Ombi.UI/Views/Admin/Sonarr.cshtml b/Ombi.UI/Views/Admin/Sonarr.cshtml index f2b4ca49a..025059e94 100644 --- a/Ombi.UI/Views/Admin/Sonarr.cshtml +++ b/Ombi.UI/Views/Admin/Sonarr.cshtml @@ -17,14 +17,14 @@ Sonarr Settings
- @if (Model.Enabled) - { - - } - else - { - - } + @if (Model.Enabled) + { + + } + else + { + + }
@@ -51,14 +51,14 @@
- @if (Model.Ssl) - { - - } - else - { - - } + @if (Model.Ssl) + { + + } + else + { + + }
@@ -69,7 +69,7 @@
- +
@@ -80,32 +80,47 @@
-
- - + + +
+ +
+
+ +
+ +
+ @*
+ +
+ + +
+
*@ +
- @if (Model.SeasonFolders) - { - - } - else - { - - } - + @if (Model.SeasonFolders) + { + + } + else + { + + } +
- +
@@ -161,6 +176,40 @@ } + @if (!string.IsNullOrEmpty(Model.RootPath)) + { + + + console.log('Hit root folders..'); + + var rootFolderSelected = @Model.RootPath; + if (!rootFolderSelected) { + return; + } + var $form = $("#mainForm"); + $.ajax({ + type: $form.prop("method"), + data: $form.serialize(), + url: "sonarrrootfolders", + dataType: "json", + success: function(response) { + response.forEach(function(result) { + $('#selectedRootFolder').html(""); + if (result.id == rootFolderSelected) { + $("#selectRootFolder").append(""); + } else { + $("#selectRootFolder").append(""); + } + }); + }, + error: function(e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); + + } + $('#save').click(function(e) { e.preventDefault(); @@ -170,11 +219,12 @@ return; } var qualityProfile = $("#profiles option:selected").val(); + var rootFolder = $("#rootFolders option:selected").val(); var $form = $("#mainForm"); var data = $form.serialize(); - data = data + "&qualityProfile=" + qualityProfile; + data = data + "&qualityProfile=" + qualityProfile + "&rootPath=" + rootFolder; $.ajax({ type: $form.prop("method"), @@ -201,17 +251,17 @@ e.preventDefault(); if (!$('#Ip').val()) { generateNotify("Please enter a valid IP/Hostname.", "warning"); - $('#getSpinner').attr("class", "fa fa-times"); + $('#getSpinner').attr("class", "fa fa-times"); return; } if (!$('#portNumber').val()) { generateNotify("Please enter a valid Port Number.", "warning"); - $('#getSpinner').attr("class", "fa fa-times"); + $('#getSpinner').attr("class", "fa fa-times"); return; } if (!$('#ApiKey').val()) { generateNotify("Please enter a valid ApiKey.", "warning"); - $('#getSpinner').attr("class", "fa fa-times"); + $('#getSpinner').attr("class", "fa fa-times"); return; } var $form = $("#mainForm"); @@ -222,18 +272,58 @@ dataType: "json", success: function (response) { response.forEach(function (result) { - $('#getSpinner').attr("class", "fa fa-check"); + $('#getSpinner').attr("class", "fa fa-check"); $("#select").append(""); }); }, error: function (e) { console.log(e); - $('#getSpinner').attr("class", "fa fa-times"); + $('#getSpinner').attr("class", "fa fa-times"); generateNotify("Something went wrong!", "danger"); } }); }); + $('#getRootFolders').click(function (e) { + + $('#getRootFolderSpinner').attr("class", "fa fa-spinner fa-spin"); + e.preventDefault(); + if (!$('#Ip').val()) { + generateNotify("Please enter a valid IP/Hostname.", "warning"); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + return; + } + if (!$('#portNumber').val()) { + generateNotify("Please enter a valid Port Number.", "warning"); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + return; + } + if (!$('#ApiKey').val()) { + generateNotify("Please enter a valid ApiKey.", "warning"); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + return; + } + var $form = $("#mainForm"); + $.ajax({ + type: $form.prop("method"), + data: $form.serialize(), + url: "sonarrrootfolders", + dataType: "json", + success: function (response) { + response.forEach(function (result) { + $('#getRootFolderSpinner').attr("class", "fa fa-check"); + $("#selectRootFolder").append(""); + }); + }, + error: function (e) { + console.log(e); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + generateNotify("Something went wrong!", "danger"); + } + }); + }); + + var base = '@Html.GetBaseUrl()'; $('#testSonarr').click(function (e) { @@ -245,7 +335,7 @@ var data = $form.serialize(); data = data + "&qualityProfile=" + qualityProfile; - + var url = createBaseUrl(base, '/test/sonarr'); $.ajax({ type: $form.prop("method"), @@ -256,16 +346,16 @@ console.log(response); if (response.result === true) { generateNotify(response.message, "success"); - $('#spinner').attr("class", "fa fa-check"); + $('#spinner').attr("class", "fa fa-check"); } else { generateNotify(response.message, "warning"); - $('#spinner').attr("class", "fa fa-times"); + $('#spinner').attr("class", "fa fa-times"); } }, error: function (e) { console.log(e); generateNotify("Something went wrong!", "danger"); - $('#spinner').attr("class", "fa fa-times"); + $('#spinner').attr("class", "fa fa-times"); } }); }); diff --git a/Ombi.UI/Views/Admin/Radarr.cshtml b/Ombi.UI/Views/Integration/Radarr.cshtml similarity index 100% rename from Ombi.UI/Views/Admin/Radarr.cshtml rename to Ombi.UI/Views/Integration/Radarr.cshtml diff --git a/Ombi.UI/Views/Requests/Index.cshtml b/Ombi.UI/Views/Requests/Index.cshtml index 0447e25fa..7199253c7 100644 --- a/Ombi.UI/Views/Requests/Index.cshtml +++ b/Ombi.UI/Views/Requests/Index.cshtml @@ -244,6 +244,11 @@
@UI.Requests_RequestedBy: {{requestedUsers}}
{{/if}}
@UI.Requests_RequestedDate: {{requestedDate}}
+ {{#if admin}} + {{#if currentRootPath}} +
Root Path: {{currentRootPath}}
+ {{/if}} + {{/if}}
{{#if_eq issueId 0}} @*Nothing*@ @@ -275,6 +280,28 @@ {{/if_eq}} + + +
+ + {{#if_eq hasRootFolders true}} +
+ + + +
+ {{/if_eq}} +
+ + + {{#unless denied}}