Web.PublishedCache - rename LegacyXmlCache into XmlPublishedCache
This commit is contained in:
@@ -0,0 +1,449 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Routing;
|
||||
using umbraco;
|
||||
using System.Linq;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.presentation.preview;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
internal class PublishedContentCache : IPublishedContentCache
|
||||
{
|
||||
#region Routes cache
|
||||
|
||||
private readonly RoutesCache _routesCache = new RoutesCache(!UnitTesting);
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal RoutesCache RoutesCache { get { return _routesCache; } }
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal static bool UnitTesting = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets content identified by a route.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext">The context.</param>
|
||||
/// <param name="route">The route</param>
|
||||
/// <param name="hideTopLevelNode">A value forcing the HideTopLevelNode setting.</param>
|
||||
/// <returns>The content, or null.</returns>
|
||||
/// <remarks>
|
||||
/// <para>A valid route is either a simple path eg <c>/foo/bar/nil</c> or a root node id and a path, eg <c>123/foo/bar/nil</c>.</para>
|
||||
/// <para>If <param name="hideTopLevelNode" /> is <c>null</c> then the settings value is used.</para>
|
||||
/// </remarks>
|
||||
public IPublishedContent GetByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException("route");
|
||||
|
||||
// try to get from cache if not previewing
|
||||
var contentId = umbracoContext.InPreviewMode
|
||||
? 0
|
||||
: _routesCache.GetNodeId(route);
|
||||
|
||||
// if found id in cache then get corresponding content
|
||||
// and clear cache if not found - for whatever reason
|
||||
IPublishedContent content = null;
|
||||
if (contentId > 0)
|
||||
{
|
||||
content = GetById(umbracoContext, contentId);
|
||||
if (content == null)
|
||||
_routesCache.ClearNode(contentId);
|
||||
}
|
||||
|
||||
// still have nothing? actually determine the id
|
||||
hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath; // default = settings
|
||||
content = content ?? DetermineIdByRoute(umbracoContext, route, hideTopLevelNode.Value);
|
||||
|
||||
// cache if we have a content and not previewing
|
||||
if (content != null && !umbracoContext.InPreviewMode)
|
||||
{
|
||||
var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/')));
|
||||
var iscanon = !UnitTesting && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(false), content.Path, domainRootNodeId);
|
||||
// and only if this is the canonical url (the one GetUrl would return)
|
||||
if (iscanon)
|
||||
_routesCache.Store(contentId, route);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route for a content identified by its unique identifier.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext">The context.</param>
|
||||
/// <param name="contentId">The content unique identifier.</param>
|
||||
/// <returns>The route.</returns>
|
||||
public string GetRouteById(UmbracoContext umbracoContext, int contentId)
|
||||
{
|
||||
// try to get from cache if not previewing
|
||||
var route = umbracoContext.InPreviewMode
|
||||
? null
|
||||
: _routesCache.GetRoute(contentId);
|
||||
|
||||
// if found in cache then return
|
||||
if (route != null)
|
||||
return route;
|
||||
|
||||
// else actually determine the route
|
||||
route = DetermineRouteById(umbracoContext, contentId);
|
||||
|
||||
// cache if we have a route and not previewing
|
||||
if (route != null && !umbracoContext.InPreviewMode)
|
||||
_routesCache.Store(contentId, route);
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
IPublishedContent DetermineIdByRoute(UmbracoContext umbracoContext, string route, bool hideTopLevelNode)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException("route");
|
||||
|
||||
//the route always needs to be lower case because we only store the urlName attribute in lower case
|
||||
route = route.ToLowerInvariant();
|
||||
|
||||
var pos = route.IndexOf('/');
|
||||
var path = pos == 0 ? route : route.Substring(pos);
|
||||
var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
|
||||
IEnumerable<XPathVariable> vars;
|
||||
|
||||
var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode, out vars);
|
||||
|
||||
//check if we can find the node in our xml cache
|
||||
var content = GetSingleByXPath(umbracoContext, xpath, vars == null ? null : vars.ToArray());
|
||||
|
||||
// if hideTopLevelNodePath is true then for url /foo we looked for /*/foo
|
||||
// but maybe that was the url of a non-default top-level node, so we also
|
||||
// have to look for /foo (see note in ApplyHideTopLevelNodeFromPath).
|
||||
if (content == null && hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
||||
{
|
||||
xpath = CreateXpathQuery(startNodeId, path, false, out vars);
|
||||
content = GetSingleByXPath(umbracoContext, xpath, vars == null ? null : vars.ToArray());
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
string DetermineRouteById(UmbracoContext umbracoContext, int contentId)
|
||||
{
|
||||
var node = GetById(umbracoContext, contentId);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
// walk up from that node until we hit a node with a domain,
|
||||
// or we reach the content root, collecting urls in the way
|
||||
var pathParts = new List<string>();
|
||||
var n = node;
|
||||
var hasDomains = DomainHelper.NodeHasDomains(n.Id);
|
||||
while (!hasDomains && n != null) // n is null at root
|
||||
{
|
||||
// get the url
|
||||
var urlName = n.UrlName;
|
||||
pathParts.Add(urlName);
|
||||
|
||||
// move to parent node
|
||||
n = n.Parent;
|
||||
hasDomains = n != null && DomainHelper.NodeHasDomains(n.Id);
|
||||
}
|
||||
|
||||
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
||||
if (!hasDomains && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
|
||||
ApplyHideTopLevelNodeFromPath(umbracoContext, node, pathParts);
|
||||
|
||||
// assemble the route
|
||||
pathParts.Reverse();
|
||||
var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
|
||||
var route = (n == null ? "" : n.Id.ToString(CultureInfo.InvariantCulture)) + path;
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContent node, IList<string> pathParts)
|
||||
{
|
||||
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
|
||||
// top-level node, or else domains should be assigned. but for backward compatibility
|
||||
// we add this check - we look for the document matching "/" and if it's not us, then
|
||||
// we do not hide the top level path
|
||||
// it has to be taken care of in GetByRoute too so if
|
||||
// "/foo" fails (looking for "/*/foo") we try also "/foo".
|
||||
// this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but
|
||||
// that's the way it works pre-4.10 and we try to be backward compat for the time being
|
||||
if (node.Parent == null)
|
||||
{
|
||||
var rootNode = umbracoContext.ContentCache.GetByRoute("/", true);
|
||||
if (rootNode.Id == node.Id) // remove only if we're the default node
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XPath Strings
|
||||
|
||||
class XPathStringsDefinition
|
||||
{
|
||||
public int Version { get; private set; }
|
||||
|
||||
public static string Root { get { return "/root"; } }
|
||||
public string RootDocuments { get; private set; }
|
||||
public string DescendantDocumentById { get; private set; }
|
||||
public string ChildDocumentByUrlName { get; private set; }
|
||||
public string ChildDocumentByUrlNameVar { get; private set; }
|
||||
public string RootDocumentWithLowestSortOrder { get; private set; }
|
||||
|
||||
public XPathStringsDefinition(int version)
|
||||
{
|
||||
Version = version;
|
||||
|
||||
switch (version)
|
||||
{
|
||||
// legacy XML schema
|
||||
case 0:
|
||||
RootDocuments = "/root/node";
|
||||
DescendantDocumentById = "//node [@id={0}]";
|
||||
ChildDocumentByUrlName = "/node [@urlName='{0}']";
|
||||
ChildDocumentByUrlNameVar = "/node [@urlName=${0}]";
|
||||
RootDocumentWithLowestSortOrder = "/root/node [not(@sortOrder > ../node/@sortOrder)][1]";
|
||||
break;
|
||||
|
||||
// default XML schema as of 4.10
|
||||
case 1:
|
||||
RootDocuments = "/root/* [@isDoc]";
|
||||
DescendantDocumentById = "//* [@isDoc and @id={0}]";
|
||||
ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']";
|
||||
ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]";
|
||||
RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(string.Format("Unsupported Xml schema version '{0}').", version));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static XPathStringsDefinition _xPathStringsValue;
|
||||
static XPathStringsDefinition XPathStrings
|
||||
{
|
||||
get
|
||||
{
|
||||
// in theory XPathStrings should be a static variable that
|
||||
// we should initialize in a static ctor - but then test cases
|
||||
// that switch schemas fail - so cache and refresh when needed,
|
||||
// ie never when running the actual site
|
||||
|
||||
var version = UmbracoSettings.UseLegacyXmlSchema ? 0 : 1;
|
||||
if (_xPathStringsValue == null || _xPathStringsValue.Version != version)
|
||||
_xPathStringsValue = new XPathStringsDefinition(version);
|
||||
return _xPathStringsValue;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Converters
|
||||
|
||||
private static IPublishedContent ConvertToDocument(XmlNode xmlNode)
|
||||
{
|
||||
return xmlNode == null ? null : new Models.XmlPublishedContent(xmlNode);
|
||||
}
|
||||
|
||||
private static IEnumerable<IPublishedContent> ConvertToDocuments(XmlNodeList xmlNodes)
|
||||
{
|
||||
return xmlNodes.Cast<XmlNode>().Select(xmlNode => new Models.XmlPublishedContent(xmlNode));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Getters
|
||||
|
||||
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, int nodeId)
|
||||
{
|
||||
return ConvertToDocument(GetXml(umbracoContext).GetElementById(nodeId.ToString()));
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext)
|
||||
{
|
||||
return (from XmlNode x in GetXml(umbracoContext).SelectNodes(XPathStrings.RootDocuments) select ConvertToDocument(x)).ToList();
|
||||
}
|
||||
|
||||
public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, string xpath, params XPathVariable[] vars)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException("xpath");
|
||||
if (string.IsNullOrWhiteSpace(xpath)) return null;
|
||||
|
||||
var xml = GetXml(umbracoContext);
|
||||
var node = vars == null
|
||||
? xml.SelectSingleNode(xpath)
|
||||
: xml.SelectSingleNode(xpath, vars);
|
||||
return ConvertToDocument(node);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, string xpath, params XPathVariable[] vars)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException("xpath");
|
||||
if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty<IPublishedContent>();
|
||||
|
||||
var xml = GetXml(umbracoContext);
|
||||
var nodes = vars == null
|
||||
? xml.SelectNodes(xpath)
|
||||
: xml.SelectNodes(xpath, vars);
|
||||
return ConvertToDocuments(nodes);
|
||||
}
|
||||
|
||||
public bool HasContent()
|
||||
{
|
||||
var xml = GetXml();
|
||||
if (xml == null)
|
||||
return false;
|
||||
var node = xml.SelectSingleNode(XPathStrings.RootDocuments);
|
||||
return node != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Legacy Xml
|
||||
|
||||
private PreviewContent _previewContent;
|
||||
private Func<User, bool, XmlDocument> _xmlDelegate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the delegate used to retreive the Xml content, generally the setter is only used for unit tests
|
||||
/// and by default if it is not set will use the standard delegate which ONLY works when in the context an Http Request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If not defined, we will use the standard delegate which ONLY works when in the context an Http Request
|
||||
/// mostly because the 'content' object heavily relies on HttpContext, SQL connections and a bunch of other stuff
|
||||
/// that when run inside of a unit test fails.
|
||||
/// </remarks>
|
||||
internal Func<User, bool, XmlDocument> GetXmlDelegate
|
||||
{
|
||||
get
|
||||
{
|
||||
return _xmlDelegate ?? (_xmlDelegate = (user, preview) =>
|
||||
{
|
||||
if (preview)
|
||||
{
|
||||
if (_previewContent == null)
|
||||
{
|
||||
_previewContent = new PreviewContent(user, new Guid(global::umbraco.BusinessLogic.StateHelper.Cookies.Preview.GetValue()), true);
|
||||
if (_previewContent.ValidPreviewSet)
|
||||
_previewContent.LoadPreviewset();
|
||||
}
|
||||
if (_previewContent.ValidPreviewSet)
|
||||
return _previewContent.XmlContent;
|
||||
}
|
||||
return content.Instance.XmlContent;
|
||||
});
|
||||
}
|
||||
set
|
||||
{
|
||||
_xmlDelegate = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal XmlDocument GetXml(UmbracoContext umbracoContext)
|
||||
{
|
||||
return GetXmlDelegate(umbracoContext.UmbracoUser, umbracoContext.InPreviewMode);
|
||||
}
|
||||
|
||||
internal XmlDocument GetXml()
|
||||
{
|
||||
return GetXmlDelegate(null, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XPathQuery
|
||||
|
||||
static readonly char[] SlashChar = new[] { '/' };
|
||||
|
||||
protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath, out IEnumerable<XPathVariable> vars)
|
||||
{
|
||||
string xpath;
|
||||
vars = null;
|
||||
|
||||
if (path == string.Empty || path == "/")
|
||||
{
|
||||
// if url is empty
|
||||
if (startNodeId > 0)
|
||||
{
|
||||
// if in a domain then use the root node of the domain
|
||||
xpath = string.Format(XPathStringsDefinition.Root + XPathStrings.DescendantDocumentById, startNodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not in a domain - what is the default page?
|
||||
// let's say it is the first one in the tree, if any -- order by sortOrder
|
||||
|
||||
// but!
|
||||
// umbraco does not consistently guarantee that sortOrder starts with 0
|
||||
// so the one that we want is the one with the smallest sortOrder
|
||||
// read http://stackoverflow.com/questions/1128745/how-can-i-use-xpath-to-find-the-minimum-value-of-an-attribute-in-a-set-of-elemen
|
||||
|
||||
// so that one does not work, because min(@sortOrder) maybe 1
|
||||
// xpath = "/root/*[@isDoc and @sortOrder='0']";
|
||||
|
||||
// and we can't use min() because that's XPath 2.0
|
||||
// that one works
|
||||
xpath = XPathStrings.RootDocumentWithLowestSortOrder;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if url is not empty, then use it to try lookup a matching page
|
||||
var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
var xpathBuilder = new StringBuilder();
|
||||
int partsIndex = 0;
|
||||
List<XPathVariable> varsList = null;
|
||||
|
||||
if (startNodeId == 0)
|
||||
{
|
||||
if (hideTopLevelNodeFromPath)
|
||||
xpathBuilder.Append(XPathStrings.RootDocuments); // first node is not in the url
|
||||
else
|
||||
xpathBuilder.Append(XPathStringsDefinition.Root);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.AppendFormat(XPathStringsDefinition.Root + XPathStrings.DescendantDocumentById, startNodeId);
|
||||
// always "hide top level" when there's a domain
|
||||
}
|
||||
|
||||
while (partsIndex < urlParts.Length)
|
||||
{
|
||||
var part = urlParts[partsIndex++];
|
||||
if (part.Contains('\'') || part.Contains('"'))
|
||||
{
|
||||
// use vars, escaping gets ugly pretty quickly
|
||||
varsList = varsList ?? new List<XPathVariable>();
|
||||
var varName = string.Format("var{0}", partsIndex);
|
||||
varsList.Add(new XPathVariable(varName, part));
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlNameVar, varName);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlName, part);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
xpath = xpathBuilder.ToString();
|
||||
if (varsList != null)
|
||||
vars = varsList.ToArray();
|
||||
}
|
||||
|
||||
return xpath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Examine;
|
||||
using Examine.LuceneEngine.SearchCriteria;
|
||||
using Examine.Providers;
|
||||
using Lucene.Net.Documents;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Dynamics;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Models;
|
||||
using UmbracoExamine;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic;
|
||||
using ContentType = umbraco.cms.businesslogic.ContentType;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly.
|
||||
/// </remarks>
|
||||
internal class PublishedMediaCache : IPublishedMediaCache
|
||||
{
|
||||
public PublishedMediaCache()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generally used for unit testing to use an explicit examine searcher
|
||||
/// </summary>
|
||||
/// <param name="searchProvider"></param>
|
||||
/// <param name="indexProvider"></param>
|
||||
internal PublishedMediaCache(BaseSearchProvider searchProvider, BaseIndexProvider indexProvider)
|
||||
{
|
||||
_searchProvider = searchProvider;
|
||||
_indexProvider = indexProvider;
|
||||
}
|
||||
|
||||
private readonly BaseSearchProvider _searchProvider;
|
||||
private readonly BaseIndexProvider _indexProvider;
|
||||
|
||||
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, int nodeId)
|
||||
{
|
||||
return GetUmbracoMedia(nodeId);
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext)
|
||||
{
|
||||
var rootMedia = global::umbraco.cms.businesslogic.media.Media.GetRootMedias();
|
||||
var result = new List<IPublishedContent>();
|
||||
//TODO: need to get a ConvertFromMedia method but we'll just use this for now.
|
||||
foreach (var media in rootMedia
|
||||
.Select(m => global::umbraco.library.GetMedia(m.Id, true))
|
||||
.Where(media => media != null && media.Current != null))
|
||||
{
|
||||
media.MoveNext();
|
||||
result.Add(ConvertFromXPathNavigator(media.Current));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath queries.");
|
||||
}
|
||||
|
||||
public IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath queries.");
|
||||
}
|
||||
|
||||
public bool HasContent() { throw new NotImplementedException(); }
|
||||
|
||||
private ExamineManager GetExamineManagerSafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
return ExamineManager.Instance;
|
||||
}
|
||||
catch (TypeInitializationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private BaseIndexProvider GetIndexProviderSafe()
|
||||
{
|
||||
if (_indexProvider != null)
|
||||
return _indexProvider;
|
||||
|
||||
var eMgr = GetExamineManagerSafe();
|
||||
if (eMgr != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//by default use the InternalSearcher
|
||||
return eMgr.IndexProviderCollection["InternalIndexer"];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<PublishedMediaCache>("Could not retreive the InternalIndexer", ex);
|
||||
//something didn't work, continue returning null.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private BaseSearchProvider GetSearchProviderSafe()
|
||||
{
|
||||
if (_searchProvider != null)
|
||||
return _searchProvider;
|
||||
|
||||
var eMgr = GetExamineManagerSafe();
|
||||
if (eMgr != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//by default use the InternalSearcher
|
||||
return eMgr.SearchProviderCollection["InternalSearcher"];
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
//TODO: Need to fix examine in LB scenarios!
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IPublishedContent GetUmbracoMedia(int id)
|
||||
{
|
||||
var searchProvider = GetSearchProviderSafe();
|
||||
|
||||
if (searchProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//first check in Examine as this is WAY faster
|
||||
var criteria = searchProvider.CreateSearchCriteria("media");
|
||||
|
||||
var filter = criteria.Id(id).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard());
|
||||
//the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene.
|
||||
//+(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media
|
||||
|
||||
var results = searchProvider.Search(filter.Compile());
|
||||
if (results.Any())
|
||||
{
|
||||
return ConvertFromSearchResult(results.First());
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
//TODO: Need to fix examine in LB scenarios!
|
||||
}
|
||||
}
|
||||
|
||||
var media = global::umbraco.library.GetMedia(id, true);
|
||||
if (media != null && media.Current != null)
|
||||
{
|
||||
media.MoveNext();
|
||||
var moved = media.Current.MoveToFirstChild();
|
||||
//first check if we have an error
|
||||
if (moved)
|
||||
{
|
||||
if (media.Current.Name.InvariantEquals("error"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (moved)
|
||||
{
|
||||
//move back to the parent and return
|
||||
media.Current.MoveToParent();
|
||||
}
|
||||
return ConvertFromXPathNavigator(media.Current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal IPublishedContent ConvertFromSearchResult(SearchResult searchResult)
|
||||
{
|
||||
//NOTE: Some fields will not be included if the config section for the internal index has been
|
||||
//mucked around with. It should index everything and so the index definition should simply be:
|
||||
// <IndexSet SetName="InternalIndexSet" IndexPath="~/App_Data/TEMP/ExamineIndexes/Internal/" />
|
||||
|
||||
|
||||
var values = new Dictionary<string, string>(searchResult.Fields);
|
||||
//we need to ensure some fields exist, because of the above issue
|
||||
if (!new []{"template", "templateId"}.Any(values.ContainsKey))
|
||||
values.Add("template", 0.ToString());
|
||||
if (!new[] { "sortOrder" }.Any(values.ContainsKey))
|
||||
values.Add("sortOrder", 0.ToString());
|
||||
if (!new[] { "urlName" }.Any(values.ContainsKey))
|
||||
values.Add("urlName", "");
|
||||
if (!new[] { "nodeType" }.Any(values.ContainsKey))
|
||||
values.Add("nodeType", 0.ToString());
|
||||
if (!new[] { "creatorName" }.Any(values.ContainsKey))
|
||||
values.Add("creatorName", "");
|
||||
if (!new[] { "writerID" }.Any(values.ContainsKey))
|
||||
values.Add("writerID", 0.ToString());
|
||||
if (!new[] { "creatorID" }.Any(values.ContainsKey))
|
||||
values.Add("creatorID", 0.ToString());
|
||||
if (!new[] { "createDate" }.Any(values.ContainsKey))
|
||||
values.Add("createDate", default(DateTime).ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
if (!new[] { "level" }.Any(values.ContainsKey))
|
||||
{
|
||||
values.Add("level", values["__Path"].Split(',').Length.ToString());
|
||||
}
|
||||
|
||||
|
||||
return new DictionaryPublishedContent(values,
|
||||
d => d.ParentId != -1 //parent should be null if -1
|
||||
? GetUmbracoMedia(d.ParentId)
|
||||
: null,
|
||||
//callback to return the children of the current node
|
||||
d => GetChildrenMedia(d.Id),
|
||||
GetProperty,
|
||||
true);
|
||||
}
|
||||
|
||||
internal IPublishedContent ConvertFromXPathNavigator(XPathNavigator xpath)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException("xpath");
|
||||
|
||||
var values = new Dictionary<string, string> {{"nodeName", xpath.GetAttribute("nodeName", "")}};
|
||||
if (!UmbracoSettings.UseLegacyXmlSchema)
|
||||
{
|
||||
values.Add("nodeTypeAlias", xpath.Name);
|
||||
}
|
||||
|
||||
var result = xpath.SelectChildren(XPathNodeType.Element);
|
||||
//add the attributes e.g. id, parentId etc
|
||||
if (result.Current != null && result.Current.HasAttributes)
|
||||
{
|
||||
if (result.Current.MoveToFirstAttribute())
|
||||
{
|
||||
//checking for duplicate keys because of the 'nodeTypeAlias' might already be added above.
|
||||
if (!values.ContainsKey(result.Current.Name))
|
||||
{
|
||||
values.Add(result.Current.Name, result.Current.Value);
|
||||
}
|
||||
while (result.Current.MoveToNextAttribute())
|
||||
{
|
||||
if (!values.ContainsKey(result.Current.Name))
|
||||
{
|
||||
values.Add(result.Current.Name, result.Current.Value);
|
||||
}
|
||||
}
|
||||
result.Current.MoveToParent();
|
||||
}
|
||||
}
|
||||
//add the user props
|
||||
while (result.MoveNext())
|
||||
{
|
||||
if (result.Current != null && !result.Current.HasAttributes)
|
||||
{
|
||||
string value = result.Current.Value;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (result.Current.HasAttributes || result.Current.SelectChildren(XPathNodeType.Element).Count > 0)
|
||||
{
|
||||
value = result.Current.OuterXml;
|
||||
}
|
||||
}
|
||||
values.Add(result.Current.Name, value);
|
||||
}
|
||||
}
|
||||
|
||||
return new DictionaryPublishedContent(values,
|
||||
d => d.ParentId != -1 //parent should be null if -1
|
||||
? GetUmbracoMedia(d.ParentId)
|
||||
: null,
|
||||
//callback to return the children of the current node based on the xml structure already found
|
||||
d => GetChildrenMedia(d.Id, xpath),
|
||||
GetProperty,
|
||||
false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists
|
||||
/// in the results, if it does not, then we'll have to revert to looking up in the db.
|
||||
/// </summary>
|
||||
/// <param name="dd"> </param>
|
||||
/// <param name="alias"></param>
|
||||
/// <returns></returns>
|
||||
private IPublishedContentProperty GetProperty(DictionaryPublishedContent dd, string alias)
|
||||
{
|
||||
if (dd.LoadedFromExamine)
|
||||
{
|
||||
//if this is from Examine, lets check if the alias does not exist on the document
|
||||
if (dd.Properties.All(x => x.Alias != alias))
|
||||
{
|
||||
//ok it doesn't exist, we might assume now that Examine didn't index this property because the index is not set up correctly
|
||||
//so before we go loading this from the database, we can check if the alias exists on the content type at all, this information
|
||||
//is cached so will be quicker to look up.
|
||||
if (dd.Properties.Any(x => x.Alias == "__NodeTypeAlias"))
|
||||
{
|
||||
var aliasesAndNames = ContentType.GetAliasesAndNames(dd.Properties.First(x => x.Alias.InvariantEquals("__NodeTypeAlias")).Value.ToString());
|
||||
if (aliasesAndNames != null)
|
||||
{
|
||||
if (!aliasesAndNames.ContainsKey(alias))
|
||||
{
|
||||
//Ok, now we know it doesn't exist on this content type anyways
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if we've made it here, that means it does exist on the content type but not in examine, we'll need to query the db :(
|
||||
var media = global::umbraco.library.GetMedia(dd.Id, true);
|
||||
if (media != null && media.Current != null)
|
||||
{
|
||||
media.MoveNext();
|
||||
var mediaDoc = ConvertFromXPathNavigator(media.Current);
|
||||
return mediaDoc.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Helper methods to return the children for media whther it is based on examine or xml
|
||||
/// </summary>
|
||||
/// <param name="parentId"></param>
|
||||
/// <param name="xpath"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<IPublishedContent> GetChildrenMedia(int parentId, XPathNavigator xpath = null)
|
||||
{
|
||||
|
||||
//if there is no navigator, try examine first, then re-look it up
|
||||
if (xpath == null)
|
||||
{
|
||||
var searchProvider = GetSearchProviderSafe();
|
||||
|
||||
if (searchProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//first check in Examine as this is WAY faster
|
||||
var criteria = searchProvider.CreateSearchCriteria("media");
|
||||
var filter = criteria.ParentId(parentId);
|
||||
ISearchResults results;
|
||||
|
||||
//we want to check if the indexer for this searcher has "sortOrder" flagged as sortable.
|
||||
//if so, we'll use Lucene to do the sorting, if not we'll have to manually sort it (slower).
|
||||
var indexer = GetIndexProviderSafe();
|
||||
var useLuceneSort = indexer != null && indexer.IndexerData.StandardFields.Any(x => x.Name.InvariantEquals("sortOrder") && x.EnableSorting);
|
||||
if (useLuceneSort)
|
||||
{
|
||||
//we have a sortOrder field declared to be sorted, so we'll use Examine
|
||||
results = searchProvider.Search(
|
||||
filter.And().OrderBy(new SortableField("sortOrder", SortType.Int)).Compile());
|
||||
}
|
||||
else
|
||||
{
|
||||
results = searchProvider.Search(filter.Compile());
|
||||
}
|
||||
|
||||
if (results.Any())
|
||||
{
|
||||
return useLuceneSort
|
||||
? results.Select(ConvertFromSearchResult) //will already be sorted by lucene
|
||||
: results.Select(ConvertFromSearchResult).OrderBy(x => x.SortOrder);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
}
|
||||
}
|
||||
|
||||
var media = library.GetMedia(parentId, true);
|
||||
if (media != null && media.Current != null)
|
||||
{
|
||||
xpath = media.Current;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//The xpath might be the whole xpath including the current ones ancestors so we need to select the current node
|
||||
var item = xpath.Select("//*[@id='" + parentId + "']");
|
||||
if (item.Current == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var children = item.Current.SelectChildren(XPathNodeType.Element);
|
||||
|
||||
var mediaList = new List<IPublishedContent>();
|
||||
foreach(XPathNavigator x in children)
|
||||
{
|
||||
//NOTE: I'm not sure why this is here, it is from legacy code of ExamineBackedMedia, but
|
||||
// will leave it here as it must have done something!
|
||||
if (x.Name != "contents")
|
||||
{
|
||||
//make sure it's actually a node, not a property
|
||||
if (!string.IsNullOrEmpty(x.GetAttribute("path", "")) &&
|
||||
!string.IsNullOrEmpty(x.GetAttribute("id", "")))
|
||||
{
|
||||
mediaList.Add(ConvertFromXPathNavigator(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
return mediaList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An IPublishedContent that is represented all by a dictionary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a helper class and definitely not intended for public use, it expects that all of the values required
|
||||
/// to create an IPublishedContent exist in the dictionary by specific aliases.
|
||||
/// </remarks>
|
||||
internal class DictionaryPublishedContent : PublishedContentBase
|
||||
{
|
||||
|
||||
public DictionaryPublishedContent(
|
||||
IDictionary<string, string> valueDictionary,
|
||||
Func<DictionaryPublishedContent, IPublishedContent> getParent,
|
||||
Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> getChildren,
|
||||
Func<DictionaryPublishedContent, string, IPublishedContentProperty> getProperty,
|
||||
bool fromExamine)
|
||||
{
|
||||
if (valueDictionary == null) throw new ArgumentNullException("valueDictionary");
|
||||
if (getParent == null) throw new ArgumentNullException("getParent");
|
||||
if (getProperty == null) throw new ArgumentNullException("getProperty");
|
||||
|
||||
_getParent = getParent;
|
||||
_getChildren = getChildren;
|
||||
_getProperty = getProperty;
|
||||
|
||||
LoadedFromExamine = fromExamine;
|
||||
|
||||
ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int!
|
||||
ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId");
|
||||
ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder");
|
||||
ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName");
|
||||
ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName");
|
||||
ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", "__NodeTypeAlias");
|
||||
ValidateAndSetProperty(valueDictionary, val => _documentTypeId = int.Parse(val), "nodeType");
|
||||
ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName");
|
||||
ValidateAndSetProperty(valueDictionary, val => _creatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132
|
||||
ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID");
|
||||
ValidateAndSetProperty(valueDictionary, val => _creatorId = int.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132
|
||||
ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path");
|
||||
ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate");
|
||||
ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate");
|
||||
ValidateAndSetProperty(valueDictionary, val => _level = int.Parse(val), "level");
|
||||
ValidateAndSetProperty(valueDictionary, val =>
|
||||
{
|
||||
int pId;
|
||||
ParentId = -1;
|
||||
if (int.TryParse(val, out pId))
|
||||
{
|
||||
ParentId = pId;
|
||||
}
|
||||
}, "parentID");
|
||||
|
||||
_properties = new Collection<IPublishedContentProperty>();
|
||||
|
||||
//loop through remaining values that haven't been applied
|
||||
foreach (var i in valueDictionary.Where(x => !_keysAdded.Contains(x.Key)))
|
||||
{
|
||||
//this is taken from examine
|
||||
_properties.Add(i.Key.InvariantStartsWith("__")
|
||||
? new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.CustomProperty)
|
||||
: new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.UserProperty));
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime ParseDateTimeValue(string val)
|
||||
{
|
||||
if (LoadedFromExamine)
|
||||
{
|
||||
try
|
||||
{
|
||||
//we might need to parse the date time using Lucene converters
|
||||
return DateTools.StringToDate(val);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
//swallow exception, its not formatted correctly so revert to just trying to parse
|
||||
}
|
||||
}
|
||||
|
||||
return DateTime.Parse(val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag to get/set if this was laoded from examine cache
|
||||
/// </summary>
|
||||
internal bool LoadedFromExamine { get; private set; }
|
||||
|
||||
private readonly Func<DictionaryPublishedContent, IPublishedContent> _getParent;
|
||||
private readonly Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> _getChildren;
|
||||
private readonly Func<DictionaryPublishedContent, string, IPublishedContentProperty> _getProperty;
|
||||
|
||||
/// <summary>
|
||||
/// Returns 'Media' as the item type
|
||||
/// </summary>
|
||||
public override PublishedItemType ItemType
|
||||
{
|
||||
get { return PublishedItemType.Media; }
|
||||
}
|
||||
|
||||
public override IPublishedContent Parent
|
||||
{
|
||||
get { return _getParent(this); }
|
||||
}
|
||||
|
||||
public int ParentId { get; private set; }
|
||||
public override int Id
|
||||
{
|
||||
get { return _id; }
|
||||
}
|
||||
|
||||
public override int TemplateId
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO: should probably throw a not supported exception since media doesn't actually support this.
|
||||
return _templateId;
|
||||
}
|
||||
}
|
||||
|
||||
public override int SortOrder
|
||||
{
|
||||
get { return _sortOrder; }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return _name; }
|
||||
}
|
||||
|
||||
public override string UrlName
|
||||
{
|
||||
get { return _urlName; }
|
||||
}
|
||||
|
||||
public override string DocumentTypeAlias
|
||||
{
|
||||
get { return _documentTypeAlias; }
|
||||
}
|
||||
|
||||
public override int DocumentTypeId
|
||||
{
|
||||
get { return _documentTypeId; }
|
||||
}
|
||||
|
||||
public override string WriterName
|
||||
{
|
||||
get { return _writerName; }
|
||||
}
|
||||
|
||||
public override string CreatorName
|
||||
{
|
||||
get { return _creatorName; }
|
||||
}
|
||||
|
||||
public override int WriterId
|
||||
{
|
||||
get { return _writerId; }
|
||||
}
|
||||
|
||||
public override int CreatorId
|
||||
{
|
||||
get { return _creatorId; }
|
||||
}
|
||||
|
||||
public override string Path
|
||||
{
|
||||
get { return _path; }
|
||||
}
|
||||
|
||||
public override DateTime CreateDate
|
||||
{
|
||||
get { return _createDate; }
|
||||
}
|
||||
|
||||
public override DateTime UpdateDate
|
||||
{
|
||||
get { return _updateDate; }
|
||||
}
|
||||
|
||||
public override Guid Version
|
||||
{
|
||||
get { return _version; }
|
||||
}
|
||||
|
||||
public override int Level
|
||||
{
|
||||
get { return _level; }
|
||||
}
|
||||
|
||||
public override ICollection<IPublishedContentProperty> Properties
|
||||
{
|
||||
get { return _properties; }
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> Children
|
||||
{
|
||||
get { return _getChildren(this); }
|
||||
}
|
||||
|
||||
public override IPublishedContentProperty GetProperty(string alias)
|
||||
{
|
||||
return _getProperty(this, alias);
|
||||
}
|
||||
|
||||
private readonly List<string> _keysAdded = new List<string>();
|
||||
private int _id;
|
||||
private int _templateId;
|
||||
private int _sortOrder;
|
||||
private string _name;
|
||||
private string _urlName;
|
||||
private string _documentTypeAlias;
|
||||
private int _documentTypeId;
|
||||
private string _writerName;
|
||||
private string _creatorName;
|
||||
private int _writerId;
|
||||
private int _creatorId;
|
||||
private string _path;
|
||||
private DateTime _createDate;
|
||||
private DateTime _updateDate;
|
||||
private Guid _version;
|
||||
private int _level;
|
||||
private readonly ICollection<IPublishedContentProperty> _properties;
|
||||
|
||||
private void ValidateAndSetProperty(IDictionary<string, string> valueDictionary, Action<string> setProperty, params string[] potentialKeys)
|
||||
{
|
||||
var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null);
|
||||
if (key == null)
|
||||
{
|
||||
throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + string.Join(",", potentialKeys) + "' elements");
|
||||
}
|
||||
|
||||
setProperty(valueDictionary[key]);
|
||||
_keysAdded.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs
Normal file
145
src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
class RoutesCache
|
||||
{
|
||||
private ConcurrentDictionary<int, string> _routes;
|
||||
private ConcurrentDictionary<string, int> _nodeIds;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutesCache"/> class.
|
||||
/// </summary>
|
||||
public RoutesCache()
|
||||
: this(true)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutesCache"/> class.
|
||||
/// </summary>
|
||||
internal RoutesCache(bool bindToEvents)
|
||||
{
|
||||
Clear();
|
||||
|
||||
if (bindToEvents)
|
||||
{
|
||||
Resolution.Frozen += ResolutionFrozen;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Once resolution is frozen, then we can bind to the events that we require
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="args"></param>
|
||||
private void ResolutionFrozen(object s, EventArgs args)
|
||||
{
|
||||
// content - whenever the entire XML cache is rebuilt (from disk cache or from database)
|
||||
// we must clear the cache entirely
|
||||
global::umbraco.content.AfterRefreshContent += (sender, e) => Clear();
|
||||
|
||||
// document - whenever a document is updated in, or removed from, the XML cache
|
||||
// we must clear the cache - at the moment, we clear the entire cache
|
||||
// TODO could we do partial updates instead of clearing the whole cache?
|
||||
global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear();
|
||||
global::umbraco.content.AfterClearDocumentCache += (sender, e) => Clear();
|
||||
|
||||
// FIXME
|
||||
// the content class needs to be refactored - at the moment
|
||||
// content.XmlContentInternal setter does not trigger any event
|
||||
// content.UpdateDocumentCache(List<Document> Documents) does not trigger any event
|
||||
// content.RefreshContentFromDatabaseAsync triggers AfterRefresh _while_ refreshing
|
||||
// etc...
|
||||
// in addition some events do not make sense... we trigger Publish when moving
|
||||
// a node, which we should not (the node is moved, not published...) etc.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used ONLY for unit tests
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal IDictionary<int, string> GetCachedRoutes()
|
||||
{
|
||||
return _routes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used ONLY for unit tests
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal IDictionary<string, int> GetCachedIds()
|
||||
{
|
||||
return _nodeIds;
|
||||
}
|
||||
|
||||
#region Public
|
||||
|
||||
/// <summary>
|
||||
/// Stores a route for a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identified.</param>
|
||||
/// <param name="route">The route.</param>
|
||||
public void Store(int nodeId, string route)
|
||||
{
|
||||
_routes.AddOrUpdate(nodeId, i => route, (i, s) => route);
|
||||
_nodeIds.AddOrUpdate(route, i => nodeId, (i, s) => nodeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a route for a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
/// <returns>The route for the node, else null.</returns>
|
||||
public string GetRoute(int nodeId)
|
||||
{
|
||||
string val;
|
||||
_routes.TryGetValue(nodeId, out val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a node for a route.
|
||||
/// </summary>
|
||||
/// <param name="route">The route.</param>
|
||||
/// <returns>The node identified for the route, else zero.</returns>
|
||||
public int GetNodeId(string route)
|
||||
{
|
||||
int val;
|
||||
_nodeIds.TryGetValue(route, out val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the route for a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
public void ClearNode(int nodeId)
|
||||
{
|
||||
if (!_routes.ContainsKey(nodeId)) return;
|
||||
|
||||
string key;
|
||||
if (!_routes.TryGetValue(nodeId, out key)) return;
|
||||
|
||||
int val;
|
||||
_nodeIds.TryRemove(key, out val);
|
||||
string val2;
|
||||
_routes.TryRemove(nodeId, out val2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all routes.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_routes = new ConcurrentDictionary<int, string>();
|
||||
_nodeIds = new ConcurrentDictionary<string, int>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user