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());