2017-07-20 11:21:28 +02:00
using System ;
2018-03-21 09:06:32 +01:00
using System.Collections.Generic ;
2016-09-01 19:06:08 +02:00
using System.Threading ;
using System.Web ;
using Semver ;
using Umbraco.Core.Configuration ;
using Umbraco.Core.Configuration.UmbracoSettings ;
2017-05-31 15:25:07 +02:00
using Umbraco.Core.Exceptions ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Logging ;
2018-12-17 18:52:43 +01:00
using Umbraco.Core.Migrations.Upgrade ;
using Umbraco.Core.Persistence ;
using Umbraco.Core.Services.Implement ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Sync ;
namespace Umbraco.Core
{
/// <summary>
/// Represents the state of the Umbraco runtime.
/// </summary>
internal class RuntimeState : IRuntimeState
{
private readonly ILogger _logger ;
2018-05-01 10:39:04 +10:00
private readonly IUmbracoSettingsSection _settings ;
private readonly IGlobalSettings _globalSettings ;
2018-03-21 09:06:32 +01:00
private readonly HashSet < string > _applicationUrls = new HashSet < string > ( ) ;
2018-12-13 10:53:50 +01:00
private readonly Lazy < IMainDom > _mainDom ;
2018-11-28 12:59:40 +01:00
private readonly Lazy < IServerRegistrar > _serverRegistrar ;
2016-09-01 19:06:08 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeState"/> class.
/// </summary>
2018-11-26 16:54:32 +01:00
public RuntimeState ( ILogger logger , IUmbracoSettingsSection settings , IGlobalSettings globalSettings ,
2018-12-13 10:53:50 +01:00
Lazy < IMainDom > mainDom , Lazy < IServerRegistrar > serverRegistrar )
2016-09-01 19:06:08 +02:00
{
_logger = logger ;
2018-05-01 10:39:04 +10:00
_settings = settings ;
_globalSettings = globalSettings ;
2018-11-26 16:54:32 +01:00
_mainDom = mainDom ;
_serverRegistrar = serverRegistrar ;
2016-09-01 19:06:08 +02:00
}
2018-11-26 16:54:32 +01:00
/// <summary>
/// Gets the server registrar.
/// </summary>
/// <remarks>
/// <para>This is NOT exposed in the interface.</para>
/// </remarks>
2016-09-01 19:06:08 +02:00
private IServerRegistrar ServerRegistrar = > _serverRegistrar . Value ;
/// <summary>
/// Gets the application MainDom.
/// </summary>
2018-11-26 16:54:32 +01:00
/// <remarks>
/// <para>This is NOT exposed in the interface as MainDom is internal.</para>
/// </remarks>
2018-12-13 10:53:50 +01:00
public IMainDom MainDom = > _mainDom . Value ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public Version Version = > UmbracoVersion . Current ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2018-11-15 08:47:47 +01:00
public string VersionComment = > UmbracoVersion . Comment ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public SemVersion SemanticVersion = > UmbracoVersion . SemanticVersion ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public bool Debug { get ; } = GlobalSettings . DebugMode ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public bool IsMainDom = > MainDom . IsMainDom ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public ServerRole ServerRole = > ServerRegistrar . GetCurrentServerRole ( ) ;
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public Uri ApplicationUrl { get ; private set ; }
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2016-09-01 19:06:08 +02:00
public string ApplicationVirtualPath { get ; } = HttpRuntime . AppDomainAppVirtualPath ;
2018-03-29 11:31:33 +02:00
/// <inheritdoc />
public string CurrentMigrationState { get ; internal set ; }
/// <inheritdoc />
public string FinalMigrationState { get ; internal set ; }
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-01-30 08:35:19 +01:00
public RuntimeLevel Level { get ; internal set ; } = RuntimeLevel . Unknown ;
2016-09-01 19:06:08 +02:00
2019-01-10 14:03:25 +01:00
/// <inheritdoc />
2019-01-30 08:35:19 +01:00
public RuntimeLevelReason Reason { get ; internal set ; } = RuntimeLevelReason . Unknown ;
2019-01-10 14:03:25 +01:00
2016-09-01 19:06:08 +02:00
/// <summary>
/// Ensures that the <see cref="ApplicationUrl"/> property has a value.
/// </summary>
/// <param name="request"></param>
2018-05-01 10:39:04 +10:00
internal void EnsureApplicationUrl ( HttpRequestBase request = null )
2016-09-01 19:06:08 +02:00
{
2018-03-21 09:06:32 +01:00
// see U4-10626 - in some cases we want to reset the application url
// (this is a simplified version of what was in 7.x)
// note: should this be optional? is it expensive?
2018-05-01 10:39:04 +10:00
var url = request = = null ? null : ApplicationUrlHelper . GetApplicationUrlFromCurrentRequest ( request , _globalSettings ) ;
2018-03-21 09:06:32 +01:00
var change = url ! = null & & ! _applicationUrls . Contains ( url ) ;
if ( change )
{
2018-09-11 08:44:58 +01:00
_logger . Info ( typeof ( ApplicationUrlHelper ) , "New url {Url} detected, re-discovering application url." , url ) ;
2018-03-21 09:06:32 +01:00
_applicationUrls . Add ( url ) ;
}
if ( ApplicationUrl ! = null & & ! change ) return ;
2018-05-01 10:39:04 +10:00
ApplicationUrl = new Uri ( ApplicationUrlHelper . GetApplicationUrl ( _logger , _globalSettings , _settings , ServerRegistrar , request ) ) ;
2016-09-01 19:06:08 +02:00
}
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2017-05-31 15:25:07 +02:00
public BootFailedException BootFailedException { get ; internal set ; }
2018-12-17 18:52:43 +01:00
/// <summary>
/// Determines the runtime level.
/// </summary>
public void DetermineRuntimeLevel ( IUmbracoDatabaseFactory databaseFactory , ILogger logger )
{
var localVersion = UmbracoVersion . LocalVersion ; // the local, files, version
var codeVersion = SemanticVersion ; // the executing code version
var connect = false ;
if ( localVersion = = null )
{
// there is no local version, we are not installed
logger . Debug < RuntimeState > ( "No local version, need to install Umbraco." ) ;
Level = RuntimeLevel . Install ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . InstallNoVersion ;
2018-12-17 18:52:43 +01:00
return ;
}
if ( localVersion < codeVersion )
{
// there *is* a local version, but it does not match the code version
// need to upgrade
logger . Debug < RuntimeState > ( "Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco." , localVersion , codeVersion ) ;
2019-01-03 09:50:51 +01:00
Level = RuntimeLevel . Upgrade ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . UpgradeOldVersion ;
2018-12-17 18:52:43 +01:00
}
else if ( localVersion > codeVersion )
{
logger . Warn < RuntimeState > ( "Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported." , localVersion , codeVersion ) ;
// in fact, this is bad enough that we want to throw
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . BootFailedCannotDowngrade ;
2018-12-17 18:52:43 +01:00
throw new BootFailedException ( $"Local version \" { localVersion } \ " > code version \"{codeVersion}\", downgrading is not supported." ) ;
}
else if ( databaseFactory . Configured = = false )
{
// local version *does* match code version, but the database is not configured
2019-01-23 21:22:49 +01:00
// install - may happen with Deploy/Cloud/etc
2018-12-17 18:52:43 +01:00
logger . Debug < RuntimeState > ( "Database is not configured, need to install Umbraco." ) ;
Level = RuntimeLevel . Install ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . InstallNoDatabase ;
2018-12-17 18:52:43 +01:00
return ;
}
// else, keep going,
// anything other than install wants a database - see if we can connect
// (since this is an already existing database, assume localdb is ready)
2019-01-23 21:22:49 +01:00
var tries = RuntimeOptions . InstallMissingDatabase ? 2 : 5 ;
2019-01-10 14:03:25 +01:00
for ( var i = 0 ; ; )
2018-12-17 18:52:43 +01:00
{
connect = databaseFactory . CanConnect ;
2019-01-10 14:03:25 +01:00
if ( connect | | + + i = = tries ) break ;
2018-12-17 18:52:43 +01:00
logger . Debug < RuntimeState > ( "Could not immediately connect to database, trying again." ) ;
Thread . Sleep ( 1000 ) ;
}
if ( connect = = false )
{
// cannot connect to configured database, this is bad, fail
logger . Debug < RuntimeState > ( "Could not connect to database." ) ;
2019-01-23 21:22:49 +01:00
if ( RuntimeOptions . InstallMissingDatabase )
2019-01-10 14:03:25 +01:00
{
// ok to install on a configured but missing database
Level = RuntimeLevel . Install ;
Reason = RuntimeLevelReason . InstallMissingDatabase ;
return ;
}
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason . BootFailedCannotConnectToDatabase ;
2018-12-17 18:52:43 +01:00
throw new BootFailedException ( "A connection string is configured but Umbraco could not connect to the database." ) ;
}
// if we already know we want to upgrade,
// still run EnsureUmbracoUpgradeState to get the states
// (v7 will just get a null state, that's ok)
// else
// look for a matching migration entry - bypassing services entirely - they are not 'up' yet
bool noUpgrade ;
try
{
noUpgrade = EnsureUmbracoUpgradeState ( databaseFactory , logger ) ;
}
catch ( Exception e )
{
// can connect to the database but cannot check the upgrade state... oops
logger . Warn < RuntimeState > ( e , "Could not check the upgrade state." ) ;
2019-01-10 14:03:25 +01:00
2019-01-23 21:22:49 +01:00
if ( RuntimeOptions . InstallEmptyDatabase )
2019-01-10 14:03:25 +01:00
{
// ok to install on an empty database
Level = RuntimeLevel . Install ;
Reason = RuntimeLevelReason . InstallEmptyDatabase ;
return ;
}
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason . BootFailedCannotCheckUpgradeState ;
2018-12-17 18:52:43 +01:00
throw new BootFailedException ( "Could not check the upgrade state." , e ) ;
}
2019-01-04 10:29:29 +01:00
// if we already know we want to upgrade, exit here
if ( Level = = RuntimeLevel . Upgrade )
return ;
2018-12-17 18:52:43 +01:00
if ( noUpgrade )
{
// the database version matches the code & files version, all clear, can run
Level = RuntimeLevel . Run ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . Run ;
2018-12-17 18:52:43 +01:00
return ;
}
// the db version does not match... but we do have a migration table
// so, at least one valid table, so we quite probably are installed & need to upgrade
// although the files version matches the code version, the database version does not
// which means the local files have been upgraded but not the database - need to upgrade
logger . Debug < RuntimeState > ( "Has not reached the final upgrade step, need to upgrade Umbraco." ) ;
Level = RuntimeLevel . Upgrade ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . UpgradeMigrations ;
2018-12-17 18:52:43 +01:00
}
protected virtual bool EnsureUmbracoUpgradeState ( IUmbracoDatabaseFactory databaseFactory , ILogger logger )
{
2018-12-21 10:58:38 +01:00
var upgrader = new UmbracoUpgrader ( ) ;
var stateValueKey = upgrader . StateValueKey ;
2018-12-17 18:52:43 +01:00
// no scope, no service - just directly accessing the database
using ( var database = databaseFactory . CreateDatabase ( ) )
{
CurrentMigrationState = KeyValueService . GetValue ( database , stateValueKey ) ;
2018-12-21 10:58:38 +01:00
FinalMigrationState = upgrader . Plan . FinalState ;
2018-12-17 18:52:43 +01:00
}
logger . Debug < RuntimeState > ( "Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}" , CurrentMigrationState , FinalMigrationState ? ? "<null>" ) ;
return CurrentMigrationState = = FinalMigrationState ;
}
2016-09-01 19:06:08 +02:00
}
2017-07-20 11:21:28 +02:00
}