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 ;
2018-12-17 18:52:43 +01:00
private RuntimeLevel _level = RuntimeLevel . Unknown ;
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 />
2016-09-01 19:06:08 +02:00
public RuntimeLevel Level
{
2017-05-31 15:25:07 +02:00
get = > _level ;
2016-09-01 19:06:08 +02:00
internal set { _level = value ; if ( value = = RuntimeLevel . Run ) _runLevel . Set ( ) ; }
}
/// <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
}
private readonly ManualResetEventSlim _runLevel = new ManualResetEventSlim ( false ) ;
/// <summary>
/// Waits for the runtime level to become RuntimeLevel.Run.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <returns>True if the runtime level became RuntimeLevel.Run before the timeout, otherwise false.</returns>
internal bool WaitForRunLevel ( TimeSpan timeout )
{
return _runLevel . WaitHandle . WaitOne ( timeout ) ;
}
2017-05-31 15:25:07 +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 ;
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 ) ;
}
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
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
// install (again? this is a weird situation...)
logger . Debug < RuntimeState > ( "Database is not configured, need to install Umbraco." ) ;
Level = RuntimeLevel . Install ;
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)
for ( var i = 0 ; i < 5 ; i + + )
{
connect = databaseFactory . CanConnect ;
if ( connect ) break ;
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." ) ;
// in fact, this is bad enough that we want to throw
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
// fixme - in a LB scenario, ensure that the DB gets upgraded only once!
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." ) ;
throw new BootFailedException ( "Could not check the upgrade state." , e ) ;
}
if ( noUpgrade )
{
// the database version matches the code & files version, all clear, can run
Level = RuntimeLevel . Run ;
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 ;
}
protected virtual bool EnsureUmbracoUpgradeState ( IUmbracoDatabaseFactory databaseFactory , ILogger logger )
{
var umbracoPlan = new UmbracoPlan ( ) ;
var stateValueKey = Upgrader . GetStateValueKey ( umbracoPlan ) ;
// no scope, no service - just directly accessing the database
using ( var database = databaseFactory . CreateDatabase ( ) )
{
CurrentMigrationState = KeyValueService . GetValue ( database , stateValueKey ) ;
FinalMigrationState = umbracoPlan . FinalState ;
}
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
}