From 8aa730d9867bea078a189ce77a259dd7577400fd Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 5 Nov 2016 12:22:57 +0100 Subject: [PATCH] Ensure U4-8720 is properly merged into v8 --- .../Cache/SnapDictionaryTests.cs | 36 +++++++++++- .../PublishedContent/PublishedContentTests.cs | 21 ++++++- src/Umbraco.Web/IPublishedContentQuery.cs | 4 +- .../PublishedCache/NuCache/ContentCache.cs | 14 +++-- .../PublishedCache/NuCache/ContentStore2.cs | 56 ++++++++++++++++--- .../PublishedCache/NuCache/MediaCache.cs | 9 +-- .../PublishedCache/NuCache/SnapDictionary.cs | 12 ++-- .../PublishedContentCache.cs | 45 +++++++++++++-- src/Umbraco.Web/PublishedContentQuery.cs | 14 +++++ src/Umbraco.Web/UmbracoHelper.cs | 2 +- 10 files changed, 179 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs index c7dc51cb07..bb4ec02c7a 100644 --- a/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs +++ b/src/Umbraco.Tests/Cache/SnapDictionaryTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using NUnit.Framework; using Umbraco.Web.PublishedCache.NuCache; @@ -448,7 +449,7 @@ namespace Umbraco.Tests.Cache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); - + Assert.AreEqual(3, d.SnapCount); s1.Dispose(); @@ -706,5 +707,38 @@ namespace Umbraco.Tests.Cache Assert.IsFalse(d.Test.NextGen); Assert.AreEqual("ein", s4.Get(1)); } + + [Test] + public void GetAll() + { + var d = new SnapDictionary(); + d.Test.CollectAuto = false; + + Assert.AreEqual(0, d.Test.GetValues(1).Length); + + d.Set(1, "one"); + d.Set(2, "two"); + d.Set(3, "three"); + d.Set(4, "four"); + + var s1 = d.CreateSnapshot(); + var all = s1.GetAll().ToArray(); + Assert.AreEqual(4, all.Length); + Assert.AreEqual("one", all[0]); + Assert.AreEqual("four", all[3]); + + d.Set(1, "uno"); + var s2 = d.CreateSnapshot(); + + all = s1.GetAll().ToArray(); + Assert.AreEqual(4, all.Length); + Assert.AreEqual("one", all[0]); + Assert.AreEqual("four", all[3]); + + all = s2.GetAll().ToArray(); + Assert.AreEqual(4, all.Length); + Assert.AreEqual("uno", all[0]); + Assert.AreEqual("four", all[3]); + } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 10cee2072c..2b72d7738c 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -72,6 +72,8 @@ namespace Umbraco.Tests.PublishedContent base.MoreSetUp(); } + private readonly Guid _node1173Guid = Guid.NewGuid(); + protected override string GetXmlContent(int templateId) { return @" @@ -87,7 +89,7 @@ namespace Umbraco.Tests.PublishedContent 1 - + This is some content]]> @@ -122,6 +124,23 @@ namespace Umbraco.Tests.PublishedContent return doc; } + [Test] + public void GetNodeByIds() + { + var ctx = GetUmbracoContext("/test"); + var contentById = ctx.ContentCache.GetById(1173); + Assert.IsNotNull(contentById); + var contentByGuid = ctx.ContentCache.GetById(_node1173Guid); + Assert.IsNotNull(contentByGuid); + Assert.AreEqual(contentById.Id, contentByGuid.Id); + Assert.AreEqual(contentById.Key, contentByGuid.Key); + + contentById = ctx.ContentCache.GetById(666); + Assert.IsNull(contentById); + contentByGuid = ctx.ContentCache.GetById(Guid.NewGuid()); + Assert.IsNull(contentByGuid); + } + [Test] public void Is_Last_From_Where_Filter_Dynamic_Linq() { diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 471b998f21..ef7b355d90 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -21,9 +21,9 @@ namespace Umbraco.Web IEnumerable ContentAtRoot(); IPublishedContent Media(int id); - //IPublishedContent Media(Guid id); + IPublishedContent Media(Guid id); IEnumerable Media(IEnumerable ids); - //IEnumerable Media(IEnumerable ids); + IEnumerable Media(IEnumerable ids); IEnumerable MediaAtRoot(); /// diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 57bb4280c2..c4b09fcc0c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -15,7 +15,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.PublishedCache.NuCache { - class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable + internal class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable { private readonly ContentStore2.Snapshot _snapshot; private readonly ICacheProvider _facadeCache; @@ -209,11 +209,15 @@ namespace Umbraco.Web.PublishedCache.NuCache : n.Published; } - public override IPublishedContent GetById(bool preview, Guid nodeId) + public override IPublishedContent GetById(bool preview, Guid contentId) { - // fixme - implement in a more efficient way - const string xpath = "//* [@isDoc and @key=$guid]"; - return GetSingleByXPath(preview, xpath, new[] { new XPathVariable("guid", nodeId.ToString()) }); + var n = _snapshot.Get(contentId); + if (n == null) return null; + + // both .Draft and .Published cannot be null at the same time + return preview + ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) + : n.Published; } public override bool HasById(bool preview, int contentId) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore2.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore2.cs index 825bcd0fce..1b16a7b93c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore2.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore2.cs @@ -180,7 +180,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _localDb = null; }); } - + #endregion #region Content types @@ -295,7 +295,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // unknown = bad if (_contentTypesById.TryGetValue(kit.ContentTypeId, out link) == false || link.Value == null) return false; - + // not checking ByAlias, assuming we don't have internal errors // register @@ -560,7 +560,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // already in the dict if (link.Gen != _liveGen) { - // for an older gen - if value is different then insert a new + // for an older gen - if value is different then insert a new // link for the new gen, with the new value if (link.Value != value) dict.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); @@ -619,7 +619,7 @@ namespace Umbraco.Web.PublishedCache.NuCache break; link = link.Next; } - if (link != null && link.Value != null) + if (link?.Value != null) yield return Get(kvp.Key, gen); } } @@ -638,6 +638,27 @@ namespace Umbraco.Web.PublishedCache.NuCache return null; } + public IEnumerable GetAll(long gen) + { + // enumerating on .Values locks the concurrent dictionary, + // so better get a shallow clone in an array and release + var links = _contentNodes.Values.ToArray(); + foreach (var l in links) + { + var link = l; + while (link != null) + { + if (link.Gen <= gen) + { + if (link.Value != null) + yield return link.Value; + break; + } + link = link.Next; + } + } + } + public bool IsEmpty(long gen) { var has = _contentNodes.Any(x => @@ -737,7 +758,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { if (_collectTask != null) return _collectTask; - + // ReSharper disable InconsistentlySynchronizedField var task = _collectTask = Task.Run(() => Collect()); _collectTask.ContinueWith(_ => @@ -795,7 +816,7 @@ namespace Umbraco.Web.PublishedCache.NuCache #if DEBUG //_logger.Debug("Collect id:" + kvp.Key + ", gen:" + link.Gen + - // ", nxt:" + (link.Next == null ? "null" : "link") + + // ", nxt:" + (link.Next == null ? "null" : "link") + // ", val:" + (link.Value == null ? "null" : "value")); #endif @@ -807,7 +828,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (link.Gen < liveGen && link.Value == null && (link.Next == null || link.Gen <= _floorGen)) { - // not live, null value, no next link = remove that one -- but only if + // not live, null value, no next link = remove that one -- but only if // the dict has not been updated, have to do it via ICollection<> (thanks // Mr Toub) -- and if the dict has been updated there is nothing to collect var idict = dict as ICollection>>; @@ -891,7 +912,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } internal TestHelper Test { get { return _unitTesting ?? (_unitTesting = new TestHelper(this)); } } - + #endregion #region Classes @@ -951,6 +972,14 @@ namespace Umbraco.Web.PublishedCache.NuCache return _store.Get(id, _gen); } + public ContentNode Get(Guid id) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + // fixme - optimize with an index - getAll/iterating is expensive + return _store.GetAll(_gen).FirstOrDefault(x => x.Uid == id); + } + public IEnumerable GetAtRoot() { if (_gen < 0) @@ -972,6 +1001,17 @@ namespace Umbraco.Web.PublishedCache.NuCache return _store.GetContentType(alias, _gen); } + // this code is here just so you don't try to implement it + // the only way we can iterate over "all" without locking the entire cache forever + // is by shallow cloning the cache, which is quite expensive, so we should probably not do it, + // and implement cache-level indexes + //public IEnumerable GetAll() + //{ + // if (_gen < 0) + // throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + // return _store.GetAll(_gen); + //} + public bool IsEmpty { get diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs index 9f59246c12..615c820eda 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml.XPath; -using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Core.Xml.XPath; @@ -12,7 +10,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable; namespace Umbraco.Web.PublishedCache.NuCache { - class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableData, IDisposable + internal class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableData, IDisposable { private readonly ContentStore2.Snapshot _snapshot; private readonly ICacheProvider _facadeCache; @@ -40,9 +38,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public override IPublishedContent GetById(bool preview, Guid contentId) { - // fixme - test + implement in a more efficient way - const string xpath = "//* [@isDoc and @key=$guid]"; - return GetSingleByXPath(preview, xpath, new[] { new XPathVariable("guid", contentId.ToString()) }); + var n = _snapshot.Get(contentId); + return n?.Published; } public override bool HasById(bool preview, int contentId) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs index e99b73ce6d..487092b14b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -252,16 +252,20 @@ namespace Umbraco.Web.PublishedCache.NuCache // enumerating on .Values locks the concurrent dictionary, // so better get a shallow clone in an array and release var links = _items.Values.ToArray(); - return links.Select(link => + foreach (var l in links) { + var link = l; while (link != null) { if (link.Gen <= gen) - return link.Value; // may be null + { + if (link.Value != null) + yield return link.Value; + break; + } link = link.Next; } - return null; - }).Where(x => x != null); + } } public bool IsEmpty(long gen) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index ae2f3f8866..803477f6e8 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Text; using System.Xml; using System.Xml.XPath; using Umbraco.Core.Configuration; @@ -11,8 +10,6 @@ using Umbraco.Core.Xml; using Umbraco.Web.Routing; using System.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Services; -using Task = System.Threading.Tasks.Task; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -358,9 +355,45 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override IPublishedContent GetById(bool preview, Guid nodeId) { - // todo - implement in a more efficient way - const string xpath = "//* [@isDoc and @key=$guid]"; - return GetSingleByXPath(preview, xpath, new [] { new XPathVariable("guid", nodeId.ToString()) }); + // implement this, but in a more efficient way + //const string xpath = "//* [@isDoc and @key=$guid]"; + //return GetSingleByXPath(preview, xpath, new[] { new XPathVariable("guid", nodeId.ToString()) }); + + var keyMatch = nodeId.ToString(); + + var nav = GetXml(preview).CreateNavigator(); + if (nav.MoveToFirstChild() == false) return null; // from / to /root + if (nav.MoveToFirstChild() == false) return null; // from /root to /root/* + + while (true) + { + var isDoc = false; + string key = null; + + if (nav.HasAttributes) + { + nav.MoveToFirstAttribute(); + do + { + if (nav.Name == "isDoc") isDoc = true; + if (nav.Name == "key") key = nav.Value; + if (isDoc && key != null) break; + } while (nav.MoveToNextAttribute()); + nav.MoveToParent(); + } + + if (isDoc == false || key != keyMatch) + { + if (isDoc && nav.MoveToFirstChild()) + continue; + while (nav.MoveToNext(XPathNodeType.Element) == false) + if (nav.MoveToParent() == false || nav.NodeType == XPathNodeType.Root) return null; + continue; + } + + var elt = nav.UnderlyingObject as XmlNode; + return ConvertToDocument(elt, preview); + } } public override bool HasById(bool preview, int contentId) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index e09cd1ae50..c2457ef5ff 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -110,6 +110,13 @@ namespace Umbraco.Web : _query.Media(id); } + public IPublishedContent Media(Guid id) + { + return _query == null + ? ItemById(id, _mediaCache) + : _query.Media(id); + } + public IEnumerable Media(IEnumerable ids) { return _query == null @@ -117,6 +124,13 @@ namespace Umbraco.Web : _query.Media(ids); } + public IEnumerable Media(IEnumerable ids) + { + return _query == null + ? ItemsByIds(_mediaCache, ids) + : _query.Media(ids); + } + public IEnumerable MediaAtRoot() { return _query == null diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 8e5da0fcf5..920449ebe9 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -670,7 +670,7 @@ namespace Umbraco.Web int intId; if (ConvertIdObjectToInt(id, out intId)) return ContentQuery.Media(intId); - Guid guidId; + //Guid guidId; //if (ConvertIdObjectToGuid(id, out guidId)) // return ContentQuery.Media(guidId); return null;