Merge branch 'release/15.0' into v15/dev
# Conflicts: # src/Umbraco.Core/Services/DocumentUrlService.cs
This commit is contained in:
@@ -30,7 +30,12 @@ public static class UmbracoBuilderExtensions
|
||||
public static IUmbracoBuilder AddUmbracoHybridCache(this IUmbracoBuilder builder)
|
||||
{
|
||||
#pragma warning disable EXTEXP0018
|
||||
builder.Services.AddHybridCache();
|
||||
builder.Services.AddHybridCache(options =>
|
||||
{
|
||||
// We'll be a bit friendlier and default this to a higher value, you quickly hit the 1MB limit with a few languages and especially blocks.
|
||||
// This can be overwritten later if needed.
|
||||
options.MaximumPayloadBytes = 1024 * 1024 * 100; // 100MB
|
||||
});
|
||||
#pragma warning restore EXTEXP0018
|
||||
builder.Services.AddSingleton<IDatabaseCacheRepository, DatabaseCacheRepository>();
|
||||
builder.Services.AddSingleton<IPublishedContentCache, DocumentCache>();
|
||||
|
||||
@@ -3,9 +3,9 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Infrastructure.HybridCache.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.HybridCache;
|
||||
@@ -95,7 +95,7 @@ public sealed class DocumentCache : IPublishedContentCache
|
||||
public IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType)
|
||||
=> _documentCacheService.GetByContentType(contentType);
|
||||
|
||||
[Obsolete("Use IDocumentUrlService.GetDocumentKeyByRoute instead, scheduled for removal in v17")]
|
||||
[Obsolete("Use IPublishedUrlProvider.GetUrl instead, scheduled for removal in v17")]
|
||||
public IPublishedContent? GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string? culture = null)
|
||||
{
|
||||
IDocumentUrlService documentUrlService = StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>();
|
||||
@@ -103,7 +103,7 @@ public sealed class DocumentCache : IPublishedContentCache
|
||||
return key is not null ? GetById(preview, key.Value) : null;
|
||||
}
|
||||
|
||||
[Obsolete("Use IDocumentUrlService.GetDocumentKeyByRoute instead, scheduled for removal in v17")]
|
||||
[Obsolete("Use IPublishedUrlProvider.GetUrl instead, scheduled for removal in v17")]
|
||||
public IPublishedContent? GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null)
|
||||
{
|
||||
IDocumentUrlService documentUrlService = StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>();
|
||||
@@ -111,14 +111,15 @@ public sealed class DocumentCache : IPublishedContentCache
|
||||
return key is not null ? GetById(key.Value) : null;
|
||||
}
|
||||
|
||||
[Obsolete("Use IDocumentUrlService.GetDocumentKeyByRoute instead, scheduled for removal in v17")]
|
||||
[Obsolete("Use IPublishedUrlProvider.GetUrl instead, scheduled for removal in v17")]
|
||||
public string? GetRouteById(bool preview, int contentId, string? culture = null)
|
||||
{
|
||||
IDocumentUrlService documentUrlService = StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>();
|
||||
IPublishedUrlProvider publishedUrlProvider = StaticServiceProvider.Instance.GetRequiredService<IPublishedUrlProvider>();
|
||||
IPublishedContent? content = GetById(preview, contentId);
|
||||
return content is not null ? documentUrlService.GetLegacyRouteFormat(content.Key, culture, preview) : null;
|
||||
|
||||
return content is not null ? publishedUrlProvider.GetUrl(content, UrlMode.Relative, culture) : null;
|
||||
}
|
||||
|
||||
[Obsolete("Use IDocumentUrlService.GetDocumentKeyByRoute instead, scheduled for removal in v17")]
|
||||
[Obsolete("Use IPublishedUrlProvider.GetUrl instead, scheduled for removal in v17")]
|
||||
public string? GetRouteById(int contentId, string? culture = null) => GetRouteById(false, contentId, culture);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using StackExchange.Profiling.Internal;
|
||||
using Umbraco.Cms.Core.Media.EmbedProviders;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -10,11 +9,13 @@ internal class CacheNodeFactory : ICacheNodeFactory
|
||||
{
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
|
||||
private readonly IDocumentUrlService _documentUrlService;
|
||||
|
||||
public CacheNodeFactory(IShortStringHelper shortStringHelper, UrlSegmentProviderCollection urlSegmentProviders)
|
||||
public CacheNodeFactory(IShortStringHelper shortStringHelper, UrlSegmentProviderCollection urlSegmentProviders, IDocumentUrlService documentUrlService)
|
||||
{
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_urlSegmentProviders = urlSegmentProviders;
|
||||
_documentUrlService = documentUrlService;
|
||||
}
|
||||
|
||||
public ContentCacheNode ToContentCacheNode(IContent content, bool preview)
|
||||
@@ -126,6 +127,7 @@ internal class CacheNodeFactory : ICacheNodeFactory
|
||||
}
|
||||
|
||||
var cultureData = new Dictionary<string, CultureVariation>();
|
||||
string? urlSegment = null;
|
||||
|
||||
// sanitize - names should be ok but ... never knows
|
||||
if (content.ContentType.VariesByCulture())
|
||||
@@ -153,10 +155,14 @@ internal class CacheNodeFactory : ICacheNodeFactory
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
urlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders);
|
||||
}
|
||||
|
||||
return new ContentData(
|
||||
content.Name,
|
||||
null,
|
||||
urlSegment,
|
||||
content.VersionId,
|
||||
content.UpdateDate,
|
||||
content.CreatorId,
|
||||
|
||||
@@ -3,6 +3,7 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.HybridCache.Services;
|
||||
|
||||
|
||||
@@ -161,6 +161,16 @@ AND cmsContentNu.nodeId IS NULL
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Guid>> GetContentKeysAsync(Guid nodeObjectType)
|
||||
{
|
||||
Sql<ISqlContext> sql = Sql()
|
||||
.Select<NodeDto>(x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == nodeObjectType);
|
||||
|
||||
return await Database.FetchAsync<Guid>(sql);
|
||||
}
|
||||
|
||||
// assumes member tree lock
|
||||
public bool VerifyMemberDbCache()
|
||||
{
|
||||
@@ -235,8 +245,13 @@ AND cmsContentNu.nodeId IS NULL
|
||||
return [];
|
||||
}
|
||||
|
||||
Sql<ISqlContext>? sql = SqlContentSourcesSelect()
|
||||
.InnerJoin<NodeDto>("n")
|
||||
Sql<ISqlContext> sql = objectType == Constants.ObjectTypes.Document
|
||||
? SqlContentSourcesSelect()
|
||||
: objectType == Constants.ObjectTypes.Media
|
||||
? SqlMediaSourcesSelect()
|
||||
: throw new ArgumentOutOfRangeException(nameof(objectType), objectType, null);
|
||||
|
||||
sql.InnerJoin<NodeDto>("n")
|
||||
.On<NodeDto, ContentDto>((n, c) => n.NodeId == c.ContentTypeId, "n", "umbracoContent")
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, objectType))
|
||||
.WhereIn<NodeDto>(x => x.UniqueId, keys,"n")
|
||||
@@ -251,7 +266,6 @@ AND cmsContentNu.nodeId IS NULL
|
||||
{
|
||||
ContentCacheDataSerializerEntityType.Document => Constants.ObjectTypes.Document,
|
||||
ContentCacheDataSerializerEntityType.Media => Constants.ObjectTypes.Media,
|
||||
ContentCacheDataSerializerEntityType.Member => Constants.ObjectTypes.Member,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(entityType), entityType, null),
|
||||
};
|
||||
|
||||
@@ -262,7 +276,15 @@ AND cmsContentNu.nodeId IS NULL
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateContentNodeKit(row, serializer, row.Published is false);
|
||||
if (entityType == ContentCacheDataSerializerEntityType.Document)
|
||||
{
|
||||
yield return CreateContentNodeKit(row, serializer, row.Published is false);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return CreateMediaNodeKit(row, serializer);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,8 +596,7 @@ WHERE cmsContentNu.nodeId IN (
|
||||
cultureData[cultureInfo.Culture] = new CultureVariation
|
||||
{
|
||||
Name = cultureInfo.Name,
|
||||
UrlSegment =
|
||||
content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders, cultureInfo.Culture),
|
||||
UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders, cultureInfo.Culture),
|
||||
Date = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue,
|
||||
IsDraft = cultureIsDraft,
|
||||
};
|
||||
@@ -843,7 +864,7 @@ WHERE cmsContentNu.nodeId IN (
|
||||
serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
|
||||
var draftContentData = new ContentData(
|
||||
dto.EditName,
|
||||
null,
|
||||
deserializedDraftContent?.UrlSegment,
|
||||
dto.VersionId,
|
||||
dto.EditVersionDate,
|
||||
dto.CreatorId,
|
||||
@@ -882,7 +903,7 @@ WHERE cmsContentNu.nodeId IN (
|
||||
ContentCacheDataModel? deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw, true);
|
||||
var publishedContentData = new ContentData(
|
||||
dto.PubName,
|
||||
null,
|
||||
deserializedContent?.UrlSegment,
|
||||
dto.VersionId,
|
||||
dto.PubVersionDate,
|
||||
dto.CreatorId,
|
||||
|
||||
@@ -67,4 +67,6 @@ internal interface IDatabaseCacheRepository
|
||||
/// Rebuilds the caches for content, media and/or members based on the content type ids specified
|
||||
/// </summary>
|
||||
bool VerifyMediaDbCache();
|
||||
|
||||
Task<IEnumerable<Guid>> GetContentKeysAsync(Guid nodeObjectType);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.HybridCache.Factories;
|
||||
@@ -23,8 +24,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
private readonly IEnumerable<IDocumentSeedKeyProvider> _seedKeyProviders;
|
||||
private readonly IPublishedModelFactory _publishedModelFactory;
|
||||
private readonly IPreviewService _previewService;
|
||||
private readonly CacheSettings _cacheSettings;
|
||||
|
||||
private readonly CacheEntrySettings _cacheEntrySettings;
|
||||
private HashSet<Guid>? _seedKeys;
|
||||
private HashSet<Guid> SeedKeys
|
||||
{
|
||||
@@ -54,7 +54,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
IPublishedContentFactory publishedContentFactory,
|
||||
ICacheNodeFactory cacheNodeFactory,
|
||||
IEnumerable<IDocumentSeedKeyProvider> seedKeyProviders,
|
||||
IOptions<CacheSettings> cacheSettings,
|
||||
IOptionsMonitor<CacheEntrySettings> cacheEntrySettings,
|
||||
IPublishedModelFactory publishedModelFactory,
|
||||
IPreviewService previewService)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
_seedKeyProviders = seedKeyProviders;
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
_previewService = previewService;
|
||||
_cacheSettings = cacheSettings.Value;
|
||||
_cacheEntrySettings = cacheEntrySettings.Get(Constants.Configuration.NamedOptions.CacheEntry.Document);
|
||||
}
|
||||
|
||||
public async Task<IPublishedContent?> GetByKeyAsync(Guid key, bool? preview = null)
|
||||
@@ -82,7 +82,8 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(key, calculatedPreview);
|
||||
scope.Complete();
|
||||
return contentCacheNode;
|
||||
});
|
||||
},
|
||||
GetEntryOptions(key));
|
||||
|
||||
return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedContent(contentCacheNode, calculatedPreview).CreateModel(_publishedModelFactory);
|
||||
}
|
||||
@@ -101,6 +102,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
}
|
||||
|
||||
bool calculatedPreview = preview ?? GetPreview();
|
||||
Guid key = keyAttempt.Result;
|
||||
|
||||
ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync(
|
||||
GetCacheKey(keyAttempt.Result, calculatedPreview), // Unique key to the cache entry
|
||||
@@ -110,7 +112,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(id, calculatedPreview);
|
||||
scope.Complete();
|
||||
return contentCacheNode;
|
||||
});
|
||||
}, GetEntryOptions(key));
|
||||
|
||||
return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedContent(contentCacheNode, calculatedPreview).CreateModel(_publishedModelFactory);;
|
||||
}
|
||||
@@ -126,6 +128,57 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
.WhereNotNull();
|
||||
}
|
||||
|
||||
public async Task ClearMemoryCacheAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: This should be done with tags, however this is not implemented yet, so for now we have to naively get all content keys and clear them all.
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
// We have to get ALL document keys in order to be able to remove them from the cache,
|
||||
IEnumerable<Guid> documentKeys = await _databaseCacheRepository.GetContentKeysAsync(Constants.ObjectTypes.Document);
|
||||
|
||||
foreach (Guid documentKey in documentKeys)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We'll remove both the draft and published cache
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(documentKey, false), cancellationToken);
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(documentKey, true), cancellationToken);
|
||||
}
|
||||
|
||||
// We have to run seeding again after the cache is cleared
|
||||
await SeedAsync(cancellationToken);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
public async Task RefreshMemoryCacheAsync(Guid key)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
ContentCacheNode? draftNode = await _databaseCacheRepository.GetContentSourceAsync(key, true);
|
||||
if (draftNode is not null)
|
||||
{
|
||||
await _hybridCache.SetAsync(GetCacheKey(draftNode.Key, true), draftNode, GetEntryOptions(draftNode.Key));
|
||||
}
|
||||
|
||||
ContentCacheNode? publishedNode = await _databaseCacheRepository.GetContentSourceAsync(key, false);
|
||||
if (publishedNode is not null)
|
||||
{
|
||||
await _hybridCache.SetAsync(GetCacheKey(publishedNode.Key, false), publishedNode, GetEntryOptions(publishedNode.Key));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
public async Task RemoveFromMemoryCacheAsync(Guid key)
|
||||
{
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(key, true));
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(key, false));
|
||||
}
|
||||
|
||||
public async Task SeedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (Guid key in SeedKeys)
|
||||
@@ -155,7 +208,8 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
|
||||
return cacheNode;
|
||||
},
|
||||
GetSeedEntryOptions());
|
||||
GetSeedEntryOptions(),
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
// If the value is null, it's likely because
|
||||
if (cachedValue is null)
|
||||
@@ -167,10 +221,24 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
|
||||
private HybridCacheEntryOptions GetSeedEntryOptions() => new()
|
||||
{
|
||||
Expiration = _cacheSettings.SeedCacheDuration,
|
||||
LocalCacheExpiration = _cacheSettings.SeedCacheDuration
|
||||
Expiration = _cacheEntrySettings.SeedCacheDuration,
|
||||
LocalCacheExpiration = _cacheEntrySettings.SeedCacheDuration
|
||||
};
|
||||
|
||||
private HybridCacheEntryOptions GetEntryOptions(Guid key)
|
||||
{
|
||||
if (SeedKeys.Contains(key))
|
||||
{
|
||||
return GetSeedEntryOptions();
|
||||
}
|
||||
|
||||
return new HybridCacheEntryOptions
|
||||
{
|
||||
Expiration = _cacheEntrySettings.RemoteCacheDuration,
|
||||
LocalCacheExpiration = _cacheEntrySettings.LocalCacheDuration,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> HasContentByIdAsync(int id, bool preview = false)
|
||||
{
|
||||
Attempt<Guid> keyAttempt = _idKeyMap.GetKeyForId(id, UmbracoObjectTypes.Document);
|
||||
@@ -195,67 +263,35 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
bool isSeeded = SeedKeys.Contains(content.Key);
|
||||
|
||||
// Always set draft node
|
||||
// We have nodes seperate in the cache, cause 99% of the time, you are only using one
|
||||
// and thus we won't get too much data when retrieving from the cache.
|
||||
ContentCacheNode draftCacheNode = _cacheNodeFactory.ToContentCacheNode(content, true);
|
||||
|
||||
await _databaseCacheRepository.RefreshContentAsync(draftCacheNode, content.PublishedState);
|
||||
_scopeProvider.Context?.Enlist($"UpdateMemoryCache_Draft_{content.Key}", completed =>
|
||||
{
|
||||
if(completed is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshHybridCache(draftCacheNode, GetCacheKey(content.Key, true), isSeeded).GetAwaiter().GetResult();
|
||||
}, 1);
|
||||
|
||||
if (content.PublishedState == PublishedState.Publishing)
|
||||
if (content.PublishedState == PublishedState.Publishing || content.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
var publishedCacheNode = _cacheNodeFactory.ToContentCacheNode(content, false);
|
||||
|
||||
await _databaseCacheRepository.RefreshContentAsync(publishedCacheNode, content.PublishedState);
|
||||
_scopeProvider.Context?.Enlist($"UpdateMemoryCache_{content.Key}", completed =>
|
||||
{
|
||||
if(completed is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshHybridCache(publishedCacheNode, GetCacheKey(content.Key, false), isSeeded).GetAwaiter().GetResult();
|
||||
}, 1);
|
||||
if (content.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(publishedCacheNode.Key, false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
private async Task RefreshHybridCache(ContentCacheNode cacheNode, string cacheKey, bool isSeeded)
|
||||
{
|
||||
// If it's seeded we want it to stick around the cache for longer.
|
||||
if (isSeeded)
|
||||
{
|
||||
await _hybridCache.SetAsync(
|
||||
cacheKey,
|
||||
cacheNode,
|
||||
GetSeedEntryOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
await _hybridCache.SetAsync(cacheKey, cacheNode);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCacheKey(Guid key, bool preview) => preview ? $"{key}+draft" : $"{key}";
|
||||
|
||||
public async Task DeleteItemAsync(IContentBase content)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
await _databaseCacheRepository.DeleteContentItemAsync(content.Id);
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(content.Key, true));
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(content.Key, false));
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
@@ -263,6 +299,14 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
_databaseCacheRepository.Rebuild(contentTypeIds.ToList());
|
||||
RebuildMemoryCacheByContentTypeAsync(contentTypeIds).GetAwaiter().GetResult();
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable<int> contentTypeIds)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
IEnumerable<ContentCacheNode> contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(contentTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.DocumentType).Result), ContentCacheDataSerializerEntityType.Document);
|
||||
scope.Complete();
|
||||
|
||||
@@ -276,6 +320,5 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.HybridCache.Services;
|
||||
|
||||
public interface IDocumentCacheService
|
||||
{
|
||||
Task<IPublishedContent?> GetByKeyAsync(Guid key, bool? preview = null);
|
||||
|
||||
Task<IPublishedContent?> GetByIdAsync(int id, bool? preview = null);
|
||||
|
||||
Task SeedAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task<bool> HasContentByIdAsync(int id, bool preview = false);
|
||||
|
||||
Task RefreshContentAsync(IContent content);
|
||||
|
||||
Task DeleteItemAsync(IContentBase content);
|
||||
|
||||
void Rebuild(IReadOnlyCollection<int> contentTypeIds);
|
||||
|
||||
internal IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.HybridCache.Services;
|
||||
|
||||
public interface IMediaCacheService
|
||||
{
|
||||
Task<IPublishedContent?> GetByKeyAsync(Guid key);
|
||||
|
||||
Task<IPublishedContent?> GetByIdAsync(int id);
|
||||
|
||||
Task<bool> HasContentByIdAsync(int id);
|
||||
|
||||
Task RefreshMediaAsync(IMedia media);
|
||||
|
||||
Task DeleteItemAsync(IContentBase media);
|
||||
|
||||
Task SeedAsync(CancellationToken cancellationToken);
|
||||
|
||||
void Rebuild(IReadOnlyCollection<int> contentTypeIds);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.HybridCache.Factories;
|
||||
@@ -22,7 +23,7 @@ internal class MediaCacheService : IMediaCacheService
|
||||
private readonly ICacheNodeFactory _cacheNodeFactory;
|
||||
private readonly IEnumerable<IMediaSeedKeyProvider> _seedKeyProviders;
|
||||
private readonly IPublishedModelFactory _publishedModelFactory;
|
||||
private readonly CacheSettings _cacheSettings;
|
||||
private readonly CacheEntrySettings _cacheEntrySettings;
|
||||
|
||||
private HashSet<Guid>? _seedKeys;
|
||||
private HashSet<Guid> SeedKeys
|
||||
@@ -54,7 +55,7 @@ internal class MediaCacheService : IMediaCacheService
|
||||
ICacheNodeFactory cacheNodeFactory,
|
||||
IEnumerable<IMediaSeedKeyProvider> seedKeyProviders,
|
||||
IPublishedModelFactory publishedModelFactory,
|
||||
IOptions<CacheSettings> cacheSettings)
|
||||
IOptionsMonitor<CacheEntrySettings> cacheEntrySettings)
|
||||
{
|
||||
_databaseCacheRepository = databaseCacheRepository;
|
||||
_idKeyMap = idKeyMap;
|
||||
@@ -64,7 +65,7 @@ internal class MediaCacheService : IMediaCacheService
|
||||
_cacheNodeFactory = cacheNodeFactory;
|
||||
_seedKeyProviders = seedKeyProviders;
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
_cacheSettings = cacheSettings.Value;
|
||||
_cacheEntrySettings = cacheEntrySettings.Get(Constants.Configuration.NamedOptions.CacheEntry.Media);
|
||||
}
|
||||
|
||||
public async Task<IPublishedContent?> GetByKeyAsync(Guid key)
|
||||
@@ -83,7 +84,7 @@ internal class MediaCacheService : IMediaCacheService
|
||||
ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(idAttempt.Result);
|
||||
scope.Complete();
|
||||
return mediaCacheNode;
|
||||
});
|
||||
}, GetEntryOptions(key));
|
||||
|
||||
return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory);
|
||||
}
|
||||
@@ -95,6 +96,7 @@ internal class MediaCacheService : IMediaCacheService
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Guid key = keyAttempt.Result;
|
||||
|
||||
ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync(
|
||||
$"{keyAttempt.Result}", // Unique key to the cache entry
|
||||
@@ -104,7 +106,7 @@ internal class MediaCacheService : IMediaCacheService
|
||||
ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(id);
|
||||
scope.Complete();
|
||||
return mediaCacheNode;
|
||||
});
|
||||
}, GetEntryOptions(key));
|
||||
|
||||
return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory);
|
||||
}
|
||||
@@ -137,7 +139,6 @@ internal class MediaCacheService : IMediaCacheService
|
||||
// We have nodes seperate in the cache, cause 99% of the time, you are only using one
|
||||
// and thus we won't get too much data when retrieving from the cache.
|
||||
var cacheNode = _cacheNodeFactory.ToContentCacheNode(media);
|
||||
await _hybridCache.SetAsync(GetCacheKey(media.Key, false), cacheNode);
|
||||
await _databaseCacheRepository.RefreshMediaAsync(cacheNode);
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -146,7 +147,6 @@ internal class MediaCacheService : IMediaCacheService
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
await _databaseCacheRepository.DeleteContentItemAsync(media.Id);
|
||||
await _hybridCache.RemoveAsync(media.Key.ToString());
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
@@ -180,6 +180,65 @@ internal class MediaCacheService : IMediaCacheService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshMemoryCacheAsync(Guid key)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
ContentCacheNode? publishedNode = await _databaseCacheRepository.GetMediaSourceAsync(key);
|
||||
if (publishedNode is not null)
|
||||
{
|
||||
await _hybridCache.SetAsync(GetCacheKey(publishedNode.Key, false), publishedNode, GetEntryOptions(publishedNode.Key));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
public async Task ClearMemoryCacheAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: This should be done with tags, however this is not implemented yet, so for now we have to naively get all content keys and clear them all.
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
// We have to get ALL document keys in order to be able to remove them from the cache,
|
||||
IEnumerable<Guid> documentKeys = await _databaseCacheRepository.GetContentKeysAsync(Constants.ObjectTypes.Media);
|
||||
|
||||
foreach (Guid documentKey in documentKeys)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We'll remove both the draft and published cache
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(documentKey, false), cancellationToken);
|
||||
}
|
||||
|
||||
// We have to run seeding again after the cache is cleared
|
||||
await SeedAsync(cancellationToken);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
public async Task RemoveFromMemoryCacheAsync(Guid key)
|
||||
=> await _hybridCache.RemoveAsync(GetCacheKey(key, false));
|
||||
|
||||
public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable<int> mediaTypeIds)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
IEnumerable<ContentCacheNode> contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(mediaTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.MediaType).Result), ContentCacheDataSerializerEntityType.Media);
|
||||
|
||||
foreach (ContentCacheNode content in contentByContentTypeKey)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(content.Key, true)).GetAwaiter().GetResult();
|
||||
|
||||
if (content.IsDraft is false)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(content.Key, false)).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
public void Rebuild(IReadOnlyCollection<int> contentTypeIds)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
@@ -200,10 +259,25 @@ internal class MediaCacheService : IMediaCacheService
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
private HybridCacheEntryOptions GetEntryOptions(Guid key)
|
||||
{
|
||||
if (SeedKeys.Contains(key))
|
||||
{
|
||||
return GetSeedEntryOptions();
|
||||
}
|
||||
|
||||
return new HybridCacheEntryOptions
|
||||
{
|
||||
Expiration = _cacheEntrySettings.RemoteCacheDuration,
|
||||
LocalCacheExpiration = _cacheEntrySettings.LocalCacheDuration,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private HybridCacheEntryOptions GetSeedEntryOptions() => new()
|
||||
{
|
||||
Expiration = _cacheSettings.SeedCacheDuration,
|
||||
LocalCacheExpiration = _cacheSettings.SeedCacheDuration,
|
||||
Expiration = _cacheEntrySettings.SeedCacheDuration,
|
||||
LocalCacheExpiration = _cacheEntrySettings.SeedCacheDuration,
|
||||
};
|
||||
|
||||
private string GetCacheKey(Guid key, bool preview) => preview ? $"{key}+draft" : $"{key}";
|
||||
|
||||
Reference in New Issue
Block a user