2019-10-17 18:41:05 +11:00
|
|
|
using System.Diagnostics;
|
2021-02-18 11:06:02 +01:00
|
|
|
using Umbraco.Cms.Core.Models.PublishedContent;
|
|
|
|
|
using Umbraco.Cms.Core.PublishedCache;
|
|
|
|
|
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
|
|
|
|
using Umbraco.Extensions;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
namespace Umbraco.Cms.Infrastructure.PublishedCache;
|
|
|
|
|
|
|
|
|
|
// represents a content "node" ie a pair of draft + published versions
|
|
|
|
|
// internal, never exposed, to be accessed from ContentStore (only!)
|
|
|
|
|
[DebuggerDisplay("Id: {Id}, Path: {Path}")]
|
|
|
|
|
public class ContentNode
|
2016-05-27 14:26:28 +02:00
|
|
|
{
|
2022-06-20 09:21:08 +02:00
|
|
|
// everything that is common to both draft and published versions
|
|
|
|
|
// keep this as small as possible
|
|
|
|
|
#pragma warning disable IDE1006 // Naming Styles
|
|
|
|
|
public readonly int Id;
|
2019-04-22 17:51:07 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
private ContentData? _draftData;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// draft and published version (either can be null, but not both)
|
|
|
|
|
// are models not direct PublishedContent instances
|
|
|
|
|
private IPublishedContent? _draftModel;
|
|
|
|
|
private ContentData? _publishedData;
|
|
|
|
|
private IPublishedContent? _publishedModel;
|
|
|
|
|
private IPublishedModelFactory? _publishedModelFactory;
|
|
|
|
|
private IPublishedSnapshotAccessor? _publishedSnapshotAccessor;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
private IVariationContextAccessor? _variationContextAccessor;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// special ctor for root pseudo node
|
|
|
|
|
public ContentNode()
|
|
|
|
|
{
|
|
|
|
|
FirstChildContentId = -1;
|
|
|
|
|
LastChildContentId = -1;
|
|
|
|
|
NextSiblingContentId = -1;
|
|
|
|
|
PreviousSiblingContentId = -1;
|
|
|
|
|
Path = string.Empty;
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// special ctor with no content data - for members
|
|
|
|
|
public ContentNode(
|
|
|
|
|
int id,
|
|
|
|
|
Guid uid,
|
|
|
|
|
IPublishedContentType contentType,
|
|
|
|
|
int level,
|
|
|
|
|
string path,
|
|
|
|
|
int sortOrder,
|
|
|
|
|
int parentContentId,
|
|
|
|
|
DateTime createDate,
|
|
|
|
|
int creatorId)
|
|
|
|
|
: this()
|
|
|
|
|
{
|
|
|
|
|
Id = id;
|
|
|
|
|
Uid = uid;
|
|
|
|
|
ContentType = contentType;
|
|
|
|
|
Level = level;
|
|
|
|
|
Path = path;
|
|
|
|
|
SortOrder = sortOrder;
|
|
|
|
|
ParentContentId = parentContentId;
|
|
|
|
|
CreateDate = createDate;
|
|
|
|
|
CreatorId = creatorId;
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public ContentNode(
|
|
|
|
|
int id,
|
|
|
|
|
Guid uid,
|
|
|
|
|
IPublishedContentType contentType,
|
|
|
|
|
int level,
|
|
|
|
|
string path,
|
|
|
|
|
int sortOrder,
|
|
|
|
|
int parentContentId,
|
|
|
|
|
DateTime createDate,
|
|
|
|
|
int creatorId,
|
|
|
|
|
ContentData draftData,
|
|
|
|
|
ContentData publishedData,
|
|
|
|
|
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
|
|
|
|
IVariationContextAccessor variationContextAccessor,
|
|
|
|
|
IPublishedModelFactory publishedModelFactory)
|
|
|
|
|
: this(id, uid, level, path, sortOrder, parentContentId, createDate, creatorId) =>
|
|
|
|
|
SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor, publishedModelFactory);
|
|
|
|
|
|
|
|
|
|
// 2-phases ctor, phase 1
|
|
|
|
|
public ContentNode(
|
|
|
|
|
int id,
|
|
|
|
|
Guid uid,
|
|
|
|
|
int level,
|
|
|
|
|
string path,
|
|
|
|
|
int sortOrder,
|
|
|
|
|
int parentContentId,
|
|
|
|
|
DateTime createDate,
|
|
|
|
|
int creatorId)
|
|
|
|
|
{
|
|
|
|
|
Id = id;
|
|
|
|
|
Uid = uid;
|
|
|
|
|
Level = level;
|
|
|
|
|
Path = path;
|
|
|
|
|
SortOrder = sortOrder;
|
|
|
|
|
ParentContentId = parentContentId;
|
|
|
|
|
FirstChildContentId = -1;
|
|
|
|
|
LastChildContentId = -1;
|
|
|
|
|
NextSiblingContentId = -1;
|
|
|
|
|
PreviousSiblingContentId = -1;
|
|
|
|
|
CreateDate = createDate;
|
|
|
|
|
CreatorId = creatorId;
|
|
|
|
|
}
|
2019-01-28 20:07:22 +01:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// clone
|
|
|
|
|
public ContentNode(ContentNode origin, IPublishedModelFactory publishedModelFactory, IPublishedContentType? contentType = null)
|
|
|
|
|
{
|
|
|
|
|
_publishedModelFactory = publishedModelFactory;
|
|
|
|
|
Id = origin.Id;
|
|
|
|
|
Uid = origin.Uid;
|
|
|
|
|
ContentType = contentType ?? origin.ContentType;
|
|
|
|
|
Level = origin.Level;
|
|
|
|
|
Path = origin.Path;
|
|
|
|
|
SortOrder = origin.SortOrder;
|
|
|
|
|
ParentContentId = origin.ParentContentId;
|
|
|
|
|
FirstChildContentId = origin.FirstChildContentId;
|
|
|
|
|
LastChildContentId = origin.LastChildContentId;
|
|
|
|
|
NextSiblingContentId = origin.NextSiblingContentId;
|
|
|
|
|
PreviousSiblingContentId = origin.PreviousSiblingContentId;
|
|
|
|
|
CreateDate = origin.CreateDate;
|
|
|
|
|
CreatorId = origin.CreatorId;
|
|
|
|
|
|
|
|
|
|
_draftData = origin._draftData;
|
|
|
|
|
_publishedData = origin._publishedData;
|
|
|
|
|
_publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
|
|
|
|
|
_variationContextAccessor = origin._variationContextAccessor;
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public bool HasPublished => _publishedData != null;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public IPublishedContent? DraftModel => GetModel(ref _draftModel, _draftData);
|
2020-01-13 17:16:55 +11:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public IPublishedContent? PublishedModel => GetModel(ref _publishedModel, _publishedData);
|
2020-01-13 17:16:55 +11:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// two-phase ctor, phase 2
|
|
|
|
|
public void SetContentTypeAndData(
|
|
|
|
|
IPublishedContentType contentType,
|
|
|
|
|
ContentData? draftData,
|
|
|
|
|
ContentData? publishedData,
|
|
|
|
|
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
|
|
|
|
IVariationContextAccessor variationContextAccessor,
|
|
|
|
|
IPublishedModelFactory publishedModelFactory)
|
|
|
|
|
{
|
|
|
|
|
ContentType = contentType;
|
2020-01-13 17:16:55 +11:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
if (draftData == null && publishedData == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Both draftData and publishedData cannot be null at the same time.");
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
_publishedSnapshotAccessor = publishedSnapshotAccessor;
|
|
|
|
|
_variationContextAccessor = variationContextAccessor;
|
|
|
|
|
_publishedModelFactory = publishedModelFactory;
|
|
|
|
|
|
|
|
|
|
_draftData = draftData;
|
|
|
|
|
_publishedData = publishedData;
|
|
|
|
|
}
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public bool HasPublishedCulture(string culture) =>
|
|
|
|
|
_publishedData != null && (_publishedData.CultureInfos?.ContainsKey(culture) ?? false);
|
2019-01-28 14:15:47 +01:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public ContentNodeKit ToKit() => new(this, ContentType.Id, _draftData, _publishedData);
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
private IPublishedContent? GetModel(ref IPublishedContent? model, ContentData? contentData)
|
|
|
|
|
{
|
|
|
|
|
if (model != null)
|
2019-09-13 09:55:53 +02:00
|
|
|
{
|
2022-06-20 09:21:08 +02:00
|
|
|
return model;
|
|
|
|
|
}
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
if (contentData == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// create the model - we want to be fast, so no lock here: we may create
|
|
|
|
|
// more than 1 instance, but the lock below ensures we only ever return
|
|
|
|
|
// 1 unique instance - and locking is a nice explicit way to ensure this
|
|
|
|
|
IPublishedContent? m =
|
|
|
|
|
new PublishedContent(this, contentData, _publishedSnapshotAccessor, _variationContextAccessor, _publishedModelFactory).CreateModel(_publishedModelFactory);
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// locking 'this' is not a best-practice but ContentNode is internal and
|
|
|
|
|
// we know what we do, so it is fine here and avoids allocating an object
|
|
|
|
|
lock (this)
|
|
|
|
|
{
|
|
|
|
|
return model ??= m;
|
2019-09-13 09:55:53 +02:00
|
|
|
}
|
2022-06-20 09:21:08 +02:00
|
|
|
}
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
public readonly Guid Uid;
|
|
|
|
|
public readonly int Level;
|
|
|
|
|
public IPublishedContentType ContentType = null!;
|
|
|
|
|
public readonly string Path;
|
|
|
|
|
public readonly int SortOrder;
|
|
|
|
|
public readonly int ParentContentId;
|
|
|
|
|
public readonly DateTime CreateDate;
|
2019-09-13 09:55:53 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
#pragma warning restore IDE1006 // Naming Styles
|
2016-05-27 14:26:28 +02:00
|
|
|
|
2022-06-20 09:21:08 +02:00
|
|
|
// TODO: Can we make everything readonly?? This would make it easier to debug and be less error prone especially for new developers.
|
|
|
|
|
// Once a Node is created and exists in the cache it is readonly so we should be able to make that happen at the API level too.
|
|
|
|
|
#pragma warning disable IDE1006 // Naming Styles
|
|
|
|
|
public int FirstChildContentId;
|
|
|
|
|
public int LastChildContentId;
|
|
|
|
|
public int NextSiblingContentId;
|
|
|
|
|
public int PreviousSiblingContentId;
|
|
|
|
|
public readonly int CreatorId;
|
|
|
|
|
#pragma warning restore IDE1006 // Naming Styles
|
2016-05-27 14:26:28 +02:00
|
|
|
}
|