2016-05-27 14:26:28 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.Globalization;
|
2019-02-05 12:20:13 +01:00
|
|
|
|
using System.IO;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using CSharpTest.Net.Collections;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Umbraco.Core;
|
|
|
|
|
|
using Umbraco.Core.Cache;
|
2018-04-06 13:51:54 +10:00
|
|
|
|
using Umbraco.Core.Configuration;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
using Umbraco.Core.Logging;
|
|
|
|
|
|
using Umbraco.Core.Models;
|
|
|
|
|
|
using Umbraco.Core.Models.Membership;
|
|
|
|
|
|
using Umbraco.Core.Models.PublishedContent;
|
|
|
|
|
|
using Umbraco.Core.Persistence;
|
2017-12-28 09:06:33 +01:00
|
|
|
|
using Umbraco.Core.Persistence.Dtos;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
using Umbraco.Core.Persistence.Repositories;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
using Umbraco.Core.Persistence.Repositories.Implement;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
using Umbraco.Core.Scoping;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
using Umbraco.Core.Services;
|
|
|
|
|
|
using Umbraco.Core.Services.Changes;
|
2017-12-28 09:18:09 +01:00
|
|
|
|
using Umbraco.Core.Services.Implement;
|
2019-03-05 11:15:30 +01:00
|
|
|
|
using Umbraco.Core.Strings;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
using Umbraco.Web.Cache;
|
2016-06-30 10:30:43 +02:00
|
|
|
|
using Umbraco.Web.Install;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
using Umbraco.Web.PublishedCache.NuCache.DataSource;
|
|
|
|
|
|
using Umbraco.Web.Routing;
|
2019-02-13 13:28:11 +01:00
|
|
|
|
using File = System.IO.File;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.PublishedCache.NuCache
|
|
|
|
|
|
{
|
2019-07-17 22:51:14 +10:00
|
|
|
|
|
|
|
|
|
|
internal class PublishedSnapshotService : PublishedSnapshotServiceBase
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
private readonly ServiceContext _serviceContext;
|
2017-10-17 17:43:15 +02:00
|
|
|
|
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
private readonly IScopeProvider _scopeProvider;
|
2018-04-18 19:46:47 +02:00
|
|
|
|
private readonly IDataSource _dataSource;
|
2019-08-15 19:05:43 +10:00
|
|
|
|
private readonly IProfilingLogger _logger;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private readonly IDocumentRepository _documentRepository;
|
|
|
|
|
|
private readonly IMediaRepository _mediaRepository;
|
|
|
|
|
|
private readonly IMemberRepository _memberRepository;
|
2018-04-06 13:51:54 +10:00
|
|
|
|
private readonly IGlobalSettings _globalSettings;
|
2019-01-10 12:44:57 +11:00
|
|
|
|
private readonly IEntityXmlSerializer _entitySerializer;
|
2019-07-03 18:11:00 +10:00
|
|
|
|
private readonly IPublishedModelFactory _publishedModelFactory;
|
2018-04-30 21:29:49 +02:00
|
|
|
|
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
|
2019-03-05 11:15:30 +01:00
|
|
|
|
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// volatile because we read it with no lock
|
|
|
|
|
|
private volatile bool _isReady;
|
|
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
private readonly ContentStore _contentStore;
|
|
|
|
|
|
private readonly ContentStore _mediaStore;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
private readonly SnapDictionary<int, Domain> _domainStore;
|
|
|
|
|
|
private readonly object _storesLock = new object();
|
|
|
|
|
|
|
|
|
|
|
|
private BPlusTree<int, ContentNodeKit> _localContentDb;
|
|
|
|
|
|
private BPlusTree<int, ContentNodeKit> _localMediaDb;
|
2019-07-17 22:51:14 +10:00
|
|
|
|
private bool _localDbExists;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// define constant - determines whether to use cache when previewing
|
|
|
|
|
|
// to store eg routes, property converted values, anything - caching
|
|
|
|
|
|
// means faster execution, but uses memory - not sure if we want it
|
2019-01-26 10:52:19 -05:00
|
|
|
|
// so making it configurable.
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public static readonly bool FullCacheWhenPreviewing = true;
|
|
|
|
|
|
|
|
|
|
|
|
#region Constructors
|
|
|
|
|
|
|
2016-07-01 16:27:23 +02:00
|
|
|
|
//private static int _singletonCheck;
|
|
|
|
|
|
|
2019-07-17 22:51:14 +10:00
|
|
|
|
public PublishedSnapshotService(PublishedSnapshotServiceOptions options, IMainDom mainDom, IRuntimeState runtime,
|
2018-03-30 14:00:44 +02:00
|
|
|
|
ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap,
|
2019-08-15 19:05:43 +10:00
|
|
|
|
IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IProfilingLogger logger, IScopeProvider scopeProvider,
|
2018-04-24 13:07:18 +10:00
|
|
|
|
IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository,
|
2018-04-30 21:29:49 +02:00
|
|
|
|
IDefaultCultureAccessor defaultCultureAccessor,
|
2019-04-24 14:25:41 +02:00
|
|
|
|
IDataSource dataSource, IGlobalSettings globalSettings,
|
2019-07-03 18:11:00 +10:00
|
|
|
|
IEntityXmlSerializer entitySerializer,
|
|
|
|
|
|
IPublishedModelFactory publishedModelFactory,
|
2019-03-05 11:15:30 +01:00
|
|
|
|
UrlSegmentProviderCollection urlSegmentProviders)
|
2018-04-30 21:29:49 +02:00
|
|
|
|
: base(publishedSnapshotAccessor, variationContextAccessor)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2016-07-01 16:27:23 +02:00
|
|
|
|
//if (Interlocked.Increment(ref _singletonCheck) > 1)
|
2019-01-26 10:52:19 -05:00
|
|
|
|
// throw new Exception("Singleton must be instantiated only once!");
|
2016-07-01 16:27:23 +02:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_serviceContext = serviceContext;
|
2017-10-17 17:43:15 +02:00
|
|
|
|
_publishedContentTypeFactory = publishedContentTypeFactory;
|
2018-04-18 19:46:47 +02:00
|
|
|
|
_dataSource = dataSource;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_logger = logger;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
_scopeProvider = scopeProvider;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
_documentRepository = documentRepository;
|
|
|
|
|
|
_mediaRepository = mediaRepository;
|
|
|
|
|
|
_memberRepository = memberRepository;
|
2018-04-30 21:29:49 +02:00
|
|
|
|
_defaultCultureAccessor = defaultCultureAccessor;
|
2018-04-06 13:51:54 +10:00
|
|
|
|
_globalSettings = globalSettings;
|
2019-03-05 11:15:30 +01:00
|
|
|
|
_urlSegmentProviders = urlSegmentProviders;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-01-16 11:37:20 +01:00
|
|
|
|
// we need an Xml serializer here so that the member cache can support XPath,
|
|
|
|
|
|
// for members this is done by navigating the serialized-to-xml member
|
2019-01-10 12:44:57 +11:00
|
|
|
|
_entitySerializer = entitySerializer;
|
2019-07-03 18:11:00 +10:00
|
|
|
|
_publishedModelFactory = publishedModelFactory;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// we always want to handle repository events, configured or not
|
|
|
|
|
|
// assuming no repository event will trigger before the whole db is ready
|
|
|
|
|
|
// (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
|
|
|
|
|
|
InitializeRepositoryEvents();
|
|
|
|
|
|
|
|
|
|
|
|
// however, the cache is NOT available until we are configured, because loading
|
|
|
|
|
|
// content (and content types) from database cannot be consistent (see notes in "Handle
|
|
|
|
|
|
// Notifications" region), so
|
|
|
|
|
|
// - notifications will be ignored
|
2017-10-31 12:48:24 +01:00
|
|
|
|
// - trying to obtain a published snapshot from the service will throw
|
2016-09-01 19:06:08 +02:00
|
|
|
|
if (runtime.Level != RuntimeLevel.Run)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
if (options.IgnoreLocalDb == false)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
var registered = mainDom.Register(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
2019-07-17 22:51:14 +10:00
|
|
|
|
//"install" phase of MainDom
|
|
|
|
|
|
//this is inside of a lock in MainDom so this is guaranteed to run if MainDom was acquired and guaranteed
|
|
|
|
|
|
//to not run if MainDom wasn't acquired.
|
|
|
|
|
|
//If MainDom was not acquired, then _localContentDb and _localMediaDb will remain null which means this appdomain
|
|
|
|
|
|
//will load in published content via the DB and in that case this appdomain will probably not exist long enough to
|
|
|
|
|
|
//serve more than a page of content.
|
|
|
|
|
|
|
|
|
|
|
|
var path = GetLocalFilesPath();
|
|
|
|
|
|
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
|
|
|
|
|
|
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
|
|
|
|
|
|
_localDbExists = File.Exists(localContentDbPath) && File.Exists(localMediaDbPath);
|
|
|
|
|
|
// if both local databases exist then GetTree will open them, else new databases will be created
|
|
|
|
|
|
_localContentDb = BTree.GetTree(localContentDbPath, _localDbExists);
|
|
|
|
|
|
_localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists);
|
|
|
|
|
|
},
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//"release" phase of MainDom
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
lock (_storesLock)
|
|
|
|
|
|
{
|
2019-07-17 22:51:14 +10:00
|
|
|
|
_contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_localContentDb = null;
|
2019-07-17 22:51:14 +10:00
|
|
|
|
_mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_localMediaDb = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// stores are created with a db so they can write to it, but they do not read from it,
|
|
|
|
|
|
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
|
2019-01-26 10:52:19 -05:00
|
|
|
|
// figure out whether it can read the databases or it should populate them from sql
|
2019-04-24 14:25:41 +02:00
|
|
|
|
_contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb);
|
|
|
|
|
|
_mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2019-04-24 14:25:41 +02:00
|
|
|
|
_contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger);
|
|
|
|
|
|
_mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_domainStore = new SnapDictionary<int, Domain>();
|
|
|
|
|
|
|
2019-09-10 17:10:46 +10:00
|
|
|
|
LoadCachesOnStartup();
|
2018-03-30 14:00:44 +02:00
|
|
|
|
|
2019-02-10 11:25:46 +01:00
|
|
|
|
Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default;
|
|
|
|
|
|
int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? default;
|
|
|
|
|
|
|
2018-03-30 14:00:44 +02:00
|
|
|
|
if (idkMap != null)
|
|
|
|
|
|
{
|
2019-02-10 11:25:46 +01:00
|
|
|
|
idkMap.SetMapper(UmbracoObjectTypes.Document, id => GetUid(_contentStore, id), uid => GetId(_contentStore, uid));
|
|
|
|
|
|
idkMap.SetMapper(UmbracoObjectTypes.Media, id => GetUid(_mediaStore, id), uid => GetId(_mediaStore, uid));
|
2018-03-30 14:00:44 +02:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 23:22:27 +10:00
|
|
|
|
private void LoadCachesOnStartup()
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
lock (_storesLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
// populate the stores
|
|
|
|
|
|
|
2019-08-19 22:07:22 +10:00
|
|
|
|
|
|
|
|
|
|
var okContent = false;
|
|
|
|
|
|
var okMedia = false;
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2018-04-29 20:02:38 +02:00
|
|
|
|
if (_localDbExists)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-08-19 23:22:27 +10:00
|
|
|
|
okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true));
|
2019-06-25 13:26:50 +02:00
|
|
|
|
if (!okContent)
|
|
|
|
|
|
_logger.Warn<PublishedSnapshotService>("Loading content from local db raised warnings, will reload from database.");
|
2019-08-19 23:22:27 +10:00
|
|
|
|
okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true));
|
2019-06-25 13:26:50 +02:00
|
|
|
|
if (!okMedia)
|
|
|
|
|
|
_logger.Warn<PublishedSnapshotService>("Loading media from local db raised warnings, will reload from database.");
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
2019-06-25 13:26:50 +02:00
|
|
|
|
|
|
|
|
|
|
if (!okContent)
|
2019-08-19 23:22:27 +10:00
|
|
|
|
LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true));
|
2019-06-25 13:26:50 +02:00
|
|
|
|
|
|
|
|
|
|
if (!okMedia)
|
2019-08-19 23:22:27 +10:00
|
|
|
|
LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
LockAndLoadDomains();
|
|
|
|
|
|
}
|
2018-08-17 15:41:58 +01:00
|
|
|
|
catch (Exception ex)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2018-08-17 15:41:58 +01:00
|
|
|
|
_logger.Fatal<PublishedSnapshotService>(ex, "Panic, exception while loading cache data.");
|
2019-08-19 22:07:22 +10:00
|
|
|
|
throw;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-26 10:52:19 -05:00
|
|
|
|
// finally, cache is ready!
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_isReady = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void InitializeRepositoryEvents()
|
|
|
|
|
|
{
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: The reason these events are in the repository is for legacy, the events should exist at the service
|
2018-10-03 19:03:22 +02:00
|
|
|
|
// level now since we can fire these events within the transaction... so move the events to service level
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// plug repository event handlers
|
|
|
|
|
|
// these trigger within the transaction to ensure consistency
|
|
|
|
|
|
// and are used to maintain the central, database-level XML cache
|
2017-12-12 15:04:13 +01:00
|
|
|
|
DocumentRepository.ScopeEntityRemove += OnContentRemovingEntity;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
//ContentRepository.RemovedVersion += OnContentRemovedVersion;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
DocumentRepository.ScopedEntityRefresh += OnContentRefreshedEntity;
|
|
|
|
|
|
MediaRepository.ScopeEntityRemove += OnMediaRemovingEntity;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
//MediaRepository.RemovedVersion += OnMediaRemovedVersion;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
MediaRepository.ScopedEntityRefresh += OnMediaRefreshedEntity;
|
|
|
|
|
|
MemberRepository.ScopeEntityRemove += OnMemberRemovingEntity;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
//MemberRepository.RemovedVersion += OnMemberRemovedVersion;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// plug
|
2018-10-03 19:03:22 +02:00
|
|
|
|
ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity;
|
|
|
|
|
|
MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity;
|
|
|
|
|
|
MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-07-17 17:59:46 +02:00
|
|
|
|
private void TearDownRepositoryEvents()
|
|
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
DocumentRepository.ScopeEntityRemove -= OnContentRemovingEntity;
|
2017-07-17 17:59:46 +02:00
|
|
|
|
//ContentRepository.RemovedVersion -= OnContentRemovedVersion;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
DocumentRepository.ScopedEntityRefresh -= OnContentRefreshedEntity;
|
|
|
|
|
|
MediaRepository.ScopeEntityRemove -= OnMediaRemovingEntity;
|
2017-07-17 17:59:46 +02:00
|
|
|
|
//MediaRepository.RemovedVersion -= OnMediaRemovedVersion;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
MediaRepository.ScopedEntityRefresh -= OnMediaRefreshedEntity;
|
|
|
|
|
|
MemberRepository.ScopeEntityRemove -= OnMemberRemovingEntity;
|
2017-07-17 17:59:46 +02:00
|
|
|
|
//MemberRepository.RemovedVersion -= OnMemberRemovedVersion;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity;
|
2017-07-17 17:59:46 +02:00
|
|
|
|
|
2018-10-03 19:03:22 +02:00
|
|
|
|
ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity;
|
|
|
|
|
|
MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
|
|
|
|
|
MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
2017-07-17 17:59:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
TearDownRepositoryEvents();
|
|
|
|
|
|
base.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2019-02-13 13:28:11 +01:00
|
|
|
|
#region Local files
|
|
|
|
|
|
|
|
|
|
|
|
private string GetLocalFilesPath()
|
|
|
|
|
|
{
|
2019-02-15 08:46:57 +01:00
|
|
|
|
var path = Path.Combine(_globalSettings.LocalTempPath, "NuCache");
|
2019-02-13 13:28:11 +01:00
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(path))
|
|
|
|
|
|
Directory.CreateDirectory(path);
|
|
|
|
|
|
|
|
|
|
|
|
return path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DeleteLocalFilesForContent()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isReady && _localContentDb != null)
|
|
|
|
|
|
throw new InvalidOperationException("Cannot delete local files while the cache uses them.");
|
|
|
|
|
|
|
|
|
|
|
|
var path = GetLocalFilesPath();
|
|
|
|
|
|
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
|
|
|
|
|
|
if (File.Exists(localContentDbPath))
|
|
|
|
|
|
File.Delete(localContentDbPath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DeleteLocalFilesForMedia()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isReady && _localMediaDb != null)
|
|
|
|
|
|
throw new InvalidOperationException("Cannot delete local files while the cache uses them.");
|
|
|
|
|
|
|
|
|
|
|
|
var path = GetLocalFilesPath();
|
|
|
|
|
|
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
|
|
|
|
|
|
if (File.Exists(localMediaDbPath))
|
|
|
|
|
|
File.Delete(localMediaDbPath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2016-06-30 10:30:43 +02:00
|
|
|
|
#region Environment
|
|
|
|
|
|
|
|
|
|
|
|
public override bool EnsureEnvironment(out IEnumerable<string> errors)
|
|
|
|
|
|
{
|
|
|
|
|
|
// must have app_data and be able to write files into it
|
2019-02-13 13:28:11 +01:00
|
|
|
|
var ok = FilePermissionHelper.TryCreateDirectory(GetLocalFilesPath());
|
|
|
|
|
|
errors = ok ? Enumerable.Empty<string>() : new[] { "NuCache local files." };
|
2016-06-30 10:30:43 +02:00
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#region Populate Stores
|
|
|
|
|
|
|
|
|
|
|
|
// sudden panic... but in RepeatableRead can a content that I haven't already read, be removed
|
|
|
|
|
|
// before I read it? NO! because the WHOLE content tree is read-locked using WithReadLocked.
|
|
|
|
|
|
// don't panic.
|
|
|
|
|
|
|
2019-06-25 13:26:50 +02:00
|
|
|
|
private bool LockAndLoadContent(Func<IScope, bool> action)
|
|
|
|
|
|
{
|
2019-08-19 22:07:22 +10:00
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
|
2019-06-25 13:26:50 +02:00
|
|
|
|
// first get a writer, then a scope
|
|
|
|
|
|
// if there already is a scope, the writer will attach to it
|
|
|
|
|
|
// otherwise, it will only exist here - cheap
|
|
|
|
|
|
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
|
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
|
|
|
|
|
{
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
|
|
|
|
|
var ok = action(scope);
|
|
|
|
|
|
scope.Complete();
|
|
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 23:22:27 +10:00
|
|
|
|
private bool LoadContentFromDatabaseLocked(IScope scope, bool onStartup)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// locks:
|
|
|
|
|
|
// contentStore is wlocked (1 thread)
|
|
|
|
|
|
// content (and types) are read-locked
|
|
|
|
|
|
|
|
|
|
|
|
var contentTypes = _serviceContext.ContentTypeService.GetAll()
|
2018-01-10 12:48:51 +01:00
|
|
|
|
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
|
2019-08-15 19:05:43 +10:00
|
|
|
|
|
2019-06-25 12:54:16 +02:00
|
|
|
|
_contentStore.SetAllContentTypes(contentTypes);
|
|
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
using (_logger.TraceDuration<PublishedSnapshotService>("Loading content from database"))
|
2019-08-19 22:07:22 +10:00
|
|
|
|
{
|
2019-08-15 19:05:43 +10:00
|
|
|
|
// beware! at that point the cache is inconsistent,
|
|
|
|
|
|
// assuming we are going to SetAll content items!
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
_localContentDb?.Clear();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-08-19 17:18:45 +10:00
|
|
|
|
// IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder
|
2019-08-15 19:05:43 +10:00
|
|
|
|
var kits = _dataSource.GetAllContentSources(scope);
|
2019-08-19 23:22:27 +10:00
|
|
|
|
return onStartup ? _contentStore.SetAllFastSorted(kits) : _contentStore.SetAll(kits);
|
2019-08-15 19:05:43 +10:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 23:22:27 +10:00
|
|
|
|
private bool LoadContentFromLocalDbLocked(bool onStartup)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
var contentTypes = _serviceContext.ContentTypeService.GetAll()
|
2019-08-15 19:05:43 +10:00
|
|
|
|
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
|
2019-06-25 12:54:16 +02:00
|
|
|
|
_contentStore.SetAllContentTypes(contentTypes);
|
|
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
using (_logger.TraceDuration<PublishedSnapshotService>("Loading content from local cache file"))
|
|
|
|
|
|
{
|
|
|
|
|
|
// beware! at that point the cache is inconsistent,
|
|
|
|
|
|
// assuming we are going to SetAll content items!
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
var kits = _localContentDb.Select(x => x.Value)
|
|
|
|
|
|
.OrderBy(x => x.Node.Level)
|
2019-08-19 17:18:45 +10:00
|
|
|
|
.ThenBy(x => x.Node.ParentContentId)
|
|
|
|
|
|
.ThenBy(x => x.Node.SortOrder); // IMPORTANT sort by level + parentId + sortOrder
|
2019-08-19 23:22:27 +10:00
|
|
|
|
return onStartup ? _contentStore.SetAllFastSorted(kits) : _contentStore.SetAll(kits);
|
2019-08-15 19:05:43 +10:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// keep these around - might be useful
|
|
|
|
|
|
|
|
|
|
|
|
//private void LoadContentBranch(IContent content)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// LoadContent(content);
|
|
|
|
|
|
|
|
|
|
|
|
// foreach (var child in content.Children())
|
|
|
|
|
|
// LoadContentBranch(child);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
//private void LoadContent(IContent content)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var contentService = _serviceContext.ContentService as ContentService;
|
|
|
|
|
|
// var newest = content;
|
|
|
|
|
|
// var published = newest.Published
|
|
|
|
|
|
// ? newest
|
|
|
|
|
|
// : (newest.HasPublishedVersion ? contentService.GetByVersion(newest.PublishedVersionGuid) : null);
|
|
|
|
|
|
|
|
|
|
|
|
// var contentNode = CreateContentNode(newest, published);
|
|
|
|
|
|
// _contentStore.Set(contentNode);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
2019-06-25 13:26:50 +02:00
|
|
|
|
private bool LockAndLoadMedia(Func<IScope, bool> action)
|
|
|
|
|
|
{
|
|
|
|
|
|
// see note in LockAndLoadContent
|
|
|
|
|
|
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
|
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
|
|
|
|
|
{
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
|
|
|
|
|
var ok = action(scope);
|
|
|
|
|
|
scope.Complete();
|
|
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 23:22:27 +10:00
|
|
|
|
private bool LoadMediaFromDatabaseLocked(IScope scope, bool onStartup)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// locks & notes: see content
|
|
|
|
|
|
|
|
|
|
|
|
var mediaTypes = _serviceContext.MediaTypeService.GetAll()
|
2018-01-10 12:48:51 +01:00
|
|
|
|
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
|
2019-06-25 12:54:16 +02:00
|
|
|
|
_mediaStore.SetAllContentTypes(mediaTypes);
|
|
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
using (_logger.TraceDuration<PublishedSnapshotService>("Loading media from database"))
|
|
|
|
|
|
{
|
|
|
|
|
|
// beware! at that point the cache is inconsistent,
|
|
|
|
|
|
// assuming we are going to SetAll content items!
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
_localMediaDb?.Clear();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
_logger.Debug<PublishedSnapshotService>("Loading media from database...");
|
2019-08-19 17:18:45 +10:00
|
|
|
|
// IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder
|
2019-08-15 19:05:43 +10:00
|
|
|
|
var kits = _dataSource.GetAllMediaSources(scope);
|
2019-08-19 23:22:27 +10:00
|
|
|
|
return onStartup ? _mediaStore.SetAllFastSorted(kits) : _mediaStore.SetAll(kits);
|
2019-08-15 19:05:43 +10:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 23:22:27 +10:00
|
|
|
|
private bool LoadMediaFromLocalDbLocked(bool onStartup)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
var mediaTypes = _serviceContext.MediaTypeService.GetAll()
|
2019-08-15 19:05:43 +10:00
|
|
|
|
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
|
2019-06-25 12:54:16 +02:00
|
|
|
|
_mediaStore.SetAllContentTypes(mediaTypes);
|
|
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
using (_logger.TraceDuration<PublishedSnapshotService>("Loading media from local cache file"))
|
|
|
|
|
|
{
|
|
|
|
|
|
// beware! at that point the cache is inconsistent,
|
|
|
|
|
|
// assuming we are going to SetAll content items!
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-08-15 19:05:43 +10:00
|
|
|
|
var kits = _localMediaDb.Select(x => x.Value)
|
|
|
|
|
|
.OrderBy(x => x.Node.Level)
|
2019-08-19 17:18:45 +10:00
|
|
|
|
.ThenBy(x => x.Node.ParentContentId)
|
|
|
|
|
|
.ThenBy(x => x.Node.SortOrder); // IMPORTANT sort by level + parentId + sortOrder
|
2019-08-19 23:22:27 +10:00
|
|
|
|
return onStartup ? _mediaStore.SetAllFastSorted(kits) : _mediaStore.SetAll(kits);
|
2019-08-15 19:05:43 +10:00
|
|
|
|
}
|
2019-08-19 22:07:22 +10:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// keep these around - might be useful
|
|
|
|
|
|
|
|
|
|
|
|
//private void LoadMediaBranch(IMedia media)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// LoadMedia(media);
|
|
|
|
|
|
|
|
|
|
|
|
// foreach (var child in media.Children())
|
|
|
|
|
|
// LoadMediaBranch(child);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
//private void LoadMedia(IMedia media)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var mediaType = _contentTypeCache.Get(PublishedItemType.Media, media.ContentTypeId);
|
|
|
|
|
|
|
|
|
|
|
|
// var mediaData = new ContentData
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Name = media.Name,
|
|
|
|
|
|
// Published = true,
|
|
|
|
|
|
// Version = media.Version,
|
|
|
|
|
|
// VersionDate = media.UpdateDate,
|
|
|
|
|
|
// WriterId = media.CreatorId, // what else?
|
|
|
|
|
|
// TemplateId = -1, // have none
|
|
|
|
|
|
// Properties = GetPropertyValues(media)
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
// var mediaNode = new ContentNode(media.Id, mediaType,
|
|
|
|
|
|
// media.Level, media.Path, media.SortOrder,
|
|
|
|
|
|
// media.ParentId, media.CreateDate, media.CreatorId,
|
|
|
|
|
|
// null, mediaData);
|
|
|
|
|
|
|
|
|
|
|
|
// _mediaStore.Set(mediaNode);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
//private Dictionary<string, object> GetPropertyValues(IContentBase content)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var propertyEditorResolver = PropertyEditorResolver.Current; // should inject
|
|
|
|
|
|
|
|
|
|
|
|
// return content
|
|
|
|
|
|
// .Properties
|
|
|
|
|
|
// .Select(property =>
|
|
|
|
|
|
// {
|
|
|
|
|
|
// var e = propertyEditorResolver.GetByAlias(property.PropertyType.PropertyEditorAlias);
|
|
|
|
|
|
// var v = e == null
|
|
|
|
|
|
// ? property.Value
|
|
|
|
|
|
// : e.ValueEditor.ConvertDbToString(property, property.PropertyType, _serviceContext.DataTypeService);
|
|
|
|
|
|
// return new KeyValuePair<string, object>(property.Alias, v);
|
|
|
|
|
|
// })
|
|
|
|
|
|
// .ToDictionary(x => x.Key, x => x.Value);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
//private ContentData CreateContentData(IContent content)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// return new ContentData
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Name = content.Name,
|
|
|
|
|
|
// Published = content.Published,
|
|
|
|
|
|
// Version = content.Version,
|
|
|
|
|
|
// VersionDate = content.UpdateDate,
|
|
|
|
|
|
// WriterId = content.WriterId,
|
|
|
|
|
|
// TemplateId = content.Template == null ? -1 : content.Template.Id,
|
|
|
|
|
|
// Properties = GetPropertyValues(content)
|
|
|
|
|
|
// };
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
//private ContentNode CreateContentNode(IContent newest, IContent published)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var contentType = _contentTypeCache.Get(PublishedItemType.Content, newest.ContentTypeId);
|
|
|
|
|
|
|
|
|
|
|
|
// var draftData = newest.Published
|
|
|
|
|
|
// ? null
|
|
|
|
|
|
// : CreateContentData(newest);
|
|
|
|
|
|
|
|
|
|
|
|
// var publishedData = newest.Published
|
|
|
|
|
|
// ? CreateContentData(newest)
|
|
|
|
|
|
// : (published == null ? null : CreateContentData(published));
|
|
|
|
|
|
|
|
|
|
|
|
// var contentNode = new ContentNode(newest.Id, contentType,
|
|
|
|
|
|
// newest.Level, newest.Path, newest.SortOrder,
|
|
|
|
|
|
// newest.ParentId, newest.CreateDate, newest.CreatorId,
|
|
|
|
|
|
// draftData, publishedData);
|
|
|
|
|
|
|
|
|
|
|
|
// return contentNode;
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
private void LockAndLoadDomains()
|
|
|
|
|
|
{
|
2019-02-22 15:27:22 +01:00
|
|
|
|
// see note in LockAndLoadContent
|
2019-03-14 19:48:44 +01:00
|
|
|
|
using (_domainStore.GetScopedWriteLock(_scopeProvider))
|
2019-02-22 15:27:22 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-02-22 15:27:22 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.Domains);
|
|
|
|
|
|
LoadDomainsLocked();
|
|
|
|
|
|
scope.Complete();
|
2017-07-14 12:51:57 +02:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void LoadDomainsLocked()
|
|
|
|
|
|
{
|
|
|
|
|
|
var domains = _serviceContext.DomainService.GetAll(true);
|
|
|
|
|
|
foreach (var domain in domains
|
|
|
|
|
|
.Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false)
|
2018-04-26 16:03:08 +02:00
|
|
|
|
.Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
_domainStore.Set(domain.Id, domain);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Handle Notifications
|
|
|
|
|
|
|
|
|
|
|
|
// note: if the service is not ready, ie _isReady is false, then notifications are ignored
|
|
|
|
|
|
|
2017-10-31 12:48:24 +01:00
|
|
|
|
// SetUmbracoVersionStep issues a DistributedCache.Instance.RefreshAll...() call which should cause
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// the entire content, media etc caches to reload from database -- and then the app restarts -- however,
|
|
|
|
|
|
// at the time SetUmbracoVersionStep runs, Umbraco is not fully initialized and therefore some property
|
|
|
|
|
|
// value converters, etc are not registered, and rebuilding the NuCache may not work properly.
|
|
|
|
|
|
//
|
|
|
|
|
|
// More details: ApplicationContext.IsConfigured being false, ApplicationEventHandler.ExecuteWhen... is
|
|
|
|
|
|
// called and in most cases events are skipped, so property value converters are not registered or
|
|
|
|
|
|
// removed, so PublishedPropertyType either initializes with the wrong converter, or throws because it
|
|
|
|
|
|
// detects more than one converter for a property type.
|
|
|
|
|
|
//
|
|
|
|
|
|
// It's not an issue for XmlStore - the app restart takes place *after* the install has refreshed the
|
|
|
|
|
|
// cache, and XmlStore just writes a new umbraco.config file upon RefreshAll, so that's OK.
|
|
|
|
|
|
//
|
|
|
|
|
|
// But for NuCache... we cannot rebuild the cache now. So it will NOT work and we are not fixing it,
|
|
|
|
|
|
// because now we should ALWAYS run with the database server messenger, and then the RefreshAll will
|
|
|
|
|
|
// be processed as soon as we are configured and the messenger processes instructions.
|
|
|
|
|
|
|
2019-02-22 19:13:25 +01:00
|
|
|
|
// note: notifications for content type and data type changes should be invoked with the
|
|
|
|
|
|
// pure live model factory, if any, locked and refreshed - see ContentTypeCacheRefresher and
|
|
|
|
|
|
// DataTypeCacheRefresher
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged)
|
|
|
|
|
|
{
|
2019-02-13 13:28:11 +01:00
|
|
|
|
// no cache, trash everything
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (_isReady == false)
|
|
|
|
|
|
{
|
2019-02-13 13:28:11 +01:00
|
|
|
|
DeleteLocalFilesForContent();
|
|
|
|
|
|
draftChanged = publishedChanged = true;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-14 19:48:44 +01:00
|
|
|
|
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
NotifyLocked(payloads, out bool draftChanged2, out bool publishedChanged2);
|
|
|
|
|
|
draftChanged = draftChanged2;
|
|
|
|
|
|
publishedChanged = publishedChanged2;
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (draftChanged || publishedChanged)
|
2018-04-29 20:02:38 +02:00
|
|
|
|
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void NotifyLocked(IEnumerable<ContentCacheRefresher.JsonPayload> payloads, out bool draftChanged, out bool publishedChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
publishedChanged = false;
|
|
|
|
|
|
draftChanged = false;
|
|
|
|
|
|
|
|
|
|
|
|
// locks:
|
|
|
|
|
|
// content (and content types) are read-locked while reading content
|
|
|
|
|
|
// contentStore is wlocked (so readable, only no new views)
|
|
|
|
|
|
// and it can be wlocked by 1 thread only at a time
|
|
|
|
|
|
// contentStore is write-locked during changes
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var payload in payloads)
|
|
|
|
|
|
{
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<PublishedSnapshotService>("Notified {ChangeTypes} for content {ContentId}", payload.ChangeTypes, payload.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
|
|
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
2019-08-19 23:22:27 +10:00
|
|
|
|
LoadContentFromDatabaseLocked(scope, false);
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
draftChanged = publishedChanged = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_contentStore.Clear(payload.Id))
|
|
|
|
|
|
draftChanged = publishedChanged = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasTypesNone(TreeChangeTypes.RefreshNode | TreeChangeTypes.RefreshBranch))
|
|
|
|
|
|
{
|
|
|
|
|
|
// ?!
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: should we do some RV check here? (later)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
var capture = payload;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch))
|
|
|
|
|
|
{
|
|
|
|
|
|
// ?? should we do some RV check here?
|
2019-04-22 19:49:49 +02:00
|
|
|
|
// IMPORTANT GetbranchContentSources sorts kits by level and by sort order
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var kits = _dataSource.GetBranchContentSources(scope, capture.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_contentStore.SetBranch(capture.Id, kits);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// ?? should we do some RV check here?
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var kit = _dataSource.GetContentSource(scope, capture.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (kit.IsEmpty)
|
|
|
|
|
|
{
|
|
|
|
|
|
_contentStore.Clear(capture.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_contentStore.Set(kit);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ?? cannot tell really because we're not doing RV checks
|
|
|
|
|
|
draftChanged = publishedChanged = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-03 18:11:00 +10:00
|
|
|
|
/// <inheritdoc />
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged)
|
|
|
|
|
|
{
|
2019-02-13 13:28:11 +01:00
|
|
|
|
// no cache, trash everything
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (_isReady == false)
|
|
|
|
|
|
{
|
2019-02-13 13:28:11 +01:00
|
|
|
|
DeleteLocalFilesForMedia();
|
|
|
|
|
|
anythingChanged = true;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-14 19:48:44 +01:00
|
|
|
|
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-07-17 10:48:48 +02:00
|
|
|
|
NotifyLocked(payloads, out bool anythingChanged2);
|
|
|
|
|
|
anythingChanged = anythingChanged2;
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (anythingChanged)
|
2018-04-29 20:02:38 +02:00
|
|
|
|
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void NotifyLocked(IEnumerable<MediaCacheRefresher.JsonPayload> payloads, out bool anythingChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
anythingChanged = false;
|
|
|
|
|
|
|
|
|
|
|
|
// locks:
|
|
|
|
|
|
// see notes for content cache refresher
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var payload in payloads)
|
|
|
|
|
|
{
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<PublishedSnapshotService>("Notified {ChangeTypes} for media {MediaId}", payload.ChangeTypes, payload.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
|
|
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
2019-08-19 23:22:27 +10:00
|
|
|
|
LoadMediaFromDatabaseLocked(scope, false);
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
anythingChanged = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_mediaStore.Clear(payload.Id))
|
|
|
|
|
|
anythingChanged = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasTypesNone(TreeChangeTypes.RefreshNode | TreeChangeTypes.RefreshBranch))
|
|
|
|
|
|
{
|
|
|
|
|
|
// ?!
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: should we do some RV checks here? (later)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
var capture = payload;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch))
|
|
|
|
|
|
{
|
|
|
|
|
|
// ?? should we do some RV check here?
|
2019-04-22 19:49:49 +02:00
|
|
|
|
// IMPORTANT GetbranchContentSources sorts kits by level and by sort order
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var kits = _dataSource.GetBranchMediaSources(scope, capture.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_mediaStore.SetBranch(capture.Id, kits);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// ?? should we do some RV check here?
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var kit = _dataSource.GetMediaSource(scope, capture.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (kit.IsEmpty)
|
|
|
|
|
|
{
|
|
|
|
|
|
_mediaStore.Clear(capture.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_mediaStore.Set(kit);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ?? cannot tell really because we're not doing RV checks
|
|
|
|
|
|
anythingChanged = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-03 18:11:00 +10:00
|
|
|
|
/// <inheritdoc />
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads)
|
|
|
|
|
|
{
|
|
|
|
|
|
// no cache, nothing we can do
|
|
|
|
|
|
if (_isReady == false)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var payload in payloads)
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<PublishedSnapshotService>("Notified {ChangeTypes} for {ItemType} {ItemId}", payload.ChangeTypes, payload.ItemType, payload.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 17:59:46 +02:00
|
|
|
|
Notify<IContentType>(_contentStore, payloads, RefreshContentTypesLocked);
|
|
|
|
|
|
Notify<IMediaType>(_mediaStore, payloads, RefreshMediaTypesLocked);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-07-03 18:11:00 +10:00
|
|
|
|
if (_publishedModelFactory.IsLiveFactory())
|
|
|
|
|
|
{
|
2019-07-03 18:25:19 +10:00
|
|
|
|
//In the case of Pure Live - we actually need to refresh all of the content and the media
|
|
|
|
|
|
//see https://github.com/umbraco/Umbraco-CMS/issues/5671
|
|
|
|
|
|
//The underlying issue is that in Pure Live the ILivePublishedModelFactory will re-compile all of the classes/models
|
|
|
|
|
|
//into a new DLL for the application which includes both content types and media types.
|
|
|
|
|
|
//Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated
|
|
|
|
|
|
//to use the newest version of the class.
|
2019-07-03 18:11:00 +10:00
|
|
|
|
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
2019-07-03 18:25:19 +10:00
|
|
|
|
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
2019-07-03 18:11:00 +10:00
|
|
|
|
{
|
2019-09-27 10:49:48 +02:00
|
|
|
|
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out var draftChanged, out var publishedChanged);
|
|
|
|
|
|
NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out var anythingChanged);
|
2019-07-03 18:11:00 +10:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-29 20:02:38 +02:00
|
|
|
|
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
|
2017-07-17 17:59:46 +02:00
|
|
|
|
}
|
2019-07-03 18:25:19 +10:00
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
private void Notify<T>(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action<List<int>, List<int>, List<int>, List<int>> action)
|
2019-07-03 18:25:19 +10:00
|
|
|
|
where T : IContentTypeComposition
|
2017-07-17 17:59:46 +02:00
|
|
|
|
{
|
2019-07-03 17:43:30 +10:00
|
|
|
|
if (payloads.Length == 0) return; //nothing to do
|
|
|
|
|
|
|
2018-04-24 01:31:01 +10:00
|
|
|
|
var nameOfT = typeof(T).Name;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
List<int> removedIds = null, refreshedIds = null, otherIds = null, newIds = null;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-17 17:59:46 +02:00
|
|
|
|
foreach (var payload in payloads)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (payload.ItemType != nameOfT) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove))
|
2019-07-03 17:43:30 +10:00
|
|
|
|
AddToList(ref removedIds, payload.Id);
|
2017-07-17 17:59:46 +02:00
|
|
|
|
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain))
|
2019-07-03 17:43:30 +10:00
|
|
|
|
AddToList(ref refreshedIds, payload.Id);
|
2017-07-17 17:59:46 +02:00
|
|
|
|
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther))
|
2019-07-03 17:43:30 +10:00
|
|
|
|
AddToList(ref otherIds, payload.Id);
|
2017-07-17 17:59:46 +02:00
|
|
|
|
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Create))
|
2019-07-03 17:43:30 +10:00
|
|
|
|
AddToList(ref newIds, payload.Id);
|
2017-07-17 17:59:46 +02:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) return;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-03-14 19:48:44 +01:00
|
|
|
|
using (store.GetScopedWriteLock(_scopeProvider))
|
2017-07-17 17:59:46 +02:00
|
|
|
|
{
|
|
|
|
|
|
// ReSharper disable AccessToModifiedClosure
|
|
|
|
|
|
action(removedIds, refreshedIds, otherIds, newIds);
|
|
|
|
|
|
// ReSharper restore AccessToModifiedClosure
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads)
|
|
|
|
|
|
{
|
|
|
|
|
|
// no cache, nothing we can do
|
|
|
|
|
|
if (_isReady == false)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var idsA = payloads.Select(x => x.Id).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var payload in payloads)
|
2018-08-14 22:36:47 +01:00
|
|
|
|
_logger.Debug<PublishedSnapshotService>("Notified {RemovedStatus} for data type {DataTypeId}",
|
|
|
|
|
|
payload.Removed ? "Removed" : "Refreshed",
|
|
|
|
|
|
payload.Id);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2019-03-14 19:48:44 +01:00
|
|
|
|
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
|
|
|
|
|
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
2017-07-17 10:48:48 +02:00
|
|
|
|
{
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: need to add a datatype lock
|
2018-02-09 14:34:28 +01:00
|
|
|
|
// this is triggering datatypes reload in the factory, and right after we create some
|
|
|
|
|
|
// content types by loading them ... there's a race condition here, which would require
|
|
|
|
|
|
// some locking on datatypes
|
|
|
|
|
|
_publishedContentTypeFactory.NotifyDataTypeChanges(idsA);
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
|
|
|
|
|
{
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
|
|
|
|
|
_contentStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Content, id));
|
|
|
|
|
|
scope.Complete();
|
|
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
|
|
|
|
|
{
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
|
|
|
|
|
_mediaStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Media, id));
|
|
|
|
|
|
scope.Complete();
|
2017-07-17 10:48:48 +02:00
|
|
|
|
}
|
2017-12-12 15:04:13 +01:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2018-04-29 20:02:38 +02:00
|
|
|
|
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Notify(DomainCacheRefresher.JsonPayload[] payloads)
|
|
|
|
|
|
{
|
|
|
|
|
|
// no cache, nothing we can do
|
|
|
|
|
|
if (_isReady == false)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2019-02-22 15:27:22 +01:00
|
|
|
|
// see note in LockAndLoadContent
|
2019-03-14 19:48:44 +01:00
|
|
|
|
using (_domainStore.GetScopedWriteLock(_scopeProvider))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
foreach (var payload in payloads)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (payload.ChangeType)
|
|
|
|
|
|
{
|
2016-06-08 14:19:15 +02:00
|
|
|
|
case DomainChangeTypes.RefreshAll:
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.Domains);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
LoadDomainsLocked();
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
2016-06-08 14:19:15 +02:00
|
|
|
|
case DomainChangeTypes.Remove:
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_domainStore.Clear(payload.Id);
|
|
|
|
|
|
break;
|
2016-06-08 14:19:15 +02:00
|
|
|
|
case DomainChangeTypes.Refresh:
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var domain = _serviceContext.DomainService.GetById(payload.Id);
|
|
|
|
|
|
if (domain == null) continue;
|
|
|
|
|
|
if (domain.RootContentId.HasValue == false) continue; // anomaly
|
|
|
|
|
|
if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly
|
|
|
|
|
|
var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode);
|
2018-04-26 16:03:08 +02:00
|
|
|
|
_domainStore.Set(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-07-14 12:51:57 +02:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
//Methods used to prevent allocations of lists
|
|
|
|
|
|
private void AddToList(ref List<int> list, int val) => GetOrCreateList(ref list).Add(val);
|
|
|
|
|
|
private List<int> GetOrCreateList(ref List<int> list) => list ?? (list = new List<int>());
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Content Types
|
|
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
private IReadOnlyCollection<IPublishedContentType> CreateContentTypes(PublishedItemType itemType, int[] ids)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-10-17 17:43:15 +02:00
|
|
|
|
// XxxTypeService.GetAll(empty) returns everything!
|
|
|
|
|
|
if (ids.Length == 0)
|
2019-07-03 17:43:30 +10:00
|
|
|
|
return Array.Empty<IPublishedContentType>();
|
2017-10-17 17:43:15 +02:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
IEnumerable<IContentTypeComposition> contentTypes;
|
|
|
|
|
|
switch (itemType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case PublishedItemType.Content:
|
|
|
|
|
|
contentTypes = _serviceContext.ContentTypeService.GetAll(ids);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case PublishedItemType.Media:
|
|
|
|
|
|
contentTypes = _serviceContext.MediaTypeService.GetAll(ids);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case PublishedItemType.Member:
|
|
|
|
|
|
contentTypes = _serviceContext.MemberTypeService.GetAll(ids);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(itemType));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// some may be missing - not checking here
|
|
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)).ToList();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-04-15 13:04:14 +02:00
|
|
|
|
private IPublishedContentType CreateContentType(PublishedItemType itemType, int id)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
IContentTypeComposition contentType;
|
|
|
|
|
|
switch (itemType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case PublishedItemType.Content:
|
|
|
|
|
|
contentType = _serviceContext.ContentTypeService.Get(id);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case PublishedItemType.Media:
|
|
|
|
|
|
contentType = _serviceContext.MediaTypeService.Get(id);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case PublishedItemType.Member:
|
|
|
|
|
|
contentType = _serviceContext.MemberTypeService.Get(id);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(itemType));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-10 12:48:51 +01:00
|
|
|
|
return contentType == null ? null : _publishedContentTypeFactory.CreateContentType(contentType);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
private void RefreshContentTypesLocked(List<int> removedIds, List<int> refreshedIds, List<int> otherIds, List<int> newIds)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-07-03 17:43:30 +10:00
|
|
|
|
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// locks:
|
|
|
|
|
|
// content (and content types) are read-locked while reading content
|
|
|
|
|
|
// contentStore is wlocked (so readable, only no new views)
|
|
|
|
|
|
// and it can be wlocked by 1 thread only at a time
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTypes);
|
2019-02-21 19:06:41 +01:00
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
var typesA = refreshedIds.IsCollectionEmpty()
|
|
|
|
|
|
? Array.Empty<IPublishedContentType>()
|
|
|
|
|
|
: CreateContentTypes(PublishedItemType.Content, refreshedIds.ToArray()).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
var kits = refreshedIds.IsCollectionEmpty()
|
|
|
|
|
|
? Array.Empty<ContentNodeKit>()
|
|
|
|
|
|
: _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray();
|
2019-02-21 19:06:41 +01:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_contentStore.UpdateContentTypes(removedIds, typesA, kits);
|
2019-07-03 17:43:30 +10:00
|
|
|
|
if (!otherIds.IsCollectionEmpty())
|
|
|
|
|
|
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
|
|
|
|
|
|
if (!newIds.IsCollectionEmpty())
|
|
|
|
|
|
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
private void RefreshMediaTypesLocked(List<int> removedIds, List<int> refreshedIds, List<int> otherIds, List<int> newIds)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-07-03 17:43:30 +10:00
|
|
|
|
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// locks:
|
|
|
|
|
|
// media (and content types) are read-locked while reading media
|
|
|
|
|
|
// mediaStore is wlocked (so readable, only no new views)
|
|
|
|
|
|
// and it can be wlocked by 1 thread only at a time
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTypes);
|
2019-02-21 19:06:41 +01:00
|
|
|
|
|
2019-07-03 17:43:30 +10:00
|
|
|
|
var typesA = refreshedIds == null
|
|
|
|
|
|
? Array.Empty<IPublishedContentType>()
|
|
|
|
|
|
: CreateContentTypes(PublishedItemType.Media, refreshedIds.ToArray()).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
var kits = refreshedIds == null
|
|
|
|
|
|
? Array.Empty<ContentNodeKit>()
|
|
|
|
|
|
: _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray();
|
2019-02-21 19:06:41 +01:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
_mediaStore.UpdateContentTypes(removedIds, typesA, kits);
|
2019-07-19 16:00:42 +10:00
|
|
|
|
if (!otherIds.IsCollectionEmpty())
|
|
|
|
|
|
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
|
|
|
|
|
|
if (!newIds.IsCollectionEmpty())
|
|
|
|
|
|
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2017-10-31 12:48:24 +01:00
|
|
|
|
#region Create, Get Published Snapshot
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
private long _contentGen, _mediaGen, _domainGen;
|
2019-01-17 11:01:23 +01:00
|
|
|
|
private IAppCache _elementsCache;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2018-04-27 11:38:50 +10:00
|
|
|
|
public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// no cache, no joy
|
|
|
|
|
|
if (_isReady == false)
|
2017-10-31 12:48:24 +01:00
|
|
|
|
throw new InvalidOperationException("The published snapshot service has not properly initialized.");
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
var preview = previewToken.IsNullOrWhiteSpace() == false;
|
2018-04-27 11:38:50 +10:00
|
|
|
|
return new PublishedSnapshot(this, preview);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-02 13:38:45 +02:00
|
|
|
|
// gets a new set of elements
|
|
|
|
|
|
// always creates a new set of elements,
|
|
|
|
|
|
// even though the underlying elements may not change (store snapshots)
|
2018-04-27 11:38:50 +10:00
|
|
|
|
public PublishedSnapshot.PublishedSnapshotElements GetElements(bool previewDefault)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-01-18 08:29:16 +01:00
|
|
|
|
// note: using ObjectCacheAppCache for elements and snapshot caches
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// is not recommended because it creates an inner MemoryCache which is a heavy
|
2019-01-18 08:29:16 +01:00
|
|
|
|
// thing - better use a dictionary-based cache which "just" creates a concurrent
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// dictionary
|
|
|
|
|
|
|
2019-01-18 08:29:16 +01:00
|
|
|
|
// for snapshot cache, DictionaryAppCache MAY be OK but it is not thread-safe,
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// nothing like that...
|
2019-01-18 08:29:16 +01:00
|
|
|
|
// for elements cache, DictionaryAppCache is a No-No, use something better.
|
|
|
|
|
|
// ie FastDictionaryAppCache (thread safe and all)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-07-12 14:09:31 +02:00
|
|
|
|
ContentStore.Snapshot contentSnap, mediaSnap;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
SnapDictionary<int, Domain>.Snapshot domainSnap;
|
2019-01-17 11:01:23 +01:00
|
|
|
|
IAppCache elementsCache;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
lock (_storesLock)
|
|
|
|
|
|
{
|
2017-07-18 19:24:27 +02:00
|
|
|
|
var scopeContext = _scopeProvider.Context;
|
|
|
|
|
|
|
|
|
|
|
|
if (scopeContext == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
contentSnap = _contentStore.CreateSnapshot();
|
|
|
|
|
|
mediaSnap = _mediaStore.CreateSnapshot();
|
|
|
|
|
|
domainSnap = _domainStore.CreateSnapshot();
|
2017-10-31 12:48:24 +01:00
|
|
|
|
elementsCache = _elementsCache;
|
2017-07-18 19:24:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
contentSnap = _contentStore.LiveSnapshot;
|
|
|
|
|
|
mediaSnap = _mediaStore.LiveSnapshot;
|
|
|
|
|
|
domainSnap = _domainStore.Test.LiveSnapshot;
|
2017-10-31 12:48:24 +01:00
|
|
|
|
elementsCache = _elementsCache;
|
2017-07-18 19:24:27 +02:00
|
|
|
|
|
2017-07-19 19:59:46 +02:00
|
|
|
|
// this is tricky
|
|
|
|
|
|
// we are returning elements composed from live snapshots, which we need to replace
|
|
|
|
|
|
// with actual snapshots when the context is gone - but when the action runs, there
|
|
|
|
|
|
// still is a context - so we cannot get elements - just resync = nulls the current
|
|
|
|
|
|
// elements
|
|
|
|
|
|
// just need to make sure nothing gets elements in another enlisted action... so using
|
|
|
|
|
|
// a MaxValue to make sure this one runs last, and it should be ok
|
2017-10-31 12:48:24 +01:00
|
|
|
|
scopeContext.Enlist("Umbraco.Web.PublishedCache.NuCache.PublishedSnapshotService.Resync", () => this, (completed, svc) =>
|
2017-07-18 19:24:27 +02:00
|
|
|
|
{
|
2018-04-29 20:02:38 +02:00
|
|
|
|
((PublishedSnapshot)svc.CurrentPublishedSnapshot)?.Resync();
|
2017-07-19 19:59:46 +02:00
|
|
|
|
}, int.MaxValue);
|
2017-07-18 19:24:27 +02:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// create a new snapshot cache if snapshots are different gens
|
2017-10-31 12:48:24 +01:00
|
|
|
|
if (contentSnap.Gen != _contentGen || mediaSnap.Gen != _mediaGen || domainSnap.Gen != _domainGen || _elementsCache == null)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
_contentGen = contentSnap.Gen;
|
|
|
|
|
|
_mediaGen = mediaSnap.Gen;
|
|
|
|
|
|
_domainGen = domainSnap.Gen;
|
2019-01-18 08:29:16 +01:00
|
|
|
|
elementsCache = _elementsCache = new FastDictionaryAppCache();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-18 08:29:16 +01:00
|
|
|
|
var snapshotCache = new DictionaryAppCache();
|
2017-07-18 19:24:27 +02:00
|
|
|
|
|
2017-10-17 17:43:15 +02:00
|
|
|
|
var memberTypeCache = new PublishedContentTypeCache(null, null, _serviceContext.MemberTypeService, _publishedContentTypeFactory, _logger);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2018-04-30 21:29:49 +02:00
|
|
|
|
var defaultCulture = _defaultCultureAccessor.DefaultCulture;
|
2018-04-26 16:03:08 +02:00
|
|
|
|
var domainCache = new DomainCache(domainSnap, defaultCulture);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2018-04-27 11:38:50 +10:00
|
|
|
|
return new PublishedSnapshot.PublishedSnapshotElements
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2019-06-07 11:15:58 +02:00
|
|
|
|
ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, _globalSettings, VariationContextAccessor),
|
|
|
|
|
|
MediaCache = new MediaCache(previewDefault, mediaSnap, VariationContextAccessor),
|
2019-04-24 14:25:41 +02:00
|
|
|
|
MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _entitySerializer),
|
2016-05-27 14:26:28 +02:00
|
|
|
|
DomainCache = domainCache,
|
2017-10-31 12:48:24 +01:00
|
|
|
|
SnapshotCache = snapshotCache,
|
|
|
|
|
|
ElementsCache = elementsCache
|
2016-05-27 14:26:28 +02:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Preview
|
|
|
|
|
|
|
|
|
|
|
|
public override string EnterPreview(IUser user, int contentId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return "preview"; // anything
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void RefreshPreview(string previewToken, int contentId)
|
|
|
|
|
|
{
|
|
|
|
|
|
// nothing
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void ExitPreview(string previewToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
// nothing
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Handle Repository Events For Database PreCache
|
|
|
|
|
|
|
|
|
|
|
|
// note: if the service is not ready, ie _isReady is false, then we still handle repository events,
|
2017-10-31 12:48:24 +01:00
|
|
|
|
// because we can, we do not need a working published snapshot to do it - the only reason why it could cause an
|
2016-05-27 14:26:28 +02:00
|
|
|
|
// issue is if the database table is not ready, but that should be prevented by migrations.
|
|
|
|
|
|
|
|
|
|
|
|
// we need them to be "repository" events ie to trigger from within the repository transaction,
|
|
|
|
|
|
// because they need to be consistent with the content that is being refreshed/removed - and that
|
|
|
|
|
|
// should be guaranteed by a DB transaction
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void OnContentRemovingEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
OnRemovedEntity(args.Scope.Database, args.Entity);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void OnMediaRemovingEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
OnRemovedEntity(args.Scope.Database, args.Entity);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void OnMemberRemovingEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
OnRemovedEntity(args.Scope.Database, args.Entity);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-16 14:18:37 +01:00
|
|
|
|
private void OnRemovedEntity(IUmbracoDatabase db, IContentBase item)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
db.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id", new { id = item.Id });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void OnContentRefreshedEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = args.Scope.Database;
|
2018-04-24 01:31:01 +10:00
|
|
|
|
var content = (Content)args.Entity;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-11-21 14:19:16 +01:00
|
|
|
|
// always refresh the edited data
|
2016-05-27 14:26:28 +02:00
|
|
|
|
OnRepositoryRefreshed(db, content, false);
|
|
|
|
|
|
|
2017-11-21 14:19:16 +01:00
|
|
|
|
// if unpublishing, remove published data from table
|
|
|
|
|
|
if (content.PublishedState == PublishedState.Unpublishing)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
db.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id AND published=1", new { id = content.Id });
|
|
|
|
|
|
|
2017-11-21 14:19:16 +01:00
|
|
|
|
// if publishing, refresh the published data
|
|
|
|
|
|
else if (content.PublishedState == PublishedState.Publishing)
|
|
|
|
|
|
OnRepositoryRefreshed(db, content, true);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void OnMediaRefreshedEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = args.Scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var media = args.Entity;
|
|
|
|
|
|
|
2017-11-21 14:19:16 +01:00
|
|
|
|
// refresh the edited data
|
|
|
|
|
|
OnRepositoryRefreshed(db, media, false);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void OnMemberRefreshedEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = args.Scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var member = args.Entity;
|
|
|
|
|
|
|
2017-11-21 14:19:16 +01:00
|
|
|
|
// refresh the edited data
|
2016-05-27 14:26:28 +02:00
|
|
|
|
OnRepositoryRefreshed(db, member, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-16 14:18:37 +01:00
|
|
|
|
private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// use a custom SQL to update row version on each update
|
|
|
|
|
|
//db.InsertOrUpdate(dto);
|
|
|
|
|
|
|
|
|
|
|
|
var dto = GetDto(content, published);
|
|
|
|
|
|
db.InsertOrUpdate(dto,
|
|
|
|
|
|
"SET data=@data, rv=rv+1 WHERE nodeId=@id AND published=@published",
|
|
|
|
|
|
new
|
|
|
|
|
|
{
|
|
|
|
|
|
data = dto.Data,
|
|
|
|
|
|
id = dto.NodeId,
|
|
|
|
|
|
published = dto.Published
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange<IContentType>.EventArgs args)
|
|
|
|
|
|
{
|
|
|
|
|
|
const ContentTypeChangeTypes types // only for those that have been refreshed
|
|
|
|
|
|
= ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther;
|
|
|
|
|
|
var contentTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray();
|
|
|
|
|
|
if (contentTypeIds.Any())
|
|
|
|
|
|
RebuildContentDbCache(contentTypeIds: contentTypeIds);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnMediaTypeRefreshedEntity(IMediaTypeService sender, ContentTypeChange<IMediaType>.EventArgs args)
|
|
|
|
|
|
{
|
|
|
|
|
|
const ContentTypeChangeTypes types // only for those that have been refreshed
|
|
|
|
|
|
= ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther;
|
|
|
|
|
|
var mediaTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray();
|
|
|
|
|
|
if (mediaTypeIds.Any())
|
|
|
|
|
|
RebuildMediaDbCache(contentTypeIds: mediaTypeIds);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnMemberTypeRefreshedEntity(IMemberTypeService sender, ContentTypeChange<IMemberType>.EventArgs args)
|
|
|
|
|
|
{
|
|
|
|
|
|
const ContentTypeChangeTypes types // only for those that have been refreshed
|
|
|
|
|
|
= ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther;
|
|
|
|
|
|
var memberTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray();
|
|
|
|
|
|
if (memberTypeIds.Any())
|
|
|
|
|
|
RebuildMemberDbCache(contentTypeIds: memberTypeIds);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-24 01:31:01 +10:00
|
|
|
|
private ContentNuDto GetDto(IContentBase content, bool published)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
// should inject these in ctor
|
|
|
|
|
|
// BUT for the time being we decide not to support ConvertDbToXml/String
|
|
|
|
|
|
//var propertyEditorResolver = PropertyEditorResolver.Current;
|
|
|
|
|
|
//var dataTypeService = ApplicationContext.Current.Services.DataTypeService;
|
|
|
|
|
|
|
2018-04-24 01:31:01 +10:00
|
|
|
|
var propertyData = new Dictionary<string, PropertyData[]>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
foreach (var prop in content.Properties)
|
|
|
|
|
|
{
|
2017-12-07 13:22:32 +01:00
|
|
|
|
var pdatas = new List<PropertyData>();
|
|
|
|
|
|
foreach (var pvalue in prop.Values)
|
|
|
|
|
|
{
|
2018-10-22 17:19:14 +02:00
|
|
|
|
// sanitize - properties should be ok but ... never knows
|
|
|
|
|
|
if (!prop.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2018-05-08 16:41:37 +02:00
|
|
|
|
// note: at service level, invariant is 'null', but here invariant becomes 'string.Empty'
|
2017-12-07 13:22:32 +01:00
|
|
|
|
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
|
|
|
|
|
|
if (value != null)
|
2018-05-08 16:41:37 +02:00
|
|
|
|
pdatas.Add(new PropertyData { Culture = pvalue.Culture ?? string.Empty, Segment = pvalue.Segment ?? string.Empty, Value = value });
|
2017-12-07 13:22:32 +01:00
|
|
|
|
|
|
|
|
|
|
//Core.Composing.Current.Logger.Debug<PublishedSnapshotService>($"{content.Id} {prop.Alias} [{pvalue.LanguageId},{pvalue.Segment}] {value} {(published?"pub":"edit")}");
|
|
|
|
|
|
|
|
|
|
|
|
//if (value != null)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var e = propertyEditorResolver.GetByAlias(prop.PropertyType.PropertyEditorAlias);
|
|
|
|
|
|
|
|
|
|
|
|
// // We are converting to string, even for database values which are integer or
|
|
|
|
|
|
// // DateTime, which is not optimum. Doing differently would require that we have a way to tell
|
|
|
|
|
|
// // whether the conversion to XML string changes something or not... which we don't, and we
|
|
|
|
|
|
// // don't want to implement it as PropertyValueEditor.ConvertDbToXml/String should die anyway.
|
|
|
|
|
|
|
|
|
|
|
|
// // Don't think about improving the situation here: this is a corner case and the real
|
|
|
|
|
|
// // thing to do is to get rig of PropertyValueEditor.ConvertDbToXml/String.
|
|
|
|
|
|
|
|
|
|
|
|
// // Use ConvertDbToString to keep it simple, although everywhere we use ConvertDbToXml and
|
|
|
|
|
|
// // nothing ensures that the two methods are consistent.
|
|
|
|
|
|
|
|
|
|
|
|
// if (e != null)
|
|
|
|
|
|
// value = e.ValueEditor.ConvertDbToString(prop, prop.PropertyType, dataTypeService);
|
|
|
|
|
|
//}
|
|
|
|
|
|
}
|
2018-04-24 01:31:01 +10:00
|
|
|
|
propertyData[prop.Alias] = pdatas.ToArray();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-24 01:31:01 +10:00
|
|
|
|
var cultureData = new Dictionary<string, CultureVariation>();
|
2018-04-26 16:03:08 +02:00
|
|
|
|
|
2018-10-22 17:19:14 +02:00
|
|
|
|
// sanitize - names should be ok but ... never knows
|
2019-02-05 14:06:48 +01:00
|
|
|
|
if (content.ContentType.VariesByCulture())
|
2018-10-22 17:19:14 +02:00
|
|
|
|
{
|
2018-10-25 10:32:05 +02:00
|
|
|
|
var infos = content is IContent document
|
2018-04-26 16:03:08 +02:00
|
|
|
|
? (published
|
2018-10-23 15:04:41 +02:00
|
|
|
|
? document.PublishCultureInfos
|
|
|
|
|
|
: document.CultureInfos)
|
|
|
|
|
|
: content.CultureInfos;
|
2018-04-26 16:03:08 +02:00
|
|
|
|
|
2019-02-05 14:13:03 +11:00
|
|
|
|
// ReSharper disable once UseDeconstruction
|
|
|
|
|
|
foreach (var cultureInfo in infos)
|
2018-10-22 17:19:14 +02:00
|
|
|
|
{
|
2019-02-05 14:13:03 +11:00
|
|
|
|
var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(cultureInfo.Culture);
|
2019-03-05 11:15:30 +01:00
|
|
|
|
cultureData[cultureInfo.Culture] = new CultureVariation
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = cultureInfo.Name,
|
|
|
|
|
|
UrlSegment = content.GetUrlSegment(_urlSegmentProviders, cultureInfo.Culture),
|
|
|
|
|
|
Date = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue,
|
|
|
|
|
|
IsDraft = cultureIsDraft
|
|
|
|
|
|
};
|
2018-10-22 17:19:14 +02:00
|
|
|
|
}
|
2018-04-24 01:31:01 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-25 15:55:27 +02:00
|
|
|
|
//the dictionary that will be serialized
|
|
|
|
|
|
var nestedData = new ContentNestedData
|
|
|
|
|
|
{
|
|
|
|
|
|
PropertyData = propertyData,
|
2019-03-05 11:15:30 +01:00
|
|
|
|
CultureData = cultureData,
|
|
|
|
|
|
UrlSegment = content.GetUrlSegment(_urlSegmentProviders)
|
2018-04-25 15:55:27 +02:00
|
|
|
|
};
|
2018-04-24 01:31:01 +10:00
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var dto = new ContentNuDto
|
|
|
|
|
|
{
|
|
|
|
|
|
NodeId = content.Id,
|
|
|
|
|
|
Published = published,
|
|
|
|
|
|
|
|
|
|
|
|
// note that numeric values (which are Int32) are serialized without their
|
|
|
|
|
|
// type (eg "value":1234) and JsonConvert by default deserializes them as Int64
|
|
|
|
|
|
|
2018-04-25 15:55:27 +02:00
|
|
|
|
Data = JsonConvert.SerializeObject(nestedData)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2017-12-07 13:22:32 +01:00
|
|
|
|
//Core.Composing.Current.Logger.Debug<PublishedSnapshotService>(dto.Data);
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return dto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Rebuild Database PreCache
|
|
|
|
|
|
|
2019-02-13 09:53:17 +01:00
|
|
|
|
public override void Rebuild()
|
|
|
|
|
|
{
|
2019-02-13 13:28:11 +01:00
|
|
|
|
_logger.Debug<PublishedSnapshotService>("Rebuilding...");
|
2019-02-13 09:53:17 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
|
|
|
|
|
|
{
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
|
|
|
|
|
scope.ReadLock(Constants.Locks.MemberTree);
|
|
|
|
|
|
RebuildContentDbCacheLocked(scope, 5000, null);
|
|
|
|
|
|
RebuildMediaDbCacheLocked(scope, 5000, null);
|
|
|
|
|
|
RebuildMemberDbCacheLocked(scope, 5000, null);
|
|
|
|
|
|
scope.Complete();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-27 14:26:28 +02:00
|
|
|
|
public void RebuildContentDbCache(int groupSize = 5000, IEnumerable<int> contentTypeIds = null)
|
|
|
|
|
|
{
|
2017-05-12 14:49:44 +02:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
|
|
|
|
|
RebuildContentDbCacheLocked(scope, groupSize, contentTypeIds);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assumes content tree lock
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private void RebuildContentDbCacheLocked(IScope scope, int groupSize, IEnumerable<int> contentTypeIds)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
var contentTypeIdsA = contentTypeIds?.ToArray();
|
2017-09-19 18:19:05 +02:00
|
|
|
|
var contentObjectType = Constants.ObjectTypes.Document;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// remove all - if anything fails the transaction will rollback
|
|
|
|
|
|
if (contentTypeIds == null || contentTypeIdsA.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// must support SQL-CE
|
|
|
|
|
|
db.Execute(@"DELETE FROM cmsContentNu
|
|
|
|
|
|
WHERE cmsContentNu.nodeId IN (
|
|
|
|
|
|
SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType
|
|
|
|
|
|
)",
|
|
|
|
|
|
new { objType = contentObjectType });
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// assume number of ctypes won't blow IN(...)
|
|
|
|
|
|
// must support SQL-CE
|
2018-03-22 17:17:01 +11:00
|
|
|
|
db.Execute($@"DELETE FROM cmsContentNu
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE cmsContentNu.nodeId IN (
|
|
|
|
|
|
SELECT id FROM umbracoNode
|
2018-03-22 17:17:01 +11:00
|
|
|
|
JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE umbracoNode.nodeObjectType=@objType
|
2018-03-22 17:17:01 +11:00
|
|
|
|
AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
)",
|
|
|
|
|
|
new { objType = contentObjectType, ctypes = contentTypeIdsA });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// insert back - if anything fails the transaction will rollback
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var query = scope.SqlContext.Query<IContent>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (contentTypeIds != null && contentTypeIdsA.Length > 0)
|
|
|
|
|
|
query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...)
|
|
|
|
|
|
|
|
|
|
|
|
long pageIndex = 0;
|
|
|
|
|
|
long processed = 0;
|
|
|
|
|
|
long total;
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
2019-04-23 11:11:53 +02:00
|
|
|
|
// the tree is locked, counting and comparing to total is safe
|
2018-09-18 11:53:33 +02:00
|
|
|
|
var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
|
2016-05-27 14:26:28 +02:00
|
|
|
|
var items = new List<ContentNuDto>();
|
2019-04-23 11:11:53 +02:00
|
|
|
|
var count = 0;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
foreach (var c in descendants)
|
2017-12-07 13:22:32 +01:00
|
|
|
|
{
|
|
|
|
|
|
// always the edited version
|
|
|
|
|
|
items.Add(GetDto(c, false));
|
2019-04-23 11:11:53 +02:00
|
|
|
|
|
2017-12-07 13:22:32 +01:00
|
|
|
|
// and also the published version if it makes any sense
|
|
|
|
|
|
if (c.Published)
|
|
|
|
|
|
items.Add(GetDto(c, true));
|
2019-04-23 11:11:53 +02:00
|
|
|
|
|
|
|
|
|
|
count++;
|
2017-12-07 13:22:32 +01:00
|
|
|
|
}
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2016-11-04 18:40:42 +01:00
|
|
|
|
db.BulkInsertRecords(items);
|
2019-04-23 11:11:53 +02:00
|
|
|
|
processed += count;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
} while (processed < total);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void RebuildMediaDbCache(int groupSize = 5000, IEnumerable<int> contentTypeIds = null)
|
|
|
|
|
|
{
|
2017-05-12 14:49:44 +02:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
|
|
|
|
|
RebuildMediaDbCacheLocked(scope, groupSize, contentTypeIds);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assumes media tree lock
|
2017-12-12 15:04:13 +01:00
|
|
|
|
public void RebuildMediaDbCacheLocked(IScope scope, int groupSize, IEnumerable<int> contentTypeIds)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
var contentTypeIdsA = contentTypeIds?.ToArray();
|
2017-09-19 18:19:05 +02:00
|
|
|
|
var mediaObjectType = Constants.ObjectTypes.Media;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// remove all - if anything fails the transaction will rollback
|
|
|
|
|
|
if (contentTypeIds == null || contentTypeIdsA.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// must support SQL-CE
|
|
|
|
|
|
db.Execute(@"DELETE FROM cmsContentNu
|
|
|
|
|
|
WHERE cmsContentNu.nodeId IN (
|
|
|
|
|
|
SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType
|
|
|
|
|
|
)",
|
|
|
|
|
|
new { objType = mediaObjectType });
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// assume number of ctypes won't blow IN(...)
|
|
|
|
|
|
// must support SQL-CE
|
2018-03-22 17:17:01 +11:00
|
|
|
|
db.Execute($@"DELETE FROM cmsContentNu
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE cmsContentNu.nodeId IN (
|
|
|
|
|
|
SELECT id FROM umbracoNode
|
2018-03-22 17:17:01 +11:00
|
|
|
|
JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE umbracoNode.nodeObjectType=@objType
|
2018-03-22 17:17:01 +11:00
|
|
|
|
AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
)",
|
|
|
|
|
|
new { objType = mediaObjectType, ctypes = contentTypeIdsA });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// insert back - if anything fails the transaction will rollback
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var query = scope.SqlContext.Query<IMedia>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (contentTypeIds != null && contentTypeIdsA.Length > 0)
|
|
|
|
|
|
query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...)
|
|
|
|
|
|
|
|
|
|
|
|
long pageIndex = 0;
|
|
|
|
|
|
long processed = 0;
|
|
|
|
|
|
long total;
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
2019-04-23 11:11:53 +02:00
|
|
|
|
// the tree is locked, counting and comparing to total is safe
|
2018-09-18 11:53:33 +02:00
|
|
|
|
var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
|
2019-04-23 11:11:53 +02:00
|
|
|
|
var items = descendants.Select(m => GetDto(m, false)).ToList();
|
2016-11-04 18:40:42 +01:00
|
|
|
|
db.BulkInsertRecords(items);
|
2019-04-23 11:11:53 +02:00
|
|
|
|
processed += items.Count;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
} while (processed < total);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void RebuildMemberDbCache(int groupSize = 5000, IEnumerable<int> contentTypeIds = null)
|
|
|
|
|
|
{
|
2017-05-12 14:49:44 +02:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MemberTree);
|
|
|
|
|
|
RebuildMemberDbCacheLocked(scope, groupSize, contentTypeIds);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assumes member tree lock
|
2017-12-12 15:04:13 +01:00
|
|
|
|
public void RebuildMemberDbCacheLocked(IScope scope, int groupSize, IEnumerable<int> contentTypeIds)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
|
|
|
|
|
var contentTypeIdsA = contentTypeIds?.ToArray();
|
2017-09-19 18:19:05 +02:00
|
|
|
|
var memberObjectType = Constants.ObjectTypes.Member;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
// remove all - if anything fails the transaction will rollback
|
|
|
|
|
|
if (contentTypeIds == null || contentTypeIdsA.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// must support SQL-CE
|
|
|
|
|
|
db.Execute(@"DELETE FROM cmsContentNu
|
|
|
|
|
|
WHERE cmsContentNu.nodeId IN (
|
|
|
|
|
|
SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType
|
|
|
|
|
|
)",
|
|
|
|
|
|
new { objType = memberObjectType });
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// assume number of ctypes won't blow IN(...)
|
|
|
|
|
|
// must support SQL-CE
|
2018-03-22 17:17:01 +11:00
|
|
|
|
db.Execute($@"DELETE FROM cmsContentNu
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE cmsContentNu.nodeId IN (
|
|
|
|
|
|
SELECT id FROM umbracoNode
|
2018-03-22 17:17:01 +11:00
|
|
|
|
JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE umbracoNode.nodeObjectType=@objType
|
2018-03-22 17:17:01 +11:00
|
|
|
|
AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
)",
|
|
|
|
|
|
new { objType = memberObjectType, ctypes = contentTypeIdsA });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// insert back - if anything fails the transaction will rollback
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var query = scope.SqlContext.Query<IMember>();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
if (contentTypeIds != null && contentTypeIdsA.Length > 0)
|
|
|
|
|
|
query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...)
|
|
|
|
|
|
|
|
|
|
|
|
long pageIndex = 0;
|
|
|
|
|
|
long processed = 0;
|
|
|
|
|
|
long total;
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
2018-09-18 11:53:33 +02:00
|
|
|
|
var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
|
2017-12-07 13:22:32 +01:00
|
|
|
|
var items = descendants.Select(m => GetDto(m, false)).ToArray();
|
2016-11-04 18:40:42 +01:00
|
|
|
|
db.BulkInsertRecords(items);
|
2016-05-27 14:26:28 +02:00
|
|
|
|
processed += items.Length;
|
|
|
|
|
|
} while (processed < total);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool VerifyContentDbCache()
|
|
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.ContentTree);
|
|
|
|
|
|
var ok = VerifyContentDbCacheLocked(scope);
|
|
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assumes content tree lock
|
2017-12-12 15:04:13 +01:00
|
|
|
|
private bool VerifyContentDbCacheLocked(IScope scope)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-07 13:22:32 +01:00
|
|
|
|
// every document should have a corresponding row for edited properties
|
|
|
|
|
|
// and if published, may have a corresponding row for published properties
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-09-19 18:19:05 +02:00
|
|
|
|
var contentObjectType = Constants.ObjectTypes.Document;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2018-03-22 17:17:01 +11:00
|
|
|
|
var count = db.ExecuteScalar<int>($@"SELECT COUNT(*)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
FROM umbracoNode
|
2018-03-22 17:17:01 +11:00
|
|
|
|
JOIN {Constants.DatabaseSchema.Tables.Document} ON umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId
|
2017-12-07 13:22:32 +01:00
|
|
|
|
LEFT JOIN cmsContentNu nuEdited ON (umbracoNode.id=nuEdited.nodeId AND nuEdited.published=0)
|
|
|
|
|
|
LEFT JOIN cmsContentNu nuPublished ON (umbracoNode.id=nuPublished.nodeId AND nuPublished.published=1)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE umbracoNode.nodeObjectType=@objType
|
2018-03-22 17:17:01 +11:00
|
|
|
|
AND nuEdited.nodeId IS NULL OR ({Constants.DatabaseSchema.Tables.Document}.published=1 AND nuPublished.nodeId IS NULL);"
|
2016-05-27 14:26:28 +02:00
|
|
|
|
, new { objType = contentObjectType });
|
|
|
|
|
|
|
|
|
|
|
|
return count == 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool VerifyMediaDbCache()
|
|
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MediaTree);
|
|
|
|
|
|
var ok = VerifyMediaDbCacheLocked(scope);
|
|
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assumes media tree lock
|
2017-12-12 15:04:13 +01:00
|
|
|
|
public bool VerifyMediaDbCacheLocked(IScope scope)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-07 13:22:32 +01:00
|
|
|
|
// every media item should have a corresponding row for edited properties
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-09-19 18:19:05 +02:00
|
|
|
|
var mediaObjectType = Constants.ObjectTypes.Media;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
var count = db.ExecuteScalar<int>(@"SELECT COUNT(*)
|
|
|
|
|
|
FROM umbracoNode
|
2017-12-07 13:22:32 +01:00
|
|
|
|
LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE umbracoNode.nodeObjectType=@objType
|
|
|
|
|
|
AND cmsContentNu.nodeId IS NULL
|
|
|
|
|
|
", new { objType = mediaObjectType });
|
|
|
|
|
|
|
|
|
|
|
|
return count == 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool VerifyMemberDbCache()
|
|
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using (var scope = _scopeProvider.CreateScope())
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-12 15:04:13 +01:00
|
|
|
|
scope.ReadLock(Constants.Locks.MemberTree);
|
|
|
|
|
|
var ok = VerifyMemberDbCacheLocked(scope);
|
|
|
|
|
|
scope.Complete();
|
2016-05-27 14:26:28 +02:00
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assumes member tree lock
|
2017-12-12 15:04:13 +01:00
|
|
|
|
public bool VerifyMemberDbCacheLocked(IScope scope)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
{
|
2017-12-07 13:22:32 +01:00
|
|
|
|
// every member item should have a corresponding row for edited properties
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
2017-09-19 18:19:05 +02:00
|
|
|
|
var memberObjectType = Constants.ObjectTypes.Member;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
var db = scope.Database;
|
2016-05-27 14:26:28 +02:00
|
|
|
|
|
|
|
|
|
|
var count = db.ExecuteScalar<int>(@"SELECT COUNT(*)
|
|
|
|
|
|
FROM umbracoNode
|
2017-12-07 13:22:32 +01:00
|
|
|
|
LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0)
|
2016-05-27 14:26:28 +02:00
|
|
|
|
WHERE umbracoNode.nodeObjectType=@objType
|
|
|
|
|
|
AND cmsContentNu.nodeId IS NULL
|
|
|
|
|
|
", new { objType = memberObjectType });
|
|
|
|
|
|
|
|
|
|
|
|
return count == 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Instrument
|
|
|
|
|
|
|
|
|
|
|
|
public string GetStatus()
|
|
|
|
|
|
{
|
|
|
|
|
|
var dbCacheIsOk = VerifyContentDbCache()
|
|
|
|
|
|
&& VerifyMediaDbCache()
|
|
|
|
|
|
&& VerifyMemberDbCache();
|
|
|
|
|
|
|
|
|
|
|
|
var cg = _contentStore.GenCount;
|
|
|
|
|
|
var mg = _mediaStore.GenCount;
|
|
|
|
|
|
var cs = _contentStore.SnapCount;
|
|
|
|
|
|
var ms = _mediaStore.SnapCount;
|
|
|
|
|
|
var ce = _contentStore.Count;
|
|
|
|
|
|
var me = _mediaStore.Count;
|
|
|
|
|
|
|
2019-04-03 11:57:18 +02:00
|
|
|
|
return
|
2016-05-27 14:26:28 +02:00
|
|
|
|
" Database cache is " + (dbCacheIsOk ? "ok" : "NOT ok (rebuild?)") + "." +
|
2019-04-03 11:57:18 +02:00
|
|
|
|
" ContentStore contains " + ce + " item" + (ce > 1 ? "s" : "") +
|
|
|
|
|
|
" and has " + cg + " generation" + (cg > 1 ? "s" : "") +
|
|
|
|
|
|
" and " + cs + " snapshot" + (cs > 1 ? "s" : "") + "." +
|
|
|
|
|
|
" MediaStore contains " + me + " item" + (ce > 1 ? "s" : "") +
|
|
|
|
|
|
" and has " + mg + " generation" + (mg > 1 ? "s" : "") +
|
|
|
|
|
|
" and " + ms + " snapshot" + (ms > 1 ? "s" : "") + ".";
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Collect()
|
|
|
|
|
|
{
|
|
|
|
|
|
var contentCollect = _contentStore.CollectAsync();
|
|
|
|
|
|
var mediaCollect = _mediaStore.CollectAsync();
|
|
|
|
|
|
System.Threading.Tasks.Task.WaitAll(contentCollect, mediaCollect);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2019-09-25 13:51:19 +02:00
|
|
|
|
|
|
|
|
|
|
#region Internals/Testing
|
|
|
|
|
|
|
|
|
|
|
|
internal ContentStore GetContentStore() => _contentStore;
|
|
|
|
|
|
internal ContentStore GetMediaStore() => _mediaStore;
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2016-05-27 14:26:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|