diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 72e7b60f7e..5b54f47be6 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -171,15 +171,17 @@ angular.module("umbraco.install").factory('installerService', function($q, $time //turn off loading bar and feedback service.switchToConfiguration(); - }else{ - var desc = getDescriptionForStepAtIndex(service.status.steps, feedback); + } + else { + var desc = getDescriptionForStepName(service.status.steps, response.data.nextStep); if (desc) { - service.status.feedback = desc; + service.status.feedback = desc; } processInstallStep(); } - }else{ + } + else { service.status.done = true; service.status.feedback = "Redirecting you to Umbraco, please wait"; service.status.loading = false; diff --git a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs index 00e30bc228..d14a24fec2 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs @@ -121,179 +121,120 @@ namespace Umbraco.Web.Install.Controllers status = InstallStatusTracker.InitializeFromFile(installModel.InstallId).ToArray(); } - //var queue = new Queue(status); - //while (queue.Count > 0) - //{ - // var stepStatus = queue.Dequeue(); - - // //if it is not complete, then we need to execute it - // if (stepStatus.IsComplete == false) - // { - // var step = InstallHelper.GetAllSteps().Single(x => x.Name == stepStatus.Name); - - // JToken instruction = null; - // if (step.HasUIElement) - // { - // //Since this is a UI instruction, we will extract the model from it - // if (installModel.Instructions.Any(x => x.Key == step.Name) == false) - // { - // throw new HttpResponseException(Request.CreateValidationErrorResponse("No instruction defined for step: " + step.Name)); - // } - // instruction = installModel.Instructions[step.Name]; - // } - - // //If this step doesn't require execution then continue to the next one. - // if (step.RequiresExecution() == false) - // { - // //set this as complete and continue - // InstallStatusTracker.SetComplete(installModel.InstallId, stepStatus.Name, null); - // continue; - // } - - // try - // { - // var setupData = ExecuteStep(step, instruction); - - // //update the status - // InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData != null ? setupData.SavedStepData : null); - - // //get the next step if there is one - - // var nextStep = ""; - // if ((index + 1) < status.Length) - // { - - // nextStep = status[index + 1].Name; - // } - - // //check if there's a custom view to return for this step - // if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false) - // { - // return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel); - // } - - // return new InstallProgressResultModel(false, step.Name, nextStep); - // } - // catch (Exception ex) - // { - // if (ex is TargetInvocationException && ex.InnerException != null) - // { - // ex = ex.InnerException; - // } - - // var installException = ex as InstallException; - // if (installException != null) - // { - // throw new HttpResponseException(Request.CreateValidationErrorResponse(new - // { - // view = installException.View, - // model = installException.ViewModel, - // message = installException.Message - // })); - // } - - // throw new HttpResponseException( - // Request.CreateValidationErrorResponse("An error occurred executing the step: " + step.Name + ". Error: " + ex.Message)); - // } - // } - - //} - - for (var index = 0; index < status.Length; index++) + //create a new queue of the non-finished ones + var queue = new Queue(status.Where(x => x.IsComplete == false)); + while (queue.Count > 0) { - var stepStatus = status[index]; - //if it is not complete, then we need to execute it - if (stepStatus.IsComplete == false) + var stepStatus = queue.Dequeue(); + + var step = InstallHelper.GetAllSteps().Single(x => x.Name == stepStatus.Name); + + JToken instruction = null; + if (step.HasUIElement) { - var step = InstallHelper.GetAllSteps().Single(x => x.Name == stepStatus.Name); - - JToken instruction = null; - if (step.HasUIElement) + //Since this is a UI instruction, we will extract the model from it + if (installModel.Instructions.Any(x => x.Key == step.Name) == false) { - //Since this is a UI instruction, we will extract the model from it - if (installModel.Instructions.Any(x => x.Key == step.Name) == false) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse("No instruction defined for step: " + step.Name)); - } - instruction = installModel.Instructions[step.Name]; + throw new HttpResponseException(Request.CreateValidationErrorResponse("No instruction defined for step: " + step.Name)); + } + instruction = installModel.Instructions[step.Name]; + } + + //If this step doesn't require execution then continue to the next one, this is just a fail-safe check. + if (step.RequiresExecution() == false) + { + //set this as complete and continue + InstallStatusTracker.SetComplete(installModel.InstallId, stepStatus.Name, null); + continue; + } + + try + { + var setupData = ExecuteStep(step, instruction); + + //update the status + InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData != null ? setupData.SavedStepData : null); + + //Determine's the next step in the queue and dequeue's any items that don't need to execute + var nextStep = IterateNextRequiredStep(step, queue, installModel.InstallId); + + //check if there's a custom view to return for this step + if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false) + { + return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel); } - //If this step doesn't require execution then continue to the next one. - if (step.RequiresExecution() == false) + return new InstallProgressResultModel(false, step.Name, nextStep); + } + catch (Exception ex) + { + if (ex is TargetInvocationException && ex.InnerException != null) { - //set this as complete and continue - InstallStatusTracker.SetComplete(installModel.InstallId, stepStatus.Name, null); - continue; + ex = ex.InnerException; } - try + var installException = ex as InstallException; + if (installException != null) { - var setupData = ExecuteStep(step, instruction); - - //update the status - InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData != null ? setupData.SavedStepData : null); - - //get the next step if there is one - var nextStep = ""; - if ((index + 1) < status.Length) + throw new HttpResponseException(Request.CreateValidationErrorResponse(new { - - nextStep = status[index + 1].Name; - } - - //check if there's a custom view to return for this step - if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false) - { - return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel); - } - - return new InstallProgressResultModel(false, step.Name, nextStep); + view = installException.View, + model = installException.ViewModel, + message = installException.Message + })); } - catch (Exception ex) + + throw new HttpResponseException(Request.CreateValidationErrorResponse(new { - if (ex is TargetInvocationException && ex.InnerException != null) - { - ex = ex.InnerException; - } - - //return custom view if we have an install exception - var installException = ex as InstallException; - if (installException != null) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(new - { - step = step.Name, - view = installException.View, - model = installException.ViewModel, - message = installException.Message - })); - } - - throw new HttpResponseException( - Request.CreateValidationErrorResponse("An error occurred executing the step: " + step.Name + ". Error: " + ex.Message)); - //return standard view + step and message to display generic message - return Json(new - { - step = step.Name, - view = "error", - message = ex.Message - }, HttpStatusCode.BadRequest); - - //return Request.CreateValidationErrorResponse("An error occurred executing the step: " + step.Name + ". Error: " + ex.Message); - } + step = step.Name, + view = "error", + message = ex.Message + })); } } InstallStatusTracker.Reset(); return new InstallProgressResultModel(true, "", ""); - - //return Json(new { complete = true }, HttpStatusCode.OK); } - //private InstallSetupStep IterateNextRequiredStep(Queue queue) - //{ - // var step = InstallHelper.GetAllSteps().Single(x => x.Name == stepStatus.Name); - //} + /// + /// We'll peek ahead and check if it's RequiresExecution is returning true. If it + /// is not, we'll dequeue that step and peek ahead again (recurse) + /// + /// + /// + /// + /// + private string IterateNextRequiredStep(InstallSetupStep current, Queue queue, Guid installId) + { + if (queue.Count > 0) + { + var next = queue.Peek(); + var step = InstallHelper.GetAllSteps().Single(x => x.Name == next.Name); + + //If the current step restarts the app pool then we must simply return the next one in the queue, + // we cannot peek ahead as the next step might rely on the app restart and therefore RequiresExecution + // will rely on that too. + //Otherwise if it requires execution then of course return it. + if (current.PerformsAppRestart || step.RequiresExecution()) + { + return step.Name; + } + + //this step no longer requires execution, this could be due to a new config change during installation, + // so we'll dequeue this one from the queue and recurse + queue.Dequeue(); + + //set this as complete + InstallStatusTracker.SetComplete(installId, step.Name, null); + + //recurse + return IterateNextRequiredStep(step, queue, installId); + } + + //there is no more steps + return string.Empty; + } internal InstallSetupResult ExecuteStep(InstallSetupStep step, JToken instruction) { diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index fdfc954d53..b2d6f1e0c5 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -14,7 +14,8 @@ using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall, - "DatabaseConfigure", "database", 10, "Configuring your database connection")] + "DatabaseConfigure", "database", 10, "Configuring your database connection", + PerformsAppRestart = true)] internal class DatabaseConfigureStep : InstallSetupStep { private readonly ApplicationContext _applicationContext; diff --git a/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs b/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs index 3495b70eaf..d2e227a951 100644 --- a/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs @@ -9,7 +9,8 @@ using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "Permissions", 0, "Ensuring your file permissions are set correctly")] + "Permissions", 0, "Ensuring your file permissions are set correctly", + PerformsAppRestart = true)] internal class FilePermissionsStep : InstallSetupStep { public override InstallSetupResult Execute(object model) diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index 3aeda70570..279d53ad66 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -11,7 +11,8 @@ using GlobalSettings = umbraco.GlobalSettings; namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "UmbracoVersion", 50, "Wrapping up the system configuration")] + "UmbracoVersion", 50, "Wrapping up the system configuration", + PerformsAppRestart = true)] internal class SetUmbracoVersionStep : InstallSetupStep { private readonly ApplicationContext _applicationContext; diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs b/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs index 72b57c0ef0..ca53a21225 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs @@ -44,12 +44,6 @@ namespace Umbraco.Web.Install.InstallSteps public override bool RequiresExecution() { - if (InstalledPackage.GetAllInstalledPackages().Count > 0) - return false; - - if (_applicationContext.Services.ContentService.GetRootContent().Any()) - return false; - var installSteps = InstallStatusTracker.GetStatus().ToArray(); //this step relies on the preious one completed - because it has stored some information we need if (installSteps.Any(x => x.Name == "StarterKitDownload") == false) diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs index 0d140d8462..e817f0ddc8 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs @@ -9,7 +9,8 @@ using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall, - "StarterKitInstall", 31, "Installing a starter website to help you get off to a great start")] + "StarterKitInstall", 31, "Installing a starter website to help you get off to a great start", + PerformsAppRestart = true)] internal class StarterKitInstallStep : InstallSetupStep { private readonly ApplicationContext _applicationContext; @@ -45,13 +46,7 @@ namespace Umbraco.Web.Install.InstallSteps } public override bool RequiresExecution() - { - if (InstalledPackage.GetAllInstalledPackages().Count > 0) - return false; - - if (_applicationContext.Services.ContentService.GetRootContent().Any()) - return false; - + { var installSteps = InstallStatusTracker.GetStatus().ToArray(); //this step relies on the preious one completed - because it has stored some information we need if (installSteps.Any(x => x.Name == "StarterKitDownload") == false) diff --git a/src/Umbraco.Web/Install/Models/InstallSetupStep.cs b/src/Umbraco.Web/Install/Models/InstallSetupStep.cs index 37aac64539..2371759196 100644 --- a/src/Umbraco.Web/Install/Models/InstallSetupStep.cs +++ b/src/Umbraco.Web/Install/Models/InstallSetupStep.cs @@ -42,6 +42,7 @@ namespace Umbraco.Web.Install.Models ServerOrder = att.ServerOrder; Description = att.Description; InstallTypeTarget = att.InstallTypeTarget; + PerformsAppRestart = att.PerformsAppRestart; } [DataMember(Name = "name")] @@ -55,6 +56,9 @@ namespace Umbraco.Web.Install.Models [IgnoreDataMember] public InstallationType InstallTypeTarget { get; private set; } + + [IgnoreDataMember] + public bool PerformsAppRestart { get; private set; } /// /// Determines if this step needs to execute based on the current state of the application and/or install process diff --git a/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs b/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs index d083e4733a..3fb99a21bb 100644 --- a/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs +++ b/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs @@ -13,7 +13,8 @@ namespace Umbraco.Web.Install.Models ServerOrder = serverOrder; Description = description; - var r = new Regex("", RegexOptions.Compiled | RegexOptions.Compiled); + //default + PerformsAppRestart = false; } public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, int serverOrder, string description) @@ -23,6 +24,9 @@ namespace Umbraco.Web.Install.Models View = string.Empty; ServerOrder = serverOrder; Description = description; + + //default + PerformsAppRestart = false; } public InstallationType InstallTypeTarget { get; private set; } @@ -30,5 +34,12 @@ namespace Umbraco.Web.Install.Models public string View { get; private set; } public int ServerOrder { get; private set; } public string Description { get; private set; } + + /// + /// A flag to notify the installer that this step performs an app pool restart, this can be handy to know since if the current + /// step is performing a restart, we cannot 'look ahead' to see if the next step can execute since we won't know until the app pool + /// is restarted. + /// + public bool PerformsAppRestart { get; set; } } } \ No newline at end of file