mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
25 changed files with 1135 additions and 454 deletions
@ -0,0 +1,150 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Lucene.Net.Store; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using MongoDB.Driver.GridFS; |
|||
using LuceneDirectory = Lucene.Net.Store.Directory; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.FullText |
|||
{ |
|||
public sealed class MongoDirectory : BaseDirectory |
|||
{ |
|||
private readonly IGridFSBucket<string> bucket; |
|||
private readonly string directory; |
|||
private readonly DirectoryInfo cacheDirectoryInfo; |
|||
private readonly LuceneDirectory cacheDirectory; |
|||
private bool isDisposed; |
|||
|
|||
public LuceneDirectory CacheDirectory |
|||
{ |
|||
get { return cacheDirectory; } |
|||
} |
|||
|
|||
public DirectoryInfo CacheDirectoryInfo |
|||
{ |
|||
get { return cacheDirectoryInfo; } |
|||
} |
|||
|
|||
public IGridFSBucket<string> Bucket |
|||
{ |
|||
get { return bucket; } |
|||
} |
|||
|
|||
public MongoDirectory(IGridFSBucket<string> bucket, string directory, DirectoryInfo cacheDirectoryInfo) |
|||
{ |
|||
this.bucket = bucket; |
|||
|
|||
this.directory = directory; |
|||
|
|||
this.cacheDirectoryInfo = cacheDirectoryInfo; |
|||
|
|||
cacheDirectoryInfo.Create(); |
|||
cacheDirectory = FSDirectory.Open(cacheDirectoryInfo); |
|||
|
|||
SetLockFactory(new NativeFSLockFactory(cacheDirectoryInfo)); |
|||
} |
|||
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (disposing) |
|||
{ |
|||
isDisposed = true; |
|||
|
|||
cacheDirectory.Dispose(); |
|||
} |
|||
} |
|||
|
|||
public override string GetLockID() |
|||
{ |
|||
return cacheDirectory.GetLockID(); |
|||
} |
|||
|
|||
public override IndexOutput CreateOutput(string name, IOContext context) |
|||
{ |
|||
return new MongoIndexOutput(this, context, name); |
|||
} |
|||
|
|||
public override IndexInput OpenInput(string name, IOContext context) |
|||
{ |
|||
return new MongoIndexInput(this, context, name); |
|||
} |
|||
|
|||
public override void DeleteFile(string name) |
|||
{ |
|||
EnsureNotDisposed(); |
|||
|
|||
var fullName = GetFullName(name); |
|||
|
|||
try |
|||
{ |
|||
Bucket.Delete(fullName); |
|||
} |
|||
catch (GridFSFileNotFoundException) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public override long FileLength(string name) |
|||
{ |
|||
EnsureNotDisposed(); |
|||
|
|||
var file = FindFile(name) ?? throw new FileNotFoundException(); |
|||
|
|||
return file.Length; |
|||
} |
|||
|
|||
public override string[] ListAll() |
|||
{ |
|||
EnsureNotDisposed(); |
|||
|
|||
var files = Bucket.Find(Builders<GridFSFileInfo<string>>.Filter.Regex(x => x.Id, new BsonRegularExpression($"^{directory}/"))).ToList(); |
|||
|
|||
return files.Select(x => x.Filename).ToArray(); |
|||
} |
|||
|
|||
public GridFSFileInfo<string>? FindFile(string name) |
|||
{ |
|||
var fullName = GetFullName(name); |
|||
|
|||
return Bucket.Find(Builders<GridFSFileInfo<string>>.Filter.Eq(x => x.Id, fullName)).FirstOrDefault(); |
|||
} |
|||
|
|||
public override void Sync(ICollection<string> names) |
|||
{ |
|||
} |
|||
|
|||
[Obsolete] |
|||
public override bool FileExists(string name) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public string GetFullName(string name) |
|||
{ |
|||
return $"{directory}/{name}"; |
|||
} |
|||
|
|||
public string GetFullPath(string name) |
|||
{ |
|||
return Path.Combine(cacheDirectoryInfo.FullName, name); |
|||
} |
|||
|
|||
private void EnsureNotDisposed() |
|||
{ |
|||
if (isDisposed) |
|||
{ |
|||
throw new ObjectDisposedException(GetType().FullName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using MongoDB.Driver.GridFS; |
|||
using Squidex.Domain.Apps.Entities.Contents.Text; |
|||
using LuceneDirectory = Lucene.Net.Store.Directory; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.FullText |
|||
{ |
|||
public sealed class MongoDirectoryFactory : IDirectoryFactory |
|||
{ |
|||
private readonly IGridFSBucket<string> bucket; |
|||
|
|||
public MongoDirectoryFactory(IGridFSBucket<string> bucket) |
|||
{ |
|||
this.bucket = bucket; |
|||
} |
|||
|
|||
public LuceneDirectory Create(Guid schemaId) |
|||
{ |
|||
var folderName = schemaId.ToString(); |
|||
|
|||
var tempFolder = Path.Combine(Path.GetTempPath(), folderName); |
|||
var tempDirectory = new DirectoryInfo(tempFolder); |
|||
|
|||
return new MongoDirectory(bucket, folderName, tempDirectory); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.IO; |
|||
using Lucene.Net.Store; |
|||
using MongoDB.Driver.GridFS; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.FullText |
|||
{ |
|||
public sealed class MongoIndexInput : IndexInput |
|||
{ |
|||
private readonly IndexInput cacheInput; |
|||
private readonly MongoDirectory indexDirectory; |
|||
private readonly IOContext context; |
|||
private readonly string indexFileName; |
|||
|
|||
public override long Length |
|||
{ |
|||
get { return cacheInput.Length; } |
|||
} |
|||
|
|||
public MongoIndexInput(MongoDirectory indexDirectory, IOContext context, string indexFileName) |
|||
: base(indexDirectory.GetFullName(indexFileName)) |
|||
{ |
|||
this.indexDirectory = indexDirectory; |
|||
this.indexFileName = indexFileName; |
|||
|
|||
this.context = context; |
|||
|
|||
try |
|||
{ |
|||
var file = indexDirectory.FindFile(indexFileName); |
|||
|
|||
if (file != null) |
|||
{ |
|||
var fileInfo = new FileInfo(indexDirectory.GetFullPath(indexFileName)); |
|||
|
|||
var writtenTime = file.Metadata["WrittenTime"].ToUniversalTime(); |
|||
|
|||
if (!fileInfo.Exists || fileInfo.LastWriteTimeUtc < writtenTime) |
|||
{ |
|||
using (var fs = new FileStream(fileInfo.FullName, FileMode.Create, FileAccess.Write)) |
|||
{ |
|||
var fullName = indexDirectory.GetFullName(indexFileName); |
|||
|
|||
indexDirectory.Bucket.DownloadToStream(fullName, fs); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
catch (GridFSFileNotFoundException) |
|||
{ |
|||
throw new FileNotFoundException(); |
|||
} |
|||
|
|||
cacheInput = indexDirectory.CacheDirectory.OpenInput(indexFileName, context); |
|||
} |
|||
|
|||
public MongoIndexInput(MongoIndexInput source) |
|||
: base("clone") |
|||
{ |
|||
cacheInput = (IndexInput)source.cacheInput.Clone(); |
|||
context = source.context; |
|||
indexDirectory = source.indexDirectory; |
|||
indexFileName = source.indexFileName; |
|||
} |
|||
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (disposing) |
|||
{ |
|||
cacheInput.Dispose(); |
|||
} |
|||
} |
|||
|
|||
public override long GetFilePointer() |
|||
{ |
|||
return cacheInput.GetFilePointer(); |
|||
} |
|||
|
|||
public override byte ReadByte() |
|||
{ |
|||
return cacheInput.ReadByte(); |
|||
} |
|||
|
|||
public override void ReadBytes(byte[] b, int offset, int len) |
|||
{ |
|||
cacheInput.ReadBytes(b, offset, len); |
|||
} |
|||
|
|||
public override void Seek(long pos) |
|||
{ |
|||
cacheInput.Seek(pos); |
|||
} |
|||
|
|||
public override object Clone() |
|||
{ |
|||
return new MongoIndexInput(indexDirectory, context, indexFileName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Lucene.Net.Store; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using MongoDB.Driver.GridFS; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.FullText |
|||
{ |
|||
public sealed class MongoIndexOutput : IndexOutput |
|||
{ |
|||
private readonly IndexOutput cacheOutput; |
|||
private readonly MongoDirectory indexDirectory; |
|||
private readonly string indexFileName; |
|||
private bool isFlushed; |
|||
private bool isWritten; |
|||
|
|||
public override long Length |
|||
{ |
|||
get { return cacheOutput.Length; } |
|||
} |
|||
|
|||
public override long Checksum |
|||
{ |
|||
get { return cacheOutput.Checksum; } |
|||
} |
|||
|
|||
public MongoIndexOutput(MongoDirectory indexDirectory, IOContext context, string indexFileName) |
|||
{ |
|||
this.indexDirectory = indexDirectory; |
|||
this.indexFileName = indexFileName; |
|||
|
|||
cacheOutput = indexDirectory.CacheDirectory.CreateOutput(indexFileName, context); |
|||
} |
|||
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (disposing) |
|||
{ |
|||
Flush(); |
|||
|
|||
cacheOutput.Dispose(); |
|||
|
|||
if (isWritten && isFlushed) |
|||
{ |
|||
var fileInfo = new FileInfo(indexDirectory.GetFullPath(indexFileName)); |
|||
|
|||
using (var fs = new FileStream(indexDirectory.GetFullPath(indexFileName), FileMode.Open, FileAccess.Read)) |
|||
{ |
|||
var fullName = indexDirectory.GetFullName(indexFileName); |
|||
|
|||
var options = new GridFSUploadOptions |
|||
{ |
|||
Metadata = new BsonDocument |
|||
{ |
|||
["WrittenTime"] = fileInfo.LastWriteTimeUtc |
|||
} |
|||
}; |
|||
|
|||
try |
|||
{ |
|||
indexDirectory.Bucket.UploadFromStream(fullName, indexFileName, fs, options); |
|||
} |
|||
catch (MongoBulkWriteException ex) when (ex.WriteErrors.Any(x => x.Code == 11000)) |
|||
{ |
|||
indexDirectory.Bucket.Delete(fullName); |
|||
indexDirectory.Bucket.UploadFromStream(fullName, indexFileName, fs, options); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override long GetFilePointer() |
|||
{ |
|||
return cacheOutput.GetFilePointer(); |
|||
} |
|||
|
|||
public override void Flush() |
|||
{ |
|||
cacheOutput.Flush(); |
|||
|
|||
isFlushed = true; |
|||
} |
|||
|
|||
public override void WriteByte(byte b) |
|||
{ |
|||
cacheOutput.WriteByte(b); |
|||
|
|||
isWritten = true; |
|||
} |
|||
|
|||
public override void WriteBytes(byte[] b, int offset, int length) |
|||
{ |
|||
cacheOutput.WriteBytes(b, offset, length); |
|||
|
|||
isWritten = true; |
|||
} |
|||
|
|||
[Obsolete] |
|||
public override void Seek(long pos) |
|||
{ |
|||
cacheOutput.Seek(pos); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using Lucene.Net.Store; |
|||
using LuceneDirectory = Lucene.Net.Store.Directory; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public sealed class FSDirectoryFactory : IDirectoryFactory |
|||
{ |
|||
public LuceneDirectory Create(Guid schemaId) |
|||
{ |
|||
var folderName = $"Indexes/{schemaId}"; |
|||
|
|||
var tempFolder = Path.Combine(Path.GetTempPath(), folderName); |
|||
|
|||
return FSDirectory.Open(tempFolder); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Lucene.Net.Store; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public interface IDirectoryFactory |
|||
{ |
|||
Directory Create(Guid schemaId); |
|||
} |
|||
} |
|||
@ -0,0 +1,157 @@ |
|||
// ==========================================================================
|
|||
// 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 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 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(IDirectoryFactory directoryFactory, Guid schemaId) |
|||
{ |
|||
directory = directoryFactory.Create(schemaId); |
|||
} |
|||
|
|||
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 void Commit() |
|||
{ |
|||
ThrowIfDisposed(); |
|||
|
|||
try |
|||
{ |
|||
MarkStale(); |
|||
|
|||
indexWriter.Commit(); |
|||
} |
|||
catch (OutOfMemoryException) |
|||
{ |
|||
RecreateIndexWriter(); |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public sealed class IndexHolderFactory : DisposableObjectBase |
|||
{ |
|||
private readonly Dictionary<Guid, IndexHolder> indices = new Dictionary<Guid, IndexHolder>(); |
|||
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) |
|||
{ |
|||
lock (indices) |
|||
{ |
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IndexHolder Acquire(Guid schemaId) |
|||
{ |
|||
IndexHolder? index; |
|||
|
|||
lock (indices) |
|||
{ |
|||
if (indices.TryGetValue(schemaId, out index)) |
|||
{ |
|||
log.LogWarning(w => w |
|||
.WriteProperty("message", "Unreleased index found.") |
|||
.WriteProperty("schemaId", schemaId.ToString())); |
|||
|
|||
index.Dispose(); |
|||
} |
|||
|
|||
index = new IndexHolder(directoryFactory, schemaId); |
|||
|
|||
indices[schemaId] = index; |
|||
} |
|||
|
|||
index.Open(); |
|||
|
|||
return index; |
|||
} |
|||
|
|||
public void Release(Guid id) |
|||
{ |
|||
lock (indices) |
|||
{ |
|||
indices.Remove(id); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Threading.Tasks; |
|||
using Lucene.Net.Index; |
|||
using Squidex.Infrastructure.Assets; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public static class PersistenceHelper |
|||
{ |
|||
private const string ArchiveFile = "Archive.zip"; |
|||
private const string LockFile = "write.lock"; |
|||
|
|||
public static async Task UploadDirectoryAsync(this IAssetStore assetStore, DirectoryInfo directory, IndexCommit commit) |
|||
{ |
|||
using (var fileStream = new FileStream( |
|||
Path.Combine(directory.FullName, ArchiveFile), |
|||
FileMode.Create, |
|||
FileAccess.ReadWrite, |
|||
FileShare.None, |
|||
4096, |
|||
FileOptions.DeleteOnClose)) |
|||
{ |
|||
using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create, true)) |
|||
{ |
|||
foreach (var fileName in commit.FileNames) |
|||
{ |
|||
var file = new FileInfo(Path.Combine(directory.FullName, fileName)); |
|||
|
|||
try |
|||
{ |
|||
if (!file.Name.Equals(ArchiveFile, StringComparison.OrdinalIgnoreCase) && |
|||
!file.Name.Equals(LockFile, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
zipArchive.CreateEntryFromFile(file.FullName, file.Name); |
|||
} |
|||
} |
|||
catch (IOException) |
|||
{ |
|||
continue; |
|||
} |
|||
} |
|||
} |
|||
|
|||
fileStream.Position = 0; |
|||
|
|||
await assetStore.UploadAsync(directory.Name, 0, string.Empty, fileStream, true); |
|||
} |
|||
} |
|||
|
|||
public static async Task DownloadAsync(this IAssetStore assetStore, DirectoryInfo directory) |
|||
{ |
|||
if (directory.Exists) |
|||
{ |
|||
directory.Delete(true); |
|||
} |
|||
|
|||
directory.Create(); |
|||
|
|||
using (var fileStream = new FileStream( |
|||
Path.Combine(directory.FullName, ArchiveFile), |
|||
FileMode.Create, |
|||
FileAccess.ReadWrite, |
|||
FileShare.None, |
|||
4096, |
|||
FileOptions.DeleteOnClose)) |
|||
{ |
|||
try |
|||
{ |
|||
await assetStore.DownloadAsync(directory.Name, 0, string.Empty, fileStream); |
|||
|
|||
fileStream.Position = 0; |
|||
|
|||
using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read, true)) |
|||
{ |
|||
zipArchive.ExtractToDirectory(directory.FullName); |
|||
} |
|||
} |
|||
catch (AssetNotFoundException) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
|
|||
#pragma warning disable ORL1001
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
[Serializable] |
|||
public sealed class TextContent : Dictionary<string, string> |
|||
{ |
|||
public TextContent() |
|||
{ |
|||
} |
|||
|
|||
public TextContent(NamedContentData data) |
|||
{ |
|||
if (data == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var languages = new Dictionary<string, StringBuilder>(); |
|||
|
|||
void AppendText(string language, string text) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(text)) |
|||
{ |
|||
var sb = languages.GetOrAddNew(language); |
|||
|
|||
if (sb.Length > 0) |
|||
{ |
|||
sb.Append(" "); |
|||
} |
|||
|
|||
sb.Append(text); |
|||
} |
|||
} |
|||
|
|||
foreach (var field in data) |
|||
{ |
|||
if (field.Value != null) |
|||
{ |
|||
foreach (var fieldValue in field.Value) |
|||
{ |
|||
var appendText = new Action<string>(text => AppendText(fieldValue.Key, text)); |
|||
|
|||
AppendJsonText(fieldValue.Value, appendText); |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (var kvp in languages) |
|||
{ |
|||
this[kvp.Key] = kvp.Value.ToString(); |
|||
} |
|||
} |
|||
|
|||
private static void AppendJsonText(IJsonValue value, Action<string> appendText) |
|||
{ |
|||
if (value.Type == JsonValueType.String) |
|||
{ |
|||
appendText(value.ToString()); |
|||
} |
|||
else if (value is JsonArray array) |
|||
{ |
|||
foreach (var item in array) |
|||
{ |
|||
AppendJsonText(item, appendText); |
|||
} |
|||
} |
|||
else if (value is JsonObject obj) |
|||
{ |
|||
foreach (var item in obj.Values) |
|||
{ |
|||
AppendJsonText(item, appendText); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public class TextIndexerBenchmark |
|||
{ |
|||
private readonly Guid schemaId = Guid.NewGuid(); |
|||
private readonly TextIndexerGrain sut; |
|||
|
|||
public TextIndexerBenchmark() |
|||
{ |
|||
var factory = new IndexHolderFactory(new FSDirectoryFactory(), A.Fake<ISemanticLog>()); |
|||
|
|||
sut = new TextIndexerGrain(factory); |
|||
sut.ActivateAsync(schemaId).Wait(); |
|||
} |
|||
|
|||
[Fact(Skip = "Only used for benchmarks")] |
|||
public async Task Should_index_many_documents() |
|||
{ |
|||
var text = new TextContent |
|||
{ |
|||
["iv"] = "Hallo Welt" |
|||
}; |
|||
|
|||
var ids = new Guid[10000]; |
|||
|
|||
for (var i = 0; i < ids.Length; i++) |
|||
{ |
|||
ids[i] = Guid.NewGuid(); |
|||
} |
|||
|
|||
var watch = ValueStopwatch.StartNew(); |
|||
|
|||
foreach (var id in ids) |
|||
{ |
|||
await sut.IndexAsync(new Update { Text = text, Id = id }); |
|||
} |
|||
|
|||
sut.OnDeactivateAsync().Wait(); |
|||
|
|||
var elapsed = watch.Stop(); |
|||
|
|||
Assert.InRange(elapsed, 0, 1); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
public class TextIndexerGrainTests_FS : TextIndexerGrainTestsBase |
|||
{ |
|||
public override IDirectoryFactory DirectoryFactory => CreateFactory(); |
|||
|
|||
private static IDirectoryFactory CreateFactory() |
|||
{ |
|||
var directoryFactory = new FSDirectoryFactory(); |
|||
|
|||
return directoryFactory; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using MongoDB.Driver; |
|||
using MongoDB.Driver.GridFS; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.FullText; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Text |
|||
{ |
|||
internal class TextIndexerGrainTests_Mongo : TextIndexerGrainTestsBase |
|||
{ |
|||
public override IDirectoryFactory DirectoryFactory => CreateFactory(); |
|||
|
|||
private static IDirectoryFactory CreateFactory() |
|||
{ |
|||
var mongoClient = new MongoClient("mongodb://localhost"); |
|||
var mongoDatabase = mongoClient.GetDatabase("FullText"); |
|||
|
|||
var mongoBucket = new GridFSBucket<string>(mongoDatabase, new GridFSBucketOptions |
|||
{ |
|||
BucketName = "fs" |
|||
}); |
|||
|
|||
var directoryFactory = new MongoDirectoryFactory(mongoBucket); |
|||
|
|||
return directoryFactory; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue