Updated more installer logic

This commit is contained in:
Shannon
2014-03-04 16:21:45 +11:00
parent 0497771750
commit 71a9fb4f68
22 changed files with 192 additions and 105 deletions

View File

@@ -9,8 +9,11 @@ angular.module("umbraco.install").factory('installerService', function($q, $time
var _installerModel = {
DatabaseConfigure: {dbType: 0},
StarterKitDownload: "69e44beb-15ff-4cee-8b64-0a7dae498657"
installId: undefined,
instructions: {
DatabaseConfigure: { dbType: 0 },
StarterKitDownload: Umbraco.Sys.ServerVariables.defaultStarterKit
}
};
var service = {
@@ -31,6 +34,7 @@ angular.module("umbraco.install").factory('installerService', function($q, $time
service.getSteps().then(function(response){
service.status.steps = response.data.steps;
service.status.index = 0;
_installerModel.installId = response.data.installId;
service.findNextStep();
$timeout(function(){
@@ -57,8 +61,8 @@ angular.module("umbraco.install").factory('installerService', function($q, $time
},
findNextStep : function(){
var step = _.find(service.status.steps, function(step, index){
if(step.view && index >= service.status.index){
var step = _.find(service.status.steps, function(s, index){
if(s.view && index >= service.status.index){
service.status.index = index;
return true;
}
@@ -80,7 +84,7 @@ angular.module("umbraco.install").factory('installerService', function($q, $time
},
storeCurrentStep : function(){
_installerModel[service.status.current.name] = service.status.current.model;
_installerModel.instructions[service.status.current.name] = service.status.current.model;
},
forward : function(){
@@ -100,16 +104,16 @@ angular.module("umbraco.install").factory('installerService', function($q, $time
service.status.feedback = [];
service.status.loading = true;
var _feedback = 0;
var feedback = 0;
service.status.feedback = service.status.steps[0].description;
function processInstallStep(){
$http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostPerformInstall",
_installerModel).then(function(response){
if(!response.data.complete){
_feedback++;
feedback++;
var step = service.status.steps[_feedback];
var step = service.status.steps[feedback];
if(step){
service.status.feedback = step.description;
}

View File

@@ -18,7 +18,7 @@
<img src="assets/img/application/logo_white.png" id="logo" />
<div class="umb-loader" id="loader" ng-if="installer.loading"></div>
@*<div class="umb-loader" id="loader" ng-if="installer.loading"></div>*@
<div id="overlay" ng-animate="'fade'" ng-show="installer.done"></div>

View File

@@ -10,7 +10,7 @@ NOTES:
* Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config
* A new version will invalidate both client and server cache and create new persisted files
-->
<clientDependency version="1801858538" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
<clientDependency version="1722535864" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
<!--
This section is used for Web Forms only, the enableCompositeFiles="true" is optional and by default is set to true.

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Web.Http;
using Newtonsoft.Json;
@@ -42,24 +43,31 @@ namespace Umbraco.Web.Install.Controllers
get { return UmbracoContext.Application; }
}
private InstallHelper _helper;
internal InstallHelper InstallHelper
{
get
{
return _helper ?? (_helper = new InstallHelper(UmbracoContext));
}
}
/// <summary>
/// Gets the install setup
/// </summary>
/// <returns></returns>
public InstallSetup GetSetup()
{
var helper = new InstallHelper(UmbracoContext);
var setup = new InstallSetup()
{
Status = helper.GetStatus()
Status = InstallHelper.GetStatus()
};
//TODO: Check for user/site token
var steps = new List<InstallSetupStep>();
steps.AddRange(helper.GetSteps());
steps.AddRange(InstallHelper.GetSteps().Where(x => x.RequiresExecution()));
setup.Steps = steps;
return setup;
@@ -87,53 +95,52 @@ namespace Umbraco.Web.Install.Controllers
{
var r = new org.umbraco.our.Repository();
var modules = r.Modules();
List<Package> retval = new List<Package>();
foreach (var package in modules)
retval.Add(new Package() { Id = package.RepoGuid, Name = package.Text, Thumbnail = package.Thumbnail });
return retval;
return modules.Select(package => new Package()
{
Id = package.RepoGuid,
Name = package.Text,
Thumbnail = package.Thumbnail
});
}
/// <summary>
/// Does the install
/// </summary>
/// <returns></returns>
public HttpResponseMessage PostPerformInstall(IDictionary<string, JToken> instructions)
public HttpResponseMessage PostPerformInstall(InstallInstructions installModel)
{
if (instructions == null) throw new ArgumentNullException("instructions");
if (installModel == null) throw new ArgumentNullException("installModel");
var steps = GetSetup().Steps.OrderBy(x => x.ServerOrder).ToArray();
var status = InstallStatusTracker.GetStatus();
if (status.Count == 0)
var status = InstallStatusTracker.GetStatus().ToArray();
if (status.Any() == false)
{
status = InstallStatusTracker.Initialize(steps);
status = InstallStatusTracker.Initialize(installModel.InstallId, InstallHelper.GetSteps()).ToArray();
}
foreach (var step in steps)
foreach (var stepStatus in status)
{
var step1 = step;
var stepStatus = status.Single(x => x.Key == step1.Name);
//if it is not complete, then we need to execute it
if (stepStatus.Value.IsComplete == false)
if (stepStatus.IsComplete == false)
{
var step = InstallHelper.GetSteps().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 (instructions.Any(x => x.Key == step.Name) == false)
if (installModel.Instructions.Any(x => x.Key == step.Name) == false)
{
return Request.CreateValidationErrorResponse("No instruction defined for step: " + step.Name);
}
instruction = instructions[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, step.Name, null);
continue;
}
@@ -142,20 +149,45 @@ namespace Umbraco.Web.Install.Controllers
var setupData = ExecuteStep(step, instruction);
//update the status
InstallStatusTracker.SetComplete(step.Name, setupData.SavedStepData);
InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData != null ? setupData.SavedStepData : null);
//check if there's a custom view to return for this step
if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false)
{
return Json(new
{
complete = false,
stepCompleted = step.Name,
view = setupData.View,
model = setupData.ViewModel
}, HttpStatusCode.OK);
}
return Json(new
{
complete = false,
stepCompleted = step.Name
stepCompleted = step.Name
}, HttpStatusCode.OK);
}
catch (InstallException iex)
{
return Json(iex.Result, HttpStatusCode.BadRequest);
}
catch (Exception ex)
{
if (ex is TargetInvocationException && ex.InnerException != null)
{
ex = ex.InnerException;
}
var installException = ex as InstallException;
if (installException != null)
{
return Json(new
{
view = installException.View,
model = installException.ViewModel,
message = installException.Message
}, HttpStatusCode.BadRequest);
}
return Request.CreateValidationErrorResponse("An error occurred executing the step: " + step.Name + ". Error: " + ex.Message);
}
}
@@ -168,19 +200,22 @@ namespace Umbraco.Web.Install.Controllers
internal InstallSetupResult ExecuteStep(InstallSetupStep step, JToken instruction)
{
var model = instruction == null ? null : instruction.ToObject(step.StepType);
var genericStepType = typeof(InstallSetupStep<>);
Type[] typeArgs = { step.StepType };
var typedStepType = genericStepType.MakeGenericType(typeArgs);
try
using (DisposableTimer.TraceDuration<InstallApiController>("Executing installation step: " + step.Name, "Step completed"))
{
var method = typedStepType.GetMethods().Single(x => x.Name == "Execute");
return (InstallSetupResult)method.Invoke(step, new object[] { model });
}
catch (Exception ex)
{
LogHelper.Error<InstallApiController>("Installation step " + step.Name + " failed.", ex);
throw;
var model = instruction == null ? null : instruction.ToObject(step.StepType);
var genericStepType = typeof(InstallSetupStep<>);
Type[] typeArgs = { step.StepType };
var typedStepType = genericStepType.MakeGenericType(typeArgs);
try
{
var method = typedStepType.GetMethods().Single(x => x.Name == "Execute");
return (InstallSetupResult)method.Invoke(step, new object[] { model });
}
catch (Exception ex)
{
LogHelper.Error<InstallApiController>("Installation step " + step.Name + " failed.", ex);
throw;
}
}
}

View File

@@ -12,17 +12,19 @@ namespace Umbraco.Web.Install
internal class InstallException : Exception
{
private readonly string _message;
public object Result { get; set; }
public string View { get; private set; }
public object ViewModel { get; private set; }
public override string Message
{
get { return _message; }
}
public InstallException(string message, object result)
public InstallException(string message, string view, object viewModel)
{
_message = message;
Result = result;
View = view;
ViewModel = viewModel;
}
}
}

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Web.Install.Models;
@@ -18,78 +19,78 @@ namespace Umbraco.Web.Install
internal static class InstallStatusTracker
{
private static ConcurrentDictionary<string, InstallTrackingItem> _steps = new ConcurrentDictionary<string, InstallTrackingItem>();
private static ConcurrentHashSet<InstallTrackingItem> _steps = new ConcurrentHashSet<InstallTrackingItem>();
private static string GetFile()
private static string GetFile(Guid installId)
{
var file = IOHelper.MapPath("~/App_Data/TEMP/Install/"
+ "status"
//+ dt.ToString("yyyyMMddHHmmss")
+ "install_"
+ installId.ToString("N")
+ ".txt");
return file;
}
public static void Reset()
{
_steps = new ConcurrentDictionary<string, InstallTrackingItem>();
File.Delete(GetFile());
_steps = new ConcurrentHashSet<InstallTrackingItem>();
var files = Directory.GetFiles(IOHelper.MapPath("~/App_Data/TEMP/Install/"));
foreach (var f in files)
{
File.Delete(f);
}
}
public static IDictionary<string, InstallTrackingItem> Initialize(IEnumerable<InstallSetupStep> steps)
public static IEnumerable<InstallTrackingItem> Initialize(Guid installId, IEnumerable<InstallSetupStep> steps)
{
//if there are no steps in memory
if (_steps.Count == 0)
{
//check if we have our persisted file and read it
var file = GetFile();
var file = GetFile(installId);
if (File.Exists(file))
{
var deserialized = JsonConvert.DeserializeObject<IDictionary<string, InstallTrackingItem>>(
var deserialized = JsonConvert.DeserializeObject<IEnumerable<InstallTrackingItem>>(
File.ReadAllText(file));
foreach (var item in deserialized)
{
_steps.TryAdd(item.Key, item.Value);
_steps.Add(item);
}
}
else
{
//otherwise just create the steps in memory (brand new install)
foreach (var step in steps)
foreach (var step in steps.OrderBy(x => x.ServerOrder))
{
_steps.TryAdd(step.Name, new InstallTrackingItem());
_steps.Add(new InstallTrackingItem(step.Name, step.ServerOrder));
}
//save the file
var serialized = JsonConvert.SerializeObject(new Dictionary<string, InstallTrackingItem>(_steps));
var serialized = JsonConvert.SerializeObject(new List<InstallTrackingItem>(_steps));
Directory.CreateDirectory(Path.GetDirectoryName(file));
File.WriteAllText(file, serialized);
}
}
return new Dictionary<string, InstallTrackingItem>(_steps);
return new List<InstallTrackingItem>(_steps);
}
public static void SetComplete(string name, IDictionary<string, object> additionalData = null)
public static void SetComplete(Guid installId, string name, IDictionary<string, object> additionalData = null)
{
var trackingItem = new InstallTrackingItem()
{
IsComplete = true
};
var trackingItem = _steps.Single(x => x.Name == name);
if (additionalData != null)
{
trackingItem.AdditionalData = additionalData;
}
_steps[name] = trackingItem;
trackingItem.IsComplete = true;
//save the file
var file = GetFile();
var serialized = JsonConvert.SerializeObject(new Dictionary<string, InstallTrackingItem>(_steps));
var file = GetFile(installId);
var serialized = JsonConvert.SerializeObject(new List<InstallTrackingItem>(_steps));
File.WriteAllText(file, serialized);
}
public static IDictionary<string, InstallTrackingItem> GetStatus()
public static IEnumerable<InstallTrackingItem> GetStatus()
{
return new Dictionary<string, InstallTrackingItem>(_steps);
return new List<InstallTrackingItem>(_steps).OrderBy(x => x.ServerOrder);
}
}
}

View File

@@ -13,7 +13,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("DatabaseConfigure", "database", 3, "Configuring your database connection")]
[InstallSetupStep("DatabaseConfigure", "database", 10, "Configuring your database connection")]
internal class DatabaseConfigureStep : InstallSetupStep<DatabaseModel>
{
private readonly ApplicationContext _applicationContext;

View File

@@ -8,7 +8,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("DatabaseInstall", 4, "Installing database tables and default system data")]
[InstallSetupStep("DatabaseInstall", 11, "Installing database tables and default system data")]
internal class DatabaseInstallStep : InstallSetupStep<object>
{
private readonly ApplicationContext _applicationContext;

View File

@@ -7,7 +7,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("DatabaseUpgrade", 5, "Upgrading your database to the latest version")]
[InstallSetupStep("DatabaseUpgrade", 12, "Upgrading your database to the latest version")]
internal class DatabaseUpgradeStep : InstallSetupStep<object>
{
private readonly ApplicationContext _applicationContext;
@@ -23,13 +23,13 @@ namespace Umbraco.Web.Install.InstallSteps
{
if (_status == InstallStatusType.NewInstall) return null;
var installSteps = InstallStatusTracker.GetStatus();
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.Key == "DatabaseConfigure") == false)
if (installSteps.Any(x => x.Name == "DatabaseConfigure") == false)
{
throw new InvalidOperationException("Could not find previous step: DatabaseConfigure of the installation, package install cannot continue");
}
var previousStep = installSteps["DatabaseConfigure"];
var previousStep = installSteps.Single(x => x.Name == "DatabaseConfigure");
var upgrade = previousStep.AdditionalData.ContainsKey("upgrade");
if (upgrade)

View File

@@ -83,7 +83,7 @@ namespace Umbraco.Web.Install.InstallSteps
if (permissionsOk == false)
{
throw new InstallException("Permission check failed", new { view = "permissionsReport", errors = report });
throw new InstallException("Permission check failed", "permissionsReport", new { errors = report });
}
return null;

View File

@@ -9,7 +9,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("MajorVersion7UpgradeReport", 2, "Checking for compatibility issues with upgrade")]
[InstallSetupStep("MajorVersion7UpgradeReport", 1, "Checking for compatibility issues with upgrade")]
internal class MajorVersion7UpgradeReport : InstallSetupStep<object>
{
private readonly ApplicationContext _applicationContext;

View File

@@ -10,7 +10,7 @@ using GlobalSettings = umbraco.GlobalSettings;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("UmbracoVersion", 9, "Wrapping up the system configuration")]
[InstallSetupStep("UmbracoVersion", 50, "Wrapping up the system configuration")]
internal class SetUmbracoVersionStep : InstallSetupStep<object>
{
private readonly ApplicationContext _applicationContext;

View File

@@ -7,7 +7,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("StarterKitCleanup", 8, "Cleaning up temporary files")]
[InstallSetupStep("StarterKitCleanup", 32, "Cleaning up temporary files")]
internal class StarterKitCleanupStep : InstallSetupStep<object>
{
private readonly InstallStatusType _status;
@@ -21,13 +21,13 @@ namespace Umbraco.Web.Install.InstallSteps
{
if (_status != InstallStatusType.NewInstall) return null;
var installSteps = InstallStatusTracker.GetStatus();
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.Key == "StarterKitDownload") == false)
if (installSteps.Any(x => x.Name == "StarterKitDownload") == false)
{
throw new InvalidOperationException("Could not find previous step: StarterKitDownload of the installation, package install cannot continue");
}
var previousStep = installSteps["StarterKitDownload"];
var previousStep = installSteps.Single(x => x.Name == "StarterKitDownload");
var manifestId = Convert.ToInt32(previousStep.AdditionalData["manifestId"]);
var packageFile = (string)previousStep.AdditionalData["packageFile"];

View File

@@ -6,7 +6,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("StarterKitDownload", "starterKit", 6, "Downloading a starter website from our.umbraco.org, hold tight, this could take a little while")]
[InstallSetupStep("StarterKitDownload", "starterKit", 30, "Downloading a starter website from our.umbraco.org, hold tight, this could take a little while")]
internal class StarterKitDownloadStep : InstallSetupStep<Guid>
{
private readonly InstallStatusType _status;

View File

@@ -7,7 +7,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("StarterKitInstall", 7, "Installing a starter website to help you get off to a great start")]
[InstallSetupStep("StarterKitInstall", 31, "Installing a starter website to help you get off to a great start")]
internal class StarterKitInstallStep : InstallSetupStep<object>
{
private readonly InstallStatusType _status;
@@ -26,13 +26,13 @@ namespace Umbraco.Web.Install.InstallSteps
{
if (_status != InstallStatusType.NewInstall) return null;
var installSteps = InstallStatusTracker.GetStatus();
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.Key == "StarterKitDownload") == false)
if (installSteps.Any(x => x.Name == "StarterKitDownload") == false)
{
throw new InvalidOperationException("Could not find previous step: StarterKitDownload of the installation, package install cannot continue");
}
var previousStep = installSteps["StarterKitDownload"];
var previousStep = installSteps.Single(x => x.Name == "StarterKitDownload");
var manifestId = Convert.ToInt32(previousStep.AdditionalData["manifestId"]);
var packageFile = (string)previousStep.AdditionalData["packageFile"];

View File

@@ -8,7 +8,7 @@ using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
{
[InstallSetupStep("User", "user", 4, "Saving your user credentials")]
[InstallSetupStep("User", "user", 20, "Saving your user credentials")]
internal class UserStep : InstallSetupStep<UserModel>
{
private readonly ApplicationContext _applicationContext;

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
namespace Umbraco.Web.Install.Models
{
[DataContract(Name = "installInstructions", Namespace = "")]
public class InstallInstructions
{
[DataMember(Name = "instructions")]
public IDictionary<string, JToken> Instructions { get; set; }
[DataMember(Name = "installId")]
public Guid InstallId { get; set; }
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Install.Models
{
/// <summary>
/// Model containing all the install steps for setting up the UI
/// </summary>
@@ -13,8 +13,12 @@ namespace Umbraco.Web.Install.Models
public InstallSetup()
{
Steps = new List<InstallSetupStep>();
InstallationId = Guid.NewGuid();
}
[DataMember(Name = "installId")]
public Guid InstallationId { get; private set; }
[DataMember(Name = "status")]
public InstallStatusType Status { get; set; }

View File

@@ -62,7 +62,7 @@ namespace Umbraco.Web.Install.Models
/// Defines what order this step needs to execute on the server side since the
/// steps might be shown out of order on the front-end
/// </summary>
[IgnoreDataMember]
[DataMember(Name = "serverOrder")]
public int ServerOrder { get; private set; }
/// <summary>

View File

@@ -17,6 +17,7 @@ namespace Umbraco.Web.Install.Models
Name = name;
View = string.Empty;
ServerOrder = serverOrder;
Description = description;
}
public string Name { get; private set; }

View File

@@ -1,16 +1,38 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Install.Models
{
internal class InstallTrackingItem
{
public InstallTrackingItem()
public InstallTrackingItem(string name, int serverOrder)
{
Name = name;
ServerOrder = serverOrder;
AdditionalData = new Dictionary<string, object>();
}
public string Name { get; set; }
public int ServerOrder { get; set; }
public bool IsComplete { get; set; }
public IDictionary<string, object> AdditionalData { get; set; }
protected bool Equals(InstallTrackingItem other)
{
return string.Equals(Name, other.Name);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((InstallTrackingItem) obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
}

View File

@@ -316,6 +316,7 @@
<Compile Include="Install\InstallSteps\UserStep.cs" />
<Compile Include="Install\Models\DatabaseModel.cs" />
<Compile Include="Install\Models\DatabaseType.cs" />
<Compile Include="Install\Models\InstallInstructions.cs" />
<Compile Include="Install\Models\InstallSetup.cs" />
<Compile Include="Install\Models\InstallSetupResult.cs" />
<Compile Include="Install\Models\InstallSetupStep.cs" />