using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text.RegularExpressions; using Dapper; using FluentMigrator; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore.Migration.Framework; namespace NzbDrone.Core.Datastore.Migration { [Migration(063)] public class add_custom_formats : NzbDroneMigrationBase { protected override void MainDbUpgrade() { Alter.Table("DelayProfiles").AddColumn("BypassIfHighestQuality").AsBoolean().WithDefaultValue(false); // Set to true for existing Delay Profiles to keep behavior the same. Update.Table("DelayProfiles").Set(new { BypassIfHighestQuality = true }).AllRows(); Alter.Table("TrackFiles").AddColumn("OriginalFilePath").AsString().Nullable(); Execute.WithConnection(ChangeRequiredIgnoredTypes); // Add Custom Format Columns Create.TableForModel("CustomFormats") .WithColumn("Name").AsString().Unique() .WithColumn("Specifications").AsString().WithDefaultValue("[]") .WithColumn("IncludeCustomFormatWhenRenaming").AsBoolean().WithDefaultValue(false); // Add Custom Format Columns to Quality Profiles Alter.Table("QualityProfiles").AddColumn("FormatItems").AsString().WithDefaultValue("[]"); Alter.Table("QualityProfiles").AddColumn("MinFormatScore").AsInt32().WithDefaultValue(0); Alter.Table("QualityProfiles").AddColumn("CutoffFormatScore").AsInt32().WithDefaultValue(0); // Migrate Preferred Words to Custom Formats Execute.WithConnection(MigratePreferredTerms); Execute.WithConnection(MigrateNamingConfigs); // Remove Preferred Word Columns from ReleaseProfiles Delete.Column("Preferred").FromTable("ReleaseProfiles"); Delete.Column("IncludePreferredWhenRenaming").FromTable("ReleaseProfiles"); // Remove Profiles that will no longer validate Execute.Sql("DELETE FROM \"ReleaseProfiles\" WHERE \"Required\" = '[]' AND \"Ignored\" = '[]'"); Alter.Table("DelayProfiles").AddColumn("BypassIfAboveCustomFormatScore").AsBoolean().WithDefaultValue(false); Alter.Table("DelayProfiles").AddColumn("MinimumCustomFormatScore").AsInt32().Nullable(); } private void ChangeRequiredIgnoredTypes(IDbConnection conn, IDbTransaction tran) { var updatedProfiles = new List(); using (var getEmailCmd = conn.CreateCommand()) { getEmailCmd.Transaction = tran; getEmailCmd.CommandText = "SELECT \"Id\", \"Required\", \"Ignored\" FROM \"ReleaseProfiles\""; using (var reader = getEmailCmd.ExecuteReader()) { while (reader.Read()) { var id = reader.GetInt32(0); var requiredObj = reader.GetValue(1); var ignoredObj = reader.GetValue(2); var required = requiredObj == DBNull.Value ? Enumerable.Empty() : requiredObj.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var ignored = ignoredObj == DBNull.Value ? Enumerable.Empty() : ignoredObj.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); updatedProfiles.Add(new { Id = id, Required = required.ToJson(), Ignored = ignored.ToJson() }); } } } var updateProfileSql = "UPDATE \"ReleaseProfiles\" SET \"Required\" = @Required, \"Ignored\" = @Ignored WHERE \"Id\" = @Id"; conn.Execute(updateProfileSql, updatedProfiles, transaction: tran); } private void MigratePreferredTerms(IDbConnection conn, IDbTransaction tran) { var updatedCollections = new List(); // Pull list of quality Profiles var qualityProfiles = new List(); using (var getProfiles = conn.CreateCommand()) { getProfiles.Transaction = tran; getProfiles.CommandText = @"SELECT ""Id"" FROM ""QualityProfiles"""; using (var definitionsReader = getProfiles.ExecuteReader()) { while (definitionsReader.Read()) { var id = definitionsReader.GetInt32(0); qualityProfiles.Add(new QualityProfile057 { Id = id, }); } } } // Generate List of Custom Formats from Preferred Words using (var cmd = conn.CreateCommand()) { cmd.Transaction = tran; cmd.CommandText = "SELECT \"Preferred\", \"IncludePreferredWhenRenaming\", \"Enabled\", \"Id\" FROM \"ReleaseProfiles\" WHERE \"Preferred\" IS NOT NULL"; using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var preferred = reader.GetString(0); var includeName = reader.GetBoolean(1); var enabled = reader.GetBoolean(2); var releaseProfileId = reader.GetInt32(3); string name = null; if (name.IsNullOrWhiteSpace()) { name = $"Unnamed_{releaseProfileId}"; } else { name = $"{name}_{releaseProfileId}"; } var data = STJson.Deserialize>(preferred); var specs = new List(); var nameIdentifier = 0; foreach (var term in data) { var regexTerm = term.Key .TrimStart('/') .TrimEnd('/') .TrimEnd("/i"); // Validate Regex before creating a CF try { Regex.Match("", regexTerm); } catch (ArgumentException) { continue; } updatedCollections.Add(new CustomFormat057 { Name = data.Count > 1 ? $"{name}_{nameIdentifier++}" : name, PreferredName = name, IncludeCustomFormatWhenRenaming = includeName, Score = term.Value, Enabled = enabled, Specifications = new List { new CustomFormatSpec057 { Type = "ReleaseTitleSpecification", Body = new CustomFormatReleaseTitleSpec057 { Order = 1, ImplementationName = "Release Title", Name = regexTerm, Value = regexTerm } } }.ToJson() }); } } } } // Insert Custom Formats var updateSql = "INSERT INTO \"CustomFormats\" (\"Name\", \"IncludeCustomFormatWhenRenaming\", \"Specifications\") VALUES (@Name, @IncludeCustomFormatWhenRenaming, @Specifications)"; conn.Execute(updateSql, updatedCollections, transaction: tran); // Pull List of Custom Formats with new Ids var formats = new List(); using (var getProfiles = conn.CreateCommand()) { getProfiles.Transaction = tran; getProfiles.CommandText = @"SELECT ""Id"", ""Name"" FROM ""CustomFormats"""; using (var definitionsReader = getProfiles.ExecuteReader()) { while (definitionsReader.Read()) { var id = definitionsReader.GetInt32(0); var name = definitionsReader.GetString(1); formats.Add(new CustomFormat057 { Id = id, Name = name }); } } } // Update each profile with original scores foreach (var profile in qualityProfiles) { profile.FormatItems = formats.Select(x => new { Format = x.Id, Score = updatedCollections.First(f => f.Name == x.Name).Enabled ? updatedCollections.First(f => f.Name == x.Name).Score : 0 }).ToJson(); } // Push profile updates to DB var updateProfilesSql = "UPDATE \"QualityProfiles\" SET \"FormatItems\" = @FormatItems WHERE \"Id\" = @Id"; conn.Execute(updateProfilesSql, qualityProfiles, transaction: tran); } private void MigrateNamingConfigs(IDbConnection conn, IDbTransaction tran) { var updatedNamingConfigs = new List(); using (var namingConfigCmd = conn.CreateCommand()) { namingConfigCmd.Transaction = tran; namingConfigCmd.CommandText = @"SELECT * FROM ""NamingConfig"" LIMIT 1"; using (var namingConfigReader = namingConfigCmd.ExecuteReader()) { var standardTrackFormatIndex = namingConfigReader.GetOrdinal("StandardTrackFormat"); var multiDiscTrackFormatIndex = namingConfigReader.GetOrdinal("MultiDiscTrackFormat"); while (namingConfigReader.Read()) { var standardTrackFormat = NameReplace(namingConfigReader.GetString(standardTrackFormatIndex)); var multiDiscTrackFormat = NameReplace(namingConfigReader.GetString(multiDiscTrackFormatIndex)); updatedNamingConfigs.Add(new { StandardTrackFormat = standardTrackFormat, MultiDiscTrackFormat = multiDiscTrackFormat }); } } } var updateProfileSql = "UPDATE \"NamingConfig\" SET \"StandardTrackFormat\" = @StandardTrackFormat, \"MultiDiscTrackFormat\" = @MultiDiscTrackFormat"; conn.Execute(updateProfileSql, updatedNamingConfigs, transaction: tran); } private string NameReplace(string oldTokenString) { var newTokenString = oldTokenString.Replace("Preferred Words", "Custom Formats") .Replace("Preferred.Words", "Custom.Formats") .Replace("Preferred-Words", "Custom-Formats") .Replace("Preferred_Words", "Custom_Formats"); return newTokenString; } private class PreferredWord056 { public string Key { get; set; } public int Value { get; set; } } private class QualityProfile057 { public int Id { get; set; } public string FormatItems { get; set; } } private class CustomFormat057 { public int Id { get; set; } public string Name { get; set; } public string PreferredName { get; set; } public bool IncludeCustomFormatWhenRenaming { get; set; } public string Specifications { get; set; } public int Score { get; set; } public bool Enabled { get; set; } } private class CustomFormatSpec057 { public string Type { get; set; } public CustomFormatReleaseTitleSpec057 Body { get; set; } } private class CustomFormatReleaseTitleSpec057 { public int Order { get; set; } public string ImplementationName { get; set; } public string Name { get; set; } public string Value { get; set; } public bool Required { get; set; } public bool Negate { get; set; } } } }