Skip to content

Commit 99330da

Browse files
authored
Merge pull request #4058 from Nexus-Mods/feat/1043-key-scoped-settings
Add support for key scoped settings
2 parents a370b7c + 7370373 commit 99330da

File tree

7 files changed

+58
-47
lines changed

7 files changed

+58
-47
lines changed

src/NexusMods.Backend/Settings/JsonStorageBackend.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ private static AbsolutePath GetConfigsFolderPath(IFileSystem fileSystem)
4646
return fileSystem.GetKnownPath(baseKnownPath).Combine(baseDirectoryName);
4747
}
4848

49-
private AbsolutePath GetConfigPath<T>()
49+
private AbsolutePath GetConfigPath<T>(string? key)
5050
{
5151
var typeName = typeof(T).FullName ?? typeof(T).Name;
52-
var fileName = $"{typeName}.json";
52+
var fileName = string.IsNullOrWhiteSpace(key) ? $"{typeName}.json" : $"{typeName}_{key}.json";
5353
return _configDirectory.Combine(fileName);
5454
}
5555

@@ -80,17 +80,17 @@ private void Serialize<T>(Stream stream ,T value) where T : class
8080
}
8181

8282
/// <inheritdoc/>
83-
public void Save<T>(T value) where T : class, ISettings, new()
83+
public void Save<T>(T value, string? key) where T : class, ISettings, new()
8484
{
85-
var configPath = GetConfigPath<T>();
85+
var configPath = GetConfigPath<T>(key);
8686
using var stream = configPath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None);
8787
Serialize(stream, value);
8888
}
8989

9090
/// <inheritdoc/>
91-
public T? Load<T>() where T : class, ISettings, new()
91+
public T? Load<T>(string? key) where T : class, ISettings, new()
9292
{
93-
var configPath = GetConfigPath<T>();
93+
var configPath = GetConfigPath<T>(key);
9494
if (!configPath.FileExists) return null;
9595

9696
using var stream = configPath.Open(FileMode.Open, FileAccess.Read, FileShare.Read);

src/NexusMods.Backend/Settings/MnemonicDBStorageBackend.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,18 @@ public MnemonicDBStorageBackend(IServiceProvider serviceProvider)
5252
}
5353
}
5454

55-
private static string GetId<T>() where T : ISettings
55+
private static string GetId<T>(string? key) where T : ISettings
5656
{
57-
return typeof(T).FullName ?? typeof(T).Name;
57+
var typeName = typeof(T).FullName ?? typeof(T).Name;
58+
return string.IsNullOrWhiteSpace(key) ? typeName : $"{typeName}|{key}";
5859
}
5960

60-
public async ValueTask Save<T>(T value, CancellationToken cancellationToken) where T : class, ISettings, new()
61+
public async ValueTask Save<T>(T value, string? key, CancellationToken cancellationToken) where T : class, ISettings, new()
6162
{
6263
var db = _conn.Value.Db;
6364
using var tx = _conn.Value.BeginTransaction();
6465

65-
var name = GetId<T>();
66+
var name = GetId<T>(key);
6667

6768
EntityId id;
6869
var settings = Setting.FindByName(db, name).ToArray();
@@ -80,9 +81,9 @@ private static string GetId<T>() where T : ISettings
8081
await tx.Commit();
8182
}
8283

83-
public ValueTask<T?> Load<T>(CancellationToken cancellationToken) where T : class, ISettings, new()
84+
public ValueTask<T?> Load<T>(string? key, CancellationToken cancellationToken) where T : class, ISettings, new()
8485
{
85-
var settings = Setting.FindByName(_conn.Value.Db, GetId<T>()).ToArray();
86+
var settings = Setting.FindByName(_conn.Value.Db, GetId<T>(key)).ToArray();
8687
if (settings.Length == 0) return ValueTask.FromResult<T?>(null);
8788
return ValueTask.FromResult(Deserialize<T>(settings.First().Value));
8889
}

src/NexusMods.Backend/Settings/SettingsManager.cs

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ internal class SettingsManager : ISettingsManager
1515
private readonly IServiceProvider _serviceProvider;
1616
private readonly ILogger _logger;
1717

18-
private readonly Subject<(Type, object)> _subject = new();
19-
private readonly Dictionary<Type, object> _values = new();
18+
private readonly Subject<(SettingKey, object)> _subject = new();
19+
private readonly Dictionary<SettingKey, object> _values = new();
2020

2121
private readonly FrozenDictionary<Type, Func<object, object>> _overrides;
2222

@@ -78,37 +78,38 @@ public SettingsManager(IServiceProvider serviceProvider)
7878
_asyncStorageBackendMappings = asyncStorageBackendMappings.ToFrozenDictionary();
7979
}
8080

81-
private void CoreSet<T>(T value, bool notify) where T : class, ISettings, new()
81+
private void CoreSet<T>(T value, SettingKey settingKey, bool notify) where T : class, ISettings, new()
8282
{
83-
var type = typeof(T);
84-
_values[type] = value;
83+
_values[settingKey] = value;
8584
if (!notify) return;
8685

87-
_subject.OnNext((type, value));
88-
Save(value);
86+
_subject.OnNext((settingKey, value));
87+
Save(value, settingKey.Key);
8988
}
9089

91-
public void Set<T>(T value) where T : class, ISettings, new() => CoreSet(value, notify: true);
90+
public void Set<T>(T value, string? key) where T : class, ISettings, new() => CoreSet(value, new SettingKey(typeof(T), key), notify: true);
9291

93-
public T Get<T>() where T : class, ISettings, new()
92+
public T Get<T>(string? key) where T : class, ISettings, new()
9493
{
95-
if (_values.TryGetValue(typeof(T), out var obj))
94+
var settingKey = new SettingKey(typeof(T), key);
95+
96+
if (_values.TryGetValue(settingKey, out var obj))
9697
{
9798
Debug.Assert(obj is T);
9899
return (obj as T)!;
99100
}
100101

101-
var savedValue = Load<T>();
102+
var savedValue = Load<T>(key);
102103
if (savedValue is not null)
103104
{
104105
savedValue = Override(savedValue, out _);
105-
CoreSet(savedValue, notify: false);
106+
CoreSet(savedValue, settingKey, notify: false);
106107
return savedValue;
107108
}
108109

109110
var defaultValue = GetDefault<T>();
110111
defaultValue = Override(defaultValue, out var didOverride);
111-
CoreSet(defaultValue, notify: !didOverride);
112+
CoreSet(defaultValue, settingKey, notify: !didOverride);
112113

113114
return defaultValue;
114115

@@ -146,49 +147,51 @@ private object GetDefault(Type settingsType)
146147
return defaultValue;
147148
}
148149

149-
public T Update<T>(Func<T, T> updater) where T : class, ISettings, new()
150+
public T Update<T>(Func<T, T> updater, string? key) where T : class, ISettings, new()
150151
{
151-
var currentValue = Get<T>();
152+
var currentValue = Get<T>(key);
152153
var newValue = updater(currentValue);
153-
Set(newValue);
154+
Set(newValue, key);
154155

155156
return newValue;
156157
}
157158

158-
public Observable<T> GetChanges<T>(bool prependCurrent = false) where T : class, ISettings, new()
159+
public Observable<T> GetChanges<T>(string? key, bool prependCurrent = false) where T : class, ISettings, new()
159160
{
161+
var settingKey = new SettingKey(typeof(T), key);
162+
160163
var result = _subject
161-
.Where(tuple => tuple.Item1 == typeof(T))
164+
.Where(settingKey, static (tuple, state) => tuple.Item1 == state)
162165
.Select(tuple => (tuple.Item2 as T)!);
163166

164-
return prependCurrent ? result.Prepend(Get<T>()) : result;
167+
return prependCurrent ? result.Prepend(Get<T>(key)) : result;
165168
}
166169

167170
#region Save/Load
168171

169-
private void Save<T>(T value) where T : class, ISettings, new()
172+
private void Save<T>(T value, string? key) where T : class, ISettings, new()
170173
{
171174
var type = typeof(T);
172175

173176
if (_storageBackendMappings.TryGetValue(type, out var storageBackend))
174177
{
175178
try
176179
{
177-
storageBackend.Save(value);
180+
storageBackend.Save(value, key);
178181
}
179182
catch (Exception e)
180183
{
181184
_logger.LogError(e, "Exception while saving settings type `{Type}` with storage backend `{StorageBackendType}`", type, storageBackend.GetType());
182185
}
183186
} else if (_asyncStorageBackendMappings.TryGetValue(type, out var asyncStorageBackend))
184187
{
185-
Scheduler.ScheduleAsync((value, asyncStorageBackend, _logger), TimeSpan.Zero, async static (_, state, cancellationToken) =>
188+
Scheduler.ScheduleAsync((value, key, asyncStorageBackend, _logger), TimeSpan.Zero, async static (_, state, cancellationToken) =>
186189
{
187-
var (valueToSave, backend, logger) = state;
190+
var (valueToSave, stringKey, backend, logger) = state;
188191

189192
try
190193
{
191-
await backend.Save(valueToSave, cancellationToken);
194+
await backend.Save(valueToSave, stringKey, cancellationToken);
192195
}
193196
catch (Exception e)
194197
{
@@ -198,15 +201,15 @@ private object GetDefault(Type settingsType)
198201
}
199202
}
200203

201-
private T? Load<T>() where T : class, ISettings, new()
204+
private T? Load<T>(string? key) where T : class, ISettings, new()
202205
{
203206
var type = typeof(T);
204207

205208
if (_storageBackendMappings.TryGetValue(type, out var storageBackend))
206209
{
207210
try
208211
{
209-
return storageBackend.Load<T>();
212+
return storageBackend.Load<T>(key);
210213
}
211214
catch (Exception e)
212215
{
@@ -223,7 +226,7 @@ private object GetDefault(Type settingsType)
223226
{
224227
try
225228
{
226-
res = await asyncStorageBackend.Load<T>(cts.Token);
229+
res = await asyncStorageBackend.Load<T>(key, cts.Token);
227230
waitHandle.Set();
228231
}
229232
catch (Exception e)

src/NexusMods.Sdk/Settings/Backend/IAsyncStorageBackend.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface IAsyncStorageBackend : IBaseStorageBackend, IAsyncDisposable
1212
/// <summary>
1313
/// Saves the given settings object to the storage backend asynchronously.
1414
/// </summary>
15-
ValueTask Save<T>(T value, CancellationToken cancellationToken) where T : class, ISettings, new();
15+
ValueTask Save<T>(T value, string? key, CancellationToken cancellationToken) where T : class, ISettings, new();
1616

1717
/// <summary>
1818
/// Loads the given settings type from the storage backend asynchronously.
@@ -21,5 +21,5 @@ public interface IAsyncStorageBackend : IBaseStorageBackend, IAsyncDisposable
2121
/// Either the loaded value or <c>null</c> if the loading failed
2222
/// or the storage backend doesn't contain this value.
2323
/// </returns>
24-
ValueTask<T?> Load<T>(CancellationToken cancellationToken) where T : class, ISettings, new();
24+
ValueTask<T?> Load<T>(string? key, CancellationToken cancellationToken) where T : class, ISettings, new();
2525
}

src/NexusMods.Sdk/Settings/Backend/IStorageBackend.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface IStorageBackend : IBaseStorageBackend, IDisposable
1212
/// <summary>
1313
/// Saves the given settings object to the storage backend synchronously.
1414
/// </summary>
15-
void Save<T>(T value) where T : class, ISettings, new();
15+
void Save<T>(T value, string? key) where T : class, ISettings, new();
1616

1717
/// <summary>
1818
/// Loads the given settings type from the storage backend synchronously.
@@ -21,5 +21,5 @@ public interface IStorageBackend : IBaseStorageBackend, IDisposable
2121
/// Either the loaded value or <c>null</c> if the loading failed
2222
/// or the storage backend doesn't contain this value.
2323
/// </returns>
24-
T? Load<T>() where T : class, ISettings, new();
24+
T? Load<T>(string? key) where T : class, ISettings, new();
2525
}

src/NexusMods.Sdk/Settings/ISettingsManager.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ public interface ISettingsManager
1313
/// <summary>
1414
/// Sets the current value of <typeparamref name="T"/>.
1515
/// </summary>
16-
void Set<T>(T value) where T : class, ISettings, new();
16+
void Set<T>(T value, string? key = null) where T : class, ISettings, new();
1717

1818
/// <summary>
1919
/// Gets the current value for <typeparamref name="T"/>.
2020
/// </summary>
2121
/// <returns>The current value.</returns>
22-
T Get<T>() where T : class, ISettings, new();
22+
T Get<T>(string? key = null) where T : class, ISettings, new();
2323

2424
/// <summary>
2525
/// Gets the default value for <typeparamref name="T"/>.
@@ -35,12 +35,12 @@ public interface ISettingsManager
3535
/// value will be ignored, unless the modified input value gets returned.
3636
/// </param>
3737
/// <returns>The updated value.</returns>
38-
T Update<T>(Func<T, T> updater) where T : class, ISettings, new();
38+
T Update<T>(Func<T, T> updater, string? key = null) where T : class, ISettings, new();
3939

4040
/// <summary>
4141
/// Gets an observable stream to be notified about changes to <typeparamref name="T"/>.
4242
/// </summary>
43-
Observable<T> GetChanges<T>(bool prependCurrent) where T : class, ISettings, new();
43+
Observable<T> GetChanges<T>(string? key = null, bool prependCurrent = false) where T : class, ISettings, new();
4444

4545
/// <summary>
4646
/// Gets configs for all registered settings.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace NexusMods.Sdk.Settings;
2+
3+
4+
/// <summary>
5+
/// Identifies a setting by its type and an optional scope key.
6+
/// </summary>
7+
public readonly record struct SettingKey(Type SettingType, string? Key);

0 commit comments

Comments
 (0)