Browse Source

Add animation back.

pull/926/head
Sebastian 4 years ago
parent
commit
f781651273
  1. 3
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupProcessor.cs
  2. 3
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreProcessor.cs
  3. 123
      backend/src/Squidex.Infrastructure/States/SimpleState.cs
  4. 53
      frontend/src/app/theme/_common.scss

3
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupProcessor.cs

@ -57,7 +57,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.userResolver = userResolver;
this.log = log;
state = new SimpleState<BackupState>(persistenceFactory, GetType(), appId);
// Enable locking for the parallel operations that might write stuff.
state = new SimpleState<BackupState>(persistenceFactory, GetType(), appId, true);
}
public async Task LoadAsync(

3
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreProcessor.cs

@ -59,7 +59,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.userResolver = userResolver;
this.log = log;
state = new SimpleState<BackupRestoreState>(persistenceFactory, GetType(), "Default");
// Enable locking for the parallel operations that might write stuff.
state = new SimpleState<BackupRestoreState>(persistenceFactory, GetType(), "Default", true);
}
public async Task LoadAsync(

123
backend/src/Squidex.Infrastructure/States/SimpleState.cs

@ -7,11 +7,14 @@
using NodaTime;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks;
using Squidex.Log;
namespace Squidex.Infrastructure.States
{
public class SimpleState<T> where T : class, new()
{
private readonly AsyncLock? lockObject;
private readonly IPersistence<T> persistence;
private bool isLoaded;
private Instant lastWrite;
@ -25,12 +28,12 @@ namespace Squidex.Infrastructure.States
public IClock Clock { get; set; } = SystemClock.Instance;
public SimpleState(IPersistenceFactory<T> persistenceFactory, Type ownerType, string id)
: this(persistenceFactory, ownerType, DomainId.Create(id))
public SimpleState(IPersistenceFactory<T> persistenceFactory, Type ownerType, string id, bool lockOperations = false)
: this(persistenceFactory, ownerType, DomainId.Create(id), lockOperations)
{
}
public SimpleState(IPersistenceFactory<T> persistenceFactory, Type ownerType, DomainId id)
public SimpleState(IPersistenceFactory<T> persistenceFactory, Type ownerType, DomainId id, bool lockOperations = false)
{
Guard.NotNull(persistenceFactory);
@ -38,53 +41,76 @@ namespace Squidex.Infrastructure.States
{
Value = state;
});
if (lockOperations)
{
lockObject = new AsyncLock();
}
}
public async Task LoadAsync(
CancellationToken ct = default)
{
await persistence.ReadAsync(ct: ct);
isLoaded = true;
using (await LockAsync())
{
await LoadInternalAsync(ct);
}
}
public Task ClearAsync(
public async Task ClearAsync(
CancellationToken ct = default)
{
Value = new T();
using (await LockAsync())
{
// Reset state first, in case the deletion fails.
Value = new T();
return persistence.DeleteAsync(ct);
await persistence.DeleteAsync(ct);
}
}
public async Task WriteAsync(int ifNotWrittenWithinMs,
CancellationToken ct = default)
{
var now = Clock.GetCurrentInstant();
if (ifNotWrittenWithinMs > 0 && now.Minus(lastWrite).TotalMilliseconds < ifNotWrittenWithinMs)
using (await LockAsync())
{
return;
}
// Calculate the timestamp once.
var now = Clock.GetCurrentInstant();
await persistence.WriteSnapshotAsync(Value, ct);
if (ifNotWrittenWithinMs > 0 && now.Minus(lastWrite).TotalMilliseconds < ifNotWrittenWithinMs)
{
return;
}
await persistence.WriteSnapshotAsync(Value, ct);
lastWrite = now;
// Only update the last write property if it is successful.
lastWrite = now;
}
}
public async Task WriteAsync(
CancellationToken ct = default)
{
await persistence.WriteSnapshotAsync(Value, ct);
using (await LockAsync())
{
await persistence.WriteSnapshotAsync(Value, ct);
lastWrite = Clock.GetCurrentInstant();
// Only update the last write property if it is successful.
lastWrite = Clock.GetCurrentInstant();
}
}
public async Task WriteEventAsync(Envelope<IEvent> envelope,
CancellationToken ct = default)
{
await persistence.WriteEventAsync(envelope, ct);
using (await LockAsync())
{
await persistence.WriteEventAsync(envelope, ct);
lastWrite = Clock.GetCurrentInstant();
// Only update the last write property if it is successful.
lastWrite = Clock.GetCurrentInstant();
}
}
public Task UpdateAsync(Func<T, bool> updater, int retries = 20,
@ -99,32 +125,55 @@ namespace Squidex.Infrastructure.States
Guard.GreaterEquals(retries, 1);
Guard.LessThan(retries, 100);
if (!isLoaded)
{
await LoadAsync(ct);
}
for (var i = 0; i < retries; i++)
using (await LockAsync())
{
try
// Ensure that the state is loaded before we make the update.
if (!isLoaded)
{
var (isChanged, result) = updater(Value);
await LoadInternalAsync(ct);
}
if (!isChanged)
for (var i = 0; i < retries; i++)
{
try
{
var (isChanged, result) = updater(Value);
// If nothing has been changed, we can avoid the call to the database.
if (!isChanged)
{
return result;
}
await WriteAsync(ct);
return result;
}
await WriteAsync(ct);
return result;
}
catch (InconsistentStateException) when (i < retries - 1)
{
await LoadAsync(ct);
catch (InconsistentStateException) when (i < retries - 1)
{
await LoadInternalAsync(ct);
}
}
return default!;
}
}
private async Task LoadInternalAsync(
CancellationToken ct = default)
{
await persistence.ReadAsync(ct: ct);
isLoaded = true;
}
private Task<IDisposable> LockAsync()
{
if (lockObject != null)
{
return lockObject.EnterAsync();
}
return default!;
return Task.FromResult<IDisposable>(NoopDisposable.Instance);
}
}
}

53
frontend/src/app/theme/_common.scss

@ -353,4 +353,57 @@ hr {
hr {
margin: .5rem 0;
}
}
//
// Animations
//
.spin {
animation: spin 3s infinite linear;
}
.spin2 {
animation: spin2 1s infinite linear;
}
i {
&.spin {
display: inline-block;
}
&.spin2 {
display: inline-block;
}
}
@keyframes spin2 {
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes spin {
20% {
transform: rotate(0deg);
}
30% {
transform: rotate(180deg);
}
70% {
transform: rotate(180deg);
}
80% {
transform: rotate(360deg);
}
100% {
transform: rotate(360deg);
}
}
Loading…
Cancel
Save