diff --git a/src/Umbraco.Core/Components/Components.cs b/src/Umbraco.Core/Components/Components.cs index d4d9226f9a..55ed26a7bd 100644 --- a/src/Umbraco.Core/Components/Components.cs +++ b/src/Umbraco.Core/Components/Components.cs @@ -35,13 +35,14 @@ namespace Umbraco.Core.Components _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + internal T Get() => _components.OfType().SingleOrDefault(); + private class EnableInfo { public bool Enabled; public int Weight = -1; } - public void Compose() { var orderedComponentTypes = PrepareComponentTypes(); diff --git a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs index 1a445ba978..7217f551e9 100644 --- a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs +++ b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs @@ -16,6 +16,7 @@ namespace Umbraco.Core.Composing.Composers composition.RegisterUnique(factory => factory.GetInstance().Content); composition.RegisterUnique(factory => factory.GetInstance().Templates); composition.RegisterUnique(factory => factory.GetInstance().RequestHandler); + composition.RegisterUnique(factory => factory.GetInstance().Security); composition.RegisterUnique(factory => factory.GetInstance().Global()); composition.RegisterUnique(factory => factory.GetInstance().Dashboards()); composition.RegisterUnique(factory => factory.GetInstance().HealthChecks()); diff --git a/src/Umbraco.Core/Logging/MessageTemplates.cs b/src/Umbraco.Core/Logging/MessageTemplates.cs index 194d47c339..47de1230ff 100644 --- a/src/Umbraco.Core/Logging/MessageTemplates.cs +++ b/src/Umbraco.Core/Logging/MessageTemplates.cs @@ -1,6 +1,10 @@ using System; +using System.IO; using System.Linq; +using System.Text; using Serilog; +using Serilog.Events; +using Serilog.Parsing; namespace Umbraco.Core.Logging { @@ -29,7 +33,23 @@ namespace Umbraco.Core.Logging if (!bound) throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args."); - return parsedTemplate.Render(boundProperties.ToDictionary(x => x.Name, x => x.Value)); + var values = boundProperties.ToDictionary(x => x.Name, x => x.Value); + + // this ends up putting every string parameter between quotes + //return parsedTemplate.Render(values); + + // this does not + var tw = new StringWriter(); + foreach (var t in parsedTemplate.Tokens) + { + if (t is PropertyToken pt && + values.TryGetValue(pt.PropertyName, out var propVal) && + (propVal as ScalarValue)?.Value is string s) + tw.Write(s); + else + t.Render(values, tw); + } + return tw.ToString(); } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index f5e3892f5d..341469a696 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -120,7 +120,7 @@ namespace Umbraco.Core.Migrations.Install #region Configure Connection String - private const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; + public const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; /// /// Configures a connection string for the embedded database. diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index 64be8161f2..b46cbb5b96 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -432,62 +432,57 @@ namespace Umbraco.Core.Migrations.Install if (tableExist == false) { - using (var transaction = _database.GetTransaction()) + //Execute the Create Table sql + var created = _database.Execute(new Sql(createSql)); + _logger.Info("Create Table '{TableName}' ({Created}): \n {Sql}", tableName, created, createSql); + + //If any statements exists for the primary key execute them here + if (string.IsNullOrEmpty(createPrimaryKeySql) == false) { - //Execute the Create Table sql - var created = _database.Execute(new Sql(createSql)); - _logger.Info("Create Table '{TableName}' ({Created}): \n {Sql}", tableName, created, createSql); + var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); + _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); + } - //If any statements exists for the primary key execute them here - if (string.IsNullOrEmpty(createPrimaryKeySql) == false) - { - var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); - _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); - } + //Turn on identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); - //Turn on identity insert if db provider is not mysql - if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); + //Call the NewTable-event to trigger the insert of base/default data + //OnNewTable(tableName, _db, e, _logger); - //Call the NewTable-event to trigger the insert of base/default data - //OnNewTable(tableName, _db, e, _logger); + dataCreation.InitializeBaseData(tableName); - dataCreation.InitializeBaseData(tableName); + //Turn off identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); - //Turn off identity insert if db provider is not mysql - if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); + //Special case for MySql + if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) + { + _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); + } - //Special case for MySql - if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) - { - _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); - } + //Loop through index statements and execute sql + foreach (var sql in indexSql) + { + var createdIndex = _database.Execute(new Sql(sql)); + _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); + } - //Loop through index statements and execute sql - foreach (var sql in indexSql) - { - var createdIndex = _database.Execute(new Sql(sql)); - _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); - } + //Loop through foreignkey statements and execute sql + foreach (var sql in foreignSql) + { + var createdFk = _database.Execute(new Sql(sql)); + _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); + } - //Loop through foreignkey statements and execute sql - foreach (var sql in foreignSql) - { - var createdFk = _database.Execute(new Sql(sql)); - _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); - } - - transaction.Complete(); - - if (overwrite) - { - _logger.Info("Table '{TableName}' was recreated", tableName); - } - else - { - _logger.Info("New table '{TableName}' was created", tableName); - } + if (overwrite) + { + _logger.Info("Table '{TableName}' was recreated", tableName); + } + else + { + _logger.Info("New table '{TableName}' was created", tableName); } } else diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 8d82b3b940..5e3b8ca00e 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -118,7 +118,7 @@ namespace Umbraco.Core.Runtime AquireMainDom(mainDom); - DetermineRuntimeLevel(databaseFactory); + DetermineRuntimeLevel(databaseFactory, profilingLogger); var componentTypes = ResolveComponentTypes(typeLoader); _components = new Components.Components(composition, componentTypes, profilingLogger); @@ -214,19 +214,19 @@ namespace Umbraco.Core.Runtime } // internal for tests - internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory) + internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) { - using (var timer = ProfilingLogger.DebugDuration("Determining runtime level.", "Determined.")) + using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) { try { - _state.Level = DetermineRuntimeLevel2(databaseFactory); + _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); - ProfilingLogger.Debug("Runtime level: {RuntimeLevel}", _state.Level); + profilingLogger.Debug("Runtime level: {RuntimeLevel}", _state.Level); if (_state.Level == RuntimeLevel.Upgrade) { - ProfilingLogger.Debug("Configure database factory for upgrades."); + profilingLogger.Debug("Configure database factory for upgrades."); databaseFactory.ConfigureForUpgrade(); } } @@ -272,111 +272,6 @@ namespace Umbraco.Core.Runtime // nothing } - private RuntimeLevel DetermineRuntimeLevel2(IUmbracoDatabaseFactory databaseFactory) - { - var localVersion = UmbracoVersion.LocalVersion; // the local, files, version - var codeVersion = _state.SemanticVersion; // the executing code version - var connect = false; - - if (localVersion == null) - { - // there is no local version, we are not installed - Logger.Debug("No local version, need to install Umbraco."); - return RuntimeLevel.Install; - } - - if (localVersion < codeVersion) - { - // there *is* a local version, but it does not match the code version - // need to upgrade - Logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); - } - else if (localVersion > codeVersion) - { - Logger.Warn("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("Database is not configured, need to install Umbraco."); - return RuntimeLevel.Install; - } - - // 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("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("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); - } - catch (Exception e) - { - // can connect to the database but cannot check the upgrade state... oops - Logger.Warn(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 - return RuntimeLevel.Run; - } - - // 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("Has not reached the final upgrade step, need to upgrade Umbraco."); - return RuntimeLevel.Upgrade; - } - - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory) - { - var umbracoPlan = new UmbracoPlan(); - var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan); - - // no scope, no service - just directly accessing the database - using (var database = databaseFactory.CreateDatabase()) - { - _state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); - _state.FinalMigrationState = umbracoPlan.FinalState; - } - - Logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? ""); - - return _state.CurrentMigrationState == _state.FinalMigrationState; - } - #region Getters // getters can be implemented by runtimes inheriting from CoreRuntime diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 82fc41bf5e..4281ad7c42 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -7,6 +7,9 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade; +using Umbraco.Core.Persistence; +using Umbraco.Core.Services.Implement; using Umbraco.Core.Sync; namespace Umbraco.Core @@ -22,7 +25,7 @@ namespace Umbraco.Core private readonly HashSet _applicationUrls = new HashSet(); private readonly Lazy _mainDom; private readonly Lazy _serverRegistrar; - private RuntimeLevel _level; + private RuntimeLevel _level = RuntimeLevel.Unknown; /// /// Initializes a new instance of the class. @@ -125,5 +128,116 @@ namespace Umbraco.Core /// public BootFailedException BootFailedException { get; internal set; } + + /// + /// Determines the runtime level. + /// + 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("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("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); + } + else if (localVersion > codeVersion) + { + logger.Warn("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("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("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("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(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("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("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", CurrentMigrationState, FinalMigrationState ?? ""); + + return CurrentMigrationState == FinalMigrationState; + } } } diff --git a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs index f4a33f735d..47c854333a 100644 --- a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs +++ b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Misc [Test] public void NoApplicationUrlByDefault() { - var state = new RuntimeState(Mock.Of(), Mock.Of(), Mock.Of(), new Lazy(), new Lazy()); + var state = new RuntimeState(Mock.Of(), Mock.Of(), Mock.Of(), new Lazy(), new Lazy()); Assert.IsNull(state.ApplicationUrl); } @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Misc var registrar = new Mock(); registrar.Setup(x => x.GetCurrentServerUmbracoApplicationUrl()).Returns("http://server1.com/umbraco"); - var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => registrar.Object)); + var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => registrar.Object)); state.EnsureApplicationUrl(); @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Misc - var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => Mock.Of())); + var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => Mock.Of())); state.EnsureApplicationUrl(); diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 7dea5bc4e9..9abd1f8aab 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -33,7 +33,7 @@ namespace Umbraco.Tests.Routing var logger = Mock.Of(); var globalSettings = TestObjects.GetGlobalSettings(); var runtime = new RuntimeState(logger, Mock.Of(), globalSettings, - new Lazy(), new Lazy()); + new Lazy(), new Lazy()); _module = new UmbracoInjectedModule ( diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index addf63f55f..e8f9a61607 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -107,12 +107,15 @@ namespace Umbraco.Tests.Runtimes return mock.Object; } + // fixme so how the f* should we do it now? + /* // pretend we have the proper migration // else BootFailedException because our mock IUmbracoDatabaseFactory does not provide databases protected override bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory) { return true; } + */ // because we don't even have the core runtime component, // there are a few required stuff that we need to compose diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 038916f09a..af879ccfcb 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -1,6 +1,11 @@ using System; +using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; using System.Linq; +using System.Reflection; using System.Text; +using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -8,17 +13,210 @@ using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Runtime; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Sync; using Umbraco.Tests.Composing; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Runtime; +using File = System.IO.File; namespace Umbraco.Tests.Runtimes { [TestFixture] public class StandaloneTests { + [Test] + [Explicit("This test must be run manually")] + public void StandaloneTest() + { + IFactory factory = null; + + // clear + foreach (var file in Directory.GetFiles(Path.Combine(IOHelper.MapPath("~/App_Data")), "NuCache.*")) + File.Delete(file); + + // settings + // reset the current version to 0.0.0 + ConfigurationManager.AppSettings["umbracoConfigurationStatus"] = ""; + // fixme we need a better management of settings here (and, true config files?) + + // create the very basic and essential things we need + var logger = new ConsoleLogger(); + var profiler = new LogProfiler(logger); + var profilingLogger = new ProfilingLogger(logger, profiler); + var appCaches = new CacheHelper(); // fixme has HttpRuntime stuff? + var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var mainDom = new SimpleMainDom(); + var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); + + // create the register and the composition + var register = RegisterFactory.Create(); + var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + + // create the core runtime and have it compose itself + var coreRuntime = new CoreRuntime(); + coreRuntime.Compose(composition); + + // determine actual runtime level + runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + // going to be Install BUT we want to force components to be there (nucache etc) + runtimeState.Level = RuntimeLevel.Run; + + var componentTypes = typeLoader.GetTypes() // all of them + .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components + .Where(x => x != typeof(WebRuntimeComponent)); // exclude web runtime + var components = new Core.Components.Components(composition, componentTypes, profilingLogger); + components.Compose(); + + // must registers stuff that WebRuntimeComponent would register otherwise + // fixme UmbracoContext creates a snapshot that it does not register with the accessor + // and so, we have to use the UmbracoContextPublishedSnapshotAccessor + // the UmbracoContext does not know about the accessor + // else that would be a catch-22 where they both know about each other? + //composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(_ => Mock.Of(), Lifetime.Singleton); + composition.RegisterUnique(f => new DistributedCache()); + composition.WithCollectionBuilder().Append(); + + // create and register the factory + Current.Factory = factory = composition.CreateFactory(); + + // initialize some components individually + components.Get().Initialize(factory.GetInstance()); + + // do stuff + Console.WriteLine(runtimeState.Level); + + // install + if (true || runtimeState.Level == RuntimeLevel.Install) + { + var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var file = Path.Combine(path, "Umbraco.sdf"); + if (File.Exists(file)) + File.Delete(file); + + // create the database file + // databaseBuilder.ConfigureEmbeddedDatabaseConnection() can do it too, + // but then it wants to write the connection string to web.config = bad + using (var engine = new SqlCeEngine("Data Source=|DataDirectory|\\Umbraco.sdf;Flush Interval=1;")) + { + engine.CreateDatabase(); + } + + //var databaseBuilder = factory.GetInstance(); + //databaseFactory.Configure(DatabaseBuilder.EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + //databaseBuilder.CreateDatabaseSchemaAndData(); + + databaseFactory.Configure(DatabaseBuilder.EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + + var scopeProvider = factory.GetInstance(); + using (var scope = scopeProvider.CreateScope()) + { + var creator = new DatabaseSchemaCreator(scope.Database, logger); + creator.InitializeDatabaseSchema(); + scope.Complete(); + } + } + + // done installing + runtimeState.Level = RuntimeLevel.Run; + + var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); + SettingsForTests.ConfigureSettings(globalSettings); + var umbracoSettings = SettingsForTests.GetDefaultUmbracoSettings(); + SettingsForTests.ConfigureSettings(umbracoSettings); + + // instantiate to register events + // should be done by Initialize? + // should we invoke Initialize? + _ = factory.GetInstance(); + + // at that point, Umbraco can run! + // though, we probably still need to figure out what depends on HttpContext... + var contentService = factory.GetInstance(); + var content = contentService.GetById(1234); + Assert.IsNull(content); + + // create a document type and a document + var contentType = new ContentType(-1) { Alias = "ctype", Name = "ctype" }; + factory.GetInstance().Save(contentType); + content = new Content("test", -1, contentType); + contentService.Save(content); + + // assert that it is possible to get the document back + content = contentService.GetById(content.Id); + Assert.IsNotNull(content); + Assert.AreEqual("test", content.Name); + + // need an UmbracoCOntext to access the cache + // fixme - not exactly pretty, should not depend on HttpContext + var httpContext = Mock.Of(); + var withUmbracoContext = UmbracoContext.EnsureContext(httpContext); + var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; + + // assert that there is no published document + var pcontent = umbracoContext.ContentCache.GetById(content.Id); + Assert.IsNull(pcontent); + + // but a draft document + pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("test", pcontent.Name); + Assert.IsTrue(pcontent.IsDraft); + + // no published url + Assert.AreEqual("#", pcontent.GetUrl()); + + // now publish the document + make some unpublished changes + contentService.SaveAndPublish(content); + content.Name = "testx"; + contentService.Save(content); + + // assert that snapshot has been updated and there is now a published document + pcontent = umbracoContext.ContentCache.GetById(content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("test", pcontent.Name); + Assert.IsFalse(pcontent.IsDraft); + + // but the url is the published one - no draft url + Assert.AreEqual("/test/", pcontent.GetUrl()); + + // and also an updated draft document + pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("testx", pcontent.Name); + Assert.IsTrue(pcontent.IsDraft); + + // and the published document has a url + Assert.AreEqual("/test/", pcontent.GetUrl()); + + withUmbracoContext.Dispose(); + mainDom.Stop(); + components.Terminate(); + + // exit! + } + [Test] [Explicit("This test must be run manually")] public void ValidateComposition() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index edef545d2a..217efceaf1 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -29,6 +29,11 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Constructor + // fixme ISSUE + // after the current snapshot has been resync-ed + // it's too late for UmbracoContext which has captured previewDefault and stuff into these ctor vars + // but, no, UmbracoContext returns snapshot.Content which comes from elements SO a resync should create a new cache + public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, ICacheProvider snapshotCache, ICacheProvider elementsCache, DomainHelper domainHelper, IGlobalSettings globalSettings, ILocalizationService localizationService) : base(previewDefault) { diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 8ba595a6b5..01d862729b 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -101,11 +101,11 @@ namespace Umbraco.Web /// If an actual current UmbracoContext is already present, the disposable object is null and this method does nothing. /// Otherwise, a temporary, dummy UmbracoContext is created and registered in the accessor. And disposed and removed from the accessor. /// - internal static IDisposable EnsureContext() // keep this internal for now! + internal static IDisposable EnsureContext(HttpContextBase httpContext = null) // keep this internal for now! { if (Composing.Current.UmbracoContext != null) return null; - var httpContext = new HttpContextWrapper(System.Web.HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); + httpContext = httpContext ?? new HttpContextWrapper(System.Web.HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); return EnsureContext( Composing.Current.UmbracoContextAccessor,