2016-05-27 14:26:28 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using CSharpTest.Net.Collections;
|
|
|
|
|
|
using Umbraco.Core.Logging;
|
|
|
|
|
|
using Umbraco.Core.Models.PublishedContent;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
using Umbraco.Core.Scoping;
|
2019-02-22 16:03:39 +01:00
|
|
|
|
using Umbraco.Web.PublishedCache.NuCache.Snap;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.PublishedCache.NuCache
|
|
|
|
|
|
{
|
|
|
|
|
|
// stores content
|
2017-07-12 14:09:31 +02:00
|
|
|
|
internal class ContentStore
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// this class is an extended version of SnapDictionary
|
|
|
|
|
|
// most of the snapshots management code, etc is an exact copy
|
|
|
|
|
|
// SnapDictionary has unit tests to ensure it all works correctly
|
|
|
|
|
|
|
2017-10-31 12:48:24 +01:00
|
|
|
|
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
2018-04-30 21:29:49 +02:00
|
|
|
|
private readonly IVariationContextAccessor _variationContextAccessor;
|
2019-01-23 14:16:42 +01:00
|
|
|
|
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
private readonly ConcurrentDictionary<int, LinkedNode<ContentNode>> _contentNodes;
|
|
|
|
|
|
private readonly ConcurrentDictionary<int, LinkedNode<object>> _contentRootNodes;
|
|
|
|
|
|
private readonly ConcurrentDictionary<int, LinkedNode<PublishedContentType>> _contentTypesById;
|
|
|
|
|
|
private readonly ConcurrentDictionary<string, LinkedNode<PublishedContentType>> _contentTypesByAlias;
|
2018-03-30 14:00:44 +02:00
|
|
|
|
private readonly ConcurrentDictionary<Guid, int> _xmap;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
private BPlusTree<int, ContentNodeKit> _localDb;
|
2019-02-22 15:30:55 +01:00
|
|
|
|
private readonly ConcurrentQueue<GenObj> _genObjs;
|
|
|
|
|
|
private GenObj _genObj;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
private readonly object _wlocko = new object();
|
|
|
|
|
|
private readonly object _rlocko = new object();
|
|
|
|
|
|
private long _liveGen, _floorGen;
|
|
|
|
|
|
private bool _nextGen, _collectAuto;
|
|
|
|
|
|
private Task _collectTask;
|
|
|
|
|
|
private volatile int _wlocked;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
private List<KeyValuePair<int, ContentNodeKit>> _wchanges;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: collection trigger (ok for now)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// see SnapDictionary notes
|
|
|
|
|
|
private const long CollectMinGenDelta = 8;
|
|
|
|
|
|
|
|
|
|
|
|
#region Ctor
|
|
|
|
|
|
|
2019-01-23 14:16:42 +01:00
|
|
|
|
public ContentStore(
|
|
|
|
|
|
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
|
|
|
|
|
IVariationContextAccessor variationContextAccessor,
|
|
|
|
|
|
IUmbracoContextAccessor umbracoContextAccessor,
|
|
|
|
|
|
ILogger logger,
|
|
|
|
|
|
BPlusTree<int, ContentNodeKit> localDb = null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-10-31 12:48:24 +01:00
|
|
|
|
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
2018-04-30 21:29:49 +02:00
|
|
|
|
_variationContextAccessor = variationContextAccessor;
|
2019-01-23 14:16:42 +01:00
|
|
|
|
_umbracoContextAccessor = umbracoContextAccessor;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_localDb = localDb;
|
|
|
|
|
|
|
|
|
|
|
|
_contentNodes = new ConcurrentDictionary<int, LinkedNode<ContentNode>>();
|
|
|
|
|
|
_contentRootNodes = new ConcurrentDictionary<int, LinkedNode<object>>();
|
|
|
|
|
|
_contentTypesById = new ConcurrentDictionary<int, LinkedNode<PublishedContentType>>();
|
|
|
|
|
|
_contentTypesByAlias = new ConcurrentDictionary<string, LinkedNode<PublishedContentType>>(StringComparer.InvariantCultureIgnoreCase);
|
2018-03-30 14:00:44 +02:00
|
|
|
|
_xmap = new ConcurrentDictionary<Guid, int>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-02-22 15:30:55 +01:00
|
|
|
|
_genObjs = new ConcurrentQueue<GenObj>();
|
|
|
|
|
|
_genObj = null; // no initial gen exists
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_liveGen = _floorGen = 0;
|
|
|
|
|
|
_nextGen = false; // first time, must create a snapshot
|
|
|
|
|
|
_collectAuto = true; // collect automatically by default
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Locking
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
// see notes on SnapDictionary
|
|
|
|
|
|
|
|
|
|
|
|
private readonly string _instanceId = Guid.NewGuid().ToString("N");
|
|
|
|
|
|
|
|
|
|
|
|
private class ReadLockInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
public bool Taken;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class WriteLockInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
public bool Taken;
|
|
|
|
|
|
public bool Count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// a scope contextual that represents a locked writer to the dictionary
|
|
|
|
|
|
private class ContentStoreWriter : ScopeContextualBase
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
|
|
|
|
|
|
private ContentStore _store;
|
|
|
|
|
|
|
|
|
|
|
|
public ContentStoreWriter(ContentStore store, bool scoped)
|
|
|
|
|
|
{
|
|
|
|
|
|
_store = store;
|
|
|
|
|
|
store.Lock(_lockinfo, scoped);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Release(bool completed)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_store== null) return;
|
|
|
|
|
|
_store.Release(_lockinfo, completed);
|
|
|
|
|
|
_store = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// gets a scope contextual representing a locked writer to the dictionary
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: GetScopedWriter? should the dict have a ref onto the scope provider?
|
2017-07-17 10:48:48 +02:00
|
|
|
|
public IDisposable GetWriter(IScopeProvider scopeProvider)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ContentStoreWriter(this, scoped));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Monitor.Enter(_wlocko, ref lockInfo.Taken);
|
|
|
|
|
|
|
|
|
|
|
|
var rtaken = false;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Monitor.Enter(_rlocko, ref rtaken);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
// see SnapDictionary
|
|
|
|
|
|
try { } finally
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
_wlocked++;
|
|
|
|
|
|
lockInfo.Count = true;
|
|
|
|
|
|
if (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
// because we are changing things, a new generation
|
|
|
|
|
|
// is created, which will trigger a new snapshot
|
|
|
|
|
|
_nextGen = true;
|
|
|
|
|
|
_liveGen += 1;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (rtaken) Monitor.Exit(_rlocko);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
private void Lock(ReadLockInfo lockInfo)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Monitor.Enter(_rlocko, ref lockInfo.Taken);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
private void Release(WriteLockInfo lockInfo, bool commit = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (commit == false)
|
|
|
|
|
|
{
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var rtaken = false;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Monitor.Enter(_rlocko, ref rtaken);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
try { }
|
2016-05-27 14:26:28 +02:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
_nextGen = false;
|
|
|
|
|
|
_liveGen -= 1;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
if (rtaken) Monitor.Exit(_rlocko);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Rollback(_contentNodes);
|
|
|
|
|
|
Rollback(_contentRootNodes);
|
|
|
|
|
|
Rollback(_contentTypesById);
|
|
|
|
|
|
Rollback(_contentTypesByAlias);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
else if (_localDb != null && _wchanges != null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
foreach (var change in _wchanges)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (change.Value.IsNull)
|
|
|
|
|
|
_localDb.TryRemove(change.Key, out ContentNodeKit unused);
|
|
|
|
|
|
else
|
|
|
|
|
|
_localDb[change.Key] = change.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
_wchanges = null;
|
|
|
|
|
|
_localDb.Commit();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
|
|
|
|
|
|
if (lockInfo.Count) _wlocked--;
|
|
|
|
|
|
if (lockInfo.Taken) Monitor.Exit(_wlocko);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
private void Release(ReadLockInfo lockInfo)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (lockInfo.Taken) Monitor.Exit(_rlocko);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
private void Rollback<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dictionary)
|
|
|
|
|
|
where TValue : class
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var item in dictionary)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var link = item.Value;
|
|
|
|
|
|
if (link.Gen <= _liveGen) continue;
|
|
|
|
|
|
|
|
|
|
|
|
var key = item.Key;
|
|
|
|
|
|
if (link.Next == null)
|
|
|
|
|
|
dictionary.TryRemove(key, out link);
|
|
|
|
|
|
else
|
|
|
|
|
|
dictionary.TryUpdate(key, link.Next, link);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region LocalDb
|
|
|
|
|
|
|
|
|
|
|
|
public void ReleaseLocalDb()
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (_localDb == null) return;
|
|
|
|
|
|
_localDb.Dispose();
|
|
|
|
|
|
_localDb = null;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RegisterChange(int id, ContentNodeKit kit)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_wchanges == null) _wchanges = new List<KeyValuePair<int, ContentNodeKit>>();
|
|
|
|
|
|
_wchanges.Add(new KeyValuePair<int, ContentNodeKit>(id, kit));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2016-11-05 12:22:57 +01:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Content types
|
|
|
|
|
|
|
2017-07-17 17:59:46 +02:00
|
|
|
|
public void NewContentTypes(IEnumerable<PublishedContentType> types)
|
|
|
|
|
|
{
|
|
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var type in types)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentTypesById, type.Id, type);
|
|
|
|
|
|
SetValueLocked(_contentTypesByAlias, type.Alias, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void UpdateContentTypes(IEnumerable<PublishedContentType> types)
|
|
|
|
|
|
{
|
|
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
|
|
|
|
|
var index = types.ToDictionary(x => x.Id, x => x);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var type in index.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentTypesById, type.Id, type);
|
|
|
|
|
|
SetValueLocked(_contentTypesByAlias, type.Alias, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var link in _contentNodes.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
var node = link.Value;
|
|
|
|
|
|
if (node == null) continue;
|
|
|
|
|
|
var contentTypeId = node.ContentType.Id;
|
|
|
|
|
|
if (index.TryGetValue(contentTypeId, out PublishedContentType contentType) == false) continue;
|
2019-01-23 14:16:42 +01:00
|
|
|
|
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor));
|
2017-07-17 17:59:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public void UpdateContentTypes(IEnumerable<int> removedIds, IEnumerable<PublishedContentType> refreshedTypes, IEnumerable<ContentNodeKit> kits)
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var removedIdsA = removedIds?.ToArray() ?? Array.Empty<int>();
|
|
|
|
|
|
var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty<PublishedContentType>();
|
|
|
|
|
|
var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray();
|
|
|
|
|
|
kits = kits ?? Array.Empty<ContentNodeKit>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
|
|
|
|
|
var removedContentTypeNodes = new List<int>();
|
|
|
|
|
|
var refreshedContentTypeNodes = new List<int>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var link in _contentNodes.Values)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var node = link.Value;
|
|
|
|
|
|
if (node == null) continue;
|
|
|
|
|
|
var contentTypeId = node.ContentType.Id;
|
|
|
|
|
|
if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id);
|
|
|
|
|
|
if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
// all content should have been deleted - but
|
|
|
|
|
|
foreach (var node in removedContentTypeNodes)
|
|
|
|
|
|
ClearBranchLocked(node);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var id in removedIdsA)
|
|
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
if (_contentTypesById.TryGetValue(id, out LinkedNode<PublishedContentType> link) == false || link.Value == null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
SetValueLocked(_contentTypesById, id, null);
|
|
|
|
|
|
SetValueLocked(_contentTypesByAlias, link.Value.Alias, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
foreach (var type in refreshedTypesA)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentTypesById, type.Id, type);
|
|
|
|
|
|
SetValueLocked(_contentTypesByAlias, type.Alias, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// skip missing type
|
|
|
|
|
|
// skip missing parents & unbuildable kits - what else could we do?
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var visited = new List<int>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
foreach (var kit in kits.Where(x =>
|
2017-07-17 10:48:48 +02:00
|
|
|
|
refreshedIdsA.Contains(x.ContentTypeId) &&
|
2016-05-27 14:26:28 +02:00
|
|
|
|
ParentExistsLocked(x) &&
|
|
|
|
|
|
BuildKit(x)))
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
visited.Add(kit.Node.Id);
|
|
|
|
|
|
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// all content should have been refreshed - but...
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var orphans = refreshedContentTypeNodes.Except(visited);
|
|
|
|
|
|
foreach (var id in orphans)
|
|
|
|
|
|
ClearBranchLocked(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void UpdateDataTypes(IEnumerable<int> dataTypeIds, Func<int, PublishedContentType> getContentType)
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var contentTypes = _contentTypesById
|
|
|
|
|
|
.Where(kvp =>
|
|
|
|
|
|
kvp.Value.Value != null &&
|
2018-01-10 12:48:51 +01:00
|
|
|
|
kvp.Value.Value.PropertyTypes.Any(p => dataTypeIds.Contains(p.DataType.Id)))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
.Select(kvp => kvp.Value.Value)
|
2017-07-17 10:48:48 +02:00
|
|
|
|
.Select(x => getContentType(x.Id))
|
|
|
|
|
|
.Where(x => x != null) // poof, gone, very unlikely and probably an anomaly
|
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray();
|
|
|
|
|
|
var contentTypeNodes = new Dictionary<int, List<int>>();
|
|
|
|
|
|
foreach (var id in contentTypeIdsA)
|
|
|
|
|
|
contentTypeNodes[id] = new List<int>();
|
|
|
|
|
|
foreach (var link in _contentNodes.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
var node = link.Value;
|
|
|
|
|
|
if (node != null && contentTypeIdsA.Contains(node.ContentType.Id))
|
|
|
|
|
|
contentTypeNodes[node.ContentType.Id].Add(node.Id);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
foreach (var contentType in contentTypes)
|
|
|
|
|
|
{
|
|
|
|
|
|
// again, weird situation
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (contentTypeNodes.ContainsKey(contentType.Id) == false)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
foreach (var id in contentTypeNodes[contentType.Id])
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
_contentNodes.TryGetValue(id, out LinkedNode<ContentNode> link);
|
|
|
|
|
|
if (link?.Value == null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
continue;
|
2019-01-23 14:16:42 +01:00
|
|
|
|
var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
SetValueLocked(_contentNodes, id, node);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_localDb != null) RegisterChange(id, node.ToKit());
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool BuildKit(ContentNodeKit kit)
|
|
|
|
|
|
{
|
|
|
|
|
|
// make sure the kit is valid
|
|
|
|
|
|
if (kit.DraftData == null && kit.PublishedData == null)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// unknown = bad
|
2017-07-12 14:09:31 +02:00
|
|
|
|
if (_contentTypesById.TryGetValue(kit.ContentTypeId, out LinkedNode<PublishedContentType> link) == false || link.Value == null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return false;
|
2016-11-05 12:22:57 +01:00
|
|
|
|
|
2018-07-05 17:08:40 +02:00
|
|
|
|
// check whether parent is published
|
|
|
|
|
|
var canBePublished = ParentPublishedLocked(kit);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// and use
|
2019-01-23 14:16:42 +01:00
|
|
|
|
kit.Build(link.Value, _publishedSnapshotAccessor, _variationContextAccessor, canBePublished, _umbracoContextAccessor);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Set, Clear, Get
|
|
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
public int Count => _contentNodes.Count;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
private static LinkedNode<TValue> GetHead<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict, TKey key)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
where TValue : class
|
|
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
dict.TryGetValue(key, out LinkedNode<TValue> link); // else null
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return link;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Set(ContentNodeKit kit)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ReSharper disable LocalizableElement
|
|
|
|
|
|
if (kit.IsEmpty)
|
2017-07-12 14:09:31 +02:00
|
|
|
|
throw new ArgumentException("Kit is empty.", nameof(kit));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (kit.Node.ChildContentIds.Count > 0)
|
2017-07-12 14:09:31 +02:00
|
|
|
|
throw new ArgumentException("Kit content cannot have children.", nameof(kit));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// ReSharper restore LocalizableElement
|
|
|
|
|
|
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<ContentStore>("Set content ID: {KitNodeId}", kit.Node.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// get existing
|
2017-07-12 14:09:31 +02:00
|
|
|
|
_contentNodes.TryGetValue(kit.Node.Id, out LinkedNode<ContentNode> link);
|
|
|
|
|
|
var existing = link?.Value;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// else ignore, what else could we do?
|
|
|
|
|
|
if (ParentExistsLocked(kit) == false || BuildKit(kit) == false)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// moving?
|
|
|
|
|
|
var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId;
|
|
|
|
|
|
|
|
|
|
|
|
// manage children
|
|
|
|
|
|
if (existing != null)
|
|
|
|
|
|
kit.Node.ChildContentIds = existing.ChildContentIds;
|
|
|
|
|
|
|
|
|
|
|
|
// set
|
|
|
|
|
|
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// manage the tree
|
|
|
|
|
|
if (existing == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// new, add to parent
|
|
|
|
|
|
AddToParentLocked(kit.Node);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (moving)
|
|
|
|
|
|
{
|
|
|
|
|
|
// moved, remove existing from its parent, add content to its parent
|
|
|
|
|
|
RemoveFromParentLocked(existing);
|
|
|
|
|
|
AddToParentLocked(kit.Node);
|
|
|
|
|
|
}
|
2018-03-30 14:00:44 +02:00
|
|
|
|
|
|
|
|
|
|
_xmap[kit.Node.Uid] = kit.Node.Id;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void SetAll(IEnumerable<ContentNodeKit> kits)
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
ClearLocked(_contentNodes);
|
|
|
|
|
|
ClearLocked(_contentRootNodes);
|
|
|
|
|
|
|
|
|
|
|
|
// do NOT clear types else they are gone!
|
|
|
|
|
|
//ClearLocked(_contentTypesById);
|
|
|
|
|
|
//ClearLocked(_contentTypesByAlias);
|
|
|
|
|
|
|
|
|
|
|
|
// skip missing parents & unbuildable kits - what else could we do?
|
|
|
|
|
|
foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x)))
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
AddToParentLocked(kit.Node);
|
2018-03-30 14:00:44 +02:00
|
|
|
|
|
|
|
|
|
|
_xmap[kit.Node.Uid] = kit.Node.Id;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void SetBranch(int rootContentId, IEnumerable<ContentNodeKit> kits)
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// get existing
|
2017-07-12 14:09:31 +02:00
|
|
|
|
_contentNodes.TryGetValue(rootContentId, out LinkedNode<ContentNode> link);
|
|
|
|
|
|
var existing = link?.Value;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// clear
|
|
|
|
|
|
if (existing != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClearBranchLocked(existing);
|
|
|
|
|
|
RemoveFromParentLocked(existing);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// now add them all back
|
|
|
|
|
|
// skip missing parents & unbuildable kits - what else could we do?
|
|
|
|
|
|
foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x)))
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
AddToParentLocked(kit.Node);
|
2018-03-30 14:00:44 +02:00
|
|
|
|
|
|
|
|
|
|
_xmap[kit.Node.Uid] = kit.Node.Id;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool Clear(int id)
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new WriteLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// try to find the content
|
|
|
|
|
|
// if it is not there, nothing to do
|
2017-07-12 14:09:31 +02:00
|
|
|
|
_contentNodes.TryGetValue(id, out LinkedNode<ContentNode> link); // else null
|
|
|
|
|
|
if (link?.Value == null) return false;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
var content = link.Value;
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<ContentStore>("Clear content ID: {ContentId}", content.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// clear the entire branch
|
|
|
|
|
|
ClearBranchLocked(content);
|
|
|
|
|
|
|
|
|
|
|
|
// manage the tree
|
|
|
|
|
|
RemoveFromParentLocked(content);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearBranchLocked(int id)
|
|
|
|
|
|
{
|
2018-03-30 14:00:44 +02:00
|
|
|
|
_contentNodes.TryGetValue(id, out var link);
|
2017-07-12 14:09:31 +02:00
|
|
|
|
if (link?.Value == null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return;
|
|
|
|
|
|
ClearBranchLocked(link.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearBranchLocked(ContentNode content)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentNodes, content.Id, null);
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null);
|
|
|
|
|
|
|
2018-03-30 14:00:44 +02:00
|
|
|
|
_xmap.TryRemove(content.Uid, out _);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
foreach (var childId in content.ChildContentIds)
|
|
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
if (_contentNodes.TryGetValue(childId, out LinkedNode<ContentNode> link) == false || link.Value == null) continue;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
ClearBranchLocked(link.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private LinkedNode<ContentNode> GetParentLink(ContentNode content)
|
|
|
|
|
|
{
|
2018-03-30 14:00:44 +02:00
|
|
|
|
_contentNodes.TryGetValue(content.ParentContentId, out var link); // else null
|
2016-05-27 14:26:28 +02:00
|
|
|
|
//if (link == null || link.Value == null)
|
|
|
|
|
|
// throw new Exception("Panic: parent not found.");
|
|
|
|
|
|
return link;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RemoveFromParentLocked(ContentNode content)
|
|
|
|
|
|
{
|
|
|
|
|
|
// remove from root content index,
|
|
|
|
|
|
// or parent's children index
|
|
|
|
|
|
if (content.ParentContentId < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetValueLocked(_contentRootNodes, content.Id, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// obviously parent has to exist
|
|
|
|
|
|
var link = GetParentLink(content);
|
|
|
|
|
|
var parent = link.Value;
|
|
|
|
|
|
if (link.Gen < _liveGen)
|
2019-01-23 14:16:42 +01:00
|
|
|
|
parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
parent.ChildContentIds.Remove(content.Id);
|
|
|
|
|
|
if (link.Gen < _liveGen)
|
|
|
|
|
|
SetValueLocked(_contentNodes, parent.Id, parent);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool ParentExistsLocked(ContentNodeKit kit)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (kit.Node.ParentContentId < 0)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
var link = GetParentLink(kit.Node);
|
2017-07-12 14:09:31 +02:00
|
|
|
|
return link?.Value != null;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-05 17:08:40 +02:00
|
|
|
|
private bool ParentPublishedLocked(ContentNodeKit kit)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (kit.Node.ParentContentId < 0)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
var link = GetParentLink(kit.Node);
|
|
|
|
|
|
var node = link?.Value;
|
2019-01-28 14:15:47 +01:00
|
|
|
|
return node?.PublishedModel != null;
|
2018-07-05 17:08:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
private void AddToParentLocked(ContentNode content)
|
|
|
|
|
|
{
|
|
|
|
|
|
// add to root content index,
|
|
|
|
|
|
// or parent's children index
|
|
|
|
|
|
if (content.ParentContentId < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// need an object reference... just use this...
|
|
|
|
|
|
SetValueLocked(_contentRootNodes, content.Id, this);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// assume parent has been validated and exists
|
|
|
|
|
|
var link = GetParentLink(content);
|
|
|
|
|
|
var parent = link.Value;
|
|
|
|
|
|
if (link.Gen < _liveGen)
|
2019-01-23 14:16:42 +01:00
|
|
|
|
parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
parent.ChildContentIds.Add(content.Id);
|
|
|
|
|
|
if (link.Gen < _liveGen)
|
|
|
|
|
|
SetValueLocked(_contentNodes, parent.Id, parent);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SetValueLocked<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict, TKey key, TValue value)
|
|
|
|
|
|
where TValue : class
|
|
|
|
|
|
{
|
|
|
|
|
|
// this is safe only because we're write-locked
|
|
|
|
|
|
var link = GetHead(dict, key);
|
|
|
|
|
|
if (link != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// already in the dict
|
|
|
|
|
|
if (link.Gen != _liveGen)
|
|
|
|
|
|
{
|
2016-11-05 12:22:57 +01:00
|
|
|
|
// for an older gen - if value is different then insert a new
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// link for the new gen, with the new value
|
|
|
|
|
|
if (link.Value != value)
|
|
|
|
|
|
dict.TryUpdate(key, new LinkedNode<TValue>(value, _liveGen, link), link);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// for the live gen - we can fix the live gen - and remove it
|
|
|
|
|
|
// if value is null and there's no next gen
|
|
|
|
|
|
if (value == null && link.Next == null)
|
|
|
|
|
|
dict.TryRemove(key, out link);
|
|
|
|
|
|
else
|
|
|
|
|
|
link.Value = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
dict.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearLocked<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict)
|
|
|
|
|
|
where TValue : class
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
// this is safe only because we're write-locked
|
|
|
|
|
|
foreach (var kvp in dict.Where(x => x.Value != null))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (kvp.Value.Gen < _liveGen)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
|
|
|
|
|
|
dict.TryUpdate(kvp.Key, link, kvp.Value);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2017-07-17 10:48:48 +02:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
kvp.Value.Value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ContentNode Get(int id, long gen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetValue(_contentNodes, id, gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-30 14:00:44 +02:00
|
|
|
|
public ContentNode Get(Guid uid, long gen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return _xmap.TryGetValue(uid, out var id)
|
|
|
|
|
|
? GetValue(_contentNodes, id, gen)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public IEnumerable<ContentNode> GetAtRoot(long gen)
|
|
|
|
|
|
{
|
|
|
|
|
|
// look ma, no lock!
|
|
|
|
|
|
foreach (var kvp in _contentRootNodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var link = kvp.Value;
|
|
|
|
|
|
while (link != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (link.Gen <= gen)
|
|
|
|
|
|
break;
|
|
|
|
|
|
link = link.Next;
|
|
|
|
|
|
}
|
2016-11-05 12:22:57 +01:00
|
|
|
|
if (link?.Value != null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
yield return Get(kvp.Key, gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private TValue GetValue<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict, TKey key, long gen)
|
|
|
|
|
|
where TValue : class
|
|
|
|
|
|
{
|
|
|
|
|
|
// look ma, no lock!
|
|
|
|
|
|
var link = GetHead(dict, key);
|
|
|
|
|
|
while (link != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (link.Gen <= gen)
|
|
|
|
|
|
return link.Value; // may be null
|
|
|
|
|
|
link = link.Next;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-05 12:22:57 +01:00
|
|
|
|
public IEnumerable<ContentNode> 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public bool IsEmpty(long gen)
|
|
|
|
|
|
{
|
|
|
|
|
|
var has = _contentNodes.Any(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var link = x.Value;
|
|
|
|
|
|
while (link != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (link.Gen <= gen && link.Value != null)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
link = link.Next;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
return has == false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public PublishedContentType GetContentType(int id, long gen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetValue(_contentTypesById, id, gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public PublishedContentType GetContentType(string alias, long gen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetValue(_contentTypesByAlias, alias, gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Snapshots
|
|
|
|
|
|
|
|
|
|
|
|
public Snapshot CreateSnapshot()
|
|
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
var lockInfo = new ReadLockInfo();
|
|
|
|
|
|
try
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
Lock(lockInfo);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// if no next generation is required, and we already have one,
|
|
|
|
|
|
// use it and create a new snapshot
|
2019-02-22 15:30:55 +01:00
|
|
|
|
if (_nextGen == false && _genObj != null)
|
|
|
|
|
|
return new Snapshot(this, _genObj.GetGenRef()
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#if DEBUG
|
|
|
|
|
|
, _logger
|
|
|
|
|
|
#endif
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// else we need to try to create a new gen ref
|
|
|
|
|
|
// whether we are wlocked or not, noone can rlock while we do,
|
|
|
|
|
|
// so _liveGen and _nextGen are safe
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_wlocked > 0) // volatile, cannot ++ but could --
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// write-locked, cannot use latest gen (at least 1) so use previous
|
|
|
|
|
|
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;
|
|
|
|
|
|
|
|
|
|
|
|
// create a new gen ref unless we already have it
|
2019-02-22 15:30:55 +01:00
|
|
|
|
if (_genObj == null)
|
|
|
|
|
|
_genObjs.Enqueue(_genObj = new GenObj(snapGen));
|
|
|
|
|
|
else if (_genObj.Gen != snapGen)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
throw new Exception("panic");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// not write-locked, can use latest gen, create a new gen ref
|
2019-02-22 15:30:55 +01:00
|
|
|
|
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_nextGen = false; // this is the ONLY thing that triggers a _liveGen++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// so...
|
|
|
|
|
|
// the genRefRef has a weak ref to the genRef, and is queued
|
|
|
|
|
|
// the snapshot has a ref to the genRef, which has a ref to the genRefRef
|
|
|
|
|
|
// when the snapshot is disposed, it decreases genRefRef counter
|
|
|
|
|
|
// so after a while, one of these conditions is going to be true:
|
|
|
|
|
|
// - the genRefRef counter is zero because all snapshots have properly been disposed
|
|
|
|
|
|
// - the genRefRef weak ref is dead because all snapshots have been collected
|
|
|
|
|
|
// in both cases, we will dequeue and collect
|
|
|
|
|
|
|
2019-02-22 15:30:55 +01:00
|
|
|
|
var snapshot = new Snapshot(this, _genObj.GetGenRef()
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#if DEBUG
|
|
|
|
|
|
, _logger
|
|
|
|
|
|
#endif
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// reading _floorGen is safe if _collectTask is null
|
|
|
|
|
|
if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta)
|
|
|
|
|
|
CollectAsyncLocked();
|
|
|
|
|
|
|
|
|
|
|
|
return snapshot;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Release(lockInfo);
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-18 19:24:27 +02:00
|
|
|
|
public Snapshot LiveSnapshot => new Snapshot(this, _liveGen
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
, _logger
|
|
|
|
|
|
#endif
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public Task CollectAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_rlocko)
|
|
|
|
|
|
{
|
|
|
|
|
|
return CollectAsyncLocked();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Task CollectAsyncLocked()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_collectTask != null)
|
|
|
|
|
|
return _collectTask;
|
2016-11-05 12:22:57 +01:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// ReSharper disable InconsistentlySynchronizedField
|
|
|
|
|
|
var task = _collectTask = Task.Run(() => Collect());
|
|
|
|
|
|
_collectTask.ContinueWith(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_rlocko)
|
|
|
|
|
|
{
|
|
|
|
|
|
_collectTask = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, TaskContinuationOptions.ExecuteSynchronously);
|
|
|
|
|
|
// ReSharper restore InconsistentlySynchronizedField
|
|
|
|
|
|
|
|
|
|
|
|
return task;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Collect()
|
|
|
|
|
|
{
|
|
|
|
|
|
// see notes in CreateSnapshot
|
|
|
|
|
|
#if DEBUG
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<ContentStore>("Collect.");
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endif
|
2019-02-22 15:30:55 +01:00
|
|
|
|
while (_genObjs.TryPeek(out var genObj) && (genObj.Count == 0 || genObj.WeakGenRef.IsAlive == false))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-02-22 15:30:55 +01:00
|
|
|
|
_genObjs.TryDequeue(out genObj); // cannot fail since TryPeek has succeeded
|
|
|
|
|
|
_floorGen = genObj.Gen;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#if DEBUG
|
2017-07-12 14:09:31 +02:00
|
|
|
|
//_logger.Debug<ContentStore>("_floorGen=" + _floorGen + ", _liveGen=" + _liveGen);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Collect(_contentNodes);
|
|
|
|
|
|
Collect(_contentRootNodes);
|
|
|
|
|
|
Collect(_contentTypesById);
|
|
|
|
|
|
Collect(_contentTypesByAlias);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Collect<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict)
|
|
|
|
|
|
where TValue : class
|
|
|
|
|
|
{
|
|
|
|
|
|
// it is OK to enumerate a concurrent dictionary and it does not lock
|
|
|
|
|
|
// it - and here it's not an issue if we skip some items, they will be
|
|
|
|
|
|
// processed next time we collect
|
|
|
|
|
|
|
|
|
|
|
|
long liveGen;
|
|
|
|
|
|
lock (_rlocko) // r is good
|
|
|
|
|
|
{
|
|
|
|
|
|
liveGen = _liveGen;
|
|
|
|
|
|
if (_nextGen == false)
|
|
|
|
|
|
liveGen += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var kvp in dict)
|
|
|
|
|
|
{
|
|
|
|
|
|
var link = kvp.Value;
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
2017-07-12 14:09:31 +02:00
|
|
|
|
//_logger.Debug<ContentStore>("Collect id:" + kvp.Key + ", gen:" + link.Gen +
|
2016-11-05 12:22:57 +01:00
|
|
|
|
// ", nxt:" + (link.Next == null ? "null" : "link") +
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// ", val:" + (link.Value == null ? "null" : "value"));
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
// reasons to collect the head:
|
|
|
|
|
|
// gen must be < liveGen (we never collect live gen)
|
|
|
|
|
|
// next == null && value == null (we have no data at all)
|
|
|
|
|
|
// next != null && value == null BUT gen > floor (noone wants us)
|
|
|
|
|
|
// not live means .Next and .Value are safe
|
|
|
|
|
|
if (link.Gen < liveGen && link.Value == null
|
|
|
|
|
|
&& (link.Next == null || link.Gen <= _floorGen))
|
|
|
|
|
|
{
|
2016-11-05 12:22:57 +01:00
|
|
|
|
// not live, null value, no next link = remove that one -- but only if
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// 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<KeyValuePair<TKey, LinkedNode<TValue>>>;
|
|
|
|
|
|
idict.Remove(kvp);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// in any other case we're not collecting the head, we need to go to Next
|
|
|
|
|
|
// and if there is no Next, skip
|
|
|
|
|
|
if (link.Next == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// else go to Next and loop while above floor, and kill everything below
|
|
|
|
|
|
while (link.Next != null && link.Next.Gen > _floorGen)
|
|
|
|
|
|
link = link.Next;
|
|
|
|
|
|
link.Next = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task WaitForPendingCollect()
|
|
|
|
|
|
{
|
|
|
|
|
|
Task task;
|
|
|
|
|
|
lock (_rlocko)
|
|
|
|
|
|
{
|
|
|
|
|
|
task = _collectTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (task != null)
|
|
|
|
|
|
await task;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-22 15:30:55 +01:00
|
|
|
|
public long GenCount => _genObjs.Count;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-02-22 15:30:55 +01:00
|
|
|
|
public long SnapCount => _genObjs.Sum(x => x.Count);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Unit testing
|
|
|
|
|
|
|
|
|
|
|
|
private TestHelper _unitTesting;
|
|
|
|
|
|
|
|
|
|
|
|
// note: nothing here is thread-safe
|
|
|
|
|
|
internal class TestHelper
|
|
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
private readonly ContentStore _store;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
public TestHelper(ContentStore store)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
_store = store;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
public long LiveGen => _store._liveGen;
|
|
|
|
|
|
public long FloorGen => _store._floorGen;
|
|
|
|
|
|
public bool NextGen => _store._nextGen;
|
|
|
|
|
|
public bool CollectAuto
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _store._collectAuto;
|
|
|
|
|
|
set => _store._collectAuto = value;
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
public Tuple<long, ContentNode>[] GetValues(int id)
|
|
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
_store._contentNodes.TryGetValue(id, out LinkedNode<ContentNode> link); // else null
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (link == null)
|
|
|
|
|
|
return new Tuple<long, ContentNode>[0];
|
|
|
|
|
|
|
|
|
|
|
|
var tuples = new List<Tuple<long, ContentNode>>();
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
tuples.Add(Tuple.Create(link.Gen, link.Value));
|
|
|
|
|
|
link = link.Next;
|
|
|
|
|
|
} while (link != null);
|
|
|
|
|
|
return tuples.ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
internal TestHelper Test => _unitTesting ?? (_unitTesting = new TestHelper(this));
|
2016-11-05 12:22:57 +01:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Classes
|
|
|
|
|
|
|
|
|
|
|
|
public class Snapshot : IDisposable
|
|
|
|
|
|
{
|
2017-07-12 14:09:31 +02:00
|
|
|
|
private readonly ContentStore _store;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
private readonly GenRef _genRef;
|
|
|
|
|
|
private long _gen;
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
//private static int _count;
|
|
|
|
|
|
//private readonly int _thisCount;
|
|
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
internal Snapshot(ContentStore store, GenRef genRef
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#if DEBUG
|
|
|
|
|
|
, ILogger logger
|
|
|
|
|
|
#endif
|
|
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
_store = store;
|
|
|
|
|
|
_genRef = genRef;
|
|
|
|
|
|
_gen = genRef.Gen;
|
2019-02-22 15:30:55 +01:00
|
|
|
|
Interlocked.Increment(ref genRef.GenObj.Count);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
//_thisCount = _count++;
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
_logger = logger;
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<Snapshot>("Creating snapshot.");
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-18 19:24:27 +02:00
|
|
|
|
internal Snapshot(ContentStore store, long gen
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
, ILogger logger
|
|
|
|
|
|
#endif
|
|
|
|
|
|
)
|
2017-07-17 10:48:48 +02:00
|
|
|
|
{
|
|
|
|
|
|
_store = store;
|
|
|
|
|
|
_gen = gen;
|
2017-07-18 19:24:27 +02:00
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
_logger = logger;
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<Snapshot>("Creating live.");
|
2017-07-18 19:24:27 +02:00
|
|
|
|
#endif
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public ContentNode Get(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _store.Get(id, _gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-05 12:22:57 +01:00
|
|
|
|
public ContentNode Get(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
2018-03-30 14:00:44 +02:00
|
|
|
|
return _store.Get(id, _gen);
|
2016-11-05 12:22:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public IEnumerable<ContentNode> GetAtRoot()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _store.GetAtRoot(_gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-30 14:00:44 +02:00
|
|
|
|
public IEnumerable<ContentNode> GetAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _store.GetAll(_gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public PublishedContentType GetContentType(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _store.GetContentType(id, _gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public PublishedContentType GetContentType(string alias)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _store.GetContentType(alias, _gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-05 12:22:57 +01:00
|
|
|
|
// 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<ContentNode> GetAll()
|
|
|
|
|
|
//{
|
|
|
|
|
|
// if (_gen < 0)
|
|
|
|
|
|
// throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
// return _store.GetAll(_gen);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public bool IsEmpty
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _store.IsEmpty(_gen);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public long Gen
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0)
|
|
|
|
|
|
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
|
|
|
|
|
return _gen;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_gen < 0) return;
|
|
|
|
|
|
#if DEBUG
|
2019-02-22 15:30:55 +01:00
|
|
|
|
_logger.Debug<Snapshot>("Dispose snapshot ({Snapshot})", _genRef?.GenObj.Count.ToString() ?? "live");
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
_gen = -1;
|
2017-07-17 10:48:48 +02:00
|
|
|
|
if (_genRef != null)
|
2019-02-22 15:30:55 +01:00
|
|
|
|
Interlocked.Decrement(ref _genRef.GenObj.Count);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|