Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Using simple settings as alternative for key-value store when ApplicationData isn't available #2373

Merged
merged 3 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions src/Uno.Extensions.Core.UI/Settings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Globalization;
using System.Text.Json;

namespace Uno.Extensions;

Expand Down Expand Up @@ -60,7 +61,7 @@ private void Initialize()
return settings.TryGetValue(key, out var value) ? value : default;
}
}
public void Set(string key, string value)
public void Set(string key, string? value)
{
Initialize();
if (!_useFileSettings)
Expand All @@ -82,4 +83,31 @@ public void Set(string key, string value)
#endif
}
}

public void Remove(string key) => Set(key, null);

public void Clear()
{
Initialize();
if (!_useFileSettings)
{
ApplicationData.Current.LocalSettings.Values.Clear();
}
else
{
settings.Clear();
#if __WINDOWS__
File.WriteAllText(SettingsFile, JsonSerializer.Serialize(settings));
#endif
}
}

public IReadOnlyCollection<string> Keys
{
get
{
Initialize();
return _useFileSettings ? settings.Keys : ApplicationData.Current.LocalSettings.Values.Keys.Select(k => k.ToString(CultureInfo.InvariantCulture)).ToArray();
}
}
}
34 changes: 32 additions & 2 deletions src/Uno.Extensions.Core/ISettings.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
namespace Uno.Extensions;

internal interface ISettings
/// <summary>
/// Simple interface for storing key-value pairs.
/// </summary>
public interface ISettings
{
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key of the value to get.</param>
/// <returns>The value associated with the specified key, or null if the key does not exist.</returns>
string? Get(string key);
void Set(string key, string value);

/// <summary>
/// Sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key of the value to set.</param>
/// <param name="value">The value to set.</param>
void Set(string key, string? value);

/// <summary>
/// Removes the value associated with the specified key.
/// </summary>
/// <param name="key">The key of the value to remove.</param>
void Remove(string key);

/// <summary>
/// Removes all key-value pairs from the settings.
/// </summary>
void Clear();

/// <summary>
/// Gets a collection of all keys in the settings.
/// </summary>
IReadOnlyCollection<string> Keys { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,75 @@ internal record ApplicationDataKeyValueStorage
(ILogger<ApplicationDataKeyValueStorage> Logger,
InMemoryKeyValueStorage InMemoryStorage,
KeyValueStorageSettings Settings,
ISerializer Serializer) : BaseKeyValueStorageWithCaching(InMemoryStorage, Settings)
ISerializer Serializer,
ISettings UnpackagedSettings
) : BaseKeyValueStorageWithCaching(InMemoryStorage, Settings)
{
public const string Name = "ApplicationData";

// Do not change this value.
private const string KeyNameSuffix = "_ADCSSS";

private readonly ApplicationDataContainer _dataContainer = ApplicationData.Current.LocalSettings;
private ApplicationDataContainer DataContainer => ApplicationData.Current.LocalSettings;

/// <inheritdoc />
public override bool IsEncrypted => false;

private bool UseApplicationData =>
#if !WINDOWS
true;
#else
PlatformHelper.IsAppPackaged;
#endif
private bool TryGetSetting(string name, out object? value)
nickrandolph marked this conversation as resolved.
Show resolved Hide resolved
{
if (UseApplicationData)
{
return DataContainer.Values.TryGetValue(GetKey(name), out value);
}
else
{
return UnpackagedSettings.Get(name) is string t
? (value = t) is not null
: (value = null) is not null;
}
}
private void RemoveSetting(string name)
{
if (UseApplicationData)
{
_ = DataContainer.Values.Remove(GetKey(name));
}
else
{
UnpackagedSettings.Remove(name);
}
}

private void SetSetting(string name, object? value)
{
if (UseApplicationData)
{
DataContainer.Values[GetKey(name)] = value;
}
else
{
UnpackagedSettings.Set(name, value?.ToString());
}
}

private void ClearSettings()
{
if (UseApplicationData)
{
DataContainer.Values.Clear();
}
else
{
UnpackagedSettings.Clear();
}
}

/// <inheritdoc />
protected override async ValueTask InternalClearAsync(string? name, CancellationToken ct)
{
Expand All @@ -27,13 +84,11 @@ protected override async ValueTask InternalClearAsync(string? name, Cancellation
if (name is not null &&
!string.IsNullOrEmpty(name))
{
_ = _dataContainer.Values.Remove(GetKey(name));

RemoveSetting(name);
}
else
{
_dataContainer.Values.Clear();

ClearSettings();
}

if (Logger.IsEnabled(LogLevel.Information))
Expand All @@ -46,46 +101,71 @@ protected override async ValueTask InternalClearAsync(string? name, Cancellation
/// <inheritdoc />
protected override async ValueTask<string[]> InternalGetKeysAsync(CancellationToken ct)
{
return _dataContainer
if (UseApplicationData)
{
return DataContainer
.Values
.Keys
.Where(key=>key.EndsWith(KeyNameSuffix))
.Select(key => GetName(key)) // filter-out non-encrypted storage
.Where(key => key.EndsWith(KeyNameSuffix))
.Select(GetName) // filter-out non-encrypted storage
.Trim()
.ToArray();
}
else
{
return UnpackagedSettings
.Keys
.Where(key => key.EndsWith(KeyNameSuffix))
.Select(GetName) // filter-out non-encrypted storage
.Trim()
.ToArray();

}
}

/// <inheritdoc />
#nullable disable
protected override async ValueTask<T> InternalGetAsync<T>(string name, CancellationToken ct)
{
if (Logger.IsEnabled(LogLevel.Debug))
try
{
Logger.LogDebugMessage($"Getting value for key '{name}'.");
}
if (Logger.IsEnabled(LogLevel.Debug))
{
Logger.LogDebugMessage($"Getting value for key '{name}'.");
}

if (!_dataContainer.Values.TryGetValue(GetKey(name), out var data))
{
throw new KeyNotFoundException(name);
}
if (!TryGetSetting(GetKey(name), out var data))
{
throw new KeyNotFoundException(name);
}

var value = await GetTypedValue<T>(data,ct);
var value = await GetTypedValue<T>(data, ct);

if (Logger.IsEnabled(LogLevel.Information))
{
Logger.LogInformationMessage($"Retrieved value for key '{name}'.");
if (Logger.IsEnabled(LogLevel.Information))
{
Logger.LogInformationMessage($"Retrieved value for key '{name}'.");
}

return value;
}
catch (Exception ex)
{
if (Logger.IsEnabled(LogLevel.Warning))
{
Logger.LogWarningMessage($"Error getting value for key '{name}'.", ex.Message);
}

return value;
return default;
}
}
#nullable restore

protected virtual async Task<T?> GetTypedValue<T>(object? data, CancellationToken ct)
protected virtual async Task<T?> GetTypedValue<T>(object? data, CancellationToken ct)
{
return this.Deserialize<T>(data as string);
}

protected virtual async Task<object> GetObjectValue<T>(T data, CancellationToken ct) where T :notnull
protected virtual async Task<object> GetObjectValue<T>(T data, CancellationToken ct) where T : notnull
{
return this.Serialize(data);
}
Expand All @@ -98,8 +178,8 @@ protected override async ValueTask InternalSetAsync<T>(string name, T value, Can
Logger.LogDebugMessage($"Setting value for key '{name}'.");
}

var data= await GetObjectValue(value, ct);
_dataContainer.Values[GetKey(name)] = data;
var data = await GetObjectValue(value, ct);
SetSetting(GetKey(name), data);

if (Logger.IsEnabled(LogLevel.Information))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ internal record EncryptedApplicationDataKeyValueStorage(
ILogger<ApplicationDataKeyValueStorage> EncryptedLogger,
InMemoryKeyValueStorage InMemoryStorage,
KeyValueStorageSettings Settings,
ISerializer Serializer)
: ApplicationDataKeyValueStorage(EncryptedLogger, InMemoryStorage, Settings, Serializer)
ISerializer Serializer,
ISettings UnpackagedSettings)
: ApplicationDataKeyValueStorage(EncryptedLogger, InMemoryStorage, Settings, Serializer, UnpackagedSettings)
{
public new const string Name = "EncryptedApplicationData";

Expand Down
13 changes: 7 additions & 6 deletions src/Uno.Extensions.Storage.UI/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ public static IServiceCollection AddFileStorage(this IServiceCollection services
private static TKeyValueStorage CreateKeyValueStorage<TKeyValueStorage>(
this IServiceProvider sp,
string name,
Func<ILogger<TKeyValueStorage>, InMemoryKeyValueStorage, KeyValueStorageSettings, ISerializer, TKeyValueStorage> creator)
Func<ILogger<TKeyValueStorage>, InMemoryKeyValueStorage, KeyValueStorageSettings, ISerializer, ISettings, TKeyValueStorage> creator)
{
var l = sp.GetRequiredService<ILogger<TKeyValueStorage>>();
var s = sp.GetRequiredService<ISerializer>();
var inmem = sp.GetRequiredService<InMemoryKeyValueStorage>();
var config = sp.GetRequiredService<IOptions<KeyValueStorageConfiguration>>();
var settings = config.Value.GetSettingsOrDefault(name);
return creator(l, inmem, settings, s);
var unpackaged = sp.GetRequiredService<ISettings>();
return creator(l, inmem, settings, s, unpackaged);
}

public static IServiceCollection AddKeyedStorage(this IServiceCollection services)
Expand All @@ -27,15 +28,15 @@ public static IServiceCollection AddKeyedStorage(this IServiceCollection service
ApplicationDataKeyValueStorage.Name,
sp => sp.CreateKeyValueStorage<ApplicationDataKeyValueStorage>(
ApplicationDataKeyValueStorage.Name,
(l,inmem, settings,s)=>new ApplicationDataKeyValueStorage(l, inmem, settings, s)
(l, inmem, settings, s, unpackaged) => new ApplicationDataKeyValueStorage(l, inmem, settings, s, unpackaged)
)
)
#if __ANDROID__
.AddNamedSingleton<IKeyValueStorage, KeyStoreKeyValueStorage>(
KeyStoreKeyValueStorage.Name,
sp => sp.CreateKeyValueStorage<KeyStoreKeyValueStorage>(
KeyStoreKeyValueStorage.Name,
(l, inmem, settings, s) => new KeyStoreKeyValueStorage(l, inmem, settings, s)
(l, inmem, settings, s, unpackaged) => new KeyStoreKeyValueStorage(l, inmem, settings, s)
)
)
#endif
Expand All @@ -44,7 +45,7 @@ public static IServiceCollection AddKeyedStorage(this IServiceCollection service
KeyChainKeyValueStorage.Name,
sp => sp.CreateKeyValueStorage<KeyChainKeyValueStorage>(
KeyChainKeyValueStorage.Name,
(l, inmem, settings, s) => new KeyChainKeyValueStorage(l, inmem, settings, s)
(l, inmem, settings, s, unpackaged) => new KeyChainKeyValueStorage(l, inmem, settings, s)
)
)
#endif
Expand All @@ -57,7 +58,7 @@ public static IServiceCollection AddKeyedStorage(this IServiceCollection service
EncryptedApplicationDataKeyValueStorage.Name,
sp => sp.CreateKeyValueStorage<EncryptedApplicationDataKeyValueStorage>(
EncryptedApplicationDataKeyValueStorage.Name,
(l, inmem, settings, s) => new EncryptedApplicationDataKeyValueStorage(l, inmem, settings, s)
(l, inmem, settings, s, unpackaged) => new EncryptedApplicationDataKeyValueStorage(l, inmem, settings, s, unpackaged)
)
)
#endif
Expand Down
Loading
Loading