2014-02-26 04:15:14 +11:00
|
|
|
|
using System;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using System.Collections.Generic;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Net;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using System.Net.Http;
|
2014-03-04 16:21:45 +11:00
|
|
|
|
using System.Reflection;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using System.Text;
|
2014-02-26 04:15:14 +11:00
|
|
|
|
using System.Web.Http;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using Umbraco.Core;
|
|
|
|
|
|
using Umbraco.Core.Configuration;
|
2014-02-26 16:01:31 +01:00
|
|
|
|
using Umbraco.Core.Logging;
|
|
|
|
|
|
using Umbraco.Core.Persistence;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
using Umbraco.Web.Install.Models;
|
|
|
|
|
|
using Umbraco.Web.WebApi;
|
2014-02-26 04:15:14 +11:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.Install.Controllers
|
|
|
|
|
|
{
|
2014-02-26 18:25:59 +11:00
|
|
|
|
[AngularJsonOnlyConfiguration]
|
2014-02-26 04:15:14 +11:00
|
|
|
|
[HttpInstallAuthorize]
|
|
|
|
|
|
public class InstallApiController : ApiController
|
|
|
|
|
|
{
|
|
|
|
|
|
protected InstallApiController()
|
|
|
|
|
|
: this(UmbracoContext.Current)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected InstallApiController(UmbracoContext umbracoContext)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
|
|
|
|
|
UmbracoContext = umbracoContext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns the current UmbracoContext
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public UmbracoContext UmbracoContext { get; private set; }
|
|
|
|
|
|
|
2014-02-26 16:01:31 +01:00
|
|
|
|
public ApplicationContext ApplicationContext
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return UmbracoContext.Application; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-04 16:21:45 +11:00
|
|
|
|
private InstallHelper _helper;
|
|
|
|
|
|
internal InstallHelper InstallHelper
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _helper ?? (_helper = new InstallHelper(UmbracoContext));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-26 18:25:59 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the install setup
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public InstallSetup GetSetup()
|
|
|
|
|
|
{
|
2014-03-04 19:20:36 +11:00
|
|
|
|
var setup = new InstallSetup();
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-02-26 18:25:59 +11:00
|
|
|
|
//TODO: Check for user/site token
|
|
|
|
|
|
|
2014-02-26 16:01:31 +01:00
|
|
|
|
var steps = new List<InstallSetupStep>();
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2014-03-04 19:20:36 +11:00
|
|
|
|
var installSteps = InstallHelper.GetStepsForCurrentInstallType().ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
//only get the steps that are targetting the current install type
|
|
|
|
|
|
steps.AddRange(installSteps);
|
2014-03-04 11:16:42 +11:00
|
|
|
|
setup.Steps = steps;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2014-03-04 19:20:36 +11:00
|
|
|
|
InstallStatusTracker.Initialize(setup.InstallId, installSteps);
|
|
|
|
|
|
|
2014-03-04 11:16:42 +11:00
|
|
|
|
return setup;
|
2014-02-26 18:25:59 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks if the db can be connected to
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public HttpResponseMessage PostCheckDbConnection()
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks if the db credentials are correct
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public HttpResponseMessage PostCheckDbCredentials()
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-03 08:57:00 +01:00
|
|
|
|
public IEnumerable<Package> GetPackages()
|
|
|
|
|
|
{
|
|
|
|
|
|
var r = new org.umbraco.our.Repository();
|
|
|
|
|
|
var modules = r.Modules();
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-03-04 16:21:45 +11:00
|
|
|
|
return modules.Select(package => new Package()
|
|
|
|
|
|
{
|
2014-03-05 10:32:59 +11:00
|
|
|
|
Id = package.RepoGuid,
|
|
|
|
|
|
Name = package.Text,
|
2014-03-04 16:21:45 +11:00
|
|
|
|
Thumbnail = package.Thumbnail
|
|
|
|
|
|
});
|
2014-03-03 08:57:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-26 18:25:59 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Does the install
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
2014-03-05 10:32:59 +11:00
|
|
|
|
public InstallProgressResultModel PostPerformInstall(InstallInstructions installModel)
|
2014-02-26 18:25:59 +11:00
|
|
|
|
{
|
2014-03-04 16:21:45 +11:00
|
|
|
|
if (installModel == null) throw new ArgumentNullException("installModel");
|
2014-02-26 18:25:59 +11:00
|
|
|
|
|
2014-03-04 16:21:45 +11:00
|
|
|
|
var status = InstallStatusTracker.GetStatus().ToArray();
|
2014-03-04 19:20:36 +11:00
|
|
|
|
//there won't be any statuses returned if the app pool has restarted so we need to re-read from file.
|
2014-03-04 16:21:45 +11:00
|
|
|
|
if (status.Any() == false)
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2014-03-04 19:20:36 +11:00
|
|
|
|
status = InstallStatusTracker.InitializeFromFile(installModel.InstallId).ToArray();
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//create a new queue of the non-finished ones
|
|
|
|
|
|
var queue = new Queue<InstallTrackingItem>(status.Where(x => x.IsComplete == false));
|
|
|
|
|
|
while (queue.Count > 0)
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2014-03-05 11:34:42 +11:00
|
|
|
|
var stepStatus = queue.Dequeue();
|
|
|
|
|
|
|
|
|
|
|
|
var step = InstallHelper.GetAllSteps().Single(x => x.Name == stepStatus.Name);
|
2014-03-04 11:16:42 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
JToken instruction = null;
|
2014-03-05 12:08:35 +11:00
|
|
|
|
//If this step has any instructions then extract them
|
|
|
|
|
|
if (installModel.Instructions.Any(x => x.Key == step.Name))
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
|
|
|
|
|
instruction = installModel.Instructions[step.Name];
|
|
|
|
|
|
}
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//If this step doesn't require execution then continue to the next one, this is just a fail-safe check.
|
2014-03-05 14:30:17 +11:00
|
|
|
|
if (StepRequiresExecution(step, instruction) == false)
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
|
|
|
|
|
//set this as complete and continue
|
|
|
|
|
|
InstallStatusTracker.SetComplete(installModel.InstallId, stepStatus.Name, null);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var setupData = ExecuteStep(step, instruction);
|
2014-03-03 08:57:00 +01:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//update the status
|
|
|
|
|
|
InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData != null ? setupData.SavedStepData : null);
|
2014-03-04 16:21:45 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//Determine's the next step in the queue and dequeue's any items that don't need to execute
|
2014-03-05 14:30:17 +11:00
|
|
|
|
var nextStep = IterateNextRequiredStep(step, queue, installModel.InstallId, installModel);
|
2014-03-05 11:34:42 +11:00
|
|
|
|
|
|
|
|
|
|
//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);
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
2014-03-05 11:34:42 +11:00
|
|
|
|
|
|
|
|
|
|
return new InstallProgressResultModel(false, step.Name, nextStep);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
|
|
|
|
|
LogHelper.Error<InstallApiController>("An error occurred during installation step " + step.Name, ex);
|
|
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
if (ex is TargetInvocationException && ex.InnerException != null)
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2014-03-05 11:34:42 +11:00
|
|
|
|
ex = ex.InnerException;
|
|
|
|
|
|
}
|
2014-03-04 16:21:45 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
var installException = ex as InstallException;
|
|
|
|
|
|
if (installException != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(Request.CreateValidationErrorResponse(new
|
2014-03-04 19:11:27 +01:00
|
|
|
|
{
|
2014-03-05 11:34:42 +11:00
|
|
|
|
view = installException.View,
|
|
|
|
|
|
model = installException.ViewModel,
|
|
|
|
|
|
message = installException.Message
|
|
|
|
|
|
}));
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
2014-03-05 11:34:42 +11:00
|
|
|
|
|
|
|
|
|
|
throw new HttpResponseException(Request.CreateValidationErrorResponse(new
|
|
|
|
|
|
{
|
|
|
|
|
|
step = step.Name,
|
|
|
|
|
|
view = "error",
|
|
|
|
|
|
message = ex.Message
|
|
|
|
|
|
}));
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
InstallStatusTracker.Reset();
|
2014-03-05 10:32:59 +11:00
|
|
|
|
return new InstallProgressResultModel(true, "", "");
|
2014-02-26 18:25:59 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 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)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="current"></param>
|
|
|
|
|
|
/// <param name="queue"></param>
|
|
|
|
|
|
/// <param name="installId"></param>
|
2014-03-05 14:30:17 +11:00
|
|
|
|
/// <param name="installModel"></param>
|
2014-03-05 11:34:42 +11:00
|
|
|
|
/// <returns></returns>
|
2014-03-05 14:30:17 +11:00
|
|
|
|
private string IterateNextRequiredStep(InstallSetupStep current, Queue<InstallTrackingItem> queue, Guid installId, InstallInstructions installModel)
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
|
|
|
|
|
if (queue.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var next = queue.Peek();
|
|
|
|
|
|
var step = InstallHelper.GetAllSteps().Single(x => x.Name == next.Name);
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//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
|
2014-03-05 14:30:17 +11:00
|
|
|
|
// will rely on that too.
|
|
|
|
|
|
if (current.PerformsAppRestart)
|
2014-03-05 11:34:42 +11:00
|
|
|
|
{
|
|
|
|
|
|
return step.Name;
|
|
|
|
|
|
}
|
2014-03-05 14:30:17 +11:00
|
|
|
|
|
|
|
|
|
|
JToken instruction = null;
|
|
|
|
|
|
//If this step has any instructions then extract them
|
|
|
|
|
|
if (installModel.Instructions.Any(x => x.Key == step.Name))
|
|
|
|
|
|
{
|
|
|
|
|
|
instruction = installModel.Instructions[step.Name];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//if the step requires execution then return it's name
|
|
|
|
|
|
if (StepRequiresExecution(step, instruction))
|
|
|
|
|
|
{
|
|
|
|
|
|
return step.Name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-05 11:34:42 +11:00
|
|
|
|
//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
|
2014-03-05 14:30:17 +11:00
|
|
|
|
return IterateNextRequiredStep(step, queue, installId, installModel);
|
2014-03-05 11:34:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//there is no more steps
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-03-05 14:30:17 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Check if the step requires execution
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="step"></param>
|
|
|
|
|
|
/// <param name="instruction"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
internal bool StepRequiresExecution(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
|
|
|
|
|
|
{
|
|
|
|
|
|
var method = typedStepType.GetMethods().Single(x => x.Name == "RequiresExecution");
|
|
|
|
|
|
return (bool)method.Invoke(step, new object[] { model });
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.Error<InstallApiController>("Checking if step requires execution (" + step.Name + ") failed.", ex);
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-04 11:16:42 +11:00
|
|
|
|
internal InstallSetupResult ExecuteStep(InstallSetupStep step, JToken instruction)
|
2014-02-26 18:25:59 +11:00
|
|
|
|
{
|
2014-03-04 16:21:45 +11:00
|
|
|
|
using (DisposableTimer.TraceDuration<InstallApiController>("Executing installation step: " + step.Name, "Step completed"))
|
2014-02-26 16:01:31 +01:00
|
|
|
|
{
|
2014-03-04 16:21:45 +11:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2014-02-26 16:01:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-02-26 16:01:31 +01:00
|
|
|
|
private HttpResponseMessage Json(object jsonObject, HttpStatusCode status)
|
|
|
|
|
|
{
|
|
|
|
|
|
var response = Request.CreateResponse(status);
|
|
|
|
|
|
var json = JObject.FromObject(jsonObject);
|
|
|
|
|
|
response.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json");
|
|
|
|
|
|
return response;
|
|
|
|
|
|
}
|
2014-03-05 10:32:59 +11:00
|
|
|
|
|
2014-02-26 04:15:14 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|