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 ;
2019-07-03 17:43:30 +10:00
using Umbraco.Core ;
2019-07-30 22:40:15 +10:00
using Umbraco.Core.Exceptions ;
2016-05-27 14:26:28 +02:00
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
{
2020-01-06 18:34:04 +11:00
/// <summary>
/// Stores content in memory and persists it back to disk
/// </summary>
/// <remarks>
/// <para>
/// Methods in this class suffixed with the term "Locked" means that those methods can only be called within a WriteLock. A WriteLock
/// is acquired by the GetScopedWriteLock method. Locks are not allowed to be recursive.
/// </para>
/// <para>
/// This class's logic is based on the <see cref="SnapDictionary{TKey, TValue}"/> class but has been slightly modified to suit these purposes.
/// </para>
/// </remarks>
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
2020-01-03 12:39:56 +11:00
// For locking information, see SnapDictionary
2016-05-27 14:26:28 +02:00
2017-10-31 12:48:24 +01:00
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor ;
2018-04-30 21:29:49 +02:00
private readonly IVariationContextAccessor _variationContextAccessor ;
2016-05-27 14:26:28 +02:00
private readonly ConcurrentDictionary < int , LinkedNode < ContentNode > > _contentNodes ;
2019-04-22 17:51:07 +02:00
private LinkedNode < ContentNode > _root ;
2019-04-15 13:04:14 +02:00
private readonly ConcurrentDictionary < int , LinkedNode < IPublishedContentType > > _contentTypesById ;
private readonly ConcurrentDictionary < string , LinkedNode < IPublishedContentType > > _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 ;
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 ,
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 ;
2016-05-27 14:26:28 +02:00
_logger = logger ;
_localDb = localDb ;
_contentNodes = new ConcurrentDictionary < int , LinkedNode < ContentNode > > ( ) ;
2019-04-22 17:51:07 +02:00
_root = new LinkedNode < ContentNode > ( new ContentNode ( ) , 0 ) ;
2019-04-15 13:04:14 +02:00
_contentTypesById = new ConcurrentDictionary < int , LinkedNode < IPublishedContentType > > ( ) ;
_contentTypesByAlias = new ConcurrentDictionary < string , LinkedNode < IPublishedContentType > > ( 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 WriteLockInfo
{
public bool Taken ;
}
// a scope contextual that represents a locked writer to the dictionary
2019-03-14 19:48:44 +01:00
private class ScopedWriteLock : ScopeContextualBase
2017-07-17 10:48:48 +02:00
{
private readonly WriteLockInfo _lockinfo = new WriteLockInfo ( ) ;
2019-02-22 15:43:37 +01:00
private readonly ContentStore _store ;
private int _released ;
2017-07-17 10:48:48 +02:00
2019-03-14 19:48:44 +01:00
public ScopedWriteLock ( ContentStore store , bool scoped )
2017-07-17 10:48:48 +02:00
{
_store = store ;
store . Lock ( _lockinfo , scoped ) ;
}
public override void Release ( bool completed )
{
2019-02-22 15:43:37 +01:00
if ( Interlocked . CompareExchange ( ref _released , 1 , 0 ) ! = 0 )
return ;
2017-07-17 10:48:48 +02:00
_store . Release ( _lockinfo , completed ) ;
}
}
// 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?
2019-03-14 19:48:44 +01:00
public IDisposable GetScopedWriteLock ( IScopeProvider scopeProvider )
2017-07-17 10:48:48 +02:00
{
2019-03-14 19:48:44 +01:00
return ScopeContextualBase . Get ( scopeProvider , _instanceId , scoped = > new ScopedWriteLock ( this , scoped ) ) ;
2017-07-17 10:48:48 +02:00
}
2020-01-03 13:21:49 +11:00
private void EnsureLocked ( )
{
if ( ! Monitor . IsEntered ( _wlocko ) )
throw new InvalidOperationException ( "Write lock must be acquried." ) ;
}
2017-07-17 10:48:48 +02:00
private void Lock ( WriteLockInfo lockInfo , bool forceGen = false )
2016-05-27 14:26:28 +02:00
{
2020-01-03 15:04:39 +11:00
if ( Monitor . IsEntered ( _wlocko ) )
throw new InvalidOperationException ( "Recursive locks not allowed" ) ;
2017-07-17 10:48:48 +02:00
Monitor . Enter ( _wlocko , ref lockInfo . Taken ) ;
2020-01-03 12:39:56 +11:00
lock ( _rlocko )
2016-05-27 14:26:28 +02:00
{
2017-07-17 10:48:48 +02:00
// see SnapDictionary
2019-10-22 11:55:05 +11:00
try { }
finally
2016-05-27 14:26:28 +02:00
{
2020-01-03 15:04:39 +11:00
if ( _nextGen = = false | | ( forceGen ) )
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
2019-03-14 19:48:44 +01:00
if ( _nextGen )
_genObjs . Enqueue ( _genObj = new GenObj ( _liveGen ) ) ;
2017-07-17 10:48:48 +02:00
_liveGen + = 1 ;
2019-03-14 19:48:44 +01:00
_nextGen = true ;
2016-05-27 14:26:28 +02:00
}
}
2020-01-03 12:39:56 +11:00
}
2017-07-17 10:48:48 +02:00
}
2016-05-27 14:26:28 +02:00
2017-07-17 10:48:48 +02:00
private void Release ( WriteLockInfo lockInfo , bool commit = true )
{
2020-01-06 21:39:26 +11:00
try
2017-07-17 10:48:48 +02:00
{
2020-01-06 21:39:26 +11:00
if ( commit = = false )
2016-05-27 14:26:28 +02:00
{
2020-01-06 21:39:26 +11:00
lock ( _rlocko )
2016-05-27 14:26:28 +02:00
{
2020-01-06 21:39:26 +11:00
// see SnapDictionary
try { }
finally
{
_nextGen = false ;
_liveGen - = 1 ;
}
2016-05-27 14:26:28 +02:00
}
2020-01-06 21:39:26 +11:00
Rollback ( _contentNodes ) ;
RollbackRoot ( ) ;
Rollback ( _contentTypesById ) ;
Rollback ( _contentTypesByAlias ) ;
2016-05-27 14:26:28 +02:00
}
2020-01-06 21:39:26 +11:00
else if ( _localDb ! = null & & _wchanges ! = null )
2016-05-27 14:26:28 +02:00
{
2020-01-06 21:39:26 +11: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
}
}
2020-01-06 21:39:26 +11:00
finally
2016-05-27 14:26:28 +02:00
{
2020-01-06 21:39:26 +11:00
if ( lockInfo . Taken )
Monitor . Exit ( _wlocko ) ;
2016-05-27 14:26:28 +02:00
}
2017-07-17 10:48:48 +02:00
}
2016-05-27 14:26:28 +02:00
2019-04-22 17:51:07 +02:00
private void RollbackRoot ( )
{
if ( _root . Gen < = _liveGen ) return ;
if ( _root . Next ! = null )
_root = _root . Next ;
}
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
}
}
#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
{
2019-10-22 11:55:05 +11:00
try
{
2020-01-03 15:04:39 +11:00
// Trying to lock could throw exceptions so always make sure to clean up.
2019-10-17 19:00:00 +01:00
Lock ( lockInfo ) ;
}
2019-10-22 11:55:05 +11:00
finally
2019-10-17 19:00:00 +01:00
{
2019-10-22 11:55:05 +11:00
try
2019-10-18 07:17:56 +01:00
{
2019-10-22 11:55:05 +11:00
_localDb ? . Dispose ( ) ;
}
2020-01-03 10:38:48 +11:00
catch ( Exception ex )
{
/* TBD: May already be throwing so don't throw again */
_logger . Error < ContentStore > ( ex , "Error trying to release DB" ) ;
}
2019-10-22 11:55:05 +11:00
finally
{
_localDb = null ;
2019-10-18 07:17:56 +01:00
}
2019-10-17 19:00:00 +01:00
}
2019-10-22 11:55:05 +11:00
2017-07-17 10:48:48 +02:00
}
2020-01-03 10:38:48 +11:00
catch ( Exception ex )
{
_logger . Error < ContentStore > ( ex , "Error trying to lock" ) ;
throw ;
}
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
2020-01-03 15:04:39 +11:00
/// <summary>
/// Sets data for new content types
/// </summary>
/// <param name="types"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void NewContentTypesLocked ( IEnumerable < IPublishedContentType > types )
2017-07-17 17:59:46 +02:00
{
2020-01-03 15:04:39 +11:00
EnsureLocked ( ) ;
2017-07-17 17:59:46 +02:00
2020-01-03 15:04:39 +11:00
foreach ( var type in types )
2017-07-17 17:59:46 +02:00
{
2020-01-03 15:04:39 +11:00
SetValueLocked ( _contentTypesById , type . Id , type ) ;
SetValueLocked ( _contentTypesByAlias , type . Alias , type ) ;
2017-07-17 17:59:46 +02:00
}
}
2020-01-03 15:04:39 +11:00
/// <summary>
/// Sets data for updated content types
/// </summary>
/// <param name="types"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void UpdateContentTypesLocked ( IEnumerable < IPublishedContentType > types )
2017-07-17 17:59:46 +02:00
{
2019-08-19 23:22:27 +10:00
//nothing to do if this is empty, no need to lock/allocate/iterate/etc...
2019-10-22 11:55:05 +11:00
if ( ! types . Any ( ) ) return ;
2019-07-03 17:43:30 +10:00
2020-01-03 15:04:39 +11:00
EnsureLocked ( ) ;
2017-07-17 17:59:46 +02:00
2020-01-03 15:04:39 +11:00
var index = types . ToDictionary ( x = > x . Id , x = > x ) ;
2017-07-17 17:59:46 +02:00
2020-01-03 15:04:39 +11:00
foreach ( var type in index . Values )
{
SetValueLocked ( _contentTypesById , type . Id , type ) ;
SetValueLocked ( _contentTypesByAlias , type . Alias , type ) ;
2017-07-17 17:59:46 +02:00
}
2020-01-03 15:04:39 +11:00
foreach ( var link in _contentNodes . Values )
2017-07-17 17:59:46 +02:00
{
2020-01-03 15:04:39 +11:00
var node = link . Value ;
if ( node = = null ) continue ;
var contentTypeId = node . ContentType . Id ;
if ( index . TryGetValue ( contentTypeId , out var contentType ) = = false ) continue ;
SetValueLocked ( _contentNodes , node . Id , new ContentNode ( node , contentType ) ) ;
2017-07-17 17:59:46 +02:00
}
}
2020-01-03 13:21:49 +11:00
/// <summary>
/// Updates/sets data for all content types
/// </summary>
/// <param name="types"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void SetAllContentTypesLocked ( IEnumerable < IPublishedContentType > types )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
EnsureLocked ( ) ;
2017-07-17 10:48:48 +02:00
2020-01-03 13:21:49 +11:00
// clear all existing content types
ClearLocked ( _contentTypesById ) ;
ClearLocked ( _contentTypesByAlias ) ;
2019-06-21 15:47:47 +10:00
2020-01-03 13:21:49 +11:00
// set all new content types
foreach ( var type in types )
2019-06-25 12:54:16 +02:00
{
2020-01-03 13:21:49 +11:00
SetValueLocked ( _contentTypesById , type . Id , type ) ;
SetValueLocked ( _contentTypesByAlias , type . Alias , type ) ;
2019-06-25 12:54:16 +02:00
}
2020-01-03 13:21:49 +11:00
// beware! at that point the cache is inconsistent,
// assuming we are going to SetAll content items!
2019-06-25 12:54:16 +02:00
}
2019-06-21 15:47:47 +10:00
2020-01-06 18:34:04 +11:00
/// <summary>
/// Updates/sets/removes data for content types
/// </summary>
/// <param name="removedIds"></param>
/// <param name="refreshedTypes"></param>
/// <param name="kits"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void UpdateContentTypesLocked ( IReadOnlyCollection < int > removedIds , IReadOnlyCollection < IPublishedContentType > refreshedTypes , IReadOnlyCollection < ContentNodeKit > kits )
2019-06-25 12:54:16 +02:00
{
2020-01-06 18:34:04 +11:00
EnsureLocked ( ) ;
2019-07-03 17:43:30 +10:00
var removedIdsA = removedIds ? ? Array . Empty < int > ( ) ;
var refreshedTypesA = refreshedTypes ? ? Array . Empty < IPublishedContentType > ( ) ;
var refreshedIdsA = refreshedTypesA . Select ( x = > x . Id ) . ToList ( ) ;
2019-06-25 12:54:16 +02:00
kits = kits ? ? Array . Empty < ContentNodeKit > ( ) ;
2019-06-21 15:47:47 +10:00
2019-07-03 17:43:30 +10:00
if ( kits . Count = = 0 & & refreshedIdsA . Count = = 0 & & removedIdsA . Count = = 0 )
return ; //exit - there is nothing to do here
2020-01-06 18:34:04 +11:00
var removedContentTypeNodes = new List < int > ( ) ;
var refreshedContentTypeNodes = new List < int > ( ) ;
2019-06-21 15:47:47 +10:00
2020-01-06 18:34:04 +11:00
// find all the nodes that are either refreshed or removed,
// because of their content type being either refreshed or removed
foreach ( var link in _contentNodes . Values )
{
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
2020-01-06 18:34:04 +11:00
// perform deletion of content with removed content type
// removing content types should have removed their content already
// but just to be 100% sure, clear again here
foreach ( var node in removedContentTypeNodes )
ClearBranchLocked ( node ) ;
2017-07-17 10:48:48 +02:00
2020-01-06 18:34:04 +11:00
// perform deletion of removed content types
foreach ( var id in removedIdsA )
{
if ( _contentTypesById . TryGetValue ( id , out var link ) = = false | | link . Value = = null )
continue ;
SetValueLocked ( _contentTypesById , id , null ) ;
SetValueLocked ( _contentTypesByAlias , link . Value . Alias , null ) ;
}
2016-05-27 14:26:28 +02:00
2020-01-06 18:34:04 +11:00
// perform update of refreshed content types
foreach ( var type in refreshedTypesA )
{
SetValueLocked ( _contentTypesById , type . Id , type ) ;
SetValueLocked ( _contentTypesByAlias , type . Alias , type ) ;
}
2019-04-11 18:55:40 +02:00
2020-01-06 18:34:04 +11:00
// perform update of content with refreshed content type - from the kits
// skip missing type, skip missing parents & un-buildable kits - what else could we do?
// kits are ordered by level, so ParentExists is ok here
var visited = new List < int > ( ) ;
foreach ( var kit in kits . Where ( x = >
refreshedIdsA . Contains ( x . ContentTypeId ) & &
BuildKit ( x , out _ ) ) )
{
// replacing the node: must preserve the parents
var node = GetHead ( _contentNodes , kit . Node . Id ) ? . Value ;
if ( node ! = null )
kit . Node . FirstChildContentId = node . FirstChildContentId ;
2019-04-11 18:55:40 +02:00
2020-01-06 18:34:04 +11:00
SetValueLocked ( _contentNodes , kit . Node . Id , kit . Node ) ;
2016-05-27 14:26:28 +02:00
2020-01-06 18:34:04 +11:00
visited . Add ( kit . Node . Id ) ;
if ( _localDb ! = null ) RegisterChange ( kit . Node . Id , kit ) ;
2017-07-17 10:48:48 +02:00
}
2020-01-06 18:34:04 +11:00
// all content should have been refreshed - but...
var orphans = refreshedContentTypeNodes . Except ( visited ) ;
foreach ( var id in orphans )
ClearBranchLocked ( id ) ;
2016-05-27 14:26:28 +02:00
}
2020-01-06 18:34:04 +11:00
/// <summary>
/// Updates data types
/// </summary>
/// <param name="dataTypeIds"></param>
/// <param name="getContentType"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void UpdateDataTypesLocked ( IEnumerable < int > dataTypeIds , Func < int , IPublishedContentType > getContentType )
2016-05-27 14:26:28 +02:00
{
2020-01-13 22:28:25 +11:00
EnsureLocked ( ) ;
2017-07-17 10:48:48 +02:00
2020-01-06 18:34:04 +11:00
var contentTypes = _contentTypesById
2016-05-27 14:26:28 +02:00
. 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 ( ) ;
2020-01-06 18:34:04 +11:00
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 ) ;
}
foreach ( var contentType in contentTypes )
{
// again, weird situation
if ( contentTypeNodes . ContainsKey ( contentType . Id ) = = false )
continue ;
2016-05-27 14:26:28 +02:00
2020-01-06 18:34:04 +11:00
foreach ( var id in contentTypeNodes [ contentType . Id ] )
2016-05-27 14:26:28 +02:00
{
2020-01-06 18:34:04 +11:00
_contentNodes . TryGetValue ( id , out var link ) ;
if ( link ? . Value = = null )
2016-05-27 14:26:28 +02:00
continue ;
2020-01-06 18:34:04 +11:00
var node = new ContentNode ( link . Value , contentType ) ;
SetValueLocked ( _contentNodes , id , node ) ;
if ( _localDb ! = null ) RegisterChange ( id , node . ToKit ( ) ) ;
2016-05-27 14:26:28 +02:00
}
2017-07-17 10:48:48 +02:00
}
2016-05-27 14:26:28 +02:00
}
2020-04-01 13:13:15 +11:00
/// <summary>
/// Validate the <see cref="ContentNodeKit"/> and try to create a parent <see cref="LinkedNode{ContentNode}"/>
/// </summary>
/// <param name="kit"></param>
/// <param name="parent"></param>
/// <returns>
/// Returns false if the parent was not found or if the kit validation failed
/// </returns>
2019-08-15 19:05:43 +10:00
private bool BuildKit ( ContentNodeKit kit , out LinkedNode < ContentNode > parent )
2016-05-27 14:26:28 +02:00
{
2019-06-25 12:54:16 +02:00
// make sure parent exists
2019-09-18 00:10:02 +10:00
parent = GetParentLink ( kit . Node , null ) ;
2019-08-15 19:05:43 +10:00
if ( parent = = null )
2019-06-25 12:54:16 +02:00
{
_logger . Warn < ContentStore > ( $"Skip item id={kit.Node.Id}, could not find parent id={kit.Node.ParentContentId}." ) ;
return false ;
}
2016-05-27 14:26:28 +02:00
// make sure the kit is valid
if ( kit . DraftData = = null & & kit . PublishedData = = null )
2019-06-25 12:54:16 +02:00
{
_logger . Warn < ContentStore > ( $"Skip item id={kit.Node.Id}, both draft and published data are null." ) ;
2016-05-27 14:26:28 +02:00
return false ;
2019-06-25 12:54:16 +02:00
}
2016-05-27 14:26:28 +02:00
// unknown = bad
2019-04-15 13:04:14 +02:00
if ( _contentTypesById . TryGetValue ( kit . ContentTypeId , out var link ) = = false | | link . Value = = null )
2019-06-25 12:54:16 +02:00
{
_logger . Warn < ContentStore > ( $"Skip item id={kit.Node.Id}, could not find content type id={kit.ContentTypeId}." ) ;
2016-05-27 14:26:28 +02:00
return false ;
2019-06-25 12:54:16 +02:00
}
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-04-24 14:25:41 +02:00
kit . Build ( link . Value , _publishedSnapshotAccessor , _variationContextAccessor , canBePublished ) ;
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
2019-09-18 00:36:12 +10:00
/// <summary>
/// Get the most recent version of the LinkedNode stored in the dictionary for the supplied key
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dict"></param>
/// <param name="key"></param>
/// <returns></returns>
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
{
2019-04-22 17:51:07 +02:00
dict . TryGetValue ( key , out var link ) ; // else null
2016-05-27 14:26:28 +02:00
return link ;
}
2020-01-03 13:21:49 +11:00
/// <summary>
/// Sets the data for a <see cref="ContentNodeKit"/>
/// </summary>
/// <param name="kit"></param>
/// <returns></returns>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetLocked ( ContentNodeKit kit )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
EnsureLocked ( ) ;
2016-05-27 14:26:28 +02:00
// ReSharper disable LocalizableElement
if ( kit . IsEmpty )
2017-07-12 14:09:31 +02:00
throw new ArgumentException ( "Kit is empty." , nameof ( kit ) ) ;
2019-04-22 17:51:07 +02:00
if ( kit . Node . FirstChildContentId > 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
2020-01-03 13:21:49 +11:00
// get existing
_contentNodes . TryGetValue ( kit . Node . Id , out var link ) ;
var existing = link ? . Value ;
2017-07-17 10:48:48 +02:00
2020-01-03 13:21:49 +11:00
if ( ! BuildKit ( kit , out var parent ) )
return false ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// moving?
var moving = existing ! = null & & existing . ParentContentId ! = kit . Node . ParentContentId ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// manage children
if ( existing ! = null )
{
kit . Node . FirstChildContentId = existing . FirstChildContentId ;
kit . Node . LastChildContentId = existing . LastChildContentId ;
}
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// set
SetValueLocked ( _contentNodes , kit . Node . Id , kit . Node ) ;
if ( _localDb ! = null ) RegisterChange ( kit . Node . Id , kit ) ;
2018-03-30 14:00:44 +02:00
2020-01-03 13:21:49 +11:00
// manage the tree
if ( existing = = null )
{
// new, add to parent
AddTreeNodeLocked ( kit . Node , parent ) ;
2017-07-17 10:48:48 +02:00
}
2020-01-03 13:21:49 +11:00
else if ( moving | | existing . SortOrder ! = kit . Node . SortOrder )
2017-07-17 10:48:48 +02:00
{
2020-01-03 13:21:49 +11:00
// moved, remove existing from its parent, add content to its parent
RemoveTreeNodeLocked ( existing ) ;
AddTreeNodeLocked ( kit . Node ) ;
}
else
2017-07-17 10:48:48 +02:00
{
2020-01-03 13:21:49 +11:00
// replacing existing, handle siblings
kit . Node . NextSiblingContentId = existing . NextSiblingContentId ;
kit . Node . PreviousSiblingContentId = existing . PreviousSiblingContentId ;
2017-07-17 10:48:48 +02:00
}
2019-06-25 12:54:16 +02:00
2020-01-03 13:21:49 +11:00
_xmap [ kit . Node . Uid ] = kit . Node . Id ;
2019-06-25 12:54:16 +02:00
return true ;
2016-05-27 14:26:28 +02:00
}
2019-04-22 17:51:07 +02:00
private void ClearRootLocked ( )
{
2020-01-13 17:16:55 +11:00
if ( _root . Gen ! = _liveGen )
2019-04-22 17:51:07 +02:00
_root = new LinkedNode < ContentNode > ( new ContentNode ( ) , _liveGen , _root ) ;
else
_root . Value . FirstChildContentId = - 1 ;
}
2019-08-15 19:05:43 +10:00
/// <summary>
2019-08-19 23:22:27 +10:00
/// Builds all kits on startup using a fast forward only cursor
2019-08-15 19:05:43 +10:00
/// </summary>
2019-08-19 23:22:27 +10:00
/// <param name="kits">
/// All kits sorted by Level + Parent Id + Sort order
/// </param>
2019-10-31 18:58:07 +11:00
/// <param name="fromDb">True if the data is coming from the database (not the local cache db)</param>
2019-08-15 19:05:43 +10:00
/// <returns></returns>
/// <remarks>
2020-01-03 13:21:49 +11:00
/// <para>
2019-08-19 22:07:22 +10:00
/// This requires that the collection is sorted by Level + ParentId + Sort Order.
2019-08-15 19:05:43 +10:00
/// This should be used only on a site startup as the first generations.
2019-08-19 23:22:27 +10:00
/// This CANNOT be used after startup since it bypasses all checks for Generations.
2020-01-03 13:21:49 +11:00
/// </para>
/// <para>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </para>
2019-08-15 19:05:43 +10:00
/// </remarks>
2020-01-03 13:21:49 +11:00
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetAllFastSortedLocked ( IEnumerable < ContentNodeKit > kits , bool fromDb )
2019-08-15 19:05:43 +10:00
{
2020-01-03 13:21:49 +11:00
EnsureLocked ( ) ;
2020-01-03 10:38:48 +11:00
2019-08-15 19:05:43 +10:00
var ok = true ;
2020-01-03 13:21:49 +11:00
ClearLocked ( _contentNodes ) ;
ClearRootLocked ( ) ;
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
// The name of the game here is to populate each kit's
// FirstChildContentId
// LastChildContentId
// NextSiblingContentId
// PreviousSiblingContentId
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
ContentNode previousNode = null ;
ContentNode parent = null ;
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
foreach ( var kit in kits )
{
if ( ! BuildKit ( kit , out var parentLink ) )
2019-08-15 19:05:43 +10:00
{
2020-01-03 13:21:49 +11:00
ok = false ;
continue ; // skip that one
}
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
var thisNode = kit . Node ;
2019-09-16 17:21:22 +02:00
2020-01-03 13:21:49 +11:00
if ( parent = = null )
{
// first parent
parent = parentLink . Value ;
parent . FirstChildContentId = thisNode . Id ; // this node is the first node
}
else if ( parent . Id ! = parentLink . Value . Id )
{
// new parent
parent = parentLink . Value ;
parent . FirstChildContentId = thisNode . Id ; // this node is the first node
previousNode = null ; // there is no previous sibling
}
2019-08-19 17:18:45 +10:00
2020-01-06 18:34:04 +11:00
_logger . Debug < ContentStore > ( $"Set {thisNode.Id} with parent {thisNode.ParentContentId}" ) ;
2020-01-03 13:21:49 +11:00
SetValueLocked ( _contentNodes , thisNode . Id , thisNode ) ;
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
// if we are initializing from the database source ensure the local db is updated
if ( fromDb & & _localDb ! = null ) RegisterChange ( thisNode . Id , kit ) ;
2019-10-31 18:58:07 +11:00
2020-01-03 13:21:49 +11:00
// this node is always the last child
parent . LastChildContentId = thisNode . Id ;
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
// wire previous node as previous sibling
if ( previousNode ! = null )
{
previousNode . NextSiblingContentId = thisNode . Id ;
thisNode . PreviousSiblingContentId = previousNode . Id ;
}
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
// this node becomes the previous node
previousNode = thisNode ;
2019-08-15 19:05:43 +10:00
2020-01-03 13:21:49 +11:00
_xmap [ kit . Node . Uid ] = kit . Node . Id ;
2019-08-15 19:05:43 +10:00
}
return ok ;
}
2020-01-03 13:21:49 +11:00
/// <summary>
/// Set all data for a collection of <see cref="ContentNodeKit"/>
/// </summary>
/// <param name="kits"></param>
/// <returns></returns>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetAllLocked ( IEnumerable < ContentNodeKit > kits )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
EnsureLocked ( ) ;
2017-07-17 10:48:48 +02:00
2019-06-25 12:54:16 +02:00
var ok = true ;
2020-01-06 18:34:04 +11:00
2020-01-03 13:21:49 +11:00
ClearLocked ( _contentNodes ) ;
ClearRootLocked ( ) ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// do NOT clear types else they are gone!
//ClearLocked(_contentTypesById);
//ClearLocked(_contentTypesByAlias);
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
foreach ( var kit in kits )
{
if ( ! BuildKit ( kit , out var parent ) )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
ok = false ;
continue ; // skip that one
}
2020-01-06 18:34:04 +11:00
_logger . Debug < ContentStore > ( $"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}" ) ;
2020-01-03 13:21:49 +11:00
SetValueLocked ( _contentNodes , kit . Node . Id , kit . Node ) ;
2019-06-21 15:47:47 +10:00
2020-01-03 13:21:49 +11:00
if ( _localDb ! = null ) RegisterChange ( kit . Node . Id , kit ) ;
AddTreeNodeLocked ( kit . Node , parent ) ;
2018-03-30 14:00:44 +02:00
2020-01-03 13:21:49 +11:00
_xmap [ kit . Node . Uid ] = kit . Node . Id ;
2017-07-17 10:48:48 +02:00
}
2019-06-25 12:54:16 +02:00
return ok ;
2016-05-27 14:26:28 +02:00
}
2020-01-03 13:21:49 +11:00
/// <summary>
/// Sets data for a branch of <see cref="ContentNodeKit"/>
/// </summary>
/// <param name="rootContentId"></param>
/// <param name="kits"></param>
/// <returns></returns>
/// <remarks>
/// <para>
/// IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER
/// </para>
/// <para>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </para>
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool SetBranchLocked ( int rootContentId , IEnumerable < ContentNodeKit > kits )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
EnsureLocked ( ) ;
2020-01-03 10:38:48 +11:00
2019-06-25 12:54:16 +02:00
var ok = true ;
2020-01-06 18:34:04 +11:00
2020-01-03 13:21:49 +11:00
// get existing
_contentNodes . TryGetValue ( rootContentId , out var link ) ;
var existing = link ? . Value ;
2017-07-17 10:48:48 +02:00
2020-01-03 13:21:49 +11:00
// clear
if ( existing ! = null )
{
//this zero's out the branch (recursively), if we're in a new gen this will add a NULL placeholder for the gen
ClearBranchLocked ( existing ) ;
//TODO: This removes the current GEN from the tree - do we really want to do that?
RemoveTreeNodeLocked ( existing ) ;
}
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// now add them all back
foreach ( var kit in kits )
{
if ( ! BuildKit ( kit , out var parent ) )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
ok = false ;
continue ; // skip that one
2016-05-27 14:26:28 +02:00
}
2020-01-03 13:21:49 +11:00
SetValueLocked ( _contentNodes , kit . Node . Id , kit . Node ) ;
if ( _localDb ! = null ) RegisterChange ( kit . Node . Id , kit ) ;
AddTreeNodeLocked ( kit . Node , parent ) ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
_xmap [ kit . Node . Uid ] = kit . Node . Id ;
2017-07-17 10:48:48 +02:00
}
2019-06-25 12:54:16 +02:00
return ok ;
2016-05-27 14:26:28 +02:00
}
2020-01-03 13:21:49 +11:00
/// <summary>
/// Clears data for a given node id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public bool ClearLocked ( int id )
2016-05-27 14:26:28 +02:00
{
2020-01-03 13:21:49 +11:00
EnsureLocked ( ) ;
2017-07-17 10:48:48 +02:00
2020-01-03 13:21:49 +11:00
// try to find the content
// if it is not there, nothing to do
_contentNodes . TryGetValue ( id , out var link ) ; // else null
if ( link ? . Value = = null ) return false ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
var content = link . Value ;
_logger . Debug < ContentStore > ( "Clear content ID: {ContentId}" , content . Id ) ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// clear the entire branch
ClearBranchLocked ( content ) ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
// manage the tree
RemoveTreeNodeLocked ( content ) ;
2016-05-27 14:26:28 +02:00
2020-01-03 13:21:49 +11:00
return true ;
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 _ ) ;
2019-04-22 17:51:07 +02:00
var id = content . FirstChildContentId ;
while ( id > 0 )
2016-05-27 14:26:28 +02:00
{
2019-09-18 00:10:02 +10:00
var link = GetRequiredLinkedNode ( id , "child" , null ) ;
2019-06-18 11:36:53 +02:00
ClearBranchLocked ( link . Value ) ;
id = link . Value . NextSiblingContentId ;
2016-05-27 14:26:28 +02:00
}
}
2019-08-15 19:05:43 +10:00
/// <summary>
/// Gets the link node and if it doesn't exist throw a <see cref="PanicException"/>
/// </summary>
/// <param name="id"></param>
/// <param name="description"></param>
2019-09-18 00:10:02 +10:00
/// <param name="gen">the generation requested, null for the latest stored</param>
2019-08-15 19:05:43 +10:00
/// <returns></returns>
2019-09-18 00:10:02 +10:00
private LinkedNode < ContentNode > GetRequiredLinkedNode ( int id , string description , long? gen )
2019-06-12 08:55:03 +02:00
{
2019-09-17 22:38:43 +10:00
if ( _contentNodes . TryGetValue ( id , out var link ) )
{
2019-09-18 00:10:02 +10:00
link = GetLinkedNodeGen ( link , gen ) ;
if ( link ! = null & & link . Value ! = null )
2019-09-17 22:38:43 +10:00
return link ;
2019-09-18 00:10:02 +10:00
}
2019-06-12 08:55:03 +02:00
2019-08-16 16:18:58 +10:00
throw new PanicException ( $"failed to get {description} with id={id}" ) ;
2019-06-12 08:55:03 +02:00
}
2019-08-19 23:22:27 +10:00
/// <summary>
/// Gets the parent link node, may be null or root if ParentContentId is less than 0
/// </summary>
2019-09-18 00:10:02 +10:00
/// <param name="gen">the generation requested, null for the latest stored</param>
private LinkedNode < ContentNode > GetParentLink ( ContentNode content , long? gen )
2016-05-27 14:26:28 +02:00
{
2019-09-17 22:38:43 +10:00
if ( content . ParentContentId < 0 )
{
2019-09-18 00:10:02 +10:00
var root = GetLinkedNodeGen ( _root , gen ) ;
2019-09-17 22:38:43 +10:00
return root ;
}
2019-08-15 19:05:43 +10:00
2019-09-17 22:38:43 +10:00
if ( _contentNodes . TryGetValue ( content . ParentContentId , out var link ) )
2019-09-18 00:10:02 +10:00
link = GetLinkedNodeGen ( link , gen ) ;
2016-05-27 14:26:28 +02:00
return link ;
}
2019-06-12 08:55:03 +02:00
2019-08-15 19:05:43 +10:00
/// <summary>
/// Gets the linked parent node and if it doesn't exist throw a <see cref="PanicException"/>
/// </summary>
/// <param name="content"></param>
2019-09-18 00:10:02 +10:00
/// <param name="gen">the generation requested, null for the latest stored</param>
2019-08-15 19:05:43 +10:00
/// <returns></returns>
2019-09-18 00:10:02 +10:00
private LinkedNode < ContentNode > GetRequiredParentLink ( ContentNode content , long? gen )
2019-08-15 19:05:43 +10:00
{
2019-09-17 22:38:43 +10:00
return content . ParentContentId < 0 ? _root : GetRequiredLinkedNode ( content . ParentContentId , "parent" , gen ) ;
2019-06-12 08:55:03 +02:00
}
2019-09-18 00:10:02 +10:00
/// <summary>
/// Iterates over the LinkedNode's generations to find the correct one
/// </summary>
/// <param name="link"></param>
/// <param name="gen">The generation requested, use null to avoid the lookup</param>
/// <returns></returns>
2019-09-18 00:36:12 +10:00
private LinkedNode < TValue > GetLinkedNodeGen < TValue > ( LinkedNode < TValue > link , long? gen )
where TValue : class
2016-05-27 14:26:28 +02:00
{
2019-09-18 00:10:02 +10:00
if ( ! gen . HasValue ) return link ;
2019-09-18 00:36:12 +10:00
//find the correct snapshot, find the first that is <= the requested gen
while ( link ! = null & & link . Gen > gen )
2019-09-18 00:10:02 +10:00
{
link = link . Next ;
}
2016-05-27 14:26:28 +02:00
return link ;
}
2020-01-13 17:16:55 +11:00
/// <summary>
/// This removes this current node from the tree hiearchy by removing it from it's parent's linked list
/// </summary>
/// <param name="content"></param>
/// <remarks>
/// This is called within a lock which means a new Gen is being created therefore this will not modify any existing content in a Gen.
/// </remarks>
2019-08-19 23:22:27 +10:00
private void RemoveTreeNodeLocked ( ContentNode content )
2016-05-27 14:26:28 +02:00
{
2020-01-13 17:16:55 +11:00
// NOTE: DO NOT modify `content` here, this would modify data for an existing Gen, all modifications are done to clones
// which would be targeting the new Gen.
2019-06-12 08:55:03 +02:00
var parentLink = content . ParentContentId < 0
? _root
2019-09-18 00:10:02 +10:00
: GetRequiredLinkedNode ( content . ParentContentId , "parent" , null ) ;
2019-04-22 17:51:07 +02:00
var parent = parentLink . Value ;
2019-06-12 15:48:19 +02:00
// must have children
if ( parent . FirstChildContentId < 0 )
2019-08-16 16:18:58 +10:00
throw new PanicException ( "no children" ) ;
2019-06-12 15:48:19 +02:00
2019-09-16 17:21:22 +02:00
// if first/last, clone parent, then remove
if ( parent . FirstChildContentId = = content . Id | | parent . LastChildContentId = = content . Id )
2019-04-22 17:51:07 +02:00
parent = GenCloneLocked ( parentLink ) ;
2019-09-16 17:21:22 +02:00
if ( parent . FirstChildContentId = = content . Id )
2019-04-22 17:51:07 +02:00
parent . FirstChildContentId = content . NextSiblingContentId ;
2019-08-19 23:22:27 +10:00
if ( parent . LastChildContentId = = content . Id )
parent . LastChildContentId = content . PreviousSiblingContentId ;
// maintain linked list
2019-04-22 17:51:07 +02:00
2019-08-19 23:22:27 +10:00
if ( content . NextSiblingContentId > 0 )
{
2019-09-18 00:10:02 +10:00
var nextLink = GetRequiredLinkedNode ( content . NextSiblingContentId , "next sibling" , null ) ;
2019-08-19 23:22:27 +10:00
var next = GenCloneLocked ( nextLink ) ;
next . PreviousSiblingContentId = content . PreviousSiblingContentId ;
2016-05-27 14:26:28 +02:00
}
2019-08-19 23:22:27 +10:00
if ( content . PreviousSiblingContentId > 0 )
{
2019-09-18 00:10:02 +10:00
var prevLink = GetRequiredLinkedNode ( content . PreviousSiblingContentId , "previous sibling" , null ) ;
2019-08-19 23:22:27 +10:00
var prev = GenCloneLocked ( prevLink ) ;
prev . NextSiblingContentId = content . NextSiblingContentId ;
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 ;
2019-09-18 00:10:02 +10:00
var link = GetParentLink ( kit . Node , null ) ;
2018-07-05 17:08:40 +02:00
var node = link ? . Value ;
2019-09-16 14:25:02 +10:00
return node ! = null & & node . HasPublished ;
2018-07-05 17:08:40 +02:00
}
2019-04-22 17:51:07 +02:00
private ContentNode GenCloneLocked ( LinkedNode < ContentNode > link )
2016-05-27 14:26:28 +02:00
{
2019-04-22 17:51:07 +02:00
var node = link . Value ;
2020-01-13 17:16:55 +11:00
if ( node ! = null & & link . Gen ! = _liveGen )
2019-04-22 17:51:07 +02:00
{
node = new ContentNode ( link . Value ) ;
if ( link = = _root )
SetRootLocked ( node ) ;
else
SetValueLocked ( _contentNodes , node . Id , node ) ;
}
return node ;
}
2019-08-19 23:22:27 +10:00
/// <summary>
/// Adds a node to the tree structure.
/// </summary>
private void AddTreeNodeLocked ( ContentNode content , LinkedNode < ContentNode > parentLink = null )
2019-04-22 17:51:07 +02:00
{
2019-09-18 00:10:02 +10:00
parentLink = parentLink ? ? GetRequiredParentLink ( content , null ) ;
2019-04-22 17:51:07 +02:00
2020-04-01 11:06:21 +11:00
// TODO: This can result in a null value? see https://github.com/umbraco/Umbraco-CMS/issues/7868
// It seems to be related to having corrupt Paths in the umbracoNode table.
2019-04-22 17:51:07 +02:00
var parent = parentLink . Value ;
2020-04-01 11:06:21 +11:00
if ( parent = = null )
throw new PanicException ( $"A null Value was returned on the {nameof(parentLink)} LinkedNode with id={content.ParentContentId}, potentially your database paths are corrupted, please see the HealthCheck dashboard and fixup data inconsistencies." ) ;
2019-06-12 15:48:19 +02:00
// if parent has no children, clone parent + add as first child
2019-04-22 17:51:07 +02:00
if ( parent . FirstChildContentId < 0 )
{
parent = GenCloneLocked ( parentLink ) ;
parent . FirstChildContentId = content . Id ;
2019-08-19 23:22:27 +10:00
parent . LastChildContentId = content . Id ;
2019-04-22 17:51:07 +02:00
return ;
}
2019-06-12 15:48:19 +02:00
// get parent's first child
2019-09-25 13:51:19 +02:00
var childLink = GetRequiredLinkedNode ( parent . FirstChildContentId , "first child" , null ) ;
2019-06-12 15:48:19 +02:00
var child = childLink . Value ;
2019-04-22 17:51:07 +02:00
2019-06-12 15:48:19 +02:00
// if first, clone parent + insert as first child
2019-08-15 19:05:43 +10:00
// NOTE: Don't perform this check if loading from local DB since we know it's already sorted
2019-06-12 15:48:19 +02:00
if ( child . SortOrder > content . SortOrder )
2019-04-22 17:51:07 +02:00
{
content . NextSiblingContentId = parent . FirstChildContentId ;
2019-08-19 23:22:27 +10:00
content . PreviousSiblingContentId = - 1 ;
2019-04-22 17:51:07 +02:00
parent = GenCloneLocked ( parentLink ) ;
parent . FirstChildContentId = content . Id ;
2019-08-19 23:22:27 +10:00
child = GenCloneLocked ( childLink ) ;
child . PreviousSiblingContentId = content . Id ;
2019-04-22 17:51:07 +02:00
return ;
}
2019-08-19 23:22:27 +10:00
// get parent's last child
2019-09-25 13:51:19 +02:00
var lastChildLink = GetRequiredLinkedNode ( parent . LastChildContentId , "last child" , null ) ;
2019-08-19 23:22:27 +10:00
var lastChild = lastChildLink . Value ;
// if last, clone parent + append as last child
if ( lastChild . SortOrder < = content . SortOrder )
{
content . PreviousSiblingContentId = parent . LastChildContentId ;
content . NextSiblingContentId = - 1 ;
parent = GenCloneLocked ( parentLink ) ;
parent . LastChildContentId = content . Id ;
lastChild = GenCloneLocked ( lastChildLink ) ;
lastChild . NextSiblingContentId = content . Id ;
2019-04-22 17:51:07 +02:00
return ;
}
2019-08-19 23:22:27 +10:00
// else it's going somewhere in the middle,
2019-10-17 18:41:05 +11:00
// TODO: There was a note about performance when this occurs and that this only happens when moving and not very often, but that is not true,
// this also happens anytime a middle node is unpublished or republished (which causes a branch update), i'm unsure if this has perf impacts,
// i think this used to but it doesn't seem bad anymore that I can see...
2019-06-12 15:48:19 +02:00
while ( child . NextSiblingContentId > 0 )
2019-04-22 17:51:07 +02:00
{
2019-06-12 15:48:19 +02:00
// get next child
2019-09-25 13:51:19 +02:00
var nextChildLink = GetRequiredLinkedNode ( child . NextSiblingContentId , "next child" , null ) ;
2019-06-12 15:48:19 +02:00
var nextChild = nextChildLink . Value ;
2019-04-22 17:51:07 +02:00
2019-06-12 15:48:19 +02:00
// if here, clone previous + append/insert
2019-08-15 19:05:43 +10:00
// NOTE: Don't perform this check if loading from local DB since we know it's already sorted
2019-04-22 17:51:07 +02:00
if ( nextChild . SortOrder > content . SortOrder )
{
content . NextSiblingContentId = nextChild . Id ;
2019-08-19 23:22:27 +10:00
content . PreviousSiblingContentId = nextChild . PreviousSiblingContentId ;
2019-06-12 15:48:19 +02:00
child = GenCloneLocked ( childLink ) ;
child . NextSiblingContentId = content . Id ;
2019-08-19 23:22:27 +10:00
var nnext = GenCloneLocked ( nextChildLink ) ;
nnext . PreviousSiblingContentId = content . Id ;
2019-04-22 17:51:07 +02:00
return ;
}
2019-06-12 15:48:19 +02:00
childLink = nextChildLink ;
child = nextChild ;
2019-04-22 17:51:07 +02:00
}
2019-08-19 23:22:27 +10:00
// should never get here
2019-09-25 13:51:19 +02:00
throw new PanicException ( "No more children." ) ;
2019-04-22 17:51:07 +02:00
}
2019-06-12 15:48:19 +02:00
// replaces the root node
2019-04-22 17:51:07 +02:00
private void SetRootLocked ( ContentNode node )
{
if ( _root . Gen ! = _liveGen )
{
_root = new LinkedNode < ContentNode > ( node , _liveGen , _root ) ;
2016-05-27 14:26:28 +02:00
}
else
{
2019-04-22 17:51:07 +02:00
_root . Value = node ;
2016-05-27 14:26:28 +02:00
}
}
2019-06-12 15:48:19 +02:00
// set a node (just the node, not the tree)
2016-05-27 14:26:28 +02:00
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
{
2020-01-13 17:16:55 +11: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 )
{
2019-09-18 00:36:12 +10:00
var root = GetLinkedNodeGen ( _root , gen ) ;
if ( root = = null )
2019-04-22 17:51:07 +02:00
yield break ;
2019-09-18 00:36:12 +10:00
var id = root . Value . FirstChildContentId ;
2019-04-22 17:51:07 +02:00
while ( id > 0 )
{
2019-09-17 22:38:43 +10:00
var link = GetRequiredLinkedNode ( id , "root" , gen ) ;
2019-04-22 17:51:07 +02:00
yield return link . Value ;
id = link . Value . NextSiblingContentId ;
2016-05-27 14:26:28 +02:00
}
}
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 ) ;
2019-09-18 00:36:12 +10:00
link = GetLinkedNodeGen ( link , gen ) ;
return link ? . Value ; // may be null
2016-05-27 14:26:28 +02:00
}
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 )
{
2019-09-18 00:36:12 +10:00
var link = GetLinkedNodeGen ( l , gen ) ;
if ( link ? . Value ! = null )
yield return link . Value ;
2016-11-05 12:22:57 +01:00
}
}
2016-05-27 14:26:28 +02:00
public bool IsEmpty ( long gen )
{
var has = _contentNodes . Any ( x = >
{
2019-09-18 00:36:12 +10:00
var link = GetLinkedNodeGen ( x . Value , gen ) ;
return link ? . Value ! = null ;
2016-05-27 14:26:28 +02:00
} ) ;
return has = = false ;
}
2019-04-15 13:04:14 +02:00
public IPublishedContentType GetContentType ( int id , long gen )
2016-05-27 14:26:28 +02:00
{
return GetValue ( _contentTypesById , id , gen ) ;
}
2019-04-15 13:04:14 +02:00
public IPublishedContentType GetContentType ( string alias , long gen )
2016-05-27 14:26:28 +02:00
{
return GetValue ( _contentTypesByAlias , alias , gen ) ;
}
#endregion
#region Snapshots
public Snapshot CreateSnapshot ( )
{
2020-01-03 12:39:56 +11:00
lock ( _rlocko )
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
2020-01-03 15:04:39 +11:00
if ( Monitor . IsEntered ( _wlocko ) )
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 )
2019-07-30 22:40:15 +10:00
throw new PanicException ( $"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}" ) ;
2016-05-27 14:26:28 +02:00
}
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
}
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
2019-04-22 17:51:07 +02:00
var task = _collectTask = Task . Run ( Collect ) ;
2016-05-27 14:26:28 +02:00
_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 ) ;
2019-04-22 17:51:07 +02:00
CollectRoot ( ) ;
2016-05-27 14:26:28 +02:00
Collect ( _contentTypesById ) ;
Collect ( _contentTypesByAlias ) ;
}
2019-04-22 17:51:07 +02:00
private void CollectRoot ( )
{
var link = _root ;
while ( link . Next ! = null & & link . Next . Gen > _floorGen )
link = link . Next ;
link . Next = null ;
}
2016-05-27 14:26:28 +02:00
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 ;
}
}
2020-01-03 12:39:56 +11:00
// TODO: This is never used? Should it be? Maybe move to TestHelper below?
//public async Task WaitForPendingCollect()
//{
// Task task;
// lock (_rlocko)
// {
// task = _collectTask;
// }
// if (task != null)
// await task;
//}
2016-05-27 14:26:28 +02:00
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
2019-09-25 13:51:19 +02:00
#region Internals / Unit testing
2016-05-27 14:26:28 +02:00
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
2019-09-25 13:51:19 +02:00
/// <summary>
/// Return a list of Gen/ContentNode values
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public ( long gen , ContentNode contentNode ) [ ] GetValues ( int id )
2016-05-27 14:26:28 +02:00
{
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 )
2019-09-25 13:51:19 +02:00
return Array . Empty < ( long , ContentNode ) > ( ) ;
2016-05-27 14:26:28 +02:00
2019-09-25 13:51:19 +02:00
var tuples = new List < ( long , ContentNode ) > ( ) ;
2016-05-27 14:26:28 +02:00
do
{
2019-09-25 13:51:19 +02:00
tuples . Add ( ( link . Gen , link . Value ) ) ;
2016-05-27 14:26:28 +02:00
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 ) ;
}
2019-04-15 13:04:14 +02:00
public IPublishedContentType GetContentType ( int id )
2016-05-27 14:26:28 +02:00
{
if ( _gen < 0 )
throw new ObjectDisposedException ( "snapshot" /*+ " (" + _thisCount + ")"*/ ) ;
return _store . GetContentType ( id , _gen ) ;
}
2019-04-15 13:04:14 +02:00
public IPublishedContentType GetContentType ( string alias )
2016-05-27 14:26:28 +02:00
{
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
}
}