From 130be58f8c71b614395a677ecaecb75a76801e4c Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 19 Jan 2013 11:42:06 -0800 Subject: [PATCH] added database recovery on app start. --- NzbDrone.Core.Test/CentralDispatchFixture.cs | 19 +-- NzbDrone.Core.Test/Framework/TestDbHelper.cs | 8 +- NzbDrone.Core/ContainerExtentions.cs | 10 +- NzbDrone.Core/Datastore/Connection.cs | 80 ---------- NzbDrone.Core/Datastore/ConnectionFactory.cs | 145 +++++++++++++++++++ NzbDrone.Core/Datastore/MigrationsHelper.cs | 8 +- NzbDrone.Core/NzbDrone.Core.csproj | 2 +- 7 files changed, 162 insertions(+), 110 deletions(-) delete mode 100644 NzbDrone.Core/Datastore/Connection.cs create mode 100644 NzbDrone.Core/Datastore/ConnectionFactory.cs diff --git a/NzbDrone.Core.Test/CentralDispatchFixture.cs b/NzbDrone.Core.Test/CentralDispatchFixture.cs index de64d8c55..a86f49bef 100644 --- a/NzbDrone.Core.Test/CentralDispatchFixture.cs +++ b/NzbDrone.Core.Test/CentralDispatchFixture.cs @@ -5,7 +5,6 @@ using System.Linq; using Autofac; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Common; using NzbDrone.Core.Jobs; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.ExternalNotification; @@ -18,12 +17,12 @@ namespace NzbDrone.Core.Test [TestFixture] class CentralDispatchFixture : CoreTest { - readonly IList indexers = typeof(CentralDispatch).Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(IndexerBase))).ToList(); - readonly IList jobs = typeof(CentralDispatch).Assembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IJob))).ToList(); + readonly IList indexers = typeof(CentralDispatch).Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(IndexerBase))).Select(c => c.ToString()).ToList(); + readonly IList jobs = typeof(CentralDispatch).Assembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IJob))).Select(c=>c.ToString()).ToList(); readonly IList extNotifications = typeof(CentralDispatch).Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(ExternalNotificationBase))).ToList(); readonly IList metadata = typeof(CentralDispatch).Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(MetadataBase))).ToList(); - private IContainer kernel; + private readonly IContainer kernel; public CentralDispatchFixture() { @@ -57,8 +56,6 @@ namespace NzbDrone.Core.Test [Test] public void All_jobs_should_be_registered() { - //Assert - var registeredJobs = kernel.Resolve>(); jobs.Should().NotBeEmpty(); @@ -69,8 +66,6 @@ namespace NzbDrone.Core.Test [Test] public void All_indexers_should_be_registered() { - //Assert - var registeredIndexers = kernel.Resolve>(); indexers.Should().NotBeEmpty(); @@ -81,8 +76,6 @@ namespace NzbDrone.Core.Test [Test] public void All_externalNotifiers_should_be_registered() { - //Assert - var externalNotificationBases = kernel.Resolve>(); extNotifications.Should().NotBeEmpty(); @@ -93,8 +86,6 @@ namespace NzbDrone.Core.Test [Test] public void All_metadata_clients_should_be_registered() { - //Assert - var metadataBases = kernel.Resolve>(); metadata.Should().NotBeEmpty(); @@ -111,7 +102,7 @@ namespace NzbDrone.Core.Test [Test] public void indexers_are_initialized() { - kernel.Resolve().All().Should().HaveSameCount(indexers); + kernel.Resolve().All().Select(c => c.IndexProviderType).Should().BeEquivalentTo(indexers); } [Test] @@ -129,7 +120,7 @@ namespace NzbDrone.Core.Test [Test] public void quality_profile_initialized() { - kernel.Resolve().All().Should().HaveCount(2); + kernel.Resolve().All().Should().HaveCount(4); } [Test] diff --git a/NzbDrone.Core.Test/Framework/TestDbHelper.cs b/NzbDrone.Core.Test/Framework/TestDbHelper.cs index cd56efe4d..36e29301e 100644 --- a/NzbDrone.Core.Test/Framework/TestDbHelper.cs +++ b/NzbDrone.Core.Test/Framework/TestDbHelper.cs @@ -32,9 +32,9 @@ namespace NzbDrone.Core.Test.Framework File.Copy(DB_TEMPLATE_NAME, fileName); - ConnectionString = Connection.GetConnectionString(fileName); + ConnectionString = ConnectionFactory.GetConnectionString(fileName); - var database = Connection.GetPetaPocoDb(ConnectionString); + var database = ConnectionFactory.GetPetaPocoDb(ConnectionString); Console.WriteLine("====================DataBase===================="); Console.WriteLine(); @@ -46,8 +46,8 @@ namespace NzbDrone.Core.Test.Framework internal static void CreateDataBaseTemplate() { Console.WriteLine("Creating an empty PetaPoco database"); - var connectionString = Connection.GetConnectionString(DB_TEMPLATE_NAME); - var database = Connection.GetPetaPocoDb(connectionString); + var connectionString = ConnectionFactory.GetConnectionString(DB_TEMPLATE_NAME); + var database = ConnectionFactory.GetPetaPocoDb(connectionString); database.Dispose(); } } diff --git a/NzbDrone.Core/ContainerExtentions.cs b/NzbDrone.Core/ContainerExtentions.cs index 84a76818a..cc24f1e12 100644 --- a/NzbDrone.Core/ContainerExtentions.cs +++ b/NzbDrone.Core/ContainerExtentions.cs @@ -34,6 +34,8 @@ namespace NzbDrone.Core private static void RegisterAssembly(this ContainerBuilder container, Assembly assembly) { + logger.Info("Registering Services from {0}", assembly.FullName); + container.RegisterAssemblyTypes(assembly) .AsSelf() .SingleInstance(); @@ -58,7 +60,7 @@ namespace NzbDrone.Core .Where(t => t.IsSubclassOf(typeof(MetadataBase))) .As().SingleInstance(); } - + private static void InitDatabase(this ContainerBuilder container) { logger.Info("Registering Database..."); @@ -66,14 +68,14 @@ namespace NzbDrone.Core var appDataPath = new EnvironmentProvider().GetAppDataPath(); if (!Directory.Exists(appDataPath)) Directory.CreateDirectory(appDataPath); - container.Register(c => c.Resolve().GetMainPetaPocoDb()) + container.Register(c => c.Resolve().GetMainPetaPocoDb()) .As(); - container.Register(c => c.Resolve().GetLogPetaPocoDb(false)) + container.Register(c => c.Resolve().GetLogPetaPocoDb(false)) .SingleInstance() .Named("DatabaseTarget"); - container.Register(c => c.Resolve().GetLogPetaPocoDb()) + container.Register(c => c.Resolve().GetLogPetaPocoDb()) .Named("LogProvider"); container.RegisterType().WithParameter(ResolvedParameter.ForNamed("DatabaseTarget")); diff --git a/NzbDrone.Core/Datastore/Connection.cs b/NzbDrone.Core/Datastore/Connection.cs deleted file mode 100644 index aaa16fb27..000000000 --- a/NzbDrone.Core/Datastore/Connection.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Configuration; -using System.Data.Common; -using System.Data.SqlServerCe; -using NzbDrone.Common; -using NzbDrone.Core.Instrumentation; -using PetaPoco; - -namespace NzbDrone.Core.Datastore -{ - public class Connection - { - private readonly EnvironmentProvider _environmentProvider; - - static Connection() - { - Database.Mapper = new CustomeMapper(); - - var dataSet = ConfigurationManager.GetSection("system.data") as System.Data.DataSet; - dataSet.Tables[0].Rows.Add("Microsoft SQL Server Compact Data Provider 4.0" - , "System.Data.SqlServerCe.4.0" - , ".NET Framework Data Provider for Microsoft SQL Server Compact" - , "System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"); - } - - public Connection(EnvironmentProvider environmentProvider) - { - _environmentProvider = environmentProvider; - } - - public String MainConnectionString - { - get - { - return GetConnectionString(_environmentProvider.GetNzbDroneDbFile()); - } - } - - public String LogConnectionString - { - get - { - return GetConnectionString(_environmentProvider.GetLogDbFileDbFile()); - } - } - - public static string GetConnectionString(string path) - { - return String.Format("Data Source=\"{0}\"; Max Database Size = 512;", path); - } - - public IDatabase GetMainPetaPocoDb(Boolean profiled = true) - { - return GetPetaPocoDb(MainConnectionString, profiled); - } - - public IDatabase GetLogPetaPocoDb(Boolean profiled = true) - { - return GetPetaPocoDb(LogConnectionString, profiled); - } - - public static IDatabase GetPetaPocoDb(string connectionString, Boolean profiled = true) - { - MigrationsHelper.Run(connectionString, true); - - var factory = new DbProviderFactory - { - IsProfiled = profiled - }; - - var db = new Database(connectionString, factory, Database.DBType.SqlServerCE) - { - KeepConnectionAlive = true, - ForceDateTimesToUtc = false, - }; - - return db; - } - } -} diff --git a/NzbDrone.Core/Datastore/ConnectionFactory.cs b/NzbDrone.Core/Datastore/ConnectionFactory.cs new file mode 100644 index 000000000..7c03e4933 --- /dev/null +++ b/NzbDrone.Core/Datastore/ConnectionFactory.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; +using NLog; +using NzbDrone.Common; +using PetaPoco; + +namespace NzbDrone.Core.Datastore +{ + public class ConnectionFactory + { + private readonly EnvironmentProvider _environmentProvider; + private static readonly Logger logger = LogManager.GetLogger("ConnectionFactory"); + + static ConnectionFactory() + { + Database.Mapper = new CustomeMapper(); + + var dataSet = (System.Data.DataSet)ConfigurationManager.GetSection("system.data"); + dataSet.Tables[0].Rows.Add("Microsoft SQL Server Compact Data Provider 4.0" + , "System.Data.SqlServerCe.4.0" + , ".NET Framework Data Provider for Microsoft SQL Server Compact" + , "System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"); + } + + public ConnectionFactory(EnvironmentProvider environmentProvider) + { + _environmentProvider = environmentProvider; + } + + public String MainConnectionString + { + get + { + return GetConnectionString(_environmentProvider.GetNzbDroneDbFile()); + } + } + + public String LogConnectionString + { + get + { + return GetConnectionString(_environmentProvider.GetLogDbFileDbFile()); + } + } + + public static string GetConnectionString(string path) + { + return String.Format("Data Source=\"{0}\"; Max Database Size = 512;", path); + } + + public IDatabase GetMainPetaPocoDb(Boolean profiled = true) + { + return GetPetaPocoDb(MainConnectionString, profiled); + } + + public IDatabase GetLogPetaPocoDb(Boolean profiled = true) + { + return GetPetaPocoDb(LogConnectionString, profiled); + } + + + static readonly HashSet initilized = new HashSet(); + + + public static IDatabase GetPetaPocoDb(string connectionString, Boolean profiled = true) + { + lock (initilized) + { + if (!initilized.Contains(connectionString)) + { + VerifyDatabase(connectionString); + MigrationsHelper.Run(connectionString, true); + initilized.Add(connectionString); + } + } + + var factory = new DbProviderFactory + { + IsProfiled = profiled + }; + + var db = new Database(connectionString, factory, Database.DBType.SqlServerCE) + { + KeepConnectionAlive = true, + ForceDateTimesToUtc = false, + }; + + return db; + } + + private static void VerifyDatabase(string connectionString) + { + logger.Debug("Verifying database {0}", connectionString); + + var sqlConnection = new SqlCeConnection(connectionString); + + if (!File.Exists(sqlConnection.Database)) + { + logger.Debug("database file doesn't exist. {0}", sqlConnection.Database); + return; + } + + using (var sqlEngine = new SqlCeEngine(connectionString)) + { + + if (sqlEngine.Verify(VerifyOption.Default)) + { + logger.Debug("Database integrity verified."); + } + else + { + logger.Error("Database verification failed."); + RepairDatabase(connectionString); + } + } + } + + private static void RepairDatabase(string connectionString) + { + logger.Info("Attempting to repair database: {0}", connectionString); + using (var sqlEngine = new SqlCeEngine(connectionString)) + { + try + { + sqlEngine.Repair(connectionString, RepairOption.RecoverAllOrFail); + logger.Info("Recovery was successful without any data loss {0}", connectionString); + } + catch (SqlCeException e) + { + logger.WarnException( + "Safe recovery failed. will attempts a more aggressive strategy. might case loss of data.", + e); + sqlEngine.Repair(connectionString, RepairOption.DeleteCorruptedRows); + logger.Warn("Database was recovered. some data might have been lost"); + + //TODO: do db cleanup to avoid broken relationships. + } + } + } + } +} diff --git a/NzbDrone.Core/Datastore/MigrationsHelper.cs b/NzbDrone.Core/Datastore/MigrationsHelper.cs index 2e1a8196f..6de4814cf 100644 --- a/NzbDrone.Core/Datastore/MigrationsHelper.cs +++ b/NzbDrone.Core/Datastore/MigrationsHelper.cs @@ -11,13 +11,9 @@ namespace NzbDrone.Core.Datastore { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - public static readonly Dictionary _migrated = new Dictionary(); public static void Run(string connetionString, bool trace) { - if (_migrated.ContainsKey(connetionString)) return; - _migrated.Add(connetionString, string.Empty); - EnsureDatabase(connetionString); Logger.Trace("Preparing to run database migration"); @@ -37,9 +33,6 @@ namespace NzbDrone.Core.Datastore migrator.MigrateToLastVersion(); - - //ForceSubSonicMigration(Connection.CreateSimpleRepository(connetionString)); - Logger.Info("Database migration completed"); @@ -47,6 +40,7 @@ namespace NzbDrone.Core.Datastore catch (Exception e) { Logger.FatalException("An error has occurred while migrating database", e); + throw; } } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index aac7024fb..ba8ae9328 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -223,7 +223,7 @@ - +