mirror of https://github.com/Squidex/squidex.git
5 changed files with 202 additions and 17 deletions
@ -0,0 +1,87 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Backup |
||||
|
{ |
||||
|
public sealed class EventStreamReader : DisposableObjectBase |
||||
|
{ |
||||
|
private const int MaxItemsPerFolder = 1000; |
||||
|
private static readonly JsonSerializer JsonSerializer = JsonSerializer.CreateDefault(); |
||||
|
private readonly ZipArchive archive; |
||||
|
|
||||
|
public EventStreamReader(Stream stream) |
||||
|
{ |
||||
|
archive = new ZipArchive(stream, ZipArchiveMode.Read, true); |
||||
|
} |
||||
|
|
||||
|
protected override void DisposeObject(bool disposing) |
||||
|
{ |
||||
|
if (disposing) |
||||
|
{ |
||||
|
archive.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task ReadEventsAsync(Func<EventData, Stream, Task> eventHandler) |
||||
|
{ |
||||
|
Guard.NotNull(eventHandler, nameof(eventHandler)); |
||||
|
|
||||
|
var readEvents = 0; |
||||
|
var readAttachments = 0; |
||||
|
|
||||
|
while (true) |
||||
|
{ |
||||
|
var eventFolder = readEvents / MaxItemsPerFolder; |
||||
|
var eventPath = $"events/{eventFolder}/{readEvents}.json"; |
||||
|
var eventEntry = archive.GetEntry(eventPath); |
||||
|
|
||||
|
if (eventEntry == null) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
EventData eventData; |
||||
|
|
||||
|
using (var stream = eventEntry.Open()) |
||||
|
{ |
||||
|
using (var textReader = new StreamReader(stream)) |
||||
|
{ |
||||
|
eventData = (EventData)JsonSerializer.Deserialize(textReader, typeof(EventData)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
readEvents++; |
||||
|
|
||||
|
var attachmentFolder = readAttachments / MaxItemsPerFolder; |
||||
|
var attachmentPath = $"attachments/{attachmentFolder}/{readEvents}.blob"; |
||||
|
var attachmentEntry = archive.GetEntry(attachmentPath); |
||||
|
|
||||
|
if (attachmentEntry != null) |
||||
|
{ |
||||
|
using (var stream = attachmentEntry.Open()) |
||||
|
{ |
||||
|
await eventHandler(eventData, stream); |
||||
|
|
||||
|
readAttachments++; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await eventHandler(eventData, null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Backup |
||||
|
{ |
||||
|
public sealed class RestoreGrain : GrainOfString |
||||
|
{ |
||||
|
public RestoreGrain() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using FluentAssertions; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Backup |
||||
|
{ |
||||
|
public class EventStreamTests |
||||
|
{ |
||||
|
public sealed class EventInfo |
||||
|
{ |
||||
|
public EventData Data { get; set; } |
||||
|
|
||||
|
public byte[] Attachment { get; set; } |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_write_and_read_events() |
||||
|
{ |
||||
|
var stream = new MemoryStream(); |
||||
|
|
||||
|
var sourceEvents = new List<EventInfo>(); |
||||
|
|
||||
|
for (var i = 0; i < 1000; i++) |
||||
|
{ |
||||
|
var eventData = new EventData { Type = i.ToString(), Metadata = i, Payload = i }; |
||||
|
var eventInfo = new EventInfo { Data = eventData }; |
||||
|
|
||||
|
if (i % 10 == 0) |
||||
|
{ |
||||
|
eventInfo.Attachment = new byte[] { (byte)i }; |
||||
|
} |
||||
|
|
||||
|
sourceEvents.Add(eventInfo); |
||||
|
} |
||||
|
|
||||
|
using (var reader = new EventStreamWriter(stream)) |
||||
|
{ |
||||
|
foreach (var @event in sourceEvents) |
||||
|
{ |
||||
|
if (@event.Attachment == null) |
||||
|
{ |
||||
|
await reader.WriteEventAsync(@event.Data); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await reader.WriteEventAsync(@event.Data, s => s.WriteAsync(@event.Attachment, 0, 1)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stream.Position = 0; |
||||
|
|
||||
|
var readEvents = new List<EventInfo>(); |
||||
|
|
||||
|
using (var reader = new EventStreamReader(stream)) |
||||
|
{ |
||||
|
await reader.ReadEventsAsync(async (eventData, attachment) => |
||||
|
{ |
||||
|
var eventInfo = new EventInfo { Data = eventData }; |
||||
|
|
||||
|
if (attachment != null) |
||||
|
{ |
||||
|
eventInfo.Attachment = new byte[1]; |
||||
|
|
||||
|
await attachment.ReadAsync(eventInfo.Attachment, 0, 1); |
||||
|
} |
||||
|
|
||||
|
readEvents.Add(eventInfo); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
readEvents.Should().BeEquivalentTo(sourceEvents); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue