using System; using System.Data.Common; using System.Data.SQLite; using NLog; using Npgsql; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Exceptions; using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Datastore.Migration.Framework; namespace NzbDrone.Core.Datastore { public interface IDbFactory { IDatabase Create(MigrationType migrationType = MigrationType.Main); IDatabase Create(MigrationContext migrationContext); } 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; private readonly IRestoreDatabase _restoreDatabaseService; static DbFactory() { InitializeEnvironment(); TableMapping.Map(); } private static void InitializeEnvironment() { // Speed up sqlite3 initialization since we don't use the config file and can't rely on preloading. Environment.SetEnvironmentVariable("No_Expand", "true"); Environment.SetEnvironmentVariable("No_SQLiteXmlConfigFile", "true"); Environment.SetEnvironmentVariable("No_PreLoadSQLite", "true"); Environment.SetEnvironmentVariable("No_SQLiteFunctions", "true"); } public DbFactory(IMigrationController migrationController, IConnectionStringFactory connectionStringFactory, IDiskProvider diskProvider, IRestoreDatabase restoreDatabaseService) { _migrationController = migrationController; _connectionStringFactory = connectionStringFactory; _diskProvider = diskProvider; _restoreDatabaseService = restoreDatabaseService; } public IDatabase Create(MigrationType migrationType = MigrationType.Main) { return Create(new MigrationContext(migrationType)); } public IDatabase Create(MigrationContext migrationContext) { string connectionString; switch (migrationContext.MigrationType) { case MigrationType.Main: { connectionString = _connectionStringFactory.MainDbConnectionString; CreateMain(connectionString, migrationContext); break; } case MigrationType.Log: { connectionString = _connectionStringFactory.LogDbConnectionString; CreateLog(connectionString, migrationContext); break; } default: { throw new ArgumentException("Invalid MigrationType"); } } var db = new Database(migrationContext.MigrationType.ToString(), () => { DbConnection conn; if (connectionString.Contains(".db")) { conn = SQLiteFactory.Instance.CreateConnection(); conn.ConnectionString = connectionString; } else { conn = new NpgsqlConnection(connectionString); } conn.Open(); return conn; }); return db; } private void CreateMain(string connectionString, MigrationContext migrationContext) { try { _restoreDatabaseService.Restore(); _migrationController.Migrate(connectionString, migrationContext); } catch (SQLiteException e) { var fileName = _connectionStringFactory.GetDatabasePath(connectionString); if (OsInfo.IsOsx) { throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/lidarr/faq#i-use-lidarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName); } throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/lidarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName); } catch (Exception e) { throw new LidarrStartupException(e, "Error creating main database"); } } private void CreateLog(string connectionString, MigrationContext migrationContext) { try { _migrationController.Migrate(connectionString, migrationContext); } catch (SQLiteException e) { var fileName = _connectionStringFactory.GetDatabasePath(connectionString); Logger.Error(e, "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); } catch (Exception e) { throw new LidarrStartupException(e, "Error creating log database"); } } } }