1349 lines
54 KiB
C#
1349 lines
54 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Web;
|
|
using System.Xml;
|
|
using System.Xml.XPath;
|
|
using umbraco.BusinessLogic;
|
|
using umbraco.BusinessLogic.Actions;
|
|
using umbraco.BusinessLogic.Utils;
|
|
using umbraco.cms.businesslogic;
|
|
using umbraco.cms.businesslogic.cache;
|
|
using umbraco.cms.businesslogic.web;
|
|
using umbraco.DataLayer;
|
|
using umbraco.IO;
|
|
using umbraco.presentation.nodeFactory;
|
|
using Action = umbraco.BusinessLogic.Actions.Action;
|
|
using Node = umbraco.NodeFactory.Node;
|
|
|
|
namespace umbraco
|
|
{
|
|
/// <summary>
|
|
/// Handles umbraco content
|
|
/// </summary>
|
|
public class content
|
|
{
|
|
#region Declarations
|
|
|
|
// Sync access to disk file
|
|
private static readonly object _readerWriterSyncLock = new object();
|
|
|
|
// Sync access to internal cache
|
|
private static readonly object _xmlContentInternalSyncLock = new object();
|
|
|
|
// Sync access to timestamps
|
|
private static readonly object _timestampSyncLock = new object();
|
|
|
|
// Sync database access
|
|
private static readonly object _dbReadSyncLock = new object();
|
|
private readonly string XmlContextContentItemKey = "UmbracoXmlContextContent";
|
|
private string _umbracoXmlDiskCacheFileName = string.Empty;
|
|
private volatile XmlDocument _xmlContent;
|
|
private DateTime _lastDiskCacheReadTime = DateTime.MinValue;
|
|
private DateTime _lastDiskCacheCheckTime = DateTime.MinValue;
|
|
|
|
/// <summary>
|
|
/// Gets the path of the umbraco XML disk cache file.
|
|
/// </summary>
|
|
/// <value>The name of the umbraco XML disk cache file.</value>
|
|
public string UmbracoXmlDiskCacheFileName
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName))
|
|
{
|
|
_umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml);
|
|
}
|
|
return _umbracoXmlDiskCacheFileName;
|
|
}
|
|
set { _umbracoXmlDiskCacheFileName = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
static content()
|
|
{
|
|
//Trace.Write("Initializing content");
|
|
//ThreadPool.QueueUserWorkItem(
|
|
// delegate
|
|
// {
|
|
// XmlDocument xmlDoc = Instance.XmlContentInternal;
|
|
// Trace.WriteLine("Content initialized");
|
|
// });
|
|
|
|
Trace.WriteLine("Checking for xml content initialisation...");
|
|
Instance.CheckXmlContentPopulation();
|
|
}
|
|
|
|
public content()
|
|
{
|
|
;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Singleton
|
|
|
|
public static content Instance
|
|
{
|
|
get { return Singleton<content>.Instance; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Get content. First call to this property will initialize xmldoc
|
|
/// subsequent calls will be blocked until initialization is done
|
|
/// Further we cache(in context) xmlContent for each request to ensure that
|
|
/// we always have the same XmlDoc throughout the whole request.
|
|
/// </summary>
|
|
public virtual XmlDocument XmlContent
|
|
{
|
|
get
|
|
{
|
|
if (HttpContext.Current == null)
|
|
return XmlContentInternal;
|
|
var content = HttpContext.Current.Items[XmlContextContentItemKey] as XmlDocument;
|
|
if (content == null)
|
|
{
|
|
content = XmlContentInternal;
|
|
HttpContext.Current.Items[XmlContextContentItemKey] = content;
|
|
}
|
|
return content;
|
|
}
|
|
}
|
|
|
|
[Obsolete("Please use: content.Instance.XmlContent")]
|
|
public static XmlDocument xmlContent
|
|
{
|
|
get { return Instance.XmlContent; }
|
|
}
|
|
|
|
public virtual bool isInitializing
|
|
{
|
|
get { return _xmlContent == null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal reference to XmlContent
|
|
/// </summary>
|
|
protected virtual XmlDocument XmlContentInternal
|
|
{
|
|
get
|
|
{
|
|
CheckXmlContentPopulation();
|
|
|
|
return _xmlContent;
|
|
}
|
|
set
|
|
{
|
|
lock (_xmlContentInternalSyncLock)
|
|
{
|
|
// Clear macro cache
|
|
Cache.ClearCacheObjectTypes("umbraco.MacroCacheContent");
|
|
Cache.ClearCacheByKeySearch("macroHtml_");
|
|
|
|
// Clear library cache
|
|
if (UmbracoSettings.UmbracoLibraryCacheDuration > 0)
|
|
{
|
|
Cache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator");
|
|
}
|
|
requestHandler.ClearProcessedRequests();
|
|
_xmlContent = value;
|
|
|
|
if (!UmbracoSettings.isXmlContentCacheDisabled && UmbracoSettings.continouslyUpdateXmlDiskCache)
|
|
QueueXmlForPersistence();
|
|
else
|
|
// Clear cache...
|
|
DeleteXmlCache();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the disk cache file has been updated and if so, clears the in-memory cache to force the file to be read.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Added to trigger updates of the in-memory cache when the disk cache file is updated.
|
|
/// </remarks>
|
|
private void CheckDiskCacheForUpdate()
|
|
{
|
|
if (UmbracoSettings.isXmlContentCacheDisabled)
|
|
return;
|
|
|
|
lock (_timestampSyncLock)
|
|
{
|
|
if (_lastDiskCacheCheckTime > DateTime.UtcNow.AddSeconds(-1.0))
|
|
return;
|
|
|
|
_lastDiskCacheCheckTime = DateTime.UtcNow;
|
|
|
|
lock (_xmlContentInternalSyncLock)
|
|
{
|
|
|
|
if (GetCacheFileUpdateTime() <= _lastDiskCacheReadTime)
|
|
return;
|
|
|
|
_xmlContent = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the XML content population if necessary.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool CheckXmlContentPopulation()
|
|
{
|
|
if (UmbracoSettings.XmlContentCheckForDiskChanges)
|
|
CheckDiskCacheForUpdate();
|
|
|
|
if (isInitializing)
|
|
{
|
|
lock (_xmlContentInternalSyncLock)
|
|
{
|
|
if (isInitializing)
|
|
{
|
|
Trace.WriteLine(string.Format("Initializing content on thread '{0}' (Threadpool? {1})",
|
|
Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread));
|
|
_xmlContent = LoadContent();
|
|
Trace.WriteLine("Content initialized (loaded)");
|
|
|
|
FireAfterRefreshContent(new RefreshContentEventArgs());
|
|
|
|
// 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())
|
|
{
|
|
QueueXmlForPersistence();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
Trace.WriteLine("Content initialized (was already in context)");
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
protected static ISqlHelper SqlHelper
|
|
{
|
|
get { return Application.SqlHelper; }
|
|
}
|
|
|
|
protected static void ReGenerateSchema(XmlDocument xmlDoc)
|
|
{
|
|
string dtd = DocumentType.GenerateXmlDocumentType();
|
|
|
|
// remove current doctype
|
|
XmlNode n = xmlDoc.FirstChild;
|
|
while (n.NodeType != XmlNodeType.DocumentType && n.NextSibling != null)
|
|
{
|
|
n = n.NextSibling;
|
|
}
|
|
if (n.NodeType == XmlNodeType.DocumentType)
|
|
{
|
|
xmlDoc.RemoveChild(n);
|
|
}
|
|
XmlDocumentType docType = xmlDoc.CreateDocumentType("root", null, null, dtd);
|
|
xmlDoc.InsertAfter(docType, xmlDoc.FirstChild);
|
|
}
|
|
|
|
protected static void ValidateSchema(string docTypeAlias, XmlDocument xmlDoc)
|
|
{
|
|
// if doctype is not defined i)n schema, then regenerate it
|
|
if (
|
|
!xmlDoc.DocumentType.InternalSubset.Contains(String.Format("<!ATTLIST {0} id ID #REQUIRED>",
|
|
docTypeAlias)))
|
|
{
|
|
// we need to re-load the content, else the dtd changes won't be picked up by the XmlDocument
|
|
Instance.XmlContentInternal = Instance.LoadContentFromDatabase();
|
|
}
|
|
}
|
|
|
|
#region Public Methods
|
|
|
|
#region Delegates
|
|
|
|
/// <summary>
|
|
/// Occurs when [after loading the xml string from the database].
|
|
/// </summary>
|
|
public delegate void ContentCacheDatabaseLoadXmlStringEventHandler(
|
|
ref string xml, ContentCacheLoadNodeEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Occurs when [after loading the xml string from the database and creating the xml node].
|
|
/// </summary>
|
|
public delegate void ContentCacheLoadNodeEventHandler(XmlNode xmlNode, ContentCacheLoadNodeEventArgs e);
|
|
|
|
public delegate void DocumentCacheEventHandler(Document sender, DocumentCacheEventArgs e);
|
|
|
|
public delegate void RefreshContentEventHandler(Document sender, RefreshContentEventArgs e);
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Load content from database in a background thread
|
|
/// Replaces active content when done.
|
|
/// </summary>
|
|
public virtual void RefreshContentFromDatabaseAsync()
|
|
{
|
|
var e = new RefreshContentEventArgs();
|
|
FireBeforeRefreshContent(e);
|
|
|
|
if (!e.Cancel)
|
|
{
|
|
ThreadPool.QueueUserWorkItem(
|
|
delegate
|
|
{
|
|
XmlDocument xmlDoc = LoadContentFromDatabase();
|
|
XmlContentInternal = xmlDoc;
|
|
|
|
// It is correct to manually call PersistXmlToFile here event though the setter of XmlContentInternal
|
|
// queues this up, because this delegate is executing on a different thread and may complete
|
|
// after the request which invoked it (which would normally persist the file on completion)
|
|
// So we are responsible for ensuring the content is persisted in this case.
|
|
if (!UmbracoSettings.isXmlContentCacheDisabled &&
|
|
UmbracoSettings.continouslyUpdateXmlDiskCache)
|
|
PersistXmlToFile(xmlDoc);
|
|
});
|
|
|
|
FireAfterRefreshContent(e);
|
|
}
|
|
}
|
|
|
|
public static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode DocumentNode, XmlNode PublishedNode)
|
|
{
|
|
// Remove all attributes and data nodes from the published node
|
|
PublishedNode.Attributes.RemoveAll();
|
|
string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./data" : "./* [not(@id)]";
|
|
foreach (XmlNode n in PublishedNode.SelectNodes(xpath))
|
|
PublishedNode.RemoveChild(n);
|
|
|
|
// Append all attributes and datanodes from the documentnode to the publishednode
|
|
foreach (XmlAttribute att in DocumentNode.Attributes)
|
|
((XmlElement)PublishedNode).SetAttribute(att.Name, att.Value);
|
|
|
|
foreach (XmlElement el in DocumentNode.SelectNodes(xpath))
|
|
{
|
|
XmlNode newDatael = PublishedNode.OwnerDocument.ImportNode(el, true);
|
|
PublishedNode.AppendChild(newDatael);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by all overloaded publish methods to do the actual "noderepresentation to xml"
|
|
/// </summary>
|
|
/// <param name="d"></param>
|
|
/// <param name="xmlContentCopy"></param>
|
|
public static void PublishNodeDo(Document d, XmlDocument xmlContentCopy, bool updateSitemapProvider)
|
|
{
|
|
// check if document *is* published, it could be unpublished by an event
|
|
if (d.Published)
|
|
{
|
|
int parentId = d.Level == 1 ? -1 : d.Parent.Id;
|
|
AppendDocumentXml(d.Id, d.Level, parentId, getPreviewOrPublishedNode(d, xmlContentCopy, false),
|
|
xmlContentCopy);
|
|
|
|
// update sitemapprovider
|
|
if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider)
|
|
{
|
|
try
|
|
{
|
|
var prov = (UmbracoSiteMapProvider)SiteMap.Provider;
|
|
Node n = new Node(d.Id, true);
|
|
if (!String.IsNullOrEmpty(n.Url) && n.Url != "/#")
|
|
prov.UpdateNode(n);
|
|
else
|
|
{
|
|
Log.Add(LogTypes.Error, d.Id, "Can't update Sitemap Provider due to empty Url in node");
|
|
}
|
|
}
|
|
catch (Exception ee)
|
|
{
|
|
Log.Add(LogTypes.Error, d.Id, string.Format("Error adding node to Sitemap Provider in PublishNodeDo(): {0}", ee));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void AppendDocumentXml(int id, int level, int parentId, XmlNode docXml, XmlDocument xmlContentCopy)
|
|
{
|
|
// Validate schema (that a definition of the current document type exists in the DTD
|
|
if (!UmbracoSettings.UseLegacyXmlSchema)
|
|
{
|
|
ValidateSchema(docXml.Name, xmlContentCopy);
|
|
}
|
|
|
|
// Find the document in the xml cache
|
|
XmlNode x = xmlContentCopy.GetElementById(id.ToString());
|
|
|
|
// Find the parent (used for sortering and maybe creation of new node)
|
|
XmlNode parentNode;
|
|
if (level == 1)
|
|
parentNode = xmlContentCopy.DocumentElement;
|
|
else
|
|
parentNode = xmlContentCopy.GetElementById(parentId.ToString());
|
|
|
|
if (parentNode != null)
|
|
{
|
|
if (x == null)
|
|
{
|
|
x = docXml;
|
|
parentNode.AppendChild(x);
|
|
}
|
|
else
|
|
TransferValuesFromDocumentXmlToPublishedXml(docXml, x);
|
|
|
|
// TODO: Update with new schema!
|
|
string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./node" : "./* [@id]";
|
|
XmlNodeList childNodes = parentNode.SelectNodes(xpath);
|
|
|
|
// Maybe sort the nodes if the added node has a lower sortorder than the last
|
|
if (childNodes.Count > 0)
|
|
{
|
|
int siblingSortOrder =
|
|
int.Parse(childNodes[childNodes.Count - 1].Attributes.GetNamedItem("sortOrder").Value);
|
|
int currentSortOrder = int.Parse(x.Attributes.GetNamedItem("sortOrder").Value);
|
|
if (childNodes.Count > 1 && siblingSortOrder > currentSortOrder)
|
|
{
|
|
SortNodes(ref parentNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static XmlNode getPreviewOrPublishedNode(Document d, XmlDocument xmlContentCopy, bool isPreview)
|
|
{
|
|
if (isPreview)
|
|
{
|
|
return d.ToPreviewXml(xmlContentCopy);
|
|
}
|
|
else
|
|
{
|
|
return d.ToXml(xmlContentCopy, false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sorts the documents.
|
|
/// </summary>
|
|
/// <param name="parentNode">The parent node.</param>
|
|
public static void SortNodes(ref XmlNode parentNode)
|
|
{
|
|
XmlNode n = parentNode.CloneNode(true);
|
|
|
|
// remove all children from original node
|
|
string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./node" : "./* [@id]";
|
|
foreach (XmlNode child in parentNode.SelectNodes(xpath))
|
|
parentNode.RemoveChild(child);
|
|
|
|
|
|
XPathNavigator nav = n.CreateNavigator();
|
|
XPathExpression expr = nav.Compile(xpath);
|
|
expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number);
|
|
XPathNodeIterator iterator = nav.Select(expr);
|
|
while (iterator.MoveNext())
|
|
parentNode.AppendChild(
|
|
((IHasXmlNode)iterator.Current).GetNode());
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Updates the document cache.
|
|
/// </summary>
|
|
/// <param name="pageId">The page id.</param>
|
|
public virtual void UpdateDocumentCache(int pageId)
|
|
{
|
|
var d = new Document(pageId);
|
|
UpdateDocumentCache(d);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Updates the document cache.
|
|
/// </summary>
|
|
/// <param name="d">The d.</param>
|
|
public virtual void UpdateDocumentCache(Document d)
|
|
{
|
|
var e = new DocumentCacheEventArgs();
|
|
FireBeforeUpdateDocumentCache(d, e);
|
|
|
|
if (!e.Cancel)
|
|
{
|
|
// We need to lock content cache here, because we cannot allow other threads
|
|
// making changes at the same time, they need to be queued
|
|
// Adding log entry before locking the xmlfile
|
|
lock (_xmlContentInternalSyncLock)
|
|
{
|
|
// Make copy of memory content, we cannot make changes to the same document
|
|
// the is read from elsewhere
|
|
if (UmbracoSettings.CloneXmlCacheOnPublish)
|
|
{
|
|
XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
|
|
|
|
PublishNodeDo(d, xmlContentCopy, true);
|
|
XmlContentInternal = xmlContentCopy;
|
|
}
|
|
else
|
|
{
|
|
PublishNodeDo(d, XmlContentInternal, true);
|
|
XmlContentInternal = _xmlContent;
|
|
}
|
|
|
|
ClearContextCache();
|
|
}
|
|
|
|
// clear cached field values
|
|
if (HttpContext.Current != null)
|
|
{
|
|
System.Web.Caching.Cache httpCache = HttpContext.Current.Cache;
|
|
string cachedFieldKeyStart = String.Format("contentItem{0}_", d.Id);
|
|
var foundKeys = new List<string>();
|
|
foreach (DictionaryEntry cacheItem in httpCache)
|
|
{
|
|
string key = cacheItem.Key.ToString();
|
|
if (key.StartsWith(cachedFieldKeyStart))
|
|
foundKeys.Add(key);
|
|
}
|
|
foreach (string foundKey in foundKeys)
|
|
{
|
|
httpCache.Remove(foundKey);
|
|
}
|
|
}
|
|
Action.RunActionHandlers(d, ActionPublish.Instance);
|
|
|
|
FireAfterUpdateDocumentCache(d, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the document cache for multiple documents
|
|
/// </summary>
|
|
/// <param name="Documents">The documents.</param>
|
|
public virtual void UpdateDocumentCache(List<Document> Documents)
|
|
{
|
|
// We need to lock content cache here, because we cannot allow other threads
|
|
// making changes at the same time, they need to be queued
|
|
int parentid = Documents[0].Id;
|
|
|
|
lock (_xmlContentInternalSyncLock)
|
|
{
|
|
// Make copy of memory content, we cannot make changes to the same document
|
|
// the is read from elsewhere
|
|
XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
|
|
foreach (Document d in Documents)
|
|
{
|
|
PublishNodeDo(d, xmlContentCopy, true);
|
|
}
|
|
XmlContentInternal = xmlContentCopy;
|
|
ClearContextCache();
|
|
}
|
|
|
|
foreach (Document d in Documents)
|
|
{
|
|
Action.RunActionHandlers(d, ActionPublish.Instance);
|
|
}
|
|
}
|
|
|
|
[Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)]
|
|
/// <summary>
|
|
/// Updates the document cache async.
|
|
/// </summary>
|
|
/// <param name="documentId">The document id.</param>
|
|
public virtual void UpdateDocumentCacheAsync(int documentId)
|
|
{
|
|
ThreadPool.QueueUserWorkItem(delegate { UpdateDocumentCache(documentId); });
|
|
}
|
|
|
|
|
|
[Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)]
|
|
/// <summary>
|
|
/// Clears the document cache async.
|
|
/// </summary>
|
|
/// <param name="documentId">The document id.</param>
|
|
public virtual void ClearDocumentCacheAsync(int documentId)
|
|
{
|
|
ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); });
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Clears the document cache and removes the document from the xml db cache.
|
|
/// This means the node gets unpublished from the website.
|
|
/// </summary>
|
|
/// <param name="documentId">The document id.</param>
|
|
public virtual void ClearDocumentCache(int documentId)
|
|
{
|
|
// Get the document
|
|
var d = new Document(documentId);
|
|
|
|
var e = new DocumentCacheEventArgs();
|
|
FireBeforeClearDocumentCache(d, e);
|
|
|
|
if (!e.Cancel)
|
|
{
|
|
XmlNode x;
|
|
|
|
// remove from xml db cache
|
|
d.XmlRemoveFromDB();
|
|
|
|
// Check if node present, before cloning
|
|
x = XmlContentInternal.GetElementById(d.Id.ToString());
|
|
if (x == null)
|
|
return;
|
|
|
|
// We need to lock content cache here, because we cannot allow other threads
|
|
// making changes at the same time, they need to be queued
|
|
lock (_xmlContentInternalSyncLock)
|
|
{
|
|
// Make copy of memory content, we cannot make changes to the same document
|
|
// the is read from elsewhere
|
|
XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
|
|
|
|
// Find the document in the xml cache
|
|
x = xmlContentCopy.GetElementById(d.Id.ToString());
|
|
if (x != null)
|
|
{
|
|
// The document already exists in cache, so repopulate it
|
|
x.ParentNode.RemoveChild(x);
|
|
XmlContentInternal = xmlContentCopy;
|
|
ClearContextCache();
|
|
}
|
|
}
|
|
|
|
if (x != null)
|
|
{
|
|
// Run Handler
|
|
Action.RunActionHandlers(d, ActionUnPublish.Instance);
|
|
}
|
|
|
|
// update sitemapprovider
|
|
if (SiteMap.Provider is UmbracoSiteMapProvider)
|
|
{
|
|
var prov = (UmbracoSiteMapProvider)SiteMap.Provider;
|
|
prov.RemoveNode(d.Id);
|
|
}
|
|
|
|
FireAfterClearDocumentCache(d, e);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Unpublishes the node.
|
|
/// </summary>
|
|
/// <param name="documentId">The document id.</param>
|
|
[Obsolete("Please use: umbraco.content.ClearDocumentCache", true)]
|
|
public virtual void UnPublishNode(int documentId)
|
|
{
|
|
ClearDocumentCache(documentId);
|
|
}
|
|
|
|
///// <summary>
|
|
///// Uns the publish node async.
|
|
///// </summary>
|
|
///// <param name="documentId">The document id.</param>
|
|
//[Obsolete("Please use: umbraco.content.ClearDocumentCacheAsync", true)]
|
|
//public virtual void UnPublishNodeAsync(int documentId)
|
|
//{
|
|
|
|
// ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); });
|
|
//}
|
|
|
|
|
|
///// <summary>
|
|
///// Legacy method - you should use the overloaded publishnode(document d) method whenever possible
|
|
///// </summary>
|
|
///// <param name="documentId"></param>
|
|
//[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)]
|
|
//public virtual void PublishNode(int documentId)
|
|
//{
|
|
|
|
// // Get the document
|
|
// var d = new Document(documentId);
|
|
// PublishNode(d);
|
|
//}
|
|
|
|
|
|
///// <summary>
|
|
///// Publishes the node async.
|
|
///// </summary>
|
|
///// <param name="documentId">The document id.</param>
|
|
//[Obsolete("Please use: umbraco.content.UpdateDocumentCacheAsync", true)]
|
|
//public virtual void PublishNodeAsync(int documentId)
|
|
//{
|
|
|
|
// UpdateDocumentCacheAsync(documentId);
|
|
//}
|
|
|
|
|
|
///// <summary>
|
|
///// Publishes the node.
|
|
///// </summary>
|
|
///// <param name="Documents">The documents.</param>
|
|
//[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)]
|
|
//public virtual void PublishNode(List<Document> Documents)
|
|
//{
|
|
|
|
// UpdateDocumentCache(Documents);
|
|
//}
|
|
|
|
|
|
|
|
///// <summary>
|
|
///// Publishes the node.
|
|
///// </summary>
|
|
///// <param name="d">The document.</param>
|
|
//[Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)]
|
|
//public virtual void PublishNode(Document d)
|
|
//{
|
|
|
|
// UpdateDocumentCache(d);
|
|
//}
|
|
|
|
|
|
/// <summary>
|
|
/// Occurs when [before document cache update].
|
|
/// </summary>
|
|
public static event DocumentCacheEventHandler BeforeUpdateDocumentCache;
|
|
|
|
/// <summary>
|
|
/// Fires the before document cache.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void FireBeforeUpdateDocumentCache(Document sender, DocumentCacheEventArgs e)
|
|
{
|
|
if (BeforeUpdateDocumentCache != null)
|
|
{
|
|
BeforeUpdateDocumentCache(sender, e);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Occurs when [after document cache update].
|
|
/// </summary>
|
|
public static event DocumentCacheEventHandler AfterUpdateDocumentCache;
|
|
|
|
/// <summary>
|
|
/// Fires after document cache updater.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void FireAfterUpdateDocumentCache(Document sender, DocumentCacheEventArgs e)
|
|
{
|
|
if (AfterUpdateDocumentCache != null)
|
|
{
|
|
AfterUpdateDocumentCache(sender, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [before document cache unpublish].
|
|
/// </summary>
|
|
public static event DocumentCacheEventHandler BeforeClearDocumentCache;
|
|
|
|
/// <summary>
|
|
/// Fires the before document cache unpublish.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void FireBeforeClearDocumentCache(Document sender, DocumentCacheEventArgs e)
|
|
{
|
|
if (BeforeClearDocumentCache != null)
|
|
{
|
|
BeforeClearDocumentCache(sender, e);
|
|
}
|
|
}
|
|
|
|
public static event DocumentCacheEventHandler AfterClearDocumentCache;
|
|
|
|
/// <summary>
|
|
/// Fires the after document cache unpublish.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void FireAfterClearDocumentCache(Document sender, DocumentCacheEventArgs e)
|
|
{
|
|
if (AfterClearDocumentCache != null)
|
|
{
|
|
AfterClearDocumentCache(sender, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [before refresh content].
|
|
/// </summary>
|
|
public static event RefreshContentEventHandler BeforeRefreshContent;
|
|
|
|
/// <summary>
|
|
/// Fires the content of the before refresh.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.RefreshContentEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void FireBeforeRefreshContent(RefreshContentEventArgs e)
|
|
{
|
|
if (BeforeRefreshContent != null)
|
|
{
|
|
BeforeRefreshContent(null, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [after refresh content].
|
|
/// </summary>
|
|
public static event RefreshContentEventHandler AfterRefreshContent;
|
|
|
|
/// <summary>
|
|
/// Fires the content of the after refresh.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.RefreshContentEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void FireAfterRefreshContent(RefreshContentEventArgs e)
|
|
{
|
|
if (AfterRefreshContent != null)
|
|
{
|
|
AfterRefreshContent(null, e);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Occurs when [after loading the xml string from the database].
|
|
/// </summary>
|
|
public static event ContentCacheDatabaseLoadXmlStringEventHandler AfterContentCacheDatabaseLoadXmlString;
|
|
|
|
/// <summary>
|
|
/// Fires the before when creating the document cache from database
|
|
/// </summary>
|
|
/// <param name="node">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.ContentCacheLoadNodeEventArgs"/> instance containing the event data.</param>
|
|
internal static void FireAfterContentCacheDatabaseLoadXmlString(ref string xml, ContentCacheLoadNodeEventArgs e)
|
|
{
|
|
if (AfterContentCacheDatabaseLoadXmlString != null)
|
|
{
|
|
AfterContentCacheDatabaseLoadXmlString(ref xml, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [before when creating the document cache from database].
|
|
/// </summary>
|
|
public static event ContentCacheLoadNodeEventHandler BeforeContentCacheLoadNode;
|
|
|
|
/// <summary>
|
|
/// Fires the before when creating the document cache from database
|
|
/// </summary>
|
|
/// <param name="node">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.ContentCacheLoadNodeEventArgs"/> instance containing the event data.</param>
|
|
internal static void FireBeforeContentCacheLoadNode(XmlNode node, ContentCacheLoadNodeEventArgs e)
|
|
{
|
|
if (BeforeContentCacheLoadNode != null)
|
|
{
|
|
BeforeContentCacheLoadNode(node, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [after loading document cache xml node from database].
|
|
/// </summary>
|
|
public static event ContentCacheLoadNodeEventHandler AfterContentCacheLoadNodeFromDatabase;
|
|
|
|
/// <summary>
|
|
/// Fires the after loading document cache xml node from database
|
|
/// </summary>
|
|
/// <param name="node">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.ContentCacheLoadNodeEventArgs"/> instance containing the event data.</param>
|
|
internal static void FireAfterContentCacheLoadNodeFromDatabase(XmlNode node, ContentCacheLoadNodeEventArgs e)
|
|
{
|
|
if (AfterContentCacheLoadNodeFromDatabase != null)
|
|
{
|
|
AfterContentCacheLoadNodeFromDatabase(node, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [before a publish action updates the content cache].
|
|
/// </summary>
|
|
public static event ContentCacheLoadNodeEventHandler BeforePublishNodeToContentCache;
|
|
|
|
/// <summary>
|
|
/// Fires the before a publish action updates the content cache
|
|
/// </summary>
|
|
/// <param name="node">The sender.</param>
|
|
/// <param name="e">The <see cref="umbraco.cms.businesslogic.ContentCacheLoadNodeEventArgs"/> instance containing the event data.</param>
|
|
public static void FireBeforePublishNodeToContentCache(XmlNode node, ContentCacheLoadNodeEventArgs e)
|
|
{
|
|
if (BeforePublishNodeToContentCache != null)
|
|
{
|
|
BeforePublishNodeToContentCache(node, e);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Protected & Private methods
|
|
|
|
internal const string PersistenceFlagContextKey = "vnc38ykjnkjdnk2jt98ygkxjng";
|
|
|
|
internal bool IsXmlQueuedForPersistenceToFile
|
|
{
|
|
get
|
|
{
|
|
if (HttpContext.Current != null)
|
|
{
|
|
bool val = HttpContext.Current.Application[PersistenceFlagContextKey] != null;
|
|
if (val)
|
|
{
|
|
DateTime persistenceTime = DateTime.MinValue;
|
|
try
|
|
{
|
|
persistenceTime = (DateTime)HttpContext.Current.Application[PersistenceFlagContextKey];
|
|
if (persistenceTime > GetCacheFileUpdateTime())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
HttpContext.Current.Application.Lock();
|
|
HttpContext.Current.Application[PersistenceFlagContextKey] = null;
|
|
HttpContext.Current.Application.UnLock();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Nothing to catch here - we'll just persist
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidates the disk content cache file. Effectively just deletes it.
|
|
/// </summary>
|
|
private void DeleteXmlCache()
|
|
{
|
|
lock (_readerWriterSyncLock)
|
|
{
|
|
if (File.Exists(UmbracoXmlDiskCacheFileName))
|
|
{
|
|
// Reset file attributes, to make sure we can delete file
|
|
try
|
|
{
|
|
File.SetAttributes(UmbracoXmlDiskCacheFileName, FileAttributes.Normal);
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(UmbracoXmlDiskCacheFileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear HTTPContext cache if any
|
|
/// </summary>
|
|
private void ClearContextCache()
|
|
{
|
|
// If running in a context very important to reset context cache orelse new nodes are missing
|
|
if (HttpContext.Current != null && HttpContext.Current.Items.Contains(XmlContextContentItemKey))
|
|
HttpContext.Current.Items.Remove(XmlContextContentItemKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidates the disk content cache file. Effectively just deletes it.
|
|
/// </summary>
|
|
[Obsolete("This method is obsolete in version 4.1 and above, please use DeleteXmlCache", true)]
|
|
private void ClearDiskCacheAsync()
|
|
{
|
|
// Queue file deletion
|
|
// We queue this function, because there can be a write process running at the same time
|
|
// and we don't want this method to block web request
|
|
ThreadPool.QueueUserWorkItem(
|
|
delegate { DeleteXmlCache(); });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load content from either disk or database
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private XmlDocument LoadContent()
|
|
{
|
|
if (!UmbracoSettings.isXmlContentCacheDisabled && IsValidDiskCachePresent())
|
|
{
|
|
try
|
|
{
|
|
return LoadContentFromDiskCache();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// This is really bad, loading from cache file failed for some reason, now fallback to loading from database
|
|
Debug.WriteLine("Content file cache load failed: " + e);
|
|
DeleteXmlCache();
|
|
}
|
|
}
|
|
return LoadContentFromDatabase();
|
|
}
|
|
|
|
private bool IsValidDiskCachePresent()
|
|
{
|
|
if (File.Exists(UmbracoXmlDiskCacheFileName))
|
|
{
|
|
// Only return true if we don't have a zero-byte file
|
|
var f = new FileInfo(UmbracoXmlDiskCacheFileName);
|
|
if (f.Length > 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load content from cache file
|
|
/// </summary>
|
|
private XmlDocument LoadContentFromDiskCache()
|
|
{
|
|
lock (_readerWriterSyncLock)
|
|
{
|
|
var xmlDoc = new XmlDocument();
|
|
Log.Add(LogTypes.System, User.GetUser(0), -1, "Loading content from disk cache...");
|
|
xmlDoc.Load(UmbracoXmlDiskCacheFileName);
|
|
_lastDiskCacheReadTime = DateTime.UtcNow;
|
|
return xmlDoc;
|
|
}
|
|
}
|
|
|
|
private static void InitContentDocument(XmlDocument xmlDoc, string dtd)
|
|
{
|
|
// Prime the xml document with an inline dtd and a root element
|
|
xmlDoc.LoadXml(String.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>{0}{1}{0}<root id=\"-1\"/>",
|
|
Environment.NewLine,
|
|
dtd));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load content from database
|
|
/// </summary>
|
|
private XmlDocument LoadContentFromDatabase()
|
|
{
|
|
// Alex N - 2010 06 - Very generic try-catch simply because at the moment, unfortunately, this method gets called inside a ThreadPool thread
|
|
// and we need to guarantee it won't tear down the app pool by throwing an unhandled exception
|
|
try
|
|
{
|
|
// 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 */
|
|
}
|
|
|
|
// Try to log to the DB
|
|
Log.Add(LogTypes.System, staticUser, -1, "Loading content from database...");
|
|
|
|
var hierarchy = new Dictionary<int, List<int>>();
|
|
var nodeIndex = new Dictionary<int, XmlNode>();
|
|
|
|
try
|
|
{
|
|
Log.Add(LogTypes.Debug, staticUser, -1, "Republishing starting");
|
|
|
|
// Lets cache the DTD to save on the DB hit on the subsequent use
|
|
string dtd = DocumentType.GenerateDtd();
|
|
|
|
// Prepare an XmlDocument with an appropriate inline DTD to match
|
|
// the expected content
|
|
var xmlDoc = new XmlDocument();
|
|
InitContentDocument(xmlDoc, dtd);
|
|
|
|
// Esben Carlsen: At some point we really need to put all data access into to a tier of its own.
|
|
string sql =
|
|
@"select umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, cmsContentXml.xml from umbracoNode
|
|
inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type
|
|
order by umbracoNode.level, umbracoNode.sortOrder";
|
|
|
|
lock (_dbReadSyncLock)
|
|
{
|
|
using (
|
|
IRecordsReader dr = SqlHelper.ExecuteReader(sql,
|
|
SqlHelper.CreateParameter("@type",
|
|
new Guid(
|
|
"C66BA18E-EAF3-4CFF-8A22-41B16D66A972")))
|
|
)
|
|
{
|
|
while (dr.Read())
|
|
{
|
|
int currentId = dr.GetInt("id");
|
|
int parentId = dr.GetInt("parentId");
|
|
string xml = dr.GetString("xml");
|
|
|
|
// Call the eventhandler to allow modification of the string
|
|
var e1 = new ContentCacheLoadNodeEventArgs();
|
|
FireAfterContentCacheDatabaseLoadXmlString(ref xml, e1);
|
|
// check if a listener has canceled the event
|
|
if (!e1.Cancel)
|
|
{
|
|
// and parse it into a DOM node
|
|
xmlDoc.LoadXml(xml);
|
|
XmlNode node = xmlDoc.FirstChild;
|
|
// same event handler loader form the xml node
|
|
var e2 = new ContentCacheLoadNodeEventArgs();
|
|
FireAfterContentCacheLoadNodeFromDatabase(node, e2);
|
|
// and checking if it was canceled again
|
|
if (!e1.Cancel)
|
|
{
|
|
nodeIndex.Add(currentId, node);
|
|
|
|
// verify if either of the handlers canceled the children to load
|
|
if (!e1.CancelChildren && !e2.CancelChildren)
|
|
{
|
|
// Build the content hierarchy
|
|
List<int> children;
|
|
if (!hierarchy.TryGetValue(parentId, out children))
|
|
{
|
|
// No children for this parent, so add one
|
|
children = new List<int>();
|
|
hierarchy.Add(parentId, children);
|
|
}
|
|
children.Add(currentId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Log.Add(LogTypes.Debug, staticUser, -1, "Xml Pages loaded");
|
|
|
|
try
|
|
{
|
|
// If we got to here we must have successfully retrieved the content from the DB so
|
|
// we can safely initialise and compose the final content DOM.
|
|
// Note: We are reusing the XmlDocument used to create the xml nodes above so
|
|
// we don't have to import them into a new XmlDocument
|
|
|
|
// Initialise the document ready for the final composition of content
|
|
InitContentDocument(xmlDoc, dtd);
|
|
|
|
// Start building the content tree recursively from the root (-1) node
|
|
GenerateXmlDocument(hierarchy, nodeIndex, -1, xmlDoc.DocumentElement);
|
|
|
|
Log.Add(LogTypes.Debug, staticUser, -1, "Done republishing Xml Index");
|
|
|
|
return xmlDoc;
|
|
}
|
|
catch (Exception ee)
|
|
{
|
|
Log.Add(LogTypes.Error, staticUser, -1,
|
|
string.Format("Error while generating XmlDocument from database: {0}", ee));
|
|
}
|
|
}
|
|
catch (OutOfMemoryException)
|
|
{
|
|
Log.Add(LogTypes.Error, staticUser, -1,
|
|
string.Format("Error Republishing: Out Of Memory. Parents: {0}, Nodes: {1}",
|
|
hierarchy.Count, nodeIndex.Count));
|
|
}
|
|
catch (Exception ee)
|
|
{
|
|
Log.Add(LogTypes.Error, staticUser, -1, string.Format("Error Republishing: {0}", ee));
|
|
}
|
|
}
|
|
catch (Exception ee)
|
|
{
|
|
Log.Add(LogTypes.Error, -1, string.Format("Error Republishing: {0}", ee));
|
|
}
|
|
|
|
// An error of some sort must have stopped us from successfully generating
|
|
// the content tree, so lets return null signifying there is no content available
|
|
return null;
|
|
}
|
|
|
|
private static void GenerateXmlDocument(IDictionary<int, List<int>> hierarchy,
|
|
IDictionary<int, XmlNode> nodeIndex, int parentId, XmlNode parentNode)
|
|
{
|
|
List<int> children;
|
|
|
|
if (hierarchy.TryGetValue(parentId, out children))
|
|
{
|
|
XmlNode childContainer = UmbracoSettings.UseLegacyXmlSchema ||
|
|
String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME)
|
|
? parentNode
|
|
: parentNode.SelectSingleNode(
|
|
UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME);
|
|
|
|
if (!UmbracoSettings.UseLegacyXmlSchema &&
|
|
!String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME))
|
|
{
|
|
if (childContainer == null)
|
|
{
|
|
childContainer = xmlHelper.addTextNode(parentNode.OwnerDocument,
|
|
UmbracoSettings.
|
|
TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME, "");
|
|
parentNode.AppendChild(childContainer);
|
|
}
|
|
}
|
|
|
|
foreach (int childId in children)
|
|
{
|
|
XmlNode childNode = nodeIndex[childId];
|
|
|
|
if (UmbracoSettings.UseLegacyXmlSchema ||
|
|
String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME))
|
|
{
|
|
parentNode.AppendChild(childNode);
|
|
}
|
|
else
|
|
{
|
|
childContainer.AppendChild(childNode);
|
|
}
|
|
|
|
// Recursively build the content tree under the current child
|
|
GenerateXmlDocument(hierarchy, nodeIndex, childId, childNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void PersistXmlToFile()
|
|
{
|
|
PersistXmlToFile(_xmlContent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Persist a XmlDocument to the Disk Cache
|
|
/// </summary>
|
|
/// <param name="xmlDoc"></param>
|
|
internal void PersistXmlToFile(XmlDocument xmlDoc)
|
|
{
|
|
lock (_readerWriterSyncLock)
|
|
{
|
|
if (xmlDoc != null)
|
|
{
|
|
Trace.Write(string.Format("Saving content to disk on thread '{0}' (Threadpool? {1})",
|
|
Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread));
|
|
|
|
// 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();
|
|
|
|
DeleteXmlCache();
|
|
|
|
// Try to create directory for cache path if it doesn't yet exist
|
|
string directoryName = Path.GetDirectoryName(UmbracoXmlDiskCacheFileName);
|
|
if (!File.Exists(UmbracoXmlDiskCacheFileName) && !Directory.Exists(directoryName))
|
|
{
|
|
// We're already in a try-catch and saving will fail if this does, so don't need another
|
|
Directory.CreateDirectory(directoryName);
|
|
}
|
|
|
|
xmlDoc.Save(UmbracoXmlDiskCacheFileName);
|
|
|
|
Trace.Write(string.Format("Saved content on thread '{0}' in {1} (Threadpool? {2})",
|
|
Thread.CurrentThread.Name, stopWatch.Elapsed,
|
|
Thread.CurrentThread.IsThreadPoolThread));
|
|
|
|
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
|
|
DeleteXmlCache();
|
|
|
|
Trace.Write(string.Format(
|
|
"Error saving content on thread '{0}' due to '{1}' (Threadpool? {2})",
|
|
Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread));
|
|
Log.Add(LogTypes.Error, staticUser, -1, string.Format("Xml wasn't saved: {0}", ee));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a flag in the HttpContext so that, upon page execution completion, the Xml cache will
|
|
/// get persisted to disk. Ensure this method is only called from a thread executing a page request
|
|
/// since umbraco.presentation.requestModule is the only monitor of this flag and is responsible
|
|
/// for enacting the persistence at the PostRequestHandlerExecute stage of the page lifecycle.
|
|
/// </summary>
|
|
private void QueueXmlForPersistence()
|
|
{
|
|
/* Alex Norcliffe 2010 06 03 - removing all launching of ThreadPool threads, instead we just
|
|
* flag on the context that the Xml should be saved and an event in the requestModule
|
|
* will check for this and call PersistXmlToFile() if necessary */
|
|
if (HttpContext.Current != null)
|
|
{
|
|
HttpContext.Current.Application.Lock();
|
|
if (HttpContext.Current.Application[PersistenceFlagContextKey] != null)
|
|
HttpContext.Current.Application.Add(PersistenceFlagContextKey, null);
|
|
HttpContext.Current.Application[PersistenceFlagContextKey] = DateTime.UtcNow;
|
|
HttpContext.Current.Application.UnLock();
|
|
}
|
|
else
|
|
{
|
|
//// Save copy of content
|
|
if (UmbracoSettings.CloneXmlCacheOnPublish)
|
|
{
|
|
XmlDocument xmlContentCopy = CloneXmlDoc(_xmlContent);
|
|
|
|
ThreadPool.QueueUserWorkItem(
|
|
delegate { PersistXmlToFile(xmlContentCopy); });
|
|
}
|
|
else
|
|
ThreadPool.QueueUserWorkItem(
|
|
delegate { PersistXmlToFile(); });
|
|
}
|
|
}
|
|
|
|
internal DateTime GetCacheFileUpdateTime()
|
|
{
|
|
if (File.Exists(UmbracoXmlDiskCacheFileName))
|
|
{
|
|
return new FileInfo(UmbracoXmlDiskCacheFileName).LastWriteTimeUtc;
|
|
}
|
|
|
|
return DateTime.MinValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make a copy of a XmlDocument
|
|
/// </summary>
|
|
/// <param name="xmlDoc"></param>
|
|
/// <returns></returns>
|
|
private static XmlDocument CloneXmlDoc(XmlDocument xmlDoc)
|
|
{
|
|
if (xmlDoc == null) return null;
|
|
|
|
Log.Add(LogTypes.Debug, -1, "Cloning...");
|
|
// Save copy of content
|
|
var xmlCopy = (XmlDocument)xmlDoc.CloneNode(true);
|
|
Log.Add(LogTypes.Debug, -1, "Cloning ended...");
|
|
return xmlCopy;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |