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(026)]
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 ( "BookFiles" ) . 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 < object > ( ) ;
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 < string > ( )
: requiredObj . ToString ( ) . Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
var ignored = ignoredObj = = DBNull . Value
? Enumerable . Empty < string > ( )
: 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 < CustomFormat026 > ( ) ;
// Pull list of quality Profiles
var qualityProfiles = new List < QualityProfile026 > ( ) ;
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 QualityProfile026
{
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 < List < PreferredWord026 > > ( preferred ) ;
var specs = new List < CustomFormatSpec026 > ( ) ;
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 CustomFormat026
{
Name = data . Count > 1 ? $"{name}_{nameIdentifier++}" : name ,
PreferredName = name ,
IncludeCustomFormatWhenRenaming = includeName ,
Score = term . Value ,
Enabled = enabled ,
Specifications = new List < CustomFormatSpec026 >
{
new CustomFormatSpec026
{
Type = "ReleaseTitleSpecification" ,
Body = new CustomFormatReleaseTitleSpec026
{
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 < CustomFormat026 > ( ) ;
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 CustomFormat026
{
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 < object > ( ) ;
using ( var namingConfigCmd = conn . CreateCommand ( ) )
{
namingConfigCmd . Transaction = tran ;
namingConfigCmd . CommandText = @"SELECT * FROM ""NamingConfig"" LIMIT 1" ;
using ( var namingConfigReader = namingConfigCmd . ExecuteReader ( ) )
{
var standardBookFormatIndex = namingConfigReader . GetOrdinal ( "StandardBookFormat" ) ;
while ( namingConfigReader . Read ( ) )
{
var standardBookFormat = NameReplace ( namingConfigReader . GetString ( standardBookFormatIndex ) ) ;
updatedNamingConfigs . Add ( new
{
StandardBookFormat = standardBookFormat ,
} ) ;
}
}
}
var updateProfileSql = "UPDATE \"NamingConfig\" SET \"StandardBookFormat\" = @StandardBookFormat" ;
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 PreferredWord026
{
public string Key { get ; set ; }
public int Value { get ; set ; }
}
private class QualityProfile026
{
public int Id { get ; set ; }
public string FormatItems { get ; set ; }
}
private class CustomFormat026
{
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 CustomFormatSpec026
{
public string Type { get ; set ; }
public CustomFormatReleaseTitleSpec026 Body { get ; set ; }
}
private class CustomFormatReleaseTitleSpec026
{
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 ; }
}
}
}