From 0227b800a0b010d0913759dbf2b8763280e0ba34 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 5 Mar 2014 11:34:42 +1100 Subject: [PATCH] Gets the installer steps working nicely and returning the next step to execute which now displays the current step details correctly and dequeues steps that are not required after the previous one has executed. --- .../src/installer/installer.service.js | 10 +- .../Controllers/InstallApiController.cs | 249 +++++++----------- .../InstallSteps/DatabaseConfigureStep.cs | 3 +- .../InstallSteps/FilePermissionsStep.cs | 3 +- .../InstallSteps/SetUmbracoVersionStep.cs | 3 +- .../InstallSteps/StarterKitCleanupStep.cs | 6 - .../InstallSteps/StarterKitInstallStep.cs | 11 +- .../Install/Models/InstallSetupStep.cs | 4 + .../Models/InstallSetupStepAttribute.cs | 13 +- 9 files changed, 126 insertions(+), 176 deletions(-) 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