diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/040_newznab_category_to_capabilities_settingsFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/040_newznab_category_to_capabilities_settingsFixture.cs new file mode 100644 index 000000000..b51bf6433 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/040_newznab_category_to_capabilities_settingsFixture.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore.Migration +{ + [TestFixture] + public class newznab_category_to_capabilities_settingsFixture : MigrationTest + { + [Test] + public void should_migrate_categories_when_capabilities_is_not_defined() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("Indexers").Row(new + { + Name = "Usenet Indexer", + Redirect = false, + AppProfileId = 0, + DownloadClientId = 0, + Priority = 25, + Added = DateTime.UtcNow, + Implementation = "Newznab", + Settings = new + { + Categories = new[] + { + new { Id = 2000, Name = "Movies" }, + new { Id = 5000, Name = "TV" } + } + }.ToJson(), + ConfigContract = "NewznabSettings" + }); + }); + + var items = db.Query("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\""); + + items.Should().HaveCount(1); + items.First().Implementation.Should().Be("Newznab"); + items.First().ConfigContract.Should().Be("NewznabSettings"); + items.First().Settings.Should().ContainKey("capabilities"); + items.First().Settings.Should().NotContainKey("categories"); + + var newznabSettings = items.First().Settings.ToObject(); + newznabSettings.Capabilities.Should().NotBeNull(); + newznabSettings.Capabilities.SupportsRawSearch.Should().Be(false); + newznabSettings.Capabilities.Categories.Should().HaveCount(2); + newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 2000 && c.Name == "Movies"); + newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 5000 && c.Name == "TV"); + } + + [Test] + public void should_migrate_categories_when_capabilities_is_defined() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("Indexers").Row(new + { + Name = "Usenet Indexer", + Redirect = false, + AppProfileId = 0, + DownloadClientId = 0, + Priority = 25, + Added = DateTime.UtcNow, + Implementation = "Newznab", + Settings = new + { + Capabilities = new + { + SupportsRawSearch = true + }, + Categories = new[] + { + new { Id = 2000, Name = "Movies" }, + new { Id = 5000, Name = "TV" } + } + }.ToJson(), + ConfigContract = "NewznabSettings" + }); + }); + + var items = db.Query("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\""); + + items.Should().HaveCount(1); + items.First().Implementation.Should().Be("Newznab"); + items.First().ConfigContract.Should().Be("NewznabSettings"); + items.First().Settings.Should().ContainKey("capabilities"); + items.First().Settings.Should().NotContainKey("categories"); + + var newznabSettings = items.First().Settings.ToObject(); + newznabSettings.Capabilities.Should().NotBeNull(); + newznabSettings.Capabilities.SupportsRawSearch.Should().Be(true); + newznabSettings.Capabilities.Categories.Should().HaveCount(2); + newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 2000 && c.Name == "Movies"); + newznabSettings.Capabilities.Categories.Should().Contain(c => c.Id == 5000 && c.Name == "TV"); + } + + [Test] + public void should_use_defaults_when_categories_are_empty() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("Indexers").Row(new + { + Name = "Usenet Indexer", + Redirect = false, + AppProfileId = 0, + DownloadClientId = 0, + Priority = 25, + Added = DateTime.UtcNow, + Implementation = "Newznab", + Settings = new + { + Categories = Array.Empty() + }.ToJson(), + ConfigContract = "NewznabSettings" + }); + }); + + var items = db.Query("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\""); + + items.Should().HaveCount(1); + items.First().Implementation.Should().Be("Newznab"); + items.First().ConfigContract.Should().Be("NewznabSettings"); + items.First().Settings.Should().ContainKey("capabilities"); + items.First().Settings.Should().NotContainKey("categories"); + + var newznabSettings = items.First().Settings.ToObject(); + newznabSettings.Capabilities.Should().NotBeNull(); + newznabSettings.Capabilities.SupportsRawSearch.Should().Be(false); + newznabSettings.Capabilities.Categories.Should().NotBeNull(); + newznabSettings.Capabilities.Categories.Should().HaveCount(0); + } + + [Test] + public void should_use_defaults_when_settings_are_empty() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("Indexers").Row(new + { + Name = "Usenet Indexer", + Redirect = false, + AppProfileId = 0, + DownloadClientId = 0, + Priority = 25, + Added = DateTime.UtcNow, + Implementation = "Newznab", + Settings = new { }.ToJson(), + ConfigContract = "NewznabSettings" + }); + }); + + var items = db.Query("SELECT \"Id\", \"Implementation\", \"ConfigContract\", \"Settings\" FROM \"Indexers\""); + + items.Should().HaveCount(1); + items.First().Implementation.Should().Be("Newznab"); + items.First().ConfigContract.Should().Be("NewznabSettings"); + items.First().Settings.Should().NotContainKey("capabilities"); + items.First().Settings.Should().NotContainKey("categories"); + items.First().Settings.ToObject().Capabilities.Should().BeNull(); + } + } + + public class IndexerDefinition40 + { + public int Id { get; set; } + public string Implementation { get; set; } + public string ConfigContract { get; set; } + public JObject Settings { get; set; } + } + + public class NewznabSettings39 + { + public object Categories { get; set; } + } + + public class NewznabSettings40 + { + public NewznabCapabilitiesSettings40 Capabilities { get; set; } + } + + public class NewznabCapabilitiesSettings40 + { + public bool SupportsRawSearch { get; set; } + public List Categories { get; set; } + } + + public class IndexerCategory40 + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/040_newznab_category_to_capabilities_settings.cs b/src/NzbDrone.Core/Datastore/Migration/040_newznab_category_to_capabilities_settings.cs new file mode 100644 index 000000000..ad573bd9c --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/040_newznab_category_to_capabilities_settings.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Data; +using Dapper; +using FluentMigrator; +using Newtonsoft.Json.Linq; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(40)] + public class newznab_category_to_capabilities_settings : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.WithConnection(MoveCategoriesToCapabilities); + } + + private void MoveCategoriesToCapabilities(IDbConnection conn, IDbTransaction tran) + { + var updated = new List(); + + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tran; + cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" IN ('Newznab', 'Torznab')"; + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetInt32(0); + var settings = Json.Deserialize(reader.GetString(1)); + + if ((settings.Value("capabilities")?.ContainsKey("categories") ?? false) == false + && settings.ContainsKey("categories") + && settings.TryGetValue("categories", out var categories)) + { + if (!settings.ContainsKey("capabilities")) + { + settings.Add("capabilities", new JObject()); + } + + settings.Value("capabilities")?.Add(new JProperty("categories", JArray.FromObject(categories))); + + if (settings.ContainsKey("categories")) + { + settings.Remove("categories"); + } + } + + updated.Add(new + { + Settings = settings.ToJson(), + Id = id + }); + } + } + } + + var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id"; + conn.Execute(updateSql, updated, transaction: tran); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs index e7109f504..bea5a92be 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Newznab/Newznab.cs @@ -61,26 +61,23 @@ namespace NzbDrone.Core.Indexers.Newznab { var caps = new IndexerCapabilities(); - if (Definition == null || Settings?.Capabilities == null) + if (Definition == null || Settings?.Capabilities?.Categories == null) { return caps; } - caps.SupportsRawSearch = Settings.Capabilities?.SupportsRawSearch ?? false; - caps.SearchParams = Settings.Capabilities?.SearchParams ?? new List { SearchParam.Q }; - caps.TvSearchParams = Settings.Capabilities?.TvSearchParams ?? new List(); - caps.MovieSearchParams = Settings.Capabilities?.MovieSearchParams ?? new List(); - caps.MusicSearchParams = Settings.Capabilities?.MusicSearchParams ?? new List(); - caps.BookSearchParams = Settings.Capabilities?.BookSearchParams ?? new List(); - - if (Settings.Capabilities?.Categories != null) + foreach (var category in Settings.Capabilities.Categories) { - foreach (var category in Settings.Capabilities.Categories) - { - caps.Categories.AddCategoryMapping(category.Name, category); - } + caps.Categories.AddCategoryMapping(category.Name, category); } + caps.SupportsRawSearch = Settings?.Capabilities?.SupportsRawSearch ?? false; + caps.SearchParams = Settings?.Capabilities?.SearchParams ?? new List { SearchParam.Q }; + caps.TvSearchParams = Settings?.Capabilities?.TvSearchParams ?? new List(); + caps.MovieSearchParams = Settings?.Capabilities?.MovieSearchParams ?? new List(); + caps.MusicSearchParams = Settings?.Capabilities?.MusicSearchParams ?? new List(); + caps.BookSearchParams = Settings?.Capabilities?.BookSearchParams ?? new List(); + return caps; } diff --git a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesSettings.cs b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesSettings.cs index 244d1c869..ed59797af 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesSettings.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Newznab/NewznabCapabilitiesSettings.cs @@ -6,17 +6,17 @@ public class NewznabCapabilitiesSettings { public bool SupportsRawSearch { get; set; } - public List SearchParams { get; set; } = new (); + public List SearchParams { get; set; } - public List TvSearchParams { get; set; } = new (); + public List TvSearchParams { get; set; } - public List MovieSearchParams { get; set; } = new (); + public List MovieSearchParams { get; set; } - public List MusicSearchParams { get; set; } = new (); + public List MusicSearchParams { get; set; } - public List BookSearchParams { get; set; } = new (); + public List BookSearchParams { get; set; } - public List Categories { get; set; } = new (); + public List Categories { get; set; } public NewznabCapabilitiesSettings() { diff --git a/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs index 049fec0b1..42008100f 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Torznab/Torznab.cs @@ -61,26 +61,23 @@ namespace NzbDrone.Core.Indexers.Torznab { var caps = new IndexerCapabilities(); - if (Definition == null || Settings?.Capabilities == null) + if (Definition == null || Settings?.Capabilities?.Categories == null) { return caps; } - caps.SupportsRawSearch = Settings.Capabilities?.SupportsRawSearch ?? false; - caps.SearchParams = Settings.Capabilities?.SearchParams ?? new List { SearchParam.Q }; - caps.TvSearchParams = Settings.Capabilities?.TvSearchParams ?? new List(); - caps.MovieSearchParams = Settings.Capabilities?.MovieSearchParams ?? new List(); - caps.MusicSearchParams = Settings.Capabilities?.MusicSearchParams ?? new List(); - caps.BookSearchParams = Settings.Capabilities?.BookSearchParams ?? new List(); - - if (Settings.Capabilities?.Categories != null) + foreach (var category in Settings.Capabilities.Categories) { - foreach (var category in Settings.Capabilities.Categories) - { - caps.Categories.AddCategoryMapping(category.Name, category); - } + caps.Categories.AddCategoryMapping(category.Name, category); } + caps.SupportsRawSearch = Settings?.Capabilities?.SupportsRawSearch ?? false; + caps.SearchParams = Settings?.Capabilities?.SearchParams ?? new List { SearchParam.Q }; + caps.TvSearchParams = Settings?.Capabilities?.TvSearchParams ?? new List(); + caps.MovieSearchParams = Settings?.Capabilities?.MovieSearchParams ?? new List(); + caps.MusicSearchParams = Settings?.Capabilities?.MusicSearchParams ?? new List(); + caps.BookSearchParams = Settings?.Capabilities?.BookSearchParams ?? new List(); + return caps; }