From 857d661ff18de5a45ae7552d73a5a98bd1f98ebc Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 19 Jul 2016 18:57:36 -0700 Subject: [PATCH] Fixed: Recreate log database if migration fails Fixes: #1050 --- .../Datastore/ConnectionStringFactory.cs | 8 +++ .../Datastore/CorruptDatabaseException.cs | 24 +++++++++ src/NzbDrone.Core/Datastore/DbFactory.cs | 52 +++++++++++++++++-- .../Framework/MigrationController.cs | 23 +++++--- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 5 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs diff --git a/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs b/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs index fc1c72a94..522b72e94 100644 --- a/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs +++ b/src/NzbDrone.Core/Datastore/ConnectionStringFactory.cs @@ -9,6 +9,7 @@ public interface IConnectionStringFactory { string MainDbConnectionString { get; } string LogDbConnectionString { get; } + string GetDatabasePath(string connectionString); } public class ConnectionStringFactory : IConnectionStringFactory @@ -22,6 +23,13 @@ public ConnectionStringFactory(IAppFolderInfo appFolderInfo) public string MainDbConnectionString { get; private set; } public string LogDbConnectionString { get; private set; } + public string GetDatabasePath(string connectionString) + { + var connectionBuilder = new SQLiteConnectionStringBuilder(connectionString); + + return connectionBuilder.DataSource; + } + private static string GetConnectionString(string dbPath) { var connectionBuilder = new SQLiteConnectionStringBuilder(); diff --git a/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs b/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs new file mode 100644 index 000000000..1d8b6696d --- /dev/null +++ b/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs @@ -0,0 +1,24 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Datastore +{ + public class CorruptDatabaseException : NzbDroneException + { + public CorruptDatabaseException(string message, params object[] args) : base(message, args) + { + } + + public CorruptDatabaseException(string message) : base(message) + { + } + + public CorruptDatabaseException(string message, Exception innerException, params object[] args) : base(message, innerException, args) + { + } + + public CorruptDatabaseException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index 7f3476cf6..f85330226 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -1,11 +1,14 @@ using System; using System.Data.SQLite; +using System.IO; using Marr.Data; using Marr.Data.Reflection; +using NLog; using NzbDrone.Common.Composition; +using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Datastore.Migration.Framework; -using NzbDrone.Core.Instrumentation; -using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Datastore @@ -18,8 +21,10 @@ public interface IDbFactory public class DbFactory : IDbFactory { + private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DbFactory)); private readonly IMigrationController _migrationController; private readonly IConnectionStringFactory _connectionStringFactory; + private readonly IDiskProvider _diskProvider; static DbFactory() { @@ -38,10 +43,13 @@ public static void RegisterDatabase(IContainer container) container.Register(logDb); } - public DbFactory(IMigrationController migrationController, IConnectionStringFactory connectionStringFactory) + public DbFactory(IMigrationController migrationController, + IConnectionStringFactory connectionStringFactory, + IDiskProvider diskProvider) { _migrationController = migrationController; _connectionStringFactory = connectionStringFactory; + _diskProvider = diskProvider; } public IDatabase Create(MigrationType migrationType = MigrationType.Main) @@ -72,7 +80,43 @@ public IDatabase Create(MigrationContext migrationContext) } } - _migrationController.Migrate(connectionString, migrationContext); + try + { + _migrationController.Migrate(connectionString, migrationContext); + } + catch (SQLiteException ex) + { + var fileName = _connectionStringFactory.GetDatabasePath(connectionString); + + if (migrationContext.MigrationType == MigrationType.Log) + { + Logger.Error(ex, "Logging database is corrupt, attempting to recreate it automatically"); + + try + { + _diskProvider.DeleteFile(fileName + "-shm"); + _diskProvider.DeleteFile(fileName + "-wal"); + _diskProvider.DeleteFile(fileName + "-journal"); + _diskProvider.DeleteFile(fileName); + } + catch (Exception) + { + Logger.Error("Unable to recreate logging database automatically. It will need to be removed manually."); + } + + _migrationController.Migrate(connectionString, migrationContext); + } + + else + { + if (OsInfo.IsOsx) + { + throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-use-sonarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", ex, fileName); + } + + throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", ex, fileName); + } + } var db = new Database(migrationContext.MigrationType.ToString(), () => { diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index f7ef7a896..793725e9f 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -1,4 +1,4 @@ -using System; +using System.Data.SQLite; using System.Diagnostics; using System.Reflection; using FluentMigrator.Runner; @@ -37,16 +37,27 @@ public void Migrate(string connectionString, MigrationContext migrationContext) var options = new MigrationOptions { PreviewOnly = false, Timeout = 60 }; var factory = new NzbDroneSqliteProcessorFactory(); var processor = factory.Create(connectionString, _announcer, options); - var runner = new MigrationRunner(assembly, runnerContext, processor); - if (migrationContext.DesiredVersion.HasValue) + try { - runner.MigrateUp(migrationContext.DesiredVersion.Value, true); + var runner = new MigrationRunner(assembly, runnerContext, processor); + + if (migrationContext.DesiredVersion.HasValue) + { + runner.MigrateUp(migrationContext.DesiredVersion.Value, true); + } + else + { + runner.MigrateUp(true); + } } - else + catch (SQLiteException) { - runner.MigrateUp(true); + processor.Dispose(); + SQLiteConnection.ClearAllPools(); + throw; } + sw.Stop(); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 2c7d66ee2..1cb0a1ab7 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -171,6 +171,7 @@ +