diff --git a/src/Umbraco.Core/ActionsResolver.cs b/src/Umbraco.Core/ActionsResolver.cs
index 2da95a3416..206182c6f2 100644
--- a/src/Umbraco.Core/ActionsResolver.cs
+++ b/src/Umbraco.Core/ActionsResolver.cs
@@ -23,12 +23,12 @@ namespace Umbraco.Core
: base(serviceProvider, logger, packageActions)
{
- }
-
- ///
- /// Gets the implementations.
- ///
- public IEnumerable Actions
+ }
+
+ ///
+ /// Gets the implementations.
+ ///
+ public IEnumerable Actions
{
get
{
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index 2ee0463435..d017505b4c 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -97,6 +97,8 @@ namespace Umbraco.Core.IO
public static string MapPath(string path, bool useHttpContext)
{
+ if (path == null) throw new ArgumentNullException("path");
+
// Check if the path is already mapped
if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar)
|| path.StartsWith(@"\\")) //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar"
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
index c9a501ba24..cb810c6edb 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
@@ -123,6 +123,16 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) {
'Failed to install package. Error during the step "InstallFiles" ');
},
+ checkRestart: function (package) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "packageInstallApiBaseUrl",
+ "CheckRestart"), package),
+ 'Failed to install package. Error during the step "CheckRestart" ');
+ },
+
installData: function (package) {
return umbRequestHelper.resourcePromise(
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js
index 5ae1d4bf2c..73d06b26cf 100644
--- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js
@@ -193,13 +193,41 @@
.import(selectedPackage)
.then(function(pack) {
vm.installState.status = localizationService.localize("packager_installStateInstalling");
- vm.installState.progress = "33";
+ vm.installState.progress = "25";
return packageResource.installFiles(pack);
},
error)
.then(function(pack) {
vm.installState.status = localizationService.localize("packager_installStateRestarting");
- vm.installState.progress = "66";
+ vm.installState.progress = "50";
+ var deferred = $q.defer();
+
+ //check if the app domain is restarted ever 2 seconds
+ var count = 0;
+ function checkRestart() {
+ $timeout(function () {
+ packageResource.checkRestart(pack).then(function (d) {
+ count++;
+ //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
+ if (d.restartId && count < 10) {
+ checkRestart();
+ }
+ else {
+ //it's restarted!
+ deferred.resolve(d);
+ }
+ },
+ error);
+ }, 2000);
+ }
+
+ checkRestart();
+
+ return deferred.promise;
+ }, error)
+ .then(function (pack) {
+ vm.installState.status = localizationService.localize("packager_installStateRestarting");
+ vm.installState.progress = "75";
return packageResource.installData(pack);
},
error)
diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs
index d3771ce82c..005f934d8a 100644
--- a/src/Umbraco.Web/Editors/PackageInstallController.cs
+++ b/src/Umbraco.Web/Editors/PackageInstallController.cs
@@ -543,6 +543,24 @@ namespace Umbraco.Web.Editors
var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id);
ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath));
ins.InstallFiles(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath));
+
+ var restartMarker = RestartMarkerManager.CreateRestartMarker();
+ model.RestartId = restartMarker;
+
+ return model;
+ }
+
+ [HttpPost]
+ public PackageInstallModel CheckRestart(PackageInstallModel model)
+ {
+ if (model.RestartId == null) return model;
+
+ var exists = RestartMarkerManager.RestartMarkerExists();
+ if (exists == false)
+ {
+ //reset it
+ model.RestartId = null;
+ }
return model;
}
diff --git a/src/Umbraco.Web/Models/PackageInstallModel.cs b/src/Umbraco.Web/Models/PackageInstallModel.cs
index f903fc1880..1003a3d4c2 100644
--- a/src/Umbraco.Web/Models/PackageInstallModel.cs
+++ b/src/Umbraco.Web/Models/PackageInstallModel.cs
@@ -24,6 +24,10 @@ namespace Umbraco.Web.Models
[DataMember(Name = "zipFilePath")]
public string ZipFilePath { get; set; }
-
+ ///
+ /// During installation this can be used to track any pending appdomain restarts
+ ///
+ [DataMember(Name = "restartId")]
+ public Guid? RestartId { get; set; }
}
}
diff --git a/src/Umbraco.Web/RestartMarkerManager.cs b/src/Umbraco.Web/RestartMarkerManager.cs
new file mode 100644
index 0000000000..14e3554be7
--- /dev/null
+++ b/src/Umbraco.Web/RestartMarkerManager.cs
@@ -0,0 +1,70 @@
+using System;
+using System.IO;
+using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Web
+{
+ internal class RestartMarkerManager
+ {
+ ///
+ /// Check if a restart marker exists, if so it means the appdomain is still restarting
+ ///
+ ///
+ internal static bool RestartMarkerExists()
+ {
+ var dir = new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP/Install"));
+ if (dir.Exists == false) return false;
+ return dir.GetFiles("restart_*.txt").Length > 0;
+ }
+
+ ///
+ /// Used during package installation in the website in order to determine if the site is marked for restarting so subsequent requests know if restart has been successful
+ ///
+ internal static Guid? CreateRestartMarker()
+ {
+ try
+ {
+ //we need to store a restart marker, this is because even though files are installed and the app domain should
+ //restart, in may not be immediate because we might have waitChangeNotification and maxWaitChangeNotification
+ //configured. In which case the response and the next request to 'InstallData' will occur on the same app domain.
+ var restartId = Guid.NewGuid();
+ Directory.CreateDirectory(IOHelper.MapPath("~/App_Data/TEMP/Install"));
+ File.CreateText(IOHelper.MapPath("~/App_Data/TEMP/Install/" + "restart_" + restartId + "_" + AppDomain.CurrentDomain.Id + ".txt")).Close();
+ return restartId;
+ }
+ catch (Exception ex)
+ {
+ //swallow! we don't want to prevent the app from shutting down which is generally when this is called
+ LogHelper.Error("Could not create restart markers", ex);
+ return null;
+ }
+ }
+
+
+
+ ///
+ /// Used during package installation in the website in order to determine if the site is marked for restarting so subsequent requests know if restart has been successful
+ ///
+ ///
+ /// The restart markers are cleared when the app is booted up
+ ///
+ internal static void ClearRestartMarkers()
+ {
+ try
+ {
+ var dir = new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP/Install"));
+ if (dir.Exists == false) return;
+ foreach (var f in dir.GetFiles("restart_*.txt"))
+ {
+ f.Delete();
+ }
+ }
+ catch (Exception ex)
+ {
+ //swallow! we don't want to prevent the app from starting
+ LogHelper.Error("Could not clear restart markers", ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 9aaab32af9..11adf48792 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -413,6 +413,7 @@
+
diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs
index 29565065e2..7c7071f234 100644
--- a/src/Umbraco.Web/WebBootManager.cs
+++ b/src/Umbraco.Web/WebBootManager.cs
@@ -228,9 +228,13 @@ namespace Umbraco.Web
//Now ensure webapi is initialized after everything
GlobalConfiguration.Configuration.EnsureInitialized();
+ RestartMarkerManager.ClearRestartMarkers();
+
return this;
}
+
+
internal static void ConfigureGlobalFilters()
{
GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute());