Add Config locking and caching
This commit is contained in:
@@ -13,7 +13,7 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
||||
where TConfiguration :
|
||||
class
|
||||
{
|
||||
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = new(() =>
|
||||
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = () =>
|
||||
{
|
||||
return new JsonSerializerOptions
|
||||
{
|
||||
@@ -26,133 +26,82 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
||||
new DictionaryStringObjectJsonConverter()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
private readonly object lockingObject = new();
|
||||
};
|
||||
|
||||
public void Set(TConfiguration value) => Set((object)value);
|
||||
|
||||
public void Set(object value)
|
||||
{
|
||||
lock (lockingObject)
|
||||
using (ConfigurationLock.EnterWrite())
|
||||
{
|
||||
IFileInfo fileInfo = configurationFile.FileInfo;
|
||||
if (!File.Exists(fileInfo.PhysicalPath))
|
||||
{
|
||||
string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath);
|
||||
if (!string.IsNullOrEmpty(fileDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(fileDirectoryPath);
|
||||
}
|
||||
EnsureFileExists(fileInfo.PhysicalPath);
|
||||
|
||||
File.WriteAllText(fileInfo.PhysicalPath!, "{}");
|
||||
string? content;
|
||||
JsonNode? rootNode;
|
||||
|
||||
using (Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||||
using (StreamReader reader = new(stream))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
rootNode = JsonNode.Parse(content);
|
||||
}
|
||||
|
||||
using Stream stream = fileInfo.PhysicalPath is not null
|
||||
? new FileStream(fileInfo.PhysicalPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
|
||||
: fileInfo.CreateReadStream();
|
||||
JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value, serializerOptions ?? defaultSerializerOptions()));
|
||||
|
||||
using StreamReader? reader = new(stream);
|
||||
ApplyConfigurationUpdates(ref rootNode, valueNode, section);
|
||||
|
||||
string? content = reader.ReadToEnd();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions());
|
||||
|
||||
JsonNode? rootNode = JsonNode.Parse(content);
|
||||
JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value,
|
||||
serializerOptions ?? defaultSerializerOptions()));
|
||||
|
||||
string[] segments = section.Split(':');
|
||||
JsonNode? currentNode = rootNode;
|
||||
int lastIndex = segments.Length - 1;
|
||||
|
||||
for (int i = 0; i < lastIndex; i++)
|
||||
{
|
||||
if (currentNode is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string currentKey = segments[i];
|
||||
if (currentNode[currentKey] is null)
|
||||
{
|
||||
currentNode[currentKey] = new JsonObject();
|
||||
}
|
||||
|
||||
currentNode = currentNode[currentKey];
|
||||
}
|
||||
|
||||
if (currentNode is not null)
|
||||
{
|
||||
string lastKey = segments[lastIndex];
|
||||
if (currentNode is JsonArray array && int.TryParse(lastKey, out int index))
|
||||
{
|
||||
if (array.Count <= index)
|
||||
{
|
||||
array.Add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
array[index] = MergeNodes(array[index], valueNode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode);
|
||||
}
|
||||
}
|
||||
|
||||
using Stream stream2 = fileInfo.PhysicalPath is not null
|
||||
? new FileStream(fileInfo.PhysicalPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)
|
||||
: fileInfo.CreateReadStream();
|
||||
|
||||
JsonSerializer.Serialize(stream, rootNode, serializerOptions ?? defaultSerializerOptions());
|
||||
ConfigurationCache.Set(section, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(out TConfiguration? value)
|
||||
{
|
||||
lock (lockingObject)
|
||||
if (ConfigurationCache.TryGet(section, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using (ConfigurationLock.EnterRead())
|
||||
{
|
||||
IFileInfo fileInfo = configurationFile.FileInfo;
|
||||
if (File.Exists(fileInfo.PhysicalPath))
|
||||
|
||||
if (!File.Exists(fileInfo.PhysicalPath))
|
||||
{
|
||||
static Stream OpenRead(IFileInfo fileInfo)
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
using Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
using StreamReader reader = new(stream);
|
||||
string content = reader.ReadToEnd();
|
||||
JsonNode? rootNode = JsonNode.Parse(content);
|
||||
|
||||
string[] segments = section.Split(':');
|
||||
JsonNode? currentNode = rootNode;
|
||||
|
||||
int lastIndex = segments.Length - 1;
|
||||
for (int i = 0; i < lastIndex; i++)
|
||||
{
|
||||
if (currentNode is null)
|
||||
{
|
||||
return fileInfo.PhysicalPath is not null
|
||||
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
|
||||
: fileInfo.CreateReadStream();
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
using Stream stream = OpenRead(fileInfo);
|
||||
using StreamReader? reader = new(stream);
|
||||
currentNode = currentNode[segments[i]];
|
||||
}
|
||||
|
||||
string? content = reader.ReadToEnd();
|
||||
JsonNode? rootNode = JsonNode.Parse(content);
|
||||
|
||||
string[] segments = section.Split(':');
|
||||
JsonNode? currentNode = rootNode;
|
||||
int lastIndex = segments.Length - 1;
|
||||
|
||||
for (int i = 0; i < lastIndex; i++)
|
||||
{
|
||||
if (currentNode is null)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
currentNode = currentNode[segments[i]];
|
||||
}
|
||||
|
||||
if (currentNode is not null)
|
||||
{
|
||||
if (currentNode[segments[lastIndex]] is JsonNode sectionNode)
|
||||
{
|
||||
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode,
|
||||
serializerOptions ?? defaultSerializerOptions());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode)
|
||||
{
|
||||
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode, serializerOptions ?? defaultSerializerOptions());
|
||||
ConfigurationCache.Set(section, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
@@ -160,6 +109,49 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyConfigurationUpdates(ref JsonNode? rootNode, JsonNode? valueNode, string section)
|
||||
{
|
||||
string[] segments = section.Split(':');
|
||||
JsonNode? currentNode = rootNode;
|
||||
int lastIndex = segments.Length - 1;
|
||||
|
||||
for (int i = 0; i < lastIndex; i++)
|
||||
{
|
||||
if (currentNode is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string currentKey = segments[i];
|
||||
if (currentNode[currentKey] is null)
|
||||
{
|
||||
currentNode[currentKey] = new JsonObject();
|
||||
}
|
||||
|
||||
currentNode = currentNode[currentKey];
|
||||
}
|
||||
|
||||
if (currentNode is not null)
|
||||
{
|
||||
string lastKey = segments[lastIndex];
|
||||
if (currentNode is JsonArray array && int.TryParse(lastKey, out int index))
|
||||
{
|
||||
if (array.Count <= index)
|
||||
{
|
||||
array.Add(valueNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
array[index] = MergeNodes(array[index], valueNode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode? CloneNode(JsonNode? node)
|
||||
{
|
||||
if (node is null)
|
||||
@@ -171,15 +163,29 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
||||
return JsonNode.Parse(serialized);
|
||||
}
|
||||
|
||||
private void EnsureFileExists(string? filePath)
|
||||
{
|
||||
if (filePath == null || File.Exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string? directoryPath = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(filePath, "{}");
|
||||
}
|
||||
|
||||
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
|
||||
{
|
||||
newNode = CloneNode(newNode);
|
||||
|
||||
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
|
||||
{
|
||||
foreach (KeyValuePair<string, JsonNode?> property in newObject)
|
||||
{
|
||||
existingObject[property.Key] = MergeNodes(existingObject[property.Key], property.Value);
|
||||
existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value));
|
||||
}
|
||||
|
||||
return existingObject;
|
||||
@@ -188,7 +194,7 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
||||
{
|
||||
foreach (JsonNode? item in newArray)
|
||||
{
|
||||
existingArray.Add(item);
|
||||
existingArray.Add(CloneNode(item));
|
||||
}
|
||||
|
||||
return existingArray;
|
||||
@@ -198,4 +204,4 @@ public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfigurati
|
||||
return newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user