/// <summary>
 ///     Initializes a new instance of the <see cref="CreatedPackageSchemaRepository" /> class.
 /// </summary>
 public CreatedPackageSchemaRepository(
     IUmbracoDatabaseFactory umbracoDatabaseFactory,
     IHostingEnvironment hostingEnvironment,
     IOptions <GlobalSettings> globalSettings,
     FileSystems fileSystems,
     IEntityXmlSerializer serializer,
     IDataTypeService dataTypeService,
     ILocalizationService localizationService,
     IFileService fileService,
     IMediaService mediaService,
     IMediaTypeService mediaTypeService,
     IContentService contentService,
     MediaFileManager mediaFileManager,
     IMacroService macroService,
     IContentTypeService contentTypeService,
     string?mediaFolderPath = null,
     string?tempFolderPath  = null)
 {
     _umbracoDatabase           = umbracoDatabaseFactory.CreateDatabase();
     _hostingEnvironment        = hostingEnvironment;
     _fileSystems               = fileSystems;
     _serializer                = serializer;
     _dataTypeService           = dataTypeService;
     _localizationService       = localizationService;
     _fileService               = fileService;
     _mediaService              = mediaService;
     _mediaTypeService          = mediaTypeService;
     _contentService            = contentService;
     _mediaFileManager          = mediaFileManager;
     _macroService              = macroService;
     _contentTypeService        = contentTypeService;
     _xmlParser                 = new PackageDefinitionXmlParser();
     _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages;
     _tempFolderPath            = tempFolderPath ?? Constants.SystemDirectories.TempData + "/PackageFiles";
 }
Example #2
0
 /// <summary>
 /// Initializes a new instance of the <see cref="MigrationContext"/> class.
 /// </summary>
 public MigrationContext(MigrationPlan plan, IUmbracoDatabase?database, ILogger <MigrationContext> logger)
 {
     Plan     = plan;
     Database = database ?? throw new ArgumentNullException(nameof(database));
     Logger   = logger ?? throw new ArgumentNullException(nameof(logger));
     _postMigrations.AddRange(plan.PostMigrationTypes);
 }
        private void ObtainWriteLock()
        {
            IUmbracoDatabase?db = _parent._scopeAccessor.Value.AmbientScope?.Database;

            if (db is null)
            {
                throw new PanicException("Could not find a database");
            }

            if (!db.InTransaction)
            {
                throw new InvalidOperationException(
                          "SqlServerDistributedLockingMechanism requires a transaction to function.");
            }

            if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
            {
                throw new InvalidOperationException(
                          "A transaction with minimum ReadCommitted isolation level is required.");
            }

            const string query =
                @"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id";

            db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");

            var i = db.Execute(query, new { id = LockId });

            if (i == 0)
            {
                // ensure we are actually locking!
                throw new ArgumentException($"LockObject with id={LockId} does not exist.");
            }
        }
Example #4
0
        public async Task <bool> AcquireLockAsync(int millisecondsTimeout)
        {
            if (!_dbFactory.Configured)
            {
                // if we aren't configured then we're in an install state, in which case we have no choice but to assume we can acquire
                return(true);
            }

            _logger.LogDebug("Acquiring lock...");

            var tempId = Guid.NewGuid().ToString();

            IUmbracoDatabase?db = null;

            try
            {
                db = _dbFactory.CreateDatabase();


                _hasTable = db !.HasTable(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue);
                if (!_hasTable)
                {
                    _logger.LogDebug("The DB does not contain the required table so we must be in an install state. We have no choice but to assume we can acquire.");
                    _acquireWhenTablesNotAvailable = true;
                    return(true);
                }

                db !.BeginTransaction(IsolationLevel.Serializable);

                var result = InsertLockRecord(tempId, db); //we change the row to a random Id to signal other MainDom to shutdown
                if (result == RecordPersistenceType.Insert)
                {
                    // if we've inserted, then there was no MainDom so we can instantly acquire

                    InsertLockRecord(_lockId, db); // so update with our appdomain id
                    _logger.LogDebug("Acquired with ID {LockId}", _lockId);
                    return(true);
                }

                // if we've updated, this means there is an active MainDom, now we need to wait to
                // for the current MainDom to shutdown which also requires releasing our write lock
            }
            catch (Exception ex)
            {
                // unexpected
                _logger.LogError(ex, "Unexpected error, cannot acquire MainDom");
                _errorDuringAcquiring = true;
                return(false);
            }
            finally
            {
                db?.CompleteTransaction();
                db?.Dispose();
            }


            return(await WaitForExistingAsync(tempId, millisecondsTimeout));
        }
        // Can always obtain a read lock (snapshot isolation in wal mode)
        // Mostly no-op just check that we didn't end up ReadUncommitted for real.
        private void ObtainReadLock()
        {
            IUmbracoDatabase?db = _parent._scopeAccessor.Value.AmbientScope?.Database;

            if (db is null)
            {
                throw new PanicException("no database was found");
            }

            if (!db.InTransaction)
            {
                throw new InvalidOperationException("SqliteDistributedLockingMechanism requires a transaction to function.");
            }
        }
Example #6
0
    public bool HasSavedPropertyValues(string propertyTypeAlias)
    {
        IUmbracoDatabase?database = _scopeAccessor.AmbientScope?.Database;

        if (database is null)
        {
            throw new InvalidOperationException("A scope is required to query the database");
        }

        Sql <ISqlContext> selectQuery = database.SqlContext.Sql()
                                        .SelectAll()
                                        .From <PropertyTypeDto>("m")
                                        .InnerJoin <PropertyDataDto>("p")
                                        .On <PropertyDataDto, PropertyTypeDto>((left, right) => left.PropertyTypeId == right.Id, "p", "m")
                                        .Where <PropertyTypeDto>(m => m.Alias == propertyTypeAlias, "m");

        Sql <ISqlContext> hasValuesQuery = database.SqlContext.Sql()
                                           .SelectAnyIfExists(selectQuery);

        return(database.ExecuteScalar <bool>(hasValuesQuery));
    }
        // Only one writer is possible at a time
        // lock occurs for entire database as opposed to row/table.
        private void ObtainWriteLock()
        {
            IUmbracoDatabase?db = _parent._scopeAccessor.Value.AmbientScope?.Database;

            if (db is null)
            {
                throw new PanicException("no database was found");
            }

            if (!db.InTransaction)
            {
                throw new InvalidOperationException(
                          "SqliteDistributedLockingMechanism requires a transaction to function.");
            }

            var query = @$ "UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id = {LockId}";

            DbCommand command = db.CreateCommand(db.Connection, CommandType.Text, query);

            // imagine there is an existing writer, whilst elapsed time is < command timeout sqlite will busy loop
            // Important to note that if this value == 0 then Command.DefaultTimeout (30s) is used.
            // Math.Ceiling such that (0 < totalseconds < 1) is rounded up to 1.
            command.CommandTimeout = (int)Math.Ceiling(_timeout.TotalSeconds);

            try
            {
                var i = command.ExecuteNonQuery();

                if (i == 0)
                {
                    // ensure we are actually locking!
                    throw new ArgumentException($"LockObject with id={LockId} does not exist.");
                }
            }
            catch (SqliteException ex) when(ex.IsBusyOrLocked())
            {
                throw new DistributedWriteLockTimeoutException(LockId);
            }
        }
    /// <summary>
    ///     Gets a dictionary of key/values directly from the database, no scope, nothing.
    /// </summary>
    /// <remarks>Used by <see cref="CoreRuntimeBootstrapper" /> to determine the runtime state.</remarks>
    public static IReadOnlyDictionary <string, string?>?GetFromKeyValueTable(
        this IUmbracoDatabase?database,
        string keyPrefix)
    {
        if (database is null)
        {
            return(null);
        }

        // create the wildcard where clause
        ISqlSyntaxProvider sqlSyntax = database.SqlContext.SqlSyntax;
        var whereParam = sqlSyntax.GetStringColumnWildcardComparison(
            sqlSyntax.GetQuotedColumnName("key"),
            0,
            TextColumnType.NVarchar);

        Sql <ISqlContext>?sql = database.SqlContext.Sql()
                                .Select <KeyValueDto>()
                                .From <KeyValueDto>()
                                .Where(whereParam, keyPrefix + sqlSyntax.GetWildcardPlaceholder());

        return(database.Fetch <KeyValueDto>(sql)
               .ToDictionary(x => x.Key, x => x.Value));
    }
Example #9
0
        private bool _disposedValue = false; // To detect redundant calls


        protected virtual void Dispose(bool disposing)
        {
            if (!_disposedValue)
            {
                if (disposing)
                {
                    lock (_locker)
                    {
                        _logger.LogDebug($"{nameof(SqlMainDomLock)} Disposing...");

                        // immediately cancel all sub-tasks, we don't want them to keep querying
                        _cancellationTokenSource.Cancel();
                        _cancellationTokenSource.Dispose();

                        if (_dbFactory.Configured && _hasTable)
                        {
                            IUmbracoDatabase?db = null;
                            try
                            {
                                db = _dbFactory.CreateDatabase();
                                db !.BeginTransaction(IsolationLevel.Serializable);

                                // When we are disposed, it means we have released the MainDom lock
                                // and called all MainDom release callbacks, in this case
                                // if another maindom is actually coming online we need
                                // to signal to the MainDom coming online that we have shutdown.
                                // To do that, we update the existing main dom DB record with a suffixed "_updated" string.
                                // Otherwise, if we are just shutting down, we want to just delete the row.
                                if (_mainDomChanging)
                                {
                                    _logger.LogDebug("Releasing MainDom, updating row, new application is booting.");
                                    var count = db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey });
                                }
                                else
                                {
                                    _logger.LogDebug("Releasing MainDom, deleting row, application is shutting down.");
                                    var count = db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
                                }
                            }
                            catch (Exception ex)
                            {
                                _logger.LogError(ex, "Unexpected error during dipsose.");
                            }
                            finally
                            {
                                try
                                {
                                    db?.CompleteTransaction();
                                    db?.Dispose();
                                }
                                catch (Exception ex)
                                {
                                    _logger.LogError(ex, "Unexpected error during dispose when completing transaction.");
                                }
                            }
                        }
                    }
                }

                _disposedValue = true;
            }
        }
Example #10
0
        private void ListeningLoop()
        {
            while (true)
            {
                // poll every couple of seconds
                // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO
                Thread.Sleep(_globalSettings.Value.MainDomReleaseSignalPollingInterval);

                if (!_dbFactory.Configured)
                {
                    // if we aren't configured, we just keep looping since we can't query the db
                    continue;
                }

                lock (_locker)
                {
                    // If cancellation has been requested we will just exit. Depending on timing of the shutdown,
                    // we will have already flagged _mainDomChanging = true, or we're shutting down faster than
                    // the other MainDom is taking to startup. In this case the db row will just be deleted and the
                    // new MainDom will just take over.
                    if (_cancellationTokenSource.IsCancellationRequested)
                    {
                        _logger.LogDebug("Task canceled, exiting loop");
                        return;
                    }
                    IUmbracoDatabase?db = null;

                    try
                    {
                        db = _dbFactory.CreateDatabase();

                        if (!_hasTable)
                        {
                            // re-check if its still false, we don't want to re-query once we know its there since this
                            // loop needs to use minimal resources
                            _hasTable = db !.HasTable(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue);
                            if (!_hasTable)
                            {
                                // the Db does not contain the required table, we just keep looping since we can't query the db
                                continue;
                            }
                        }

                        // In case we acquired the main dom doing install when there was no database. We therefore have to insert our lockId now, but only handle this once.
                        if (_acquireWhenTablesNotAvailable)
                        {
                            _acquireWhenTablesNotAvailable = false;
                            InsertLockRecord(_lockId, db !);
                        }

                        db !.BeginTransaction(IsolationLevel.Serializable);

                        if (!IsMainDomValue(_lockId, db))
                        {
                            // we are no longer main dom, another one has come online, exit
                            _mainDomChanging = true;
                            _logger.LogDebug("Detected new booting application, releasing MainDom lock.");
                            return;
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Unexpected error during listening.");

                        // We need to keep on listening unless we've been notified by our own AppDomain to shutdown since
                        // we don't want to shutdown resources controlled by MainDom inadvertently. We'll just keep listening otherwise.
                        if (_cancellationTokenSource.IsCancellationRequested)
                        {
                            _logger.LogDebug("Task canceled, exiting loop");
                            return;
                        }
                    }
                    finally
                    {
                        db?.CompleteTransaction();
                        db?.Dispose();
                    }
                }
            }
        }
Example #11
0
 public DatabaseSchemaCreator Create(IUmbracoDatabase?database)
 {
     return(new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion, _eventAggregator, _installDefaultDataSettings));
 }
Example #12
0
        public Task HandleAsync(RuntimeUnattendedInstallNotification notification, CancellationToken cancellationToken)
        {
            // unattended install is not enabled
            if (_unattendedSettings.Value.InstallUnattended == false)
            {
                return(Task.CompletedTask);
            }

            // no connection string set
            if (_databaseFactory.Configured == false)
            {
                return(Task.CompletedTask);
            }

            _runtimeState.DetermineRuntimeLevel();
            if (_runtimeState.Reason == RuntimeLevelReason.InstallMissingDatabase)
            {
                _dbProviderFactoryCreator.CreateDatabase(_databaseFactory.ProviderName !, _databaseFactory.ConnectionString !);
            }

            bool connect;

            try
            {
                for (var i = 0; ;)
                {
                    connect = _databaseFactory.CanConnect;
                    if (connect || ++i == 5)
                    {
                        break;
                    }

                    _logger.LogDebug("Could not immediately connect to database, trying again.");

                    Thread.Sleep(1000);
                }
            }
            catch (Exception ex)
            {
                _logger.LogInformation(ex, "Error during unattended install.");

                var innerException = new UnattendedInstallException("Unattended installation failed.", ex);
                _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException);
                return(Task.CompletedTask);
            }

            // could not connect to the database
            if (connect == false)
            {
                return(Task.CompletedTask);
            }

            IUmbracoDatabase?database = null;

            try
            {
                using (database = _databaseFactory.CreateDatabase())
                {
                    var hasUmbracoTables = database?.IsUmbracoInstalled() ?? false;

                    // database has umbraco tables, assume Umbraco is already installed
                    if (hasUmbracoTables)
                    {
                        return(Task.CompletedTask);
                    }

                    // all conditions fulfilled, do the install
                    _logger.LogInformation("Starting unattended install.");

                    database?.BeginTransaction();
                    DatabaseSchemaCreator creator = _databaseSchemaCreatorFactory.Create(database);
                    creator.InitializeDatabaseSchema();
                    database?.CompleteTransaction();
                    _logger.LogInformation("Unattended install completed.");

                    // Emit an event with EventAggregator that unattended install completed
                    // Then this event can be listened for and create an unattended user
                    _eventAggregator.Publish(new UnattendedInstallNotification());
                }
            }
            catch (Exception ex)
            {
                _logger.LogInformation(ex, "Error during unattended install.");
                database?.AbortTransaction();

                var innerException = new UnattendedInstallException(
                    "The database configuration failed."
                    + "\n Please check log file for additional information (can be found in '/Umbraco/Data/Logs/')",
                    ex);

                _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException);
            }

            return(Task.CompletedTask);
        }