From bbf1a05681d700580040ef2bd91d5c8ef548e20a Mon Sep 17 00:00:00 2001 From: boxbinary Date: Tue, 16 Feb 2010 04:12:10 +0000 Subject: [PATCH] WORK IN PROGRESS, GET THE STABLE SOURCE FROM THE DOWNLOADS TAB Merging into main 4.1.0 branch: Resolves a number of open issues, see associated WorkItems - Fix WP crash on invalid connection string due to generation of XML cache on a separate thread - Added option to generate xml cache file on local filesystem (in ASP.NET Temporary Files / CodeGenDir location) by adding to web.config file. This allows you to run from a SAN on a load-balanced environment whilst allowing each web node to have isolated copies of the xml cache file to avoid WP crashes due to multiple file locks - Others in WorkItems :) [TFS Changeset #64002] --- umbraco/businesslogic/BasePages/BasePage.cs | 46 ++++-- umbraco/businesslogic/IO/IOHelper.cs | 4 + umbraco/businesslogic/IO/SystemFiles.cs | 22 +++ umbraco/cms/businesslogic/web/DocumentType.cs | 35 ++++- umbraco/presentation/content.cs | 140 ++++++++++++++---- .../presentation/install/steps/detect.ascx.cs | 5 + umbraco/presentation/requestModule.cs | 13 +- .../umbraco_client/CodeMirror/css/docs.css | 4 + umbraco/presentation/web.STANDARD.config | 3 +- umbraco/presentation/web.config | 4 +- 10 files changed, 221 insertions(+), 55 deletions(-) diff --git a/umbraco/businesslogic/BasePages/BasePage.cs b/umbraco/businesslogic/BasePages/BasePage.cs index 373a5c4bd4..e42e004c4a 100644 --- a/umbraco/businesslogic/BasePages/BasePage.cs +++ b/umbraco/businesslogic/BasePages/BasePage.cs @@ -181,26 +181,40 @@ namespace umbraco.BasePages { /// /// The umbraco user context ID. public static string umbracoUserContextID { - get { - if (System.Web.HttpContext.Current.Request.Cookies.Get("UserContext") != null) - return System.Web.HttpContext.Current.Request.Cookies.Get("UserContext").Value; - else - return ""; + get + { + if (HttpContext.Current != null) + if (HttpContext.Current.Request != null) + if (HttpContext.Current.Request.Cookies != null) + { + HttpCookie userContext = HttpContext.Current.Request.Cookies.Get("UserContext"); + if (userContext != null) + return userContext.Value; + } + return ""; } set { - // Clearing all old cookies before setting a new one. - try { - if (System.Web.HttpContext.Current.Request.Cookies["UserContext"] != null) { - System.Web.HttpContext.Current.Response.Cookies.Clear(); + if (HttpContext.Current != null) + { + // Clearing all old cookies before setting a new one. + try + { + if (HttpContext.Current.Request != null) + if (HttpContext.Current.Request.Cookies["UserContext"] != null) + { + HttpContext.Current.Response.Cookies.Clear(); + } } - } catch { + catch + { + } + // Create new cookie. + var c = new HttpCookie("UserContext"); + c.Name = "UserContext"; + c.Value = value; + c.Expires = DateTime.Now.AddDays(1); + HttpContext.Current.Response.Cookies.Add(c); } - // Create new cookie. - System.Web.HttpCookie c = new System.Web.HttpCookie("UserContext"); - c.Name = "UserContext"; - c.Value = value; - c.Expires = DateTime.Now.AddDays(1); - System.Web.HttpContext.Current.Response.Cookies.Add(c); } } diff --git a/umbraco/businesslogic/IO/IOHelper.cs b/umbraco/businesslogic/IO/IOHelper.cs index 7d229049d8..e5fabe343c 100644 --- a/umbraco/businesslogic/IO/IOHelper.cs +++ b/umbraco/businesslogic/IO/IOHelper.cs @@ -46,6 +46,10 @@ namespace umbraco.IO public static string MapPath(string path, bool useHttpContext) { + // Check if the path is already mapped + if (path.Length >= 2 && path[1] == Path.VolumeSeparatorChar) + return path; + if (useHttpContext) { //string retval; diff --git a/umbraco/businesslogic/IO/SystemFiles.cs b/umbraco/businesslogic/IO/SystemFiles.cs index a24639f6e5..48c30aaba9 100644 --- a/umbraco/businesslogic/IO/SystemFiles.cs +++ b/umbraco/businesslogic/IO/SystemFiles.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Configuration; +using System.IO; using System.Linq; using System.Text; +using System.Web; namespace umbraco.IO { @@ -76,8 +79,27 @@ namespace umbraco.IO { get { + if (ContentCacheXmlIsEphemeral) + { + return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config"); + } return IOHelper.returnPath("umbracoContentXML", "~/data/umbraco.config"); } } + + public static bool ContentCacheXmlIsEphemeral + { + get + { + bool returnValue = false; + string configSetting = ConfigurationManager.AppSettings["umbracoContentXMLUseLocalTemp"]; + + if (!string.IsNullOrEmpty(configSetting)) + if(bool.TryParse(configSetting, out returnValue)) + return returnValue; + + return false; + } + } } } diff --git a/umbraco/cms/businesslogic/web/DocumentType.cs b/umbraco/cms/businesslogic/web/DocumentType.cs index fe3e4d9e22..7d51541897 100644 --- a/umbraco/cms/businesslogic/web/DocumentType.cs +++ b/umbraco/cms/businesslogic/web/DocumentType.cs @@ -320,7 +320,10 @@ namespace umbraco.cms.businesslogic.web public static string GenerateDtd() { StringBuilder dtd = new StringBuilder(); - dtd.AppendLine(" dts = GetAllAsList(); - foreach (DocumentType dt in dts) + // TEMPORARY: Added Try-Catch to this call since trying to generate a DTD against a corrupt db + // or a broken connection string is not handled yet + // (Alex N 20100212) + try { - string safeAlias = helpers.Casing.SafeAlias(dt.Alias); - dtd.AppendLine(String.Format("", safeAlias)); - dtd.AppendLine(String.Format("", safeAlias)); + StringBuilder strictSchemaBuilder = new StringBuilder(); + + List dts = GetAllAsList(); + foreach (DocumentType dt in dts) + { + string safeAlias = helpers.Casing.SafeAlias(dt.Alias); + if (safeAlias != null) + { + strictSchemaBuilder.AppendLine(String.Format("", safeAlias)); + strictSchemaBuilder.AppendLine(String.Format("", safeAlias)); + } + } + + // Only commit the strong schema to the container if we didn't generate an error building it + dtd.Append(strictSchemaBuilder); } + catch (Exception exception) + { + // Note, Log.Add quietly swallows the exception if it can't write to the database + Log.Add(LogTypes.System, -1, string.Format("{0} while trying to build DTD for Xml schema; is Umbraco installed correctly and the connection string configured?", exception.Message)); + } + } dtd.AppendLine("]>"); diff --git a/umbraco/presentation/content.cs b/umbraco/presentation/content.cs index 131f92df36..ab40a024ed 100644 --- a/umbraco/presentation/content.cs +++ b/umbraco/presentation/content.cs @@ -29,7 +29,25 @@ namespace umbraco { #region Declarations - private readonly string UmbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml, false); + private string _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml, false); + + /// + /// Gets the path of the umbraco XML disk cache file. + /// + /// The name of the umbraco XML disk cache file. + public string UmbracoXmlDiskCacheFileName + { + get + { + return _umbracoXmlDiskCacheFileName; + } + set + { + _umbracoXmlDiskCacheFileName = value; + } + } + + /* HttpRuntime.AppDomainAppPath + '\\' + SystemFiles.ContentCacheXml.Replace('/', '\\').TrimStart('\\'); @@ -60,13 +78,16 @@ namespace umbraco static content() { - Trace.Write("Initializing content"); - ThreadPool.QueueUserWorkItem( - delegate - { - XmlDocument xmlDoc = Instance.XmlContentInternal; - Trace.WriteLine("Content initialized"); - }); + //Trace.Write("Initializing content"); + //ThreadPool.QueueUserWorkItem( + // delegate + // { + // XmlDocument xmlDoc = Instance.XmlContentInternal; + // Trace.WriteLine("Content initialized"); + // }); + + Trace.WriteLine("Checking for xml content initialisation..."); + Instance.CheckXmlContentPopulation(); } #endregion @@ -124,18 +145,7 @@ namespace umbraco { get { - if (isInitializing) - { - lock (_xmlContentInternalSyncLock) - { - if (isInitializing) - { - _xmlContent = LoadContent(); - if (!UmbracoSettings.isXmlContentCacheDisabled && !IsValidDiskCachePresent()) - SaveContentToDiskAsync(_xmlContent); - } - } - } + CheckXmlContentPopulation(); return _xmlContent; } @@ -157,6 +167,36 @@ namespace umbraco } } + /// + /// Triggers the XML content population if necessary. + /// + /// + private bool CheckXmlContentPopulation() + { + if (isInitializing) + { + lock (_xmlContentInternalSyncLock) + { + if (isInitializing) + { + Trace.WriteLine(string.Format("Initializing content on thread '{0}' (Threadpool? {1})", Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread.ToString())); + _xmlContent = LoadContent(); + Trace.WriteLine("Content initialized (loaded)"); + + // Only save new XML cache to disk if we just repopulated it + // TODO: Re-architect this so that a call to this method doesn't invoke a new thread for saving disk cache + if (!UmbracoSettings.isXmlContentCacheDisabled && !IsValidDiskCachePresent()) + { + SaveContentToDiskAsync(_xmlContent); + } + return true; + } + } + } + Trace.WriteLine("Content initialized (was already in context)"); + return false; + } + #endregion protected static ISqlHelper SqlHelper @@ -164,6 +204,8 @@ namespace umbraco get { return Application.SqlHelper; } } + + #region Public Methods /// @@ -760,15 +802,30 @@ namespace umbraco private XmlDocument LoadContentFromDatabase() { XmlDocument xmlDoc = new XmlDocument(); - InitContentDocumentBase(xmlDoc); + + // Moved User to a local variable - why are we causing user 0 to load from the DB though? + // Alex N 20100212 + User staticUser = null; + try + { + staticUser = User.GetCurrent(); //User.GetUser(0); + } + catch + { + /* We don't care later if the staticUser is null */ + } - Log.Add(LogTypes.System, User.GetUser(0), -1, "Loading content from database..."); + // Try to log to the DB + Log.Add(LogTypes.System, staticUser, -1, "Loading content from database..."); + + // Moved this to after the logging since the 2010 schema accesses the DB just to generate the DTD + InitContentDocumentBase(xmlDoc); Hashtable nodes = new Hashtable(); Hashtable parents = new Hashtable(); try { - Log.Add(LogTypes.Debug, User.GetUser(0), -1, "Republishing starting"); + Log.Add(LogTypes.Debug, staticUser, -1, "Republishing starting"); // Esben Carlsen: At some point we really need to put all data access into to a tier of its own. string sql = @@ -800,8 +857,9 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } } - Log.Add(LogTypes.Debug, User.GetUser(0), -1, "Xml Pages loaded"); + Log.Add(LogTypes.Debug, staticUser, -1, "Xml Pages loaded"); + // TODO: Why is the following line here, it should have already been generated? Alex N 20100212 // Reset InitContentDocumentBase(xmlDoc); @@ -811,23 +869,23 @@ order by umbracoNode.level, umbracoNode.sortOrder"; } catch (Exception ee) { - Log.Add(LogTypes.Error, User.GetUser(0), -1, + Log.Add(LogTypes.Error, staticUser, -1, string.Format("Error while generating XmlDocument from database: {0}", ee)); } } catch (OutOfMemoryException) { - Log.Add(LogTypes.Error, User.GetUser(0), -1, + Log.Add(LogTypes.Error, staticUser, -1, string.Format("Error Republishing: Out Of Memory. Parents: {0}, Nodes: {1}", parents.Count, nodes.Count)); } catch (Exception ee) { - Log.Add(LogTypes.Error, User.GetUser(0), -1, string.Format("Error Republishing: {0}", ee)); + Log.Add(LogTypes.Error, staticUser, -1, string.Format("Error Republishing: {0}", ee)); } finally { - Log.Add(LogTypes.Debug, User.GetUser(0), -1, "Done republishing Xml Index"); + Log.Add(LogTypes.Debug, staticUser, -1, "Done republishing Xml Index"); } return xmlDoc; @@ -874,20 +932,42 @@ order by umbracoNode.level, umbracoNode.sortOrder"; { lock (_readerWriterSyncLock) { + Trace.Write(string.Format("Saving content to disk on thread '{0}' (Threadpool? {1})", Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread.ToString())); + + // Moved the user into a variable and avoided it throwing an error if one can't be loaded (e.g. empty / corrupt db on initial install) + User staticUser = null; + try + { + staticUser = User.GetCurrent(); + } + catch + {} + try { Stopwatch stopWatch = Stopwatch.StartNew(); ClearDiskCache(); + + // Try to create directory for cache path if it doesn't yet exist + if (!File.Exists(UmbracoXmlDiskCacheFileName) && !Directory.Exists(Path.GetDirectoryName(UmbracoXmlDiskCacheFileName))) + { + // We're already in a try-catch and saving will fail if this does, so don't need another + Directory.CreateDirectory(UmbracoXmlDiskCacheFileName); + } + xmlDoc.Save(UmbracoXmlDiskCacheFileName); - Log.Add(LogTypes.Debug, User.GetUser(0), -1, string.Format("Xml saved in {0}", stopWatch.Elapsed)); + Trace.Write(string.Format("Saved content on thread '{0}' in {1} (Threadpool? {2})", Thread.CurrentThread.Name, stopWatch.Elapsed, Thread.CurrentThread.IsThreadPoolThread.ToString())); + Log.Add(LogTypes.Debug, staticUser, -1, string.Format("Xml saved in {0}", stopWatch.Elapsed)); } catch (Exception ee) { // If for whatever reason something goes wrong here, invalidate disk cache ClearDiskCache(); - Log.Add(LogTypes.Error, User.GetUser(0), -1, string.Format("Xml wasn't saved: {0}", ee)); + + Trace.Write(string.Format("Error saving content on thread '{0}' due to '{1}' (Threadpool? {2})", Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread.ToString())); + Log.Add(LogTypes.Error, staticUser, -1, string.Format("Xml wasn't saved: {0}", ee)); } } } diff --git a/umbraco/presentation/install/steps/detect.ascx.cs b/umbraco/presentation/install/steps/detect.ascx.cs index f2fd26545b..649a3d65ca 100644 --- a/umbraco/presentation/install/steps/detect.ascx.cs +++ b/umbraco/presentation/install/steps/detect.ascx.cs @@ -83,6 +83,8 @@ namespace umbraco.presentation.install.steps DbConnectionStringBuilder connectionStringBuilder = new DbConnectionStringBuilder(); connectionStringBuilder.ConnectionString = GlobalSettings.DbDSN; + // "Data Source=.\\SQLEXPRESS;Initial Catalog=BB_Umbraco_Sandbox1;integrated security=false;user id=umbraco;pwd=umbraco" + // Prepare the fields // Prepare data layer type @@ -97,9 +99,12 @@ namespace umbraco.presentation.install.steps // Prepare other fields DatabaseServer.Text = GetConnectionStringValue(connectionStringBuilder, "server"); + if (string.IsNullOrEmpty(DatabaseServer.Text)) DatabaseServer.Text = GetConnectionStringValue(connectionStringBuilder, "Data Source"); DatabaseName.Text = GetConnectionStringValue(connectionStringBuilder, "database"); + if (string.IsNullOrEmpty(DatabaseName.Text)) DatabaseName.Text = GetConnectionStringValue(connectionStringBuilder, "Initial Catalog"); DatabaseUsername.Text = GetConnectionStringValue(connectionStringBuilder, "user id"); DatabasePassword.Text = GetConnectionStringValue(connectionStringBuilder, "password"); + if (string.IsNullOrEmpty(DatabasePassword.Text)) DatabasePassword.Text = GetConnectionStringValue(connectionStringBuilder, "pwd"); } // Make sure ASP.Net displays the password text diff --git a/umbraco/presentation/requestModule.cs b/umbraco/presentation/requestModule.cs index 1b857298cd..46ed967aa1 100644 --- a/umbraco/presentation/requestModule.cs +++ b/umbraco/presentation/requestModule.cs @@ -232,7 +232,18 @@ namespace umbraco.presentation else error = "No Context available -> " + mApp.Context.Server.GetLastError().InnerException; - Log.Add(LogTypes.Error, User.GetUser(0), -1, error); + + // Hide error if getting the user throws an error (e.g. corrupt / blank db) + User staticUser = null; + try + { + User.GetCurrent(); + } + catch + { + } + + Log.Add(LogTypes.Error, staticUser, -1, error); Trace.TraceError(error); lock (unhandledErrors) { diff --git a/umbraco/presentation/umbraco_client/CodeMirror/css/docs.css b/umbraco/presentation/umbraco_client/CodeMirror/css/docs.css index c86b7d7c51..88ea256097 100644 --- a/umbraco/presentation/umbraco_client/CodeMirror/css/docs.css +++ b/umbraco/presentation/umbraco_client/CodeMirror/css/docs.css @@ -17,6 +17,10 @@ h2 { font-size: 14pt; } +h3 { + font-size: 12pt; +} + p.rel { padding-left: 2em; text-indent: -2em; diff --git a/umbraco/presentation/web.STANDARD.config b/umbraco/presentation/web.STANDARD.config index 57374c21e9..25ee4719ef 100644 --- a/umbraco/presentation/web.STANDARD.config +++ b/umbraco/presentation/web.STANDARD.config @@ -47,6 +47,7 @@ + @@ -226,4 +227,4 @@ - \ No newline at end of file + diff --git a/umbraco/presentation/web.config b/umbraco/presentation/web.config index b7699b83bf..be28a290aa 100644 --- a/umbraco/presentation/web.config +++ b/umbraco/presentation/web.config @@ -28,7 +28,7 @@ - + @@ -47,6 +47,8 @@ + +