Added the ability to automate any c# scripts for an upgrade process. I realize this is superceded already in 6.0

but we need a way to do this in 4.x too especially for this release since we need to run a script to fix some db
issues. I've added a framework using an UpgradeScriptManager and another install step + unit tests for some of the
UpgradeScriptManager methods.
This commit is contained in:
Shannon Deminick
2013-01-31 04:26:37 +06:00
parent 97556de447
commit a5bea7fc59
22 changed files with 601 additions and 56 deletions

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Install.UpgradeScripts
{
/// <summary>
/// An upgrade script to fix a moving issue in 4.10+
/// http://issues.umbraco.org/issue/U4-1491
/// </summary>
public class ContentPathFix : IUpgradeScript
{
private readonly StringBuilder _report = new StringBuilder();
public void Execute()
{
//return;
if (ApplicationContext.Current == null) return;
if (HasBeenFixed()) return;
Fix();
WriteReport();
}
private void Fix()
{
AddReportLine("Starting fix paths script");
//fix content
AddReportLine("Fixing content");
foreach (var d in Document.GetRootDocuments())
{
FixPathsForChildren(d, content => ((Document)content).Children);
}
AddReportLine("Fixing content recycle bin");
var contentRecycleBin = new RecycleBin(RecycleBin.RecycleBinType.Content);
foreach (var d in contentRecycleBin.Children)
{
FixPathsForChildren(new Document(d.Id), content => ((Document)content).Children);
}
//fix media
AddReportLine("Fixing media");
foreach (var d in global::umbraco.cms.businesslogic.media.Media.GetRootMedias())
{
FixPathsForChildren(d, media => ((global::umbraco.cms.businesslogic.media.Media)media).Children);
}
AddReportLine("Fixing media recycle bin");
var mediaRecycleBin = new RecycleBin(RecycleBin.RecycleBinType.Media);
foreach (var d in mediaRecycleBin.Children)
{
FixPathsForChildren(new global::umbraco.cms.businesslogic.media.Media(d.Id), media => ((global::umbraco.cms.businesslogic.media.Media)media).Children);
}
AddReportLine("Complete!");
}
/// <summary>
/// Returns true if this script has run based on a temp file written to
/// ~/App_Data/TEMP/FixPaths/report.txt
/// </summary>
/// <returns></returns>
private bool HasBeenFixed()
{
return File.Exists(IOHelper.MapPath("~/App_Data/TEMP/FixPaths/report.txt"));
}
/// <summary>
/// Creates the report
/// </summary>
private void WriteReport()
{
var filePath = IOHelper.MapPath("~/App_Data/TEMP/FixPaths/report.txt");
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using (var writer = File.CreateText(IOHelper.MapPath("~/App_Data/TEMP/FixPaths/report.txt")))
{
writer.Write(_report.ToString());
}
}
/// <summary>
/// Recursively iterates over the children of the document and fixes the path
/// </summary>
/// <param name="d"></param>
/// <param name="getChildren">Callback to get the children of the conent item</param>
/// <remarks>
/// We cannot use GetDescendants() because that is based on the paths of documents and if they are invalid then
/// we cannot use that method.
/// </remarks>
private void FixPathsForChildren(Content d, Func<Content, IEnumerable<Content>> getChildren)
{
AddReportLine("Fixing paths for children of " + d.Id);
foreach (var c in getChildren(d))
{
FixPath(c);
if (c.HasChildren)
{
FixPathsForChildren(c, getChildren);
}
}
}
/// <summary>
/// Check if the path is correct based on the document's parent if it is not correct, then fix it
/// </summary>
/// <param name="d"></param>
private void FixPath(CMSNode d)
{
AddReportLine("Checking path for " + d.Id + ". Current = " + d.Path);
//check if the path is correct
var correctpath = d.Parent.Path + "," + d.Id.ToString();
if (d.Path != correctpath)
{
AddReportLine(" INVALID PATH DETECTED. Path for " + d.Id + " changed to: " + d.Parent.Path + "," + d.Id.ToString());
d.Path = correctpath;
d.Level = d.Parent.Level + 1;
}
}
private void AddReportLine(string str)
{
_report.AppendLine(string.Format("{0} - " + str, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")));
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Web.Install.UpgradeScripts
{
internal interface IUpgradeScript
{
void Execute();
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Configuration;
using umbraco.DataLayer.Utility.Installer;
namespace Umbraco.Web.Install.UpgradeScripts
{
/// <summary>
/// Class used to register and execute upgrade scripts during install if they are required.
/// </summary>
internal static class UpgradeScriptManager
{
/// <summary>
/// Returns true if there are scripts to execute for the version
/// </summary>
/// <param name="version"></param>
/// <returns></returns>
public static bool HasScriptsForVersion(Version version)
{
return Scripts.Any(x => x.Item2.InRange(version));
}
/// <summary>
/// Executes all of the scripts for a database version
/// </summary>
/// <param name="version"></param>
/// <returns></returns>
public static void ExecuteScriptsForVersion(Version version)
{
var types = Scripts.Where(x => x.Item2.InRange(version)).Select(x => x.Item1);
foreach (var instance in types.Select(x => x()))
{
instance.Execute();
}
}
public static void AddUpgradeScript(Func<IUpgradeScript> script, VersionRange version)
{
Scripts.Add(new Tuple<Func<IUpgradeScript>, VersionRange>(script, version));
}
///// <summary>
///// Adds a script to execute for a database version
///// </summary>
///// <param name="assemblyQualifiedTypeName"></param>
///// <param name="version"></param>
//public static void AddUpgradeScript(string assemblyQualifiedTypeName, VersionRange version)
//{
// AddUpgradeScript(new Lazy<Type>(() => Type.GetType(assemblyQualifiedTypeName)), version);
//}
///// <summary>
///// Adds a script to execute for a database version
///// </summary>
///// <typeparam name="T"></typeparam>
///// <param name="version"></param>
//public static void AddUpgradeScript<T>(VersionRange version)
//{
// AddUpgradeScript(new Lazy<Type>(() => typeof(T)), version);
//}
/// <summary>
/// Used for testing
/// </summary>
internal static void Clear()
{
Scripts.Clear();
}
///// <summary>
///// Adds a script to execute for a database version
///// </summary>
///// <param name="type"></param>
///// <param name="version"></param>
//public static void AddUpgradeScript(Lazy<Type> type, VersionRange version)
//{
// Scripts.Add(new Tuple<Lazy<Type>, VersionRange>(type, version));
//}
private static readonly List<Tuple<Func<IUpgradeScript>, VersionRange>> Scripts = new List<Tuple<Func<IUpgradeScript>, VersionRange>>();
//private static readonly List<Tuple<Lazy<Type>, VersionRange>> Scripts = new List<Tuple<Lazy<Type>, VersionRange>>();
}
}

View File

@@ -0,0 +1,28 @@
using System;
using Umbraco.Core;
namespace Umbraco.Web.Install.UpgradeScripts
{
internal class UpgradeScriptRegistrar : IApplicationEventHandler
{
public void OnApplicationInitialized(UmbracoApplication httpApplication, ApplicationContext applicationContext)
{
//Add contnet path fixup for any version from 4.10 up to 4.11.4
UpgradeScriptManager.AddUpgradeScript(
() => new ContentPathFix(),
new VersionRange(
new Version(4, 10),
new Version(4, 11, 4)));
}
public void OnApplicationStarting(UmbracoApplication httpApplication, ApplicationContext applicationContext)
{
}
public void OnApplicationStarted(UmbracoApplication httpApplication, ApplicationContext applicationContext)
{
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
namespace Umbraco.Web.Install.UpgradeScripts
{
internal class VersionRange
{
private readonly Version _specificVersion;
private readonly Version _startVersion;
private readonly Version _endVersion;
public VersionRange(Version specificVersion)
{
_specificVersion = specificVersion;
}
public VersionRange(Version startVersion, Version endVersion)
{
_startVersion = startVersion;
_endVersion = endVersion;
}
/// <summary>
/// Checks if the versionCheck is in the range (in between) the start and end version
/// </summary>
/// <param name="versionCheck"></param>
/// <returns></returns>
/// <remarks>
/// For example if our version range is 4.10 -> 4.11.4, we want to return true if the version being checked is:
/// greater than or equal to the start version but less than the end version.
/// </remarks>
public bool InRange(Version versionCheck)
{
//if it is a specific version
if (_specificVersion != null)
return versionCheck == _specificVersion;
return versionCheck >= _startVersion && versionCheck < _endVersion;
}
}
}

View File

@@ -30,3 +30,4 @@ using System.Security;
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("umbraco.MacroEngines")]
[assembly: InternalsVisibleTo("umbraco.webservices")]
[assembly: InternalsVisibleTo("Umbraco.Web.UI")]

View File

@@ -252,6 +252,11 @@
<Compile Include="Dynamics\Grouping.cs" />
<Compile Include="Install\InstallPackageController.cs" />
<Compile Include="Install\UmbracoInstallAuthorizeAttribute.cs" />
<Compile Include="Install\UpgradeScripts\ContentPathFix.cs" />
<Compile Include="Install\UpgradeScripts\IUpgradeScript.cs" />
<Compile Include="Install\UpgradeScripts\UpgradeScriptRegistrar.cs" />
<Compile Include="Install\UpgradeScripts\UpgradeScriptManager.cs" />
<Compile Include="Install\UpgradeScripts\VersionRange.cs" />
<Compile Include="Macros\PartialViewMacroController.cs" />
<Compile Include="Macros\PartialViewMacroEngine.cs" />
<Compile Include="Macros\PartialViewMacroPage.cs" />
@@ -329,6 +334,7 @@
<Compile Include="umbraco.presentation\install\Default.aspx.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="umbraco.presentation\install\steps\Definitions\UpgradeScripts.cs" />
<Compile Include="umbraco.presentation\install\steps\Skinning\loadStarterKitDesigns.ascx.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
@@ -1894,7 +1900,9 @@
<SubType>ASPXCodeBehind</SubType>
</Content>
<Content Include="umbraco.presentation\umbraco\controls\Tree\TreeControl.ascx" />
<Content Include="umbraco.presentation\install\steps\license.ascx" />
<Content Include="umbraco.presentation\install\steps\license.ascx">
<SubType>ASPXCodeBehind</SubType>
</Content>
<Content Include="umbraco.presentation\umbraco\actions\delete.aspx" />
<Content Include="umbraco.presentation\umbraco\actions\editContent.aspx" />
<Content Include="umbraco.presentation\umbraco\actions\preview.aspx" />

View File

@@ -9,6 +9,7 @@ using Umbraco.Core.Dictionary;
using Umbraco.Core.Dynamics;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Dictionary;
using Umbraco.Web.Install.UpgradeScripts;
using Umbraco.Web.Media;
using Umbraco.Web.Media.ThumbnailProviders;
using Umbraco.Web.Models;
@@ -90,6 +91,7 @@ namespace Umbraco.Web
//add the internal types since we don't want to mark these public
ApplicationEventsResolver.Current.AddType<CacheHelperExtensions.CacheHelperApplicationEventListener>();
ApplicationEventsResolver.Current.AddType<LegacyScheduledTasks>();
ApplicationEventsResolver.Current.AddType<UpgradeScriptRegistrar>();
//now we need to call the initialize methods
ApplicationEventsResolver.Current.ApplicationEventHandlers

View File

@@ -9,7 +9,7 @@ using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Collections.Specialized;
using umbraco.IO;
using Umbraco.Core.IO;
using umbraco.cms.businesslogic.installer;
using System.Collections.Generic;
@@ -36,7 +36,7 @@ namespace umbraco.presentation.install
private void loadContent(InstallerStep currentStep)
{
PlaceHolderStep.Controls.Clear();
PlaceHolderStep.Controls.Add(new System.Web.UI.UserControl().LoadControl(IOHelper.ResolveUrl(currentStep.UserControl)));
PlaceHolderStep.Controls.Add(LoadControl(IOHelper.ResolveUrl(currentStep.UserControl)));
step.Value = currentStep.Alias;
currentStepClass = currentStep.Alias;
}
@@ -136,6 +136,7 @@ namespace umbraco.presentation.install
ics.Add(new install.steps.Definitions.Welcome());
ics.Add(new install.steps.Definitions.License());
ics.Add(new install.steps.Definitions.FilePermissions());
ics.Add(new install.steps.Definitions.UpgradeScripts());
ics.Add(new install.steps.Definitions.Database());
ics.Add(new install.steps.Definitions.DefaultUser());
ics.Add(new install.steps.Definitions.Skinning());

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.IO;
using umbraco.cms.businesslogic.installer;
using umbraco.IO;
using umbraco.DataLayer.Utility.Installer;
using umbraco.DataLayer;
@@ -18,7 +18,7 @@ namespace umbraco.presentation.install.steps.Definitions
public override string Name
{
get { return "Database"; }
get { return "Database"; }
}
public override string UserControl
@@ -26,7 +26,7 @@ namespace umbraco.presentation.install.steps.Definitions
get { return SystemDirectories.Install + "/steps/database.ascx"; }
}
public override bool MoveToNextStepAutomaticly
{
get
@@ -38,13 +38,14 @@ namespace umbraco.presentation.install.steps.Definitions
//here we determine if the installer should skip this step...
public override bool Completed()
{
bool retval = false;
bool retval;
try
{
IInstallerUtility m_Installer = BusinessLogic.Application.SqlHelper.Utility.CreateInstaller();
retval = m_Installer.IsLatestVersion;
m_Installer = null;
} catch {
var installer = BusinessLogic.Application.SqlHelper.Utility.CreateInstaller();
retval = installer.IsLatestVersion;
}
catch
{
// this step might fail due to missing connectionstring
return false;
}
@@ -52,6 +53,6 @@ namespace umbraco.presentation.install.steps.Definitions
return retval;
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.IO;
using umbraco.cms.businesslogic.installer;
namespace umbraco.presentation.install.steps.Definitions
@@ -20,7 +21,7 @@ namespace umbraco.presentation.install.steps.Definitions
public override string UserControl
{
get { return IO.SystemDirectories.Install + "/steps/validatepermissions.ascx"; }
get { return SystemDirectories.Install + "/steps/validatepermissions.ascx"; }
}
public override bool HideFromNavigation {

View File

@@ -0,0 +1,78 @@
using System;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Web.Install.UpgradeScripts;
using umbraco.DataLayer.Utility.Installer;
using umbraco.cms.businesslogic.installer;
namespace umbraco.presentation.install.steps.Definitions
{
internal class UpgradeScripts : InstallerStep
{
public override string Alias
{
get { return "upgradeScripts"; }
}
public override bool HideFromNavigation
{
get { return true; }
}
/// <summary>
/// If there are no scripts for this version the skip
/// </summary>
/// <returns></returns>
public override bool Completed()
{
var canConnect = CanConnectToDb();
//if we cannot connect to the db, then we cannot run the script and most likely the database doesn't exist yet anyways.
if (!canConnect) return true; //skip
//if the version is empty then it's probably a new installation, we cannot run scripts
if (GlobalSettings.CurrentVersion.IsNullOrWhiteSpace()) return true; //skip
var currentUmbVersion = Umbraco.Core.Configuration.GlobalSettings.GetConfigurationVersion();
if (currentUmbVersion == null)
return true; //skip, could not get a version
//check if we have upgrade script to run for this version
var hasScripts = UpgradeScriptManager.HasScriptsForVersion(currentUmbVersion);
return !hasScripts;
}
public override string Name
{
get { return "Upgrade scripts"; }
}
public override string UserControl
{
get { return SystemDirectories.Install + "/steps/UpgradeScripts.ascx"; }
}
public override bool MoveToNextStepAutomaticly
{
get
{
return true;
}
}
private bool CanConnectToDb()
{
try
{
var installer = BusinessLogic.Application.SqlHelper.Utility.CreateInstaller();
var latest = installer.IsLatestVersion;
return true; //if we got this far, we can connect.
}
catch
{
return false;
}
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Umbraco.Web.Install.UpgradeScripts;
using umbraco.DataLayer.Utility.Installer;
using umbraco.DataLayer;
@@ -97,6 +98,8 @@ namespace umbraco.presentation.install.utills
Helper.setProgress(100, "Database is up-to-date", "");
return "upgraded";
}
else
{
@@ -136,6 +139,7 @@ namespace umbraco.presentation.install.utills
installer = null;
library.RefreshContent();
return "upgraded";
}
}
@@ -146,5 +150,8 @@ namespace umbraco.presentation.install.utills
return "no connection;";
}
}
}