Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7

This commit is contained in:
Shannon
2015-06-15 17:13:42 +02:00
5 changed files with 225 additions and 74 deletions

View File

@@ -146,7 +146,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
result.Fields.Add("writerName", "Shannon");
var store = new PublishedMediaCache(ctx.Application);
var doc = store.ConvertFromSearchResult(result);
var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result));
DoAssert(doc, 1234, 0, 0, "", "Image", 0, "Shannon", "", 0, 0, "-1,1234", default(DateTime), DateTime.Parse("2012-07-16T10:34:09"), 2);
Assert.AreEqual(null, doc.Parent);
@@ -160,7 +160,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
var xmlDoc = GetMediaXml();
var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator();
var cache = new PublishedMediaCache(ctx.Application);
var doc = cache.ConvertFromXPathNavigator(navigator);
var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true));
DoAssert(doc, 2000, 0, 2, "image1", "Image", 2044, "Shannon", "Shannon2", 22, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1);
Assert.AreEqual(null, doc.Parent);
@@ -246,12 +246,14 @@ namespace Umbraco.Tests.Cache.PublishedCache
//there is no parent
a => null,
//we're not going to test this so ignore
a => new List<IPublishedContent>(),
(dd, n) => new List<IPublishedContent>(),
(dd, a) => dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(a)),
null,
false),
//callback to get the children
d => children,
(dd, n) => children,
(dd, a) => dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(a)),
null,
false);
return dicDoc;
}

View File

@@ -445,7 +445,8 @@ namespace Umbraco.Tests.PublishedContent
var nav = node.CreateNavigator();
var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/node"), nodeId);
var converted = publishedMedia.CreateFromCacheValues(
publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/node"), nodeId));
Assert.AreEqual(nodeId, converted.Id);
Assert.AreEqual(3, converted.Level);
@@ -486,7 +487,8 @@ namespace Umbraco.Tests.PublishedContent
var nav = node.CreateNavigator();
var converted = publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/Image"), nodeId);
var converted = publishedMedia.CreateFromCacheValues(
publishedMedia.ConvertFromXPathNodeIterator(nav.Select("/Image"), nodeId));
Assert.AreEqual(nodeId, converted.Id);
Assert.AreEqual(3, converted.Level);

View File

@@ -18,6 +18,9 @@ using Umbraco.Core.Xml;
using Umbraco.Web.Models;
using UmbracoExamine;
using umbraco;
using Umbraco.Core.Cache;
using Umbraco.Core.Sync;
using Umbraco.Web.Cache;
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
@@ -33,6 +36,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
if (applicationContext == null) throw new ArgumentNullException("applicationContext");
_applicationContext = applicationContext;
MediaCacheRefresher.CacheUpdated += MediaCacheUpdated;
}
/// <summary>
@@ -167,9 +172,20 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return null;
}
private IPublishedContent GetUmbracoMedia(int id)
{
var searchProvider = GetSearchProviderSafe();
private IPublishedContent GetUmbracoMedia(int id)
{
// this recreates an IPublishedContent and model each time
// it is called, but at least it should NOT hit the database
// nor Lucene each time, relying on the memory cache instead
var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues);
return cacheValues == null ? null : CreateFromCacheValues(cacheValues);
}
private CacheValues GetUmbracoMediaCacheValues(int id)
{
var searchProvider = GetSearchProviderSafe();
if (searchProvider != null)
{
@@ -202,12 +218,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
"Could not retrieve media {0} from Examine index, reverting to looking up media via legacy library.GetMedia method",
() => id);
var media = global::umbraco.library.GetMedia(id, true);
var media = global::umbraco.library.GetMedia(id, false);
return ConvertFromXPathNodeIterator(media, id);
}
internal IPublishedContent ConvertFromXPathNodeIterator(XPathNodeIterator media, int id)
internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id)
{
if (media != null && media.Current != null)
{
@@ -223,7 +239,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return null;
}
internal IPublishedContent ConvertFromSearchResult(SearchResult searchResult)
internal CacheValues 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:
@@ -253,19 +269,24 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
values.Add("level", values["__Path"].Split(',').Length.ToString());
}
return new CacheValues
{
Values = values,
FromExamine = true
};
var content = 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);
return content.CreateModel();
//var content = 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);
//return content.CreateModel();
}
internal IPublishedContent ConvertFromXPathNavigator(XPathNavigator xpath)
internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false)
{
if (xpath == null) throw new ArgumentNullException("xpath");
@@ -313,15 +334,21 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
}
var content = 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);
return content.CreateModel();
return new CacheValues
{
Values = values,
XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator!
};
//var content = 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);
//return content.CreateModel();
}
/// <summary>
@@ -398,9 +425,17 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
if (results.Any())
{
return useLuceneSort
? results.Select(ConvertFromSearchResult) //will already be sorted by lucene
: results.Select(ConvertFromSearchResult).OrderBy(x => x.SortOrder);
// var medias = results.Select(ConvertFromSearchResult);
var medias = results.Select(x =>
{
int nid;
if (int.TryParse(x["__NodeId"], out nid) == false && int.TryParse(x["NodeId"], out nid) == false)
throw new Exception("Failed to extract NodeId from search result.");
var cacheValues = GetCacheValues(nid, id => ConvertFromSearchResult(x));
return CreateFromCacheValues(cacheValues);
});
return useLuceneSort ? medias : medias.OrderBy(x => x.SortOrder);
}
else
{
@@ -432,29 +467,51 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
}
//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 Enumerable.Empty<IPublishedContent>();
}
var children = item.Current.SelectChildren(XPathNodeType.Element);
var mediaList = new List<IPublishedContent>();
// this is so bad, really
var item = xpath.Select("//*[@id='" + parentId + "']");
if (item.Current == null)
return Enumerable.Empty<IPublishedContent>();
var items = item.Current.SelectChildren(XPathNodeType.Element);
// and this does not work, because... meh
//var q = "//* [@id='" + parentId + "']/* [@id]";
//var items = xpath.Select(q);
foreach (XPathNavigator itemm in items)
{
int id;
if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false)
continue; // wtf?
var captured = itemm;
var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured));
mediaList.Add(CreateFromCacheValues(cacheValues));
}
////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 Enumerable.Empty<IPublishedContent>();
//}
//var children = item.Current.SelectChildren(XPathNodeType.Element);
//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));
// }
// }
//}
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;
}
@@ -477,23 +534,25 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
public DictionaryPublishedContent(
IDictionary<string, string> valueDictionary,
Func<DictionaryPublishedContent, IPublishedContent> getParent,
Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> getChildren,
Func<int, IPublishedContent> getParent,
Func<int, XPathNavigator, IEnumerable<IPublishedContent>> getChildren,
Func<DictionaryPublishedContent, string, IPublishedProperty> getProperty,
XPathNavigator nav,
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;
_getParent = new Lazy<IPublishedContent>(() => getParent(ParentId));
_getChildren = new Lazy<IEnumerable<IPublishedContent>>(() => getChildren(Id, nav));
_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");
// wtf are we dealing with templates for medias?!
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");
@@ -576,8 +635,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
/// </summary>
internal bool LoadedFromExamine { get; private set; }
private readonly Func<DictionaryPublishedContent, IPublishedContent> _getParent;
private readonly Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> _getChildren;
//private readonly Func<DictionaryPublishedContent, IPublishedContent> _getParent;
private readonly Lazy<IPublishedContent> _getParent;
//private readonly Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> _getChildren;
private readonly Lazy<IEnumerable<IPublishedContent>> _getChildren;
private readonly Func<DictionaryPublishedContent, string, IPublishedProperty> _getProperty;
/// <summary>
@@ -590,7 +651,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
public override IPublishedContent Parent
{
get { return _getParent(this); }
get { return _getParent.Value; }
}
public int ParentId { get; private set; }
@@ -690,7 +751,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
public override IEnumerable<IPublishedContent> Children
{
get { return _getChildren(this); }
get { return _getChildren.Value; }
}
public override IPublishedProperty GetProperty(string alias)
@@ -768,5 +829,91 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
_keysAdded.Add(key);
}
}
}
// REFACTORING
internal class CacheValues
{
public IDictionary<string, string> Values { get; set; }
public XPathNavigator XPath { get; set; }
public bool FromExamine { get; set; }
}
internal IPublishedContent CreateFromCacheValues(CacheValues cacheValues)
{
var content = new DictionaryPublishedContent(
cacheValues.Values,
parentId => parentId < 0 ? null : GetUmbracoMedia(parentId),
(id, nav) => GetChildrenMedia(id, nav),
GetProperty,
cacheValues.XPath, // though, outside of tests, that should be null
cacheValues.FromExamine
);
return content.CreateModel();
}
private const string CachePrefix = "MediaCacheMeh.";
private CacheValues GetCacheValues(int id, Func<int, CacheValues> func)
{
var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache;
var key = CachePrefix + id;
// cache for 30s - should we be more aggressive?
return (CacheValues) cache.GetCacheItem(key, () => func(id), TimeSpan.FromSeconds(30));
}
private void MediaCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args)
{
var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache;
int id;
switch (args.MessageType)
{
case MessageType.RefreshAll:
cache.ClearCacheByKeySearch(CachePrefix);
break;
case MessageType.RefreshById:
case MessageType.RemoveById:
id = (int) args.MessageObject;
ClearCache(id);
break;
case MessageType.RefreshByInstance:
case MessageType.RemoveByInstance:
id = ((IMedia) args.MessageObject).Id;
ClearCache(id);
break;
case MessageType.RefreshByJson:
var payloads = MediaCacheRefresher.DeserializeFromJsonPayload((string) args.MessageObject);
foreach (var pid in payloads.Select(x => x.Id))
ClearCache(pid);
break;
}
}
private void ClearCache(int id)
{
var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache;
var sid = id.ToString();
var key = CachePrefix + sid;
// clear the parent
var exist = (CacheValues) cache.GetCacheItem(key);
if (exist != null)
cache.ClearCacheItem(CachePrefix + GetValuesValue(exist.Values, "parentID"));
// clear the item
cache.ClearCacheItem(key);
// clear all children
var fid = "/" + sid + "/";
cache.ClearCacheObjectTypes<CacheValues>((k, v) =>
GetValuesValue(v.Values, "path", "__Path").Contains(fid));
}
private string GetValuesValue(IDictionary<string, string> d, params string[] keys)
{
string value = null;
var found = keys.Any(x => d.TryGetValue(x, out value));
return value ?? "";
}
}
}

View File

@@ -104,11 +104,14 @@ namespace Umbraco.Web.Routing
{
// find the document & template
FindPublishedContentAndTemplate();
// set the culture on the thread -- again, 'cos it might have changed due to a wildcard domain
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture;
}
// handle wildcard domains
HandleWildcardDomains();
// set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture;
// trigger the Prepared event - at that point it is still possible to change about anything
// even though the request might be flagged for redirection - we'll redirect _after_ the event
//
@@ -380,9 +383,6 @@ namespace Umbraco.Web.Routing
// handle umbracoRedirect
FollowExternalRedirect();
// handle wildcard domains
HandleWildcardDomains();
}
/// <summary>

View File

@@ -537,8 +537,8 @@ namespace umbraco
/// <returns></returns>
private Control AddMacroResultToCache(Control macroControl)
{
// Add result to cache if successful
if (Model.CacheDuration > 0)
// Add result to cache if successful (and cache is enabled)
if (UmbracoContext.Current.InPreviewMode == false && Model.CacheDuration > 0)
{
// do not add to cache if there's no member and it should cache by personalization
if (!Model.CacheByMember || (Model.CacheByMember && Member.IsLoggedOn()))