Browse Source

Small fixes.

pull/344/head
Sebastian Stehle 7 years ago
parent
commit
91d150e6da
  1. 18
      src/Squidex.Infrastructure/Log/ILogStore.cs
  2. 62
      src/Squidex.Infrastructure/Log/LockingLogStore.cs
  3. 19
      src/Squidex.Infrastructure/Orleans/ILockGrain.cs
  4. 46
      src/Squidex.Infrastructure/Orleans/LockGrain.cs
  5. 2
      src/Squidex/app/framework/angular/forms/error-formatting.spec.ts
  6. 2
      src/Squidex/app/framework/angular/forms/error-formatting.ts
  7. 2
      src/Squidex/app/framework/angular/forms/tag-editor.component.scss
  8. 2
      src/Squidex/app/framework/angular/forms/tag-editor.component.ts
  9. 2
      src/Squidex/app/framework/angular/forms/validators.ts
  10. 60
      tests/Squidex.Infrastructure.Tests/Log/LockingLogStoreTests.cs
  11. 50
      tests/Squidex.Infrastructure.Tests/Orleans/LockGrainTests.cs

18
src/Squidex.Infrastructure/Log/ILogStore.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.Log
{
public interface ILogStore
{
Task ReadLockAsync(string key, DateTime from, DateTime to, Stream stream);
}
}

62
src/Squidex.Infrastructure/Log/LockingLogStore.cs

@ -0,0 +1,62 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Log
{
public sealed class LockingLogStore : ILogStore
{
private static readonly TimeSpan LockWaitingTime = TimeSpan.FromMinutes(10);
private readonly ILogStore inner;
private readonly ILockGrain lockGrain;
public LockingLogStore(ILogStore inner, IGrainFactory grainFactory)
{
Guard.NotNull(inner, nameof(inner));
Guard.NotNull(grainFactory, nameof(grainFactory));
this.inner = inner;
lockGrain = grainFactory.GetGrain<ILockGrain>(SingleGrain.Id);
}
public async Task ReadLockAsync(string key, DateTime from, DateTime to, Stream stream)
{
string releaseToken = null;
using (var cts = new CancellationTokenSource(LockWaitingTime))
{
while (!cts.IsCancellationRequested)
{
releaseToken = await lockGrain.AcquireLockAsync(key);
if (releaseToken != null)
{
break;
}
await Task.Delay(2000);
}
}
try
{
await inner.ReadLockAsync(key, from, to, stream);
}
finally
{
await lockGrain.ReleaseLockAsync(releaseToken);
}
}
}
}

19
src/Squidex.Infrastructure/Orleans/ILockGrain.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Orleans;
namespace Squidex.Infrastructure.Orleans
{
public interface ILockGrain : IGrainWithStringKey
{
Task<string> AcquireLockAsync(string key);
Task ReleaseLockAsync(string releaseToken);
}
}

46
src/Squidex.Infrastructure/Orleans/LockGrain.cs

@ -0,0 +1,46 @@
// ==========================================================================
// 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.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Orleans
{
public sealed class LockGrain : GrainOfString, ILockGrain
{
private readonly Dictionary<string, string> locks = new Dictionary<string, string>();
public Task<string> AcquireLockAsync(string key)
{
string releaseToken = null;
if (!locks.ContainsKey(key))
{
releaseToken = Guid.NewGuid().ToString();
locks.Add(key, releaseToken);
}
return Task.FromResult(releaseToken);
}
public Task ReleaseLockAsync(string releaseToken)
{
var key = locks.FirstOrDefault(x => x.Value == releaseToken).Key;
if (!string.IsNullOrWhiteSpace(key))
{
locks.Remove(key);
}
return TaskHelper.Done;
}
}
}

2
src/Squidex/app/framework/angular/forms/error-formatting.spec.ts

@ -129,7 +129,7 @@ describe('formatErrors', () => {
it('should format validArrayValues', () => { it('should format validArrayValues', () => {
const error = validate([2, 4], ValidatorsEx.validArrayValues([1, 2, 3])); const error = validate([2, 4], ValidatorsEx.validArrayValues([1, 2, 3]));
expect(error).toEqual('MY_FIELD contains an invalid value.'); expect(error).toEqual('MY_FIELD contains an invalid value: 4.');
}); });
it('should format match', () => { it('should format match', () => {

2
src/Squidex/app/framework/angular/forms/error-formatting.ts

@ -28,7 +28,7 @@ const DEFAULT_ERRORS: { [key: string]: string } = {
requiredTrue: '{field} is required.', requiredTrue: '{field} is required.',
validdatetime: '{field} is not a valid date time.', validdatetime: '{field} is not a valid date time.',
validvalues: '{field} is not a valid value.', validvalues: '{field} is not a valid value.',
validarrayvalues: '{field} contains an invalid value.' validarrayvalues: '{field} contains an invalid value: {invalidvalue}.'
}; };
export function formatError(field: string, type: string, properties: any, value: any, errors?: any) { export function formatError(field: string, type: string, properties: any, value: any, errors?: any) {

2
src/Squidex/app/framework/angular/forms/tag-editor.component.scss

@ -72,7 +72,7 @@ div {
@include border-radius(10px); @include border-radius(10px);
color: $color-dark-foreground; color: $color-dark-foreground;
cursor: default; cursor: default;
padding: 0 .6rem; padding: 1px .6rem;
background: $color-theme-blue; background: $color-theme-blue;
border: 0; border: 0;
font-size: .8rem; font-size: .8rem;

2
src/Squidex/app/framework/angular/forms/tag-editor.component.ts

@ -211,6 +211,8 @@ export class TagEditorComponent implements AfterViewInit, ControlValueAccessor,
} }
public markTouched() { public markTouched() {
this.selectValue(this.addInput.value);
this.callTouched(); this.callTouched();
this.resetAutocompletion(); this.resetAutocompletion();

2
src/Squidex/app/framework/angular/forms/validators.ts

@ -167,7 +167,7 @@ export module ValidatorsEx {
if (ns) { if (ns) {
for (let n of ns) { for (let n of ns) {
if (values.indexOf(n) < 0) { if (values.indexOf(n) < 0) {
return { validarrayvalues: false }; return { validarrayvalues: { invalidvalue: n } };
} }
} }
} }

60
tests/Squidex.Infrastructure.Tests/Log/LockingLogStoreTests.cs

@ -0,0 +1,60 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Infrastructure.Orleans;
using Xunit;
namespace Squidex.Infrastructure.Log
{
public class LockingLogStoreTests
{
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
private readonly ILockGrain lockGrain = A.Fake<ILockGrain>();
private readonly ILogStore inner = A.Fake<ILogStore>();
private readonly LockingLogStore sut;
public LockingLogStoreTests()
{
A.CallTo(() => grainFactory.GetGrain<ILockGrain>(SingleGrain.Id, null))
.Returns(lockGrain);
sut = new LockingLogStore(inner, grainFactory);
}
[Fact]
public async Task Should_lock_and_call_inner()
{
var stream = new MemoryStream();
var dateFrom = DateTime.Today;
var dateTo = dateFrom.AddDays(2);
var key = "MyKey";
var releaseToken = Guid.NewGuid().ToString();
A.CallTo(() => lockGrain.AcquireLockAsync(key))
.Returns(releaseToken);
await sut.ReadLockAsync(key, dateFrom, dateTo, stream);
A.CallTo(() => lockGrain.AcquireLockAsync(key))
.MustHaveHappened();
A.CallTo(() => lockGrain.ReleaseLockAsync(releaseToken))
.MustHaveHappened();
A.CallTo(() => inner.ReadLockAsync(key, dateFrom, dateTo, stream))
.MustHaveHappened();
}
}
}

50
tests/Squidex.Infrastructure.Tests/Orleans/LockGrainTests.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Xunit;
namespace Squidex.Infrastructure.Orleans
{
public class LockGrainTests
{
private readonly LockGrain sut = new LockGrain();
[Fact]
public async Task Should_not_acquire_lock_when_locked()
{
var releaseLock1 = await sut.AcquireLockAsync("Key1");
var releaseLock2 = await sut.AcquireLockAsync("Key1");
Assert.NotNull(releaseLock1);
Assert.Null(releaseLock2);
}
[Fact]
public async Task Should_acquire_lock_with_other_key()
{
var releaseLock1 = await sut.AcquireLockAsync("Key1");
var releaseLock2 = await sut.AcquireLockAsync("Key2");
Assert.NotNull(releaseLock1);
Assert.NotNull(releaseLock2);
}
[Fact]
public async Task Should_acquire_lock_after_released()
{
var releaseLock1 = await sut.AcquireLockAsync("Key1");
await sut.ReleaseLockAsync(releaseLock1);
var releaseLock2 = await sut.AcquireLockAsync("Key1");
Assert.NotNull(releaseLock1);
Assert.NotNull(releaseLock2);
}
}
}
Loading…
Cancel
Save