diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoDirectoryFactory.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoIndexStorage.cs similarity index 80% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoDirectoryFactory.cs rename to backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoIndexStorage.cs index 1fd9e5806..b2c26bd15 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoDirectoryFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoIndexStorage.cs @@ -15,16 +15,16 @@ using LuceneDirectory = Lucene.Net.Store.Directory; namespace Squidex.Domain.Apps.Entities.MongoDb.FullText { - public sealed class MongoDirectoryFactory : IDirectoryFactory + public sealed class MongoIndexStorage : IIndexStorage { private readonly IGridFSBucket bucket; - public MongoDirectoryFactory(IGridFSBucket bucket) + public MongoIndexStorage(IGridFSBucket bucket) { this.bucket = bucket; } - public Task CreateAsync(Guid schemaId) + public Task CreateDirectoryAsync(Guid schemaId) { var folderName = schemaId.ToString(); @@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText return Task.FromResult(directory); } - public Task WriteAsync(IndexWriter writer, SnapshotDeletionPolicy snapshotter) + public Task WriteAsync(LuceneDirectory directory, SnapshotDeletionPolicy snapshotter) { return Task.CompletedTask; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/AssetStoreDirectoryFactory.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/AssetIndexStorage.cs similarity index 89% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/Text/AssetStoreDirectoryFactory.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/Text/AssetIndexStorage.cs index f0af46aa5..718fe8aa4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/AssetStoreDirectoryFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/AssetIndexStorage.cs @@ -17,20 +17,20 @@ using LuceneDirectory = Lucene.Net.Store.Directory; namespace Squidex.Domain.Apps.Entities.Contents.Text { - public sealed class AssetStoreDirectoryFactory : IDirectoryFactory + public sealed class AssetIndexStorage : IIndexStorage { private const string ArchiveFile = "Archive.zip"; private const string LockFile = "write.lock"; private readonly IAssetStore assetStore; - public AssetStoreDirectoryFactory(IAssetStore assetStore) + public AssetIndexStorage(IAssetStore assetStore) { Guard.NotNull(assetStore); this.assetStore = assetStore; } - public async Task CreateAsync(Guid schemaId) + public async Task CreateDirectoryAsync(Guid schemaId) { var directoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "LocalIndices", schemaId.ToString())); @@ -64,12 +64,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text return directory; } - public async Task WriteAsync(IndexWriter writer, SnapshotDeletionPolicy snapshotter) + public async Task WriteAsync(LuceneDirectory directory, SnapshotDeletionPolicy snapshotter) { - Guard.NotNull(writer); - Guard.NotNull(writer); + Guard.NotNull(directory); + Guard.NotNull(snapshotter); - var directoryInfo = ((FSDirectory)writer.Directory).Directory; + var directoryInfo = ((FSDirectory)directory).Directory; var commit = snapshotter.Snapshot(); try diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/FSDirectoryFactory.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/FileIndexStorage.cs similarity index 79% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/Text/FSDirectoryFactory.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/Text/FileIndexStorage.cs index 7b3706f24..e08a7bbb0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/FSDirectoryFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/FileIndexStorage.cs @@ -14,9 +14,9 @@ using LuceneDirectory = Lucene.Net.Store.Directory; namespace Squidex.Domain.Apps.Entities.Contents.Text { - public sealed class FSDirectoryFactory : IDirectoryFactory + public sealed class FileIndexStorage : IIndexStorage { - public Task CreateAsync(Guid schemaId) + public Task CreateDirectoryAsync(Guid schemaId) { var folderName = $"Indexes/{schemaId}"; var folderPath = Path.Combine(Path.GetTempPath(), folderName); @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text return Task.FromResult(FSDirectory.Open(folderPath)); } - public Task WriteAsync(IndexWriter writer, SnapshotDeletionPolicy snapshotter) + public Task WriteAsync(LuceneDirectory directory, SnapshotDeletionPolicy snapshotter) { return Task.CompletedTask; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IIndex.cs new file mode 100644 index 000000000..08435f933 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IIndex.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Lucene.Net.Analysis; +using Lucene.Net.Index; +using Lucene.Net.Search; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public interface IIndex + { + Analyzer? Analyzer { get; } + + IndexReader? Reader { get; } + + IndexSearcher? Searcher { get; } + + IndexWriter Writer { get; } + + void EnsureReader(); + + void MarkStale(); + } +} \ No newline at end of file diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IDirectoryFactory.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IIndexStorage.cs similarity index 75% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IDirectoryFactory.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IIndexStorage.cs index 7e49ba737..56d7432a1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IDirectoryFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IIndexStorage.cs @@ -12,10 +12,10 @@ using Lucene.Net.Store; namespace Squidex.Domain.Apps.Entities.Contents.Text { - public interface IDirectoryFactory + public interface IIndexStorage { - Task CreateAsync(Guid schemaId); + Task CreateDirectoryAsync(Guid schemaId); - Task WriteAsync(IndexWriter writer, SnapshotDeletionPolicy snapshotter); + Task WriteAsync(Directory directory, SnapshotDeletionPolicy snapshotter); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexHolder.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexHolder.cs deleted file mode 100644 index 3437502e4..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexHolder.cs +++ /dev/null @@ -1,162 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Lucene.Net.Analysis; -using Lucene.Net.Index; -using Lucene.Net.Search; -using Lucene.Net.Store; -using Lucene.Net.Util; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Contents.Text -{ - public sealed class IndexHolder : DisposableObjectBase - { - private const LuceneVersion Version = LuceneVersion.LUCENE_48; - private static readonly MergeScheduler MergeScheduler = new ConcurrentMergeScheduler(); - private static readonly Analyzer SharedAnalyzer = new MultiLanguageAnalyzer(Version); - private readonly SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); - private readonly Directory directory; - private readonly IDirectoryFactory directoryFactory; - private IndexWriter indexWriter; - private IndexSearcher? indexSearcher; - private DirectoryReader? indexReader; - - public Analyzer Analyzer - { - get - { - ThrowIfDisposed(); - - return SharedAnalyzer; - } - } - - public SnapshotDeletionPolicy Snapshotter - { - get - { - ThrowIfDisposed(); - - return snapshotter; - } - } - - public IndexWriter Writer - { - get - { - ThrowIfDisposed(); - - return indexWriter; - } - } - - public IndexReader? Reader - { - get - { - ThrowIfDisposed(); - - return indexReader; - } - } - - public IndexSearcher? Searcher - { - get - { - ThrowIfDisposed(); - - return indexSearcher; - } - } - - public IndexHolder(Directory directory, IDirectoryFactory directoryFactory) - { - this.directory = directory; - this.directoryFactory = directoryFactory; - } - - public void Open() - { - RecreateIndexWriter(); - - if (indexWriter.NumDocs > 0) - { - EnsureReader(); - } - } - - protected override void DisposeObject(bool disposing) - { - if (disposing) - { - indexWriter?.Dispose(); - } - } - - private void RecreateIndexWriter() - { - var config = new IndexWriterConfig(Version, Analyzer) - { - IndexDeletionPolicy = snapshotter, - MergePolicy = new TieredMergePolicy(), - MergeScheduler = MergeScheduler - }; - - indexWriter = new IndexWriter(directory, config); - - MarkStale(); - } - - public void EnsureReader() - { - ThrowIfDisposed(); - - if (indexReader == null) - { - indexReader = indexWriter.GetReader(true); - indexSearcher = new IndexSearcher(indexReader); - } - } - - public void MarkStale() - { - ThrowIfDisposed(); - - if (indexReader != null) - { - indexReader.Dispose(); - indexReader = null; - indexSearcher = null; - } - } - - public async Task CommitAsync() - { - ThrowIfDisposed(); - - try - { - MarkStale(); - - indexWriter.Commit(); - - await directoryFactory.WriteAsync(indexWriter, snapshotter); - } - catch (OutOfMemoryException) - { - RecreateIndexWriter(); - - throw; - } - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexHolderFactory.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexHolderFactory.cs deleted file mode 100644 index ba66f1457..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexHolderFactory.cs +++ /dev/null @@ -1,110 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Log; - -namespace Squidex.Domain.Apps.Entities.Contents.Text -{ - public sealed class IndexHolderFactory : DisposableObjectBase - { - private readonly Dictionary indices = new Dictionary(); - private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); - private readonly IDirectoryFactory directoryFactory; - private readonly ISemanticLog log; - - public IndexHolderFactory(IDirectoryFactory directoryFactory, ISemanticLog log) - { - Guard.NotNull(directoryFactory); - Guard.NotNull(log); - - this.directoryFactory = directoryFactory; - - this.log = log; - } - - protected override void DisposeObject(bool disposing) - { - if (disposing) - { - try - { - lockObject.Wait(); - - if (indices.Count > 0) - { - log.LogWarning(w => w - .WriteProperty("message", "Unreleased indices found.") - .WriteProperty("count", indices.Count)); - - foreach (var index in indices) - { - index.Value.Dispose(); - } - - indices.Clear(); - } - } - finally - { - lockObject.Release(); - } - } - } - - public async Task AcquireAsync(Guid schemaId) - { - IndexHolder? index; - - try - { - await lockObject.WaitAsync(); - - if (indices.TryGetValue(schemaId, out index)) - { - log.LogWarning(w => w - .WriteProperty("message", "Unreleased index found.") - .WriteProperty("schemaId", schemaId.ToString())); - - index.Dispose(); - } - - var directory = await directoryFactory.CreateAsync(schemaId); - - index = new IndexHolder(directory, directoryFactory); - - indices[schemaId] = index; - } - finally - { - lockObject.Release(); - } - - index.Open(); - - return index; - } - - public void Release(Guid id) - { - try - { - lockObject.Wait(); - - indices.Remove(id); - } - finally - { - lockObject.Release(); - } - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexManager.cs new file mode 100644 index 000000000..bea681988 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexManager.cs @@ -0,0 +1,148 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Log; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public sealed partial class IndexManager : DisposableObjectBase + { + private readonly Dictionary indices = new Dictionary(); + private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); + private readonly IIndexStorage directoryFactory; + private readonly ISemanticLog log; + + public IndexManager(IIndexStorage directoryFactory, ISemanticLog log) + { + Guard.NotNull(directoryFactory); + Guard.NotNull(log); + + this.directoryFactory = directoryFactory; + + this.log = log; + } + + protected override void DisposeObject(bool disposing) + { + if (disposing) + { + ReleaseAllAsync().Wait(); + } + } + + public async Task AcquireAsync(Guid schemaId) + { + IndexHolder? indexHolder; + + try + { + await lockObject.WaitAsync(); + + if (indices.TryGetValue(schemaId, out indexHolder)) + { + log.LogWarning(w => w + .WriteProperty("message", "Unreleased index found.") + .WriteProperty("schemaId", schemaId.ToString())); + + await CommitInternalAsync(indexHolder, true); + } + + indexHolder = new IndexHolder(schemaId); + indices[schemaId] = indexHolder; + } + finally + { + lockObject.Release(); + } + + var directory = await directoryFactory.CreateDirectoryAsync(schemaId); + + indexHolder.Open(directory); + + return indexHolder; + } + + public async Task ReleaseAsync(IIndex index) + { + Guard.NotNull(index); + + var indexHolder = (IndexHolder)index; + + try + { + lockObject.Wait(); + + indexHolder.Release(); + indices.Remove(indexHolder.Id); + } + finally + { + lockObject.Release(); + } + + await CommitInternalAsync(indexHolder, true); + } + + public Task CommitAsync(IIndex index) + { + Guard.NotNull(index); + + return CommitInternalAsync(index, false); + } + + private async Task CommitInternalAsync(IIndex index, bool dispose) + { + if (index is IndexHolder holder) + { + if (dispose) + { + holder.Dispose(); + } + else + { + holder.Commit(); + } + + await directoryFactory.WriteAsync(holder.GetUnsafeWriter().Directory, holder.Snapshotter); + } + } + + private async Task ReleaseAllAsync() + { + var current = indices.Values.ToList(); + + try + { + lockObject.Wait(); + + indices.Clear(); + } + finally + { + lockObject.Release(); + } + + if (current.Count > 0) + { + log.LogWarning(w => w + .WriteProperty("message", "Unreleased indices found.") + .WriteProperty("count", indices.Count)); + + foreach (var index in current) + { + await CommitInternalAsync(index, true); + } + } + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexManager_Impl.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexManager_Impl.cs new file mode 100644 index 000000000..c74375f34 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexManager_Impl.cs @@ -0,0 +1,176 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Lucene.Net.Analysis; +using Lucene.Net.Index; +using Lucene.Net.Search; +using Lucene.Net.Store; +using Lucene.Net.Util; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public sealed partial class IndexManager + { + private sealed class IndexHolder : IDisposable, IIndex + { + private const LuceneVersion Version = LuceneVersion.LUCENE_48; + private static readonly MergeScheduler MergeScheduler = new ConcurrentMergeScheduler(); + private static readonly Analyzer SharedAnalyzer = new MultiLanguageAnalyzer(Version); + private readonly SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); + private Directory directory; + private IndexWriter indexWriter; + private IndexSearcher? indexSearcher; + private DirectoryReader? indexReader; + private bool isReleased; + + public Analyzer Analyzer + { + get { return SharedAnalyzer; } + } + + public SnapshotDeletionPolicy Snapshotter + { + get { return snapshotter; } + } + + public IndexWriter Writer + { + get + { + ThrowIfReleased(); + + return indexWriter; + } + } + + public IndexReader? Reader + { + get + { + ThrowIfReleased(); + + return indexReader; + } + } + + public IndexSearcher? Searcher + { + get + { + ThrowIfReleased(); + + return indexSearcher; + } + } + + public Guid Id { get; } + + public IndexHolder(Guid id) + { + Id = id; + } + + public void Dispose() + { + indexReader?.Dispose(); + indexWriter?.Dispose(); + } + + public void Open(Directory directory) + { + Guard.NotNull(directory); + + this.directory = directory; + + RecreateIndexWriter(); + + if (indexWriter.NumDocs > 0) + { + EnsureReader(); + } + } + + private void RecreateIndexWriter() + { + var config = new IndexWriterConfig(Version, Analyzer) + { + IndexDeletionPolicy = snapshotter, + MergePolicy = new TieredMergePolicy(), + MergeScheduler = MergeScheduler + }; + + indexWriter = new IndexWriter(directory, config); + + MarkStale(); + } + + public void EnsureReader() + { + ThrowIfReleased(); + + if (indexReader == null) + { + indexReader = indexWriter.GetReader(true); + indexSearcher = new IndexSearcher(indexReader); + } + } + + public void MarkStale() + { + ThrowIfReleased(); + + MarkStaleInternal(); + } + + private void MarkStaleInternal() + { + if (indexReader != null) + { + indexReader.Dispose(); + indexReader = null; + indexSearcher = null; + } + } + + internal void Commit() + { + try + { + MarkStaleInternal(); + + indexWriter.Commit(); + } + catch (OutOfMemoryException) + { + RecreateIndexWriter(); + + throw; + } + } + + internal void ThrowIfReleased() + { + if (isReleased) + { + throw new InvalidOperationException("Index is already released."); + } + } + + internal void Release() + { + isReleased = true; + } + + internal IndexWriter GetUnsafeWriter() + { + return indexWriter; + } + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexState.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexState.cs index a2416ce12..e5004c4d2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexState.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexState.cs @@ -20,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private const int NotFound = -1; private const string MetaFor = "_fd"; private readonly Dictionary<(Guid, Scope), BytesRef> lastChanges = new Dictionary<(Guid, Scope), BytesRef>(); - private readonly IndexHolder index; + private readonly IIndex index; private IndexReader? lastReader; private BinaryDocValues binaryValues; - public IndexState(IndexHolder index) + public IndexState(IIndex index) { this.index = index; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs index 71d28db6a..cd26c21b0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexContent.cs @@ -16,11 +16,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { private const string MetaId = "_id"; private const string MetaKey = "_key"; - private readonly IndexHolder index; + private readonly IIndex index; private readonly IndexState indexState; private readonly Guid id; - public TextIndexContent(IndexHolder index, IndexState indexState, Guid id) + public TextIndexContent(IIndex index, IndexState indexState, Guid id) { this.index = index; this.indexState = indexState; @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text index.Writer.DeleteDocuments(new Term(MetaId, id.ToString())); } - public static bool TryGetId(int docId, Scope scope, IndexHolder index, IndexState indexState, out Guid result) + public static bool TryGetId(int docId, Scope scope, IIndex index, IndexState indexState, out Guid result) { result = Guid.Empty; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs index ff1c22510..a74fa1e60 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs @@ -26,32 +26,32 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private const int MaxUpdates = 400; private static readonly TimeSpan CommitDelay = TimeSpan.FromSeconds(10); private static readonly string[] Invariant = { InvariantPartitioning.Key }; - private readonly IndexHolderFactory indexHolderFactory; + private readonly IndexManager indexManager; private IDisposable? timer; - private IndexHolder index; + private IIndex index; private IndexState indexState; private QueryParser? queryParser; private HashSet? currentLanguages; private int updates; - public TextIndexerGrain(IndexHolderFactory indexHolderFactory) + public TextIndexerGrain(IndexManager indexManager) { - Guard.NotNull(indexHolderFactory); + Guard.NotNull(indexManager); - this.indexHolderFactory = indexHolderFactory; + this.indexManager = indexManager; } - public override Task OnDeactivateAsync() + public override async Task OnDeactivateAsync() { - index?.Dispose(); - indexHolderFactory.Release(Key); - - return Task.CompletedTask; + if (index != null) + { + await indexManager.ReleaseAsync(index); + } } protected override async Task OnActivateAsync(Guid key) { - index = await indexHolderFactory.AcquireAsync(key); + index = await indexManager.AcquireAsync(key); indexState = new IndexState(index); } @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text content.Index(update.Text, update.OnlyDraft); - return TryFlushAsync(); + return TryCommitAsync(); } public Task CopyAsync(Guid id, bool fromDraft) @@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text content.Copy(fromDraft); - return TryFlushAsync(); + return TryCommitAsync(); } public Task DeleteAsync(Guid id) @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text content.Delete(); - return TryFlushAsync(); + return TryCommitAsync(); } public Task> SearchAsync(string queryText, SearchContext context) @@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text } } - private async Task TryFlushAsync() + private async Task TryCommitAsync() { timer?.Dispose(); @@ -147,7 +147,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text if (updates >= MaxUpdates) { - await FlushAsync(); + await CommitAsync(); return true; } @@ -157,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text try { - timer = RegisterTimer(_ => FlushAsync(), null, CommitDelay, CommitDelay); + timer = RegisterTimer(_ => CommitAsync(), null, CommitDelay, CommitDelay); } catch (InvalidOperationException) { @@ -168,11 +168,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text return false; } - public async Task FlushAsync() + public async Task CommitAsync() { if (updates > 0) { - await index.CommitAsync(); + await indexManager.CommitAsync(index); updates = 0; } diff --git a/backend/src/Squidex/Config/Domain/ContentsServices.cs b/backend/src/Squidex/Config/Domain/ContentsServices.cs index a0f3beb64..ac994f199 100644 --- a/backend/src/Squidex/Config/Domain/ContentsServices.cs +++ b/backend/src/Squidex/Config/Domain/ContentsServices.cs @@ -51,7 +51,7 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As().As(); - services.AddSingletonAs() + services.AddSingletonAs() .AsSelf(); services.AddSingletonAs>() diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 5bb0f26ef..45ace12e9 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -133,8 +133,8 @@ namespace Squidex.Config.Domain BucketName = "fullText" }); - return new MongoDirectoryFactory(mongoBucket); - }).As(); + return new MongoIndexStorage(mongoBucket); + }).As(); } }); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerBenchmark.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerBenchmark.cs index 77c450fd4..5dc9f60d0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerBenchmark.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerBenchmark.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text public TextIndexerBenchmark() { - var factory = new IndexHolderFactory(new FSDirectoryFactory(), A.Fake()); + var factory = new IndexManager(new FileIndexStorage(), A.Fake()); sut = new TextIndexerGrain(factory); sut.ActivateAsync(schemaId).Wait(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTestsBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTestsBase.cs index 6e4c83fe8..7037c1f0d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTestsBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTestsBase.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private readonly SearchContext context; private readonly TextIndexerGrain sut; - public abstract IDirectoryFactory DirectoryFactory { get; } + public abstract IIndexStorage Storage { get; } protected TextIndexerGrainTestsBase() { @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text Languages = new HashSet { "de", "en" } }; - var factory = new IndexHolderFactory(DirectoryFactory, A.Fake()); + var factory = new IndexManager(Storage, A.Fake()); sut = new TextIndexerGrain(factory); sut.ActivateAsync(schemaId).Wait(); @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text await sut.OnDeactivateAsync(); - var other = new TextIndexerGrain(new IndexHolderFactory(DirectoryFactory, A.Fake())); + var other = new TextIndexerGrain(new IndexManager(Storage, A.Fake())); try { await other.ActivateAsync(schemaId); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Assets.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Assets.cs new file mode 100644 index 000000000..fb71b3ef7 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Assets.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Assets; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public class TextIndexerGrainTests_Assets : TextIndexerGrainTestsBase + { + public override IIndexStorage Storage { get; } = CreateStorage(); + + private static IIndexStorage CreateStorage() + { + var storage = new AssetIndexStorage(new MemoryAssetStore()); + + return storage; + } + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_FS.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_FS.cs index f785a59ff..4da109c0c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_FS.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_FS.cs @@ -9,13 +9,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { public class TextIndexerGrainTests_FS : TextIndexerGrainTestsBase { - public override IDirectoryFactory DirectoryFactory => CreateFactory(); + public override IIndexStorage Storage => CreateStorage(); - private static IDirectoryFactory CreateFactory() + private static IIndexStorage CreateStorage() { - var directoryFactory = new FSDirectoryFactory(); + var storage = new FileIndexStorage(); - return directoryFactory; + return storage; } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Mongo.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Mongo.cs index ac0572a7e..70f8d64be 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Mongo.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests_Mongo.cs @@ -13,9 +13,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { internal class TextIndexerGrainTests_Mongo : TextIndexerGrainTestsBase { - public override IDirectoryFactory DirectoryFactory => CreateFactory(); + public override IIndexStorage Storage => CreateStorage(); - private static IDirectoryFactory CreateFactory() + private static IIndexStorage CreateStorage() { var mongoClient = new MongoClient("mongodb://localhost"); var mongoDatabase = mongoClient.GetDatabase("FullText"); @@ -25,9 +25,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text BucketName = "fs" }); - var directoryFactory = new MongoDirectoryFactory(mongoBucket); + var storage = new MongoIndexStorage(mongoBucket); - return directoryFactory; + return storage; } } }