diff --git a/LocalStorage.Tests/Helpers.cs b/LocalStorage.Tests/Helpers.cs
new file mode 100644
index 0000000..9409bf6
--- /dev/null
+++ b/LocalStorage.Tests/Helpers.cs
@@ -0,0 +1,19 @@
+using System;
+using Hanssens.Net;
+
+namespace LocalStorageTests
+{
+ internal static class TestHelpers
+ {
+ ///
+ /// Configuration that can be used for initializing a unique LocalStorage instance.
+ ///
+ internal static ILocalStorageConfiguration UniqueInstance()
+ {
+ return new LocalStorageConfiguration()
+ {
+ Filename = Guid.NewGuid().ToString()
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/LocalStorage.Tests/LocalStorage.Tests.csproj b/LocalStorage.Tests/LocalStorage.Tests.csproj
index e924767..579929e 100644
--- a/LocalStorage.Tests/LocalStorage.Tests.csproj
+++ b/LocalStorage.Tests/LocalStorage.Tests.csproj
@@ -1,13 +1,13 @@
- netcoreapp1.0
+ netcoreapp2.2
LocalStorageTests
-
-
-
-
+
+
+
+
diff --git a/LocalStorage.Tests/LocalStorageTests.cs b/LocalStorage.Tests/LocalStorageTests.cs
index 1c4d169..58c087f 100644
--- a/LocalStorage.Tests/LocalStorageTests.cs
+++ b/LocalStorage.Tests/LocalStorageTests.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using FluentAssertions.Common;
using Hanssens.Net.Helpers;
using Xunit;
using LocalStorageTests.Stubs;
@@ -126,7 +127,7 @@ public void LocalStorage_Store_Should_Overwrite_Existing_Key()
// assert - last stored value should be the truth
target.Should().NotBeNull();
- target.ShouldBeEquivalentTo(expected_value);
+ target.IsSameOrEqualTo(expected_value);
}
[Fact(DisplayName = "LocalStorage.Clear() should clear all in-memory content")]
@@ -236,6 +237,84 @@ public void LocalStorage_Should_Perform_Decently_With_Large_Collections()
// assert - make sure the entire operation is done in < 1sec. (psychological boundry, if you will)
stopwatch.ElapsedMilliseconds.Should().BeLessOrEqualTo(1000);
}
+
+ [Fact(DisplayName = "LocalStorage should perform decently with many iterations collections")]
+ public void LocalStorage_Should_Perform_Decently_With_Many_Opens_And_Writes()
+ {
+ // arrange - iterate a lot of times through open/persist/close
+ for (var i = 0; i < 1000; i++)
+ {
+ var storage = new LocalStorage();
+ // storage.Clear();
+ storage.Store(Guid.NewGuid().ToString(), i);
+ storage.Persist();
+ }
+
+ // cleanup
+ var store = new LocalStorage();
+ store.Destroy();
+ }
+
+ [Fact(DisplayName = "LocalStorage.Exists() should locate existing key")]
+ public void LocalStorage_Exists_Should_Locate_Existing_Key()
+ {
+ // arrange
+ var storage = new LocalStorage();
+ var expected_key = Guid.NewGuid().ToString();
+ storage.Store(expected_key, Guid.NewGuid().ToString());
+
+ // act
+ var target = storage.Exists(expected_key);
+
+ // assert
+ target.Should().BeTrue();
+ }
+
+ [Fact(DisplayName = "LocalStorage.Exists() should ignore non-existing key")]
+ public void LocalStorage_Exists_Should_Ignore_NonExisting_Key()
+ {
+ // arrange
+ var storage = new LocalStorage();
+ var nonexisting_key = Guid.NewGuid().ToString();
+
+ // act
+ var target = storage.Exists(nonexisting_key);
+
+ // assert
+ target.Should().BeFalse();
+ }
+
+ [Fact(DisplayName = "LocalStorage.Keys() should return collection of all keys")]
+ public void LocalStorage_Keys_Should_Return_Collection_Of_Keys()
+ {
+ // arrange
+ var storage = new LocalStorage(TestHelpers.UniqueInstance());
+ for (var i = 0; i < 10; i++)
+ storage.Store(Guid.NewGuid().ToString(), i);
+ var expected_keycount = storage.Count;
+
+ // act
+ var target = storage.Keys();
+
+ // assert
+ target.Should().NotBeNullOrEmpty();
+ target.Count.Should().Be(expected_keycount);
+ }
+
+ [Fact(DisplayName = "LocalStorage.Keys() should return 0 on empty collection")]
+ public void LocalStorage_Keys_Should_Return_Zero_On_Empty_Collection()
+ {
+ // arrange
+ var storage = new LocalStorage(TestHelpers.UniqueInstance());
+
+ // act
+ var target = storage.Keys();
+
+ // assert
+ target.Should().NotBeNull();
+ target.Should().BeEmpty();
+ target.Count.Should().Be(0, because: "nothing is added to the LocalStorage");
+ }
[Fact(DisplayName = "LocalStorage.Query() should cast to a collection")]
public void LocalStorage_Query_Should_Cast_Response_To_Collection()
diff --git a/LocalStorage/ILocalStorageConfiguration.cs b/LocalStorage/ILocalStorageConfiguration.cs
index 05b5b7b..4c9ca8a 100644
--- a/LocalStorage/ILocalStorageConfiguration.cs
+++ b/LocalStorage/ILocalStorageConfiguration.cs
@@ -23,6 +23,11 @@ public interface ILocalStorageConfiguration
///
bool EnableEncryption { get; set; }
+ ///
+ /// [Optional] Add a custom salt to encryption, when EnableEncryption is enabled.
+ ///
+ string EncryptionSalt { get; set; }
+
///
/// Filename for the persisted state on disk (defaults to ".localstorage").
///
diff --git a/LocalStorage/LocalStorage.cs b/LocalStorage/LocalStorage.cs
index 68f6e8a..0cfef8b 100644
--- a/LocalStorage/LocalStorage.cs
+++ b/LocalStorage/LocalStorage.cs
@@ -20,7 +20,7 @@ public class LocalStorage : IDisposable
///
/// Configurable behaviour for this LocalStorage instance.
///
- private readonly LocalStorageConfiguration _config;
+ private readonly ILocalStorageConfiguration _config;
///
/// User-provided encryption key, used for encrypting/decrypting values.
@@ -31,16 +31,16 @@ public class LocalStorage : IDisposable
/// Most current actual, in-memory state representation of the LocalStorage.
///
private Dictionary Storage { get; set; } = new Dictionary();
+
+ private object writeLock = new object();
- public LocalStorage() : this(new LocalStorageConfiguration()) { }
+ public LocalStorage() : this(new LocalStorageConfiguration(), string.Empty) { }
- public LocalStorage(LocalStorageConfiguration configuration) : this(configuration, string.Empty) { }
+ public LocalStorage(ILocalStorageConfiguration configuration) : this(configuration, string.Empty) { }
- public LocalStorage(LocalStorageConfiguration configuration, string encryptionKey)
+ public LocalStorage(ILocalStorageConfiguration configuration, string encryptionKey)
{
- if (configuration == null) throw new ArgumentNullException(nameof(configuration));
-
- _config = configuration;
+ _config = configuration ?? throw new ArgumentNullException(nameof(configuration));
if (_config.EnableEncryption) {
if (string.IsNullOrEmpty(encryptionKey)) throw new ArgumentNullException(nameof(encryptionKey), "When EnableEncryption is enabled, an encryptionKey is required when initializing the LocalStorage.");
@@ -75,6 +75,16 @@ public void Destroy()
File.Delete(FileHelpers.GetLocalStoreFilePath(_config.Filename));
}
+ ///
+ /// Determines whether this LocalStorage instance contains the specified key.
+ ///
+ ///
+ ///
+ public bool Exists(string key)
+ {
+ return Storage.ContainsKey(key: key);
+ }
+
///
/// Gets an object from the LocalStorage, without knowing its type.
///
@@ -99,6 +109,14 @@ public T Get(string key)
return JsonConvert.DeserializeObject(raw);
}
+ ///
+ /// Gets a collection containing all the keys in the LocalStorage.
+ ///
+ public IReadOnlyCollection Keys()
+ {
+ return Storage.Keys.OrderBy(x => x).ToList();
+ }
+
///
/// Loads the persisted state from disk into memory, overriding the current memory instance.
///
@@ -152,13 +170,22 @@ public IEnumerable Query(string key, Func predicate = null)
///
public void Persist()
{
- var serialized = JsonConvert.SerializeObject(Storage);
+ var serialized = JsonConvert.SerializeObject(Storage, Formatting.Indented);
- using (var fileStream = new FileStream(FileHelpers.GetLocalStoreFilePath(_config.Filename), FileMode.OpenOrCreate, FileAccess.Write))
+ var writemode = File.Exists(FileHelpers.GetLocalStoreFilePath(_config.Filename))
+ ? FileMode.Truncate
+ : FileMode.Create;
+
+ lock (writeLock)
{
- using (var writer = new StreamWriter(fileStream))
+ using (var fileStream = new FileStream(FileHelpers.GetLocalStoreFilePath(_config.Filename),
+ mode: writemode,
+ access: FileAccess.Write))
{
- writer.Write(serialized);
+ using (var writer = new StreamWriter(fileStream))
+ {
+ writer.Write(serialized);
+ }
}
}
}
diff --git a/LocalStorage/LocalStorage.csproj b/LocalStorage/LocalStorage.csproj
index 9e88800..1ae30a9 100644
--- a/LocalStorage/LocalStorage.csproj
+++ b/LocalStorage/LocalStorage.csproj
@@ -1,22 +1,25 @@
- netcoreapp1.0;netstandard1.3;net46
+ netstandard2.0
True
Juliën Hanssens
https://github.com/hanssens/localstorage-for-dotnet
https://github.com/hanssens/localstorage-for-dotnet
- c#, dotnet, local, storage
+ c#, dotnet, storage, cache, nosql, lightweight
A simple and lightweight tool for persisting data in dotnet (core) apps.
git
https://d17oy1vhnax1f7.cloudfront.net/items/1x2w321G3u3x2z0g120Q/hanssens-beer.png
- https://github.com/hanssens/localstorage-for-dotnet/blob/master/LICENSE
- 1.1.0
+ LocalStorage
+ 2.0.0
Hanssens.Net
See the releases page on GitHub for release notes:
https://github.com/hanssens/localstorage-for-dotnet/releases
-
+
+
+
+
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..e3a09a2
--- /dev/null
+++ b/build.sh
@@ -0,0 +1 @@
+dotnet pack ./LocalStorage/LocalStorage.csproj -c Release --include-symbols