diff --git a/NzbDrone.Core.Test/App.config b/NzbDrone.Core.Test/App.config
index 88b220807..6b4327abb 100644
--- a/NzbDrone.Core.Test/App.config
+++ b/NzbDrone.Core.Test/App.config
@@ -8,4 +8,12 @@
type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index aad12a791..2a5407133 100644
--- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -59,6 +59,18 @@
..\packages\Unity.2.0\lib\20\Microsoft.Practices.Unity.Interception.Configuration.dll
+
+ False
+ ..\packages\MigratorDotNet.0.9.0.28138\lib\Net40\Migrator.dll
+
+
+ False
+ ..\packages\MigratorDotNet.0.9.0.28138\lib\Net40\Migrator.Framework.dll
+
+
+ False
+ ..\packages\MigratorDotNet.0.9.0.28138\lib\Net40\Migrator.Providers.dll
+
..\packages\Moq.4.0.10827\lib\NET40\Moq.dll
@@ -100,6 +112,7 @@
+
diff --git a/NzbDrone.Core.Test/RepositoryProviderTest.cs b/NzbDrone.Core.Test/RepositoryProviderTest.cs
new file mode 100644
index 000000000..f2bd21428
--- /dev/null
+++ b/NzbDrone.Core.Test/RepositoryProviderTest.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Text;
+using Gallio.Framework;
+using MbUnit.Framework;
+using MbUnit.Framework.ContractVerifiers;
+using Migrator.Framework;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Instrumentation;
+using NzbDrone.Core.Repository;
+using NzbDrone.Core.Repository.Quality;
+using NzbDrone.Core.Test.Framework;
+using SubSonic.DataProviders;
+using SubSonic.Repository;
+using SubSonic.Schema;
+using SubSonic.SqlGeneration.Schema;
+
+namespace NzbDrone.Core.Test
+{
+ [TestFixture]
+ // ReSharper disable InconsistentNaming
+ public class RepositoryProviderTest
+ {
+ [Test]
+ public void Get_Assembly_repos()
+ {
+ var provider = new RepositoryProvider();
+ var types = provider.GetRepositoryTypes();
+
+ Assert.IsNotEmpty(types);
+ Assert.Contains(types, typeof(Config));
+ Assert.Contains(types, typeof(Episode));
+ Assert.Contains(types, typeof(EpisodeFile));
+ Assert.Contains(types, typeof(ExternalNotificationSetting));
+ Assert.Contains(types, typeof(History));
+ Assert.Contains(types, typeof(IndexerSetting));
+ Assert.Contains(types, typeof(JobSetting));
+ Assert.Contains(types, typeof(RootDir));
+ Assert.Contains(types, typeof(Season));
+ Assert.Contains(types, typeof(Series));
+
+ Assert.Contains(types, typeof(QualityProfile));
+
+ Assert.DoesNotContain(types, typeof(QualityTypes));
+ }
+
+
+
+
+
+ [Test]
+ public void Get_table_columns()
+ {
+ var provider = new RepositoryProvider();
+ var typeTable = provider.GetSchemaFromType(typeof(TestRepoType));
+
+ Assert.IsNotNull(typeTable.Columns);
+ Assert.Count(3, typeTable.Columns);
+ Assert.AreEqual("TestRepoTypes", typeTable.Name);
+ }
+
+ [Test]
+ public void ConvertToMigratorColumn()
+ {
+ var provider = new RepositoryProvider();
+
+ var subsonicColumn = new DatabaseColumn
+ {
+ Name = "Name",
+ DataType = DbType.Boolean,
+ IsPrimaryKey = true,
+ IsNullable = true
+ };
+
+ var migColumn = provider.ConvertToMigratorColumn(subsonicColumn);
+
+ Assert.IsTrue(migColumn.IsPrimaryKey);
+ Assert.AreEqual(ColumnProperty.Null | ColumnProperty.PrimaryKey, migColumn.ColumnProperty);
+ }
+
+
+ [Test]
+ public void GetDbColumns()
+ {
+ string connectionString = "Data Source=" + Guid.NewGuid() + ".db;Version=3;New=True";
+ var dbProvider = ProviderFactory.GetProvider(connectionString, "System.Data.SQLite");
+ var repo = new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations);
+
+ repo.Add(new TestRepoType(){Value = "Dummy"});
+
+ var repositoryProvider = new RepositoryProvider();
+ var columns = repositoryProvider.GetColumnsFromDatabase(connectionString, "TestRepoTypes");
+
+ Assert.Count(3, columns);
+
+ }
+
+
+ [Test]
+ public void DeleteColumns()
+ {
+ string connectionString = "Data Source=" + Guid.NewGuid() + ".db;Version=3;New=True";
+ var dbProvider = ProviderFactory.GetProvider(connectionString, "System.Data.SQLite");
+ var repo = new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations);
+
+ repo.Add(new TestRepoType(){Value = "Dummy"});
+
+ var repositoryProvider = new RepositoryProvider();
+ var typeSchema = repositoryProvider.GetSchemaFromType(typeof(TestRepoType2));
+ var columns = repositoryProvider.GetColumnsFromDatabase(connectionString, "TestRepoTypes");
+
+
+ var deletedColumns = repositoryProvider.GetDeletedColumns(typeSchema, columns);
+
+
+ Assert.Count(1, deletedColumns);
+ Assert.AreEqual("NewName", deletedColumns[0].Name.Trim('[', ']'));
+ }
+
+
+ [Test]
+ public void NewColumns()
+ {
+ string connectionString = "Data Source=" + Guid.NewGuid() + ".db;Version=3;New=True";
+ var dbProvider = ProviderFactory.GetProvider(connectionString, "System.Data.SQLite");
+ var repo = new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations);
+
+ repo.Add(new TestRepoType2() { Value = "dummy" });
+
+ var repositoryProvider = new RepositoryProvider();
+ var typeSchema = repositoryProvider.GetSchemaFromType(typeof(TestRepoType));
+ var columns = repositoryProvider.GetColumnsFromDatabase(connectionString, "TestRepoType2s");
+
+
+ var deletedColumns = repositoryProvider.GetNewColumns(typeSchema, columns);
+
+
+ Assert.Count(1, deletedColumns);
+ Assert.AreEqual("NewName", deletedColumns[0].Name.Trim('[', ']'));
+ }
+
+ }
+
+
+ public class TestRepoType
+ {
+ [SubSonicPrimaryKey]
+ public int TestId { get; set; }
+
+ [SubSonicColumnNameOverride("NewName")]
+ public Boolean BaddBoolean { get; set; }
+
+
+ public string Value { get; set; }
+
+ [SubSonicIgnore]
+ public Boolean BaddBooleanIgnored { get; set; }
+ }
+
+
+ public class TestRepoType2
+ {
+ [SubSonicPrimaryKey]
+ public int TestId { get; set; }
+
+ public string Value { get; set; }
+
+ [SubSonicIgnore]
+ public Boolean BaddBooleanIgnored { get; set; }
+ }
+}
diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs
index 8c60db6ff..1b299fb4d 100644
--- a/NzbDrone.Core/CentralDispatch.cs
+++ b/NzbDrone.Core/CentralDispatch.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Web.Hosting;
using Ninject;
using NLog;
+using NzbDrone.Core.Datastore;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core;
@@ -43,12 +44,28 @@ namespace NzbDrone.Core
{
if (_kernel == null)
{
- BindKernel();
+ InitializeApp();
}
return _kernel;
}
}
+ private static void InitializeApp()
+ {
+ BindKernel();
+
+ LogConfiguration.Setup();
+
+ Migrations.Run();
+ ForceMigration(_kernel.Get());
+
+ SetupDefaultQualityProfiles(_kernel.Get()); //Setup the default QualityProfiles on start-up
+
+ BindIndexers();
+ BindJobs();
+ BindExternalNotifications();
+ }
+
public static void BindKernel()
{
lock (KernelLock)
@@ -56,27 +73,7 @@ namespace NzbDrone.Core
Logger.Debug("Binding Ninject's Kernel");
_kernel = new StandardKernel();
- //Sqlite
- var appDataPath = new DirectoryInfo(Path.Combine(AppPath, "App_Data"));
- if (!appDataPath.Exists) appDataPath.Create();
-
- string connectionString = String.Format("Data Source={0};Version=3;",
- Path.Combine(appDataPath.FullName, "nzbdrone.db"));
- var dbProvider = ProviderFactory.GetProvider(connectionString, "System.Data.SQLite");
-
- string logConnectionString = String.Format("Data Source={0};Version=3;",
- Path.Combine(appDataPath.FullName, "log.db"));
- var logDbProvider = ProviderFactory.GetProvider(logConnectionString, "System.Data.SQLite");
-
-
- //SQLExpress
- //string logConnectionString = String.Format(@"server=.\SQLExpress; database=NzbDroneLogs; Trusted_Connection=True;");
- //var logDbProvider = ProviderFactory.GetProvider(logConnectionString, "System.Data.SqlClient");
- var logRepository = new SimpleRepository(logDbProvider, SimpleRepositoryOptions.RunMigrations);
- //dbProvider.ExecuteQuery(new QueryCommand("VACUUM", dbProvider));
-
//dbProvider.Log = new NlogWriter();
-
_kernel.Bind().ToSelf().InSingletonScope();
_kernel.Bind().ToSelf().InTransientScope();
_kernel.Bind().ToSelf().InSingletonScope();
@@ -100,21 +97,10 @@ namespace NzbDrone.Core
_kernel.Bind().ToSelf().InSingletonScope();
_kernel.Bind().ToSelf().InSingletonScope();
_kernel.Bind().ToSelf().InSingletonScope();
- _kernel.Bind().ToMethod(
- c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope();
- _kernel.Bind().ToConstant(logRepository).WhenInjectedInto().
- InSingletonScope();
- _kernel.Bind().ToConstant(logRepository).WhenInjectedInto().InSingletonScope();
-
- LogConfiguration.Setup();
-
- ForceMigration(_kernel.Get());
- SetupDefaultQualityProfiles(_kernel.Get()); //Setup the default QualityProfiles on start-up
-
- BindIndexers();
- BindJobs();
- BindExternalNotifications();
+ _kernel.Bind().ToConstant(Connection.MainDataRepository).InSingletonScope();
+ _kernel.Bind().ToConstant(Connection.LogDataRepository).WhenInjectedInto().InSingletonScope();
+ _kernel.Bind().ToConstant(Connection.LogDataRepository).WhenInjectedInto().InSingletonScope();
}
}
diff --git a/NzbDrone.Core/Datastore/Connection.cs b/NzbDrone.Core/Datastore/Connection.cs
new file mode 100644
index 000000000..e8e7b6a10
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Connection.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using SubSonic.DataProviders;
+using SubSonic.Repository;
+
+namespace NzbDrone.Core.Datastore
+{
+ public static class Connection
+ {
+ private static readonly DirectoryInfo AppDataPath = new DirectoryInfo(Path.Combine(CentralDispatch.AppPath, "App_Data"));
+
+ static Connection()
+ {
+ if (!AppDataPath.Exists) AppDataPath.Create();
+ }
+
+
+ public static String MainConnectionString
+ {
+ get
+ {
+ return String.Format("Data Source={0};Version=3;", Path.Combine(AppDataPath.FullName, "nzbdrone.db"));
+ }
+ }
+
+ public static String LogConnectionString
+ {
+ get
+ {
+ return String.Format("Data Source={0};Version=3;", Path.Combine(AppDataPath.FullName, "log.db"));
+ }
+ }
+
+
+ private static IDataProvider _mainDataProvider;
+ public static IDataProvider MainDataProvider
+ {
+ get
+ {
+ if (_mainDataProvider == null)
+ {
+ _mainDataProvider = ProviderFactory.GetProvider(Connection.MainConnectionString, "System.Data.SQLite");
+ }
+ return _mainDataProvider;
+ }
+
+ }
+
+ private static IDataProvider _logDataProvider;
+ public static IDataProvider LogDataProvider
+ {
+ get
+ {
+ if (_logDataProvider == null)
+ {
+ _logDataProvider = ProviderFactory.GetProvider(Connection.LogConnectionString, "System.Data.SQLite");
+ }
+ return _logDataProvider;
+ }
+
+ }
+
+
+ private static SimpleRepository _mainDataRepository;
+ public static SimpleRepository MainDataRepository
+ {
+ get
+ {
+ if (_mainDataRepository == null)
+ {
+ _mainDataRepository = new SimpleRepository(MainDataProvider, SimpleRepositoryOptions.RunMigrations);
+ }
+
+ return _mainDataRepository;
+ }
+
+ }
+
+ private static SimpleRepository _logDataRepository;
+ public static SimpleRepository LogDataRepository
+ {
+ get
+ {
+ if (_logDataRepository == null)
+ {
+ _logDataRepository = new SimpleRepository(LogDataProvider, SimpleRepositoryOptions.RunMigrations);
+ }
+ return _logDataRepository;
+ }
+
+ }
+
+
+
+ }
+}
diff --git a/NzbDrone.Core/Datastore/MigrationLogger.cs b/NzbDrone.Core/Datastore/MigrationLogger.cs
new file mode 100644
index 000000000..f9adaa38e
--- /dev/null
+++ b/NzbDrone.Core/Datastore/MigrationLogger.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using Migrator.Framework;
+using NLog;
+
+namespace NzbDrone.Core.Datastore
+{
+ class MigrationLogger : ILogger
+ {
+
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ public void Started(List currentVersion, long finalVersion)
+ {
+ Logger.Info("Starting Datastore migration {0} -> {1}", String.Join(",", currentVersion), finalVersion);
+ }
+
+ public void MigrateUp(long version, string migrationName)
+ {
+ Logger.Info("Starting MigrateUp {0} [{1}]", version, migrationName);
+ }
+
+ public void MigrateDown(long version, string migrationName)
+ {
+ Logger.Info("Starting MigrateDown {0} [{1}]", version, migrationName);
+ }
+
+ public void Skipping(long version)
+ {
+ Logger.Info("Skipping MigrateDown {0}", version);
+ }
+
+ public void RollingBack(long originalVersion)
+ {
+ Logger.Info("Rolling Back to {0}", originalVersion);
+ }
+
+ public void ApplyingDBChange(string sql)
+ {
+ Logger.Info("Applying DB Change {0}", sql);
+ }
+
+ public void Exception(long version, string migrationName, Exception ex)
+ {
+ Logger.ErrorException(migrationName + " " + version, ex);
+ }
+
+ public void Exception(string message, Exception ex)
+ {
+ Logger.ErrorException(message, ex);
+ }
+
+ public void Finished(List currentVersion, long finalVersion)
+ {
+ Logger.Info("Finished Datastore migration {0} -> {1}", String.Join(",", currentVersion), finalVersion);
+ }
+
+ public void Log(string format, params object[] args)
+ {
+ Logger.Info(format, args);
+ }
+
+ public void Warn(string format, params object[] args)
+ {
+ Logger.Warn(format, args);
+ }
+
+ public void Trace(string format, params object[] args)
+ {
+ Logger.Trace(format, args);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/Migrations.cs b/NzbDrone.Core/Datastore/Migrations.cs
new file mode 100644
index 000000000..a3e27791a
--- /dev/null
+++ b/NzbDrone.Core/Datastore/Migrations.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Migrator.Framework;
+using NLog;
+using SubSonic.Extensions;
+using SubSonic.Schema;
+
+namespace NzbDrone.Core.Datastore
+{
+ public class Migrations
+ {
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ public static void Run()
+ {
+ Logger.Info("Preparing to migrate databse");
+
+ try
+ {
+ var mig = new Migrator.Migrator("Sqlite", Connection.MainConnectionString,
+ Assembly.GetAssembly(typeof(Migrations)), true, new MigrationLogger());
+
+ mig.MigrateToLastVersion();
+
+ Logger.Info("Database migration completed");
+ }
+ catch (Exception e)
+ {
+ Logger.FatalException("An error has occured while migrating database", e);
+ }
+ }
+
+
+ public static void RemoveDeletedColumns(ITransformationProvider transformationProvider)
+ {
+ var provider = new RepositoryProvider();
+ var repoTypes = provider.GetRepositoryTypes();
+
+ foreach (var repoType in repoTypes)
+ {
+ var typeSchema = provider.GetSchemaFromType(repoType);
+ var dbColumns = provider.GetColumnsFromDatabase(Connection.MainConnectionString, typeSchema.Name);
+
+ var deletedColumns = provider.GetDeletedColumns(typeSchema, dbColumns);
+
+ foreach (var deletedColumn in deletedColumns)
+ {
+ Logger.Info("Removing column '{0}' from '{1}'", deletedColumn.Name, repoType.Name);
+ transformationProvider.RemoveColumn(typeSchema.Name, deletedColumn.Name);
+ }
+
+ }
+
+ }
+
+ public static void AddNewColumns(ITransformationProvider transformationProvider)
+ {
+ var provider = new RepositoryProvider();
+ var repoTypes = provider.GetRepositoryTypes();
+
+ foreach (var repoType in repoTypes)
+ {
+ var typeSchema = provider.GetSchemaFromType(repoType);
+ var dbColumns = provider.GetColumnsFromDatabase(Connection.MainConnectionString, typeSchema.Name);
+
+ var newColumns = provider.GetNewColumns(typeSchema, dbColumns);
+
+ foreach (var newColumn in newColumns)
+ {
+ Logger.Info("Adding column '{0}' to '{1}'", newColumn.Name, repoType.Name);
+ transformationProvider.AddColumn(typeSchema.Name, newColumn);
+ }
+
+ }
+
+ }
+
+ }
+
+ [Migration(20110523)]
+ public class Migration20110523 : Migration
+ {
+ public override void Up()
+ {
+ Migrations.RemoveDeletedColumns(Database);
+ Migrations.AddNewColumns(Database);
+ }
+
+ public override void Down()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/NzbDrone.Core/Datastore/RepositoryProvider.cs b/NzbDrone.Core/Datastore/RepositoryProvider.cs
new file mode 100644
index 000000000..c4cc3da67
--- /dev/null
+++ b/NzbDrone.Core/Datastore/RepositoryProvider.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using Migrator.Providers;
+using Migrator.Providers.SQLite;
+using SubSonic.DataProviders;
+using SubSonic.Extensions;
+using SubSonic.Schema;
+using Migrator.Framework;
+
+
+namespace NzbDrone.Core.Datastore
+{
+ public class RepositoryProvider
+ {
+ public virtual IList GetRepositoryTypes()
+ {
+ var coreAssembly = Assembly.GetExecutingAssembly();
+ var repoTypes = coreAssembly.GetTypes().Where(t => !String.IsNullOrWhiteSpace(t.Namespace) && t.Namespace.StartsWith("NzbDrone.Core.Repository"));
+
+ repoTypes = repoTypes.Where(r => !r.IsEnum);
+ return repoTypes.ToList();
+ }
+
+ public virtual ITable GetSchemaFromType(Type type)
+ {
+ return type.ToSchemaTable(Connection.MainDataProvider);
+ }
+
+ public virtual Column[] GetColumnsFromDatabase(string connectionString, string tableName)
+ {
+ var dialact = new SQLiteDialect();
+ var mig = new SQLiteTransformationProvider(dialact, connectionString);
+
+ return mig.GetColumns(tableName);
+ }
+
+
+ public virtual List GetDeletedColumns(ITable typeSchema, Column[] dbColumns)
+ {
+ var deleteColumns = new List();
+ foreach (var dbColumn in dbColumns)
+ {
+ if (!typeSchema.Columns.ToList().Exists(c => c.Name == dbColumn.Name.Trim('[', ']')))
+ {
+ deleteColumns.Add(dbColumn);
+ }
+ }
+
+ return deleteColumns;
+ }
+
+
+ public virtual List GetNewColumns(ITable typeSchema, Column[] dbColumns)
+ {
+ var newColumns = new List();
+ foreach (var typeColumn in typeSchema.Columns)
+ {
+ if (!dbColumns.ToList().Exists(c => c.Name.Trim('[', ']') == typeColumn.Name))
+ {
+ newColumns.Add(ConvertToMigratorColumn(typeColumn));
+ }
+ }
+
+ return newColumns;
+ }
+
+ public virtual Column ConvertToMigratorColumn(SubSonic.Schema.IColumn subsonicColumns)
+ {
+ var migColumn = new Column(subsonicColumns.Name, subsonicColumns.DataType);
+
+ if (subsonicColumns.IsPrimaryKey)
+ {
+ migColumn.ColumnProperty = ColumnProperty.PrimaryKey;
+ }
+
+ if (subsonicColumns.IsNullable)
+ {
+ migColumn.ColumnProperty = migColumn.ColumnProperty | ColumnProperty.Null;
+ }
+ else
+ {
+ migColumn.ColumnProperty = migColumn.ColumnProperty | ColumnProperty.NotNull;
+ migColumn.DefaultValue = false;
+ }
+
+ return migColumn;
+ }
+ }
+}
diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj
index 3b2e156cf..5d9c058b9 100644
--- a/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/NzbDrone.Core/NzbDrone.Core.csproj
@@ -128,13 +128,16 @@
False
Libraries\Exceptioneer.WindowsFormsClient.dll
-
+
+ False
..\packages\MigratorDotNet.0.9.0.28138\lib\Net40\Migrator.dll
-
+
+ False
..\packages\MigratorDotNet.0.9.0.28138\lib\Net40\Migrator.Framework.dll
-
+
+ False
..\packages\MigratorDotNet.0.9.0.28138\lib\Net40\Migrator.Providers.dll
@@ -162,6 +165,10 @@
+
+
+
+
@@ -269,7 +276,6 @@
-