diff --git a/src/Gemstone/ArrayExtensions/ArrayExtensions.cs b/src/Gemstone/ArrayExtensions/ArrayExtensions.cs
index 688d9a43a2..bd9b807ab9 100644
--- a/src/Gemstone/ArrayExtensions/ArrayExtensions.cs
+++ b/src/Gemstone/ArrayExtensions/ArrayExtensions.cs
@@ -59,6 +59,21 @@ namespace Gemstone.ArrayExtensions;
///
public static class ArrayExtensions
{
+ ///
+ /// Zero the given buffer in a way that will not be optimized away.
+ ///
+ /// Buffer to zero.
+ /// of array.
+ public static void Zero(this T[] buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException(nameof(buffer));
+
+ // Zero buffer
+ for (int i = 0; i < buffer.Length; i++)
+ buffer[i] = default!;
+ }
+
///
/// Validates that the specified and are valid within the given .
///
@@ -101,7 +116,7 @@ private static void RaiseValidationError(T[]? array, int startIndex, int leng
/// Source array.
/// Offset into array.
/// Length of array to copy at offset.
- /// A array of data copied from the specified portion of the source array.
+ /// An array of data copied from the specified portion of the source array.
///
///
/// Returned array will be extended as needed to make it the specified , but
@@ -272,7 +287,10 @@ public static T[] Combine(this T[] source, int sourceOffset, int sourceCount,
///
///
/// of array.
- public static T[] Combine(this T[] source, T[] other1, T[] other2) => new[] { source, other1, other2 }.Combine();
+ public static T[] Combine(this T[] source, T[] other1, T[] other2)
+ {
+ return new[] { source, other1, other2 }.Combine();
+ }
///
/// Combines arrays together into a single array.
@@ -294,7 +312,10 @@ public static T[] Combine(this T[] source, int sourceOffset, int sourceCount,
///
///
/// of array.
- public static T[] Combine(this T[] source, T[] other1, T[] other2, T[] other3) => new[] { source, other1, other2, other3 }.Combine();
+ public static T[] Combine(this T[] source, T[] other1, T[] other2, T[] other3)
+ {
+ return new[] { source, other1, other2, other3 }.Combine();
+ }
///
/// Combines arrays together into a single array.
@@ -317,7 +338,10 @@ public static T[] Combine(this T[] source, int sourceOffset, int sourceCount,
///
///
/// of array.
- public static T[] Combine(this T[] source, T[] other1, T[] other2, T[] other3, T[] other4) => new[] { source, other1, other2, other3, other4 }.Combine();
+ public static T[] Combine(this T[] source, T[] other1, T[] other2, T[] other3, T[] other4)
+ {
+ return new[] { source, other1, other2, other3, other4 }.Combine();
+ }
///
/// Combines array of arrays together into a single array.
@@ -447,33 +471,33 @@ public static int IndexOfSequence(this T[] array, T[] sequenceToFind, int sta
// Search for first item in the sequence, if this doesn't exist then sequence doesn't exist
int index = Array.IndexOf(array, sequenceToFind[0], startIndex, length);
- if (sequenceToFind.Length > 1)
- {
- bool foundSequence = false;
+ if (sequenceToFind.Length <= 1)
+ return index;
- while (index > -1 && !foundSequence)
+ bool foundSequence = false;
+
+ while (index > -1 && !foundSequence)
+ {
+ // See if next bytes in sequence match
+ for (int x = 1; x < sequenceToFind.Length; x++)
{
- // See if next bytes in sequence match
- for (int x = 1; x < sequenceToFind.Length; x++)
+ // Make sure there's enough array remaining to accommodate this item
+ if (index + x < startIndex + length)
{
- // Make sure there's enough array remaining to accommodate this item
- if (index + x < startIndex + length)
- {
- // If sequence doesn't match, search for next first-item
- if (array[index + x].CompareTo(sequenceToFind[x]) != 0)
- {
- index = Array.IndexOf(array, sequenceToFind[0], index + 1, startIndex + length - (index + 1));
- break;
- }
-
- // If each item to find matched, we found the sequence
- foundSequence = x == sequenceToFind.Length - 1;
- }
- else
+ // If sequence doesn't match, search for next first-item
+ if (array[index + x].CompareTo(sequenceToFind[x]) != 0)
{
- // Ran out of array, return -1
- index = -1;
+ index = Array.IndexOf(array, sequenceToFind[0], index + 1, startIndex + length - (index + 1));
+ break;
}
+
+ // If each item to find matched, we found the sequence
+ foundSequence = x == sequenceToFind.Length - 1;
+ }
+ else
+ {
+ // Ran out of array, return -1
+ index = -1;
}
}
}
@@ -566,11 +590,11 @@ public static int CountOfSequence(this T[] array, T[] sequenceToCount, int st
if (index < 0)
return 0;
- // Occurances counter
+ // Occurrences counter
int foundCount = 0;
// Search when the first array element is found, and the sequence can fit in the search range
- bool searching = (index > -1) && (sequenceToCount.Length <= startIndex + searchLength - index);
+ bool searching = sequenceToCount.Length <= startIndex + searchLength - index;
while (searching)
{
@@ -595,7 +619,7 @@ public static int CountOfSequence(this T[] array, T[] sequenceToCount, int st
}
// Continue searching if the array remaining can accommodate the sequence to find
- searching = (index > -1) && (sequenceToCount.Length <= startIndex + searchLength - index);
+ searching = index > -1 && sequenceToCount.Length <= startIndex + searchLength - index;
}
return foundCount;
@@ -653,7 +677,7 @@ public static int CompareTo(this T[]? source, T[]? other) where T : IComparab
int length1 = source.Length;
int length2 = other.Length;
- // If array lengths are unequal, array with largest number of elements is assumed to be largest
+ // If array lengths are unequal, array with the largest number of elements is assumed to be largest
if (length1 != length2)
return length1.CompareTo(length2);
@@ -787,7 +811,10 @@ public static int CompareTo(this T[]? source, int sourceOffset, T[]? other, i
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
- public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2) => new[] { source, other1, other2 }.Combine();
+ public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2)
+ {
+ return new[] { source, other1, other2 }.Combine();
+ }
///
/// Combines buffers together as a single image.
@@ -803,7 +830,10 @@ public static int CompareTo(this T[]? source, int sourceOffset, T[]? other, i
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
- public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2, byte[] other3) => new[] { source, other1, other2, other3 }.Combine();
+ public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2, byte[] other3)
+ {
+ return new[] { source, other1, other2, other3 }.Combine();
+ }
///
/// Combines buffers together as a single image.
@@ -820,7 +850,10 @@ public static int CompareTo(this T[]? source, int sourceOffset, T[]? other, i
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
- public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2, byte[] other3, byte[] other4) => new[] { source, other1, other2, other3, other4 }.Combine();
+ public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2, byte[] other3, byte[] other4)
+ {
+ return new[] { source, other1, other2, other3, other4 }.Combine();
+ }
///
/// Combines an array of buffers together as a single image.
diff --git a/src/Gemstone/Caching/MemoryCache.cs b/src/Gemstone/Caching/MemoryCache.cs
new file mode 100644
index 0000000000..672082a831
--- /dev/null
+++ b/src/Gemstone/Caching/MemoryCache.cs
@@ -0,0 +1,125 @@
+//******************************************************************************************************
+// MemoryCache.cs - Gbtc
+//
+// Copyright © 2024, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 03/17/2024 - Ritchie Carroll
+// Generated original version of source code.
+//
+//******************************************************************************************************
+// ReSharper disable StaticMemberInGenericType
+
+using System;
+using System.Runtime.Caching;
+using Gemstone.TypeExtensions;
+
+namespace Gemstone.Caching;
+
+///
+/// Represents a generic memory cache for a specific type .
+///
+/// Type of value to cache.
+///
+/// Each type T should be unique unless cache can be safely shared.
+///
+internal static class MemoryCache
+{
+ // Desired use case is one static MemoryCache per type T:
+ private static readonly MemoryCache s_memoryCache;
+
+ static MemoryCache()
+ {
+ // Reflected type name is used to ensure unique cache name for generic types
+ string cacheName = $"{nameof(Gemstone)}Cache:{typeof(T).GetReflectedTypeName()}";
+ s_memoryCache = new MemoryCache(cacheName);
+ }
+
+ ///
+ /// Try to get a value from the memory cache.
+ ///
+ /// Name to use as cache key -- this should be unique per .
+ /// Value from cache if already cached; otherwise, default value for .
+ ///
+ public static bool TryGet(string cacheName, out T? value)
+ {
+ if (s_memoryCache.Get(cacheName) is not Lazy cachedValue)
+ {
+ value = default;
+ return false;
+ }
+
+ value = cachedValue.Value;
+ return true;
+ }
+
+ ///
+ /// Gets or adds a value, based on result of , to the memory cache. Cache defaults to a 1-minute expiration.
+ ///
+ /// Name to use as cache key -- this should be unique per .
+ /// Function to generate value to add to cache -- only called if value is not already cached.
+ ///
+ /// Value from cache if already cached; otherwise, new value generated by .
+ ///
+ public static T GetOrAdd(string cacheName, Func valueFactory)
+ {
+ return GetOrAdd(cacheName, 1.0D, valueFactory);
+ }
+
+ ///
+ /// Gets or adds a value, based on result of , to the memory cache.
+ ///
+ /// Name to use as cache key -- this should be unique per .
+ /// Expiration time, in minutes, for cached value.
+ /// Function to generate value to add to cache -- only called if value is not already cached.
+ ///
+ /// Value from cache if already cached; otherwise, new value generated by .
+ ///
+ public static T GetOrAdd(string cacheName, double expirationTime, Func valueFactory)
+ {
+ Lazy newValue = new(valueFactory);
+ Lazy? oldValue;
+
+ try
+ {
+ // Race condition exists here such that memory cache being referenced may
+ // be disposed between access and method invocation - hence the try/catch
+ oldValue = s_memoryCache.AddOrGetExisting(cacheName, newValue, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(expirationTime) }) as Lazy;
+ }
+ catch
+ {
+ oldValue = null;
+ }
+
+ try
+ {
+ return (oldValue ?? newValue).Value;
+ }
+ catch
+ {
+ s_memoryCache.Remove(cacheName);
+ throw;
+ }
+ }
+
+ ///
+ /// Removes a value from the memory cache.
+ ///
+ /// Specific named memory cache instance to remove from cache.
+ public static void Remove(string cacheName)
+ {
+ s_memoryCache.Remove(cacheName);
+ }
+}
diff --git a/src/Gemstone/Collections/HashHelpers.cs b/src/Gemstone/Collections/HashHelpers.cs
index c4bf9fdd50..949e887a8d 100644
--- a/src/Gemstone/Collections/HashHelpers.cs
+++ b/src/Gemstone/Collections/HashHelpers.cs
@@ -85,14 +85,14 @@ public static bool IsPrime(int candidate)
for (int divisor = 3; divisor <= limit; divisor += 2)
{
- if ((candidate % divisor) == 0)
+ if (candidate % divisor == 0)
return false;
}
return true;
}
- return (candidate == 2);
+ return candidate == 2;
}
public static int GetPrime(int min)
@@ -110,9 +110,9 @@ public static int GetPrime(int min)
// outside of our predefined table.
// compute the hard way.
- for (int i = (min | 1); i < int.MaxValue; i += 2)
+ for (int i = min | 1; i < int.MaxValue; i += 2)
{
- if (IsPrime(i) && ((i - 1) % HashPrime != 0))
+ if (IsPrime(i) && (i - 1) % HashPrime != 0)
return i;
}
diff --git a/src/Gemstone/Common.cs b/src/Gemstone/Common.cs
index bdf65222f1..3e608da272 100644
--- a/src/Gemstone/Common.cs
+++ b/src/Gemstone/Common.cs
@@ -46,8 +46,10 @@
using System;
using System.ComponentModel;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Gemstone.Console;
@@ -85,6 +87,8 @@ public enum UpdateType
///
public static class Common
{
+ private static string? s_applicationName;
+
///
/// Determines if the current system is a POSIX style environment.
///
@@ -95,12 +99,19 @@ public static class Common
///
///
/// This property will return true for both MacOSX and Unix environments. Use the Platform property
- /// of the to determine more specific platform type, e.g.,
+ /// of the to determine more specific platform type, e.g.,
/// MacOSX or Unix.
///
///
public static readonly bool IsPosixEnvironment = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+
+ ///
+ /// Gets the name of the current application.
+ ///
+ public static string ApplicationName =>
+ s_applicationName ??= Assembly.GetEntryAssembly()?.GetName().Name ?? Process.GetCurrentProcess().ProcessName;
+
///
/// Converts to a using an appropriate .
///
diff --git a/src/Gemstone/Configuration/AppSettings/AppSettingsExtensions.cs b/src/Gemstone/Configuration/AppSettings/AppSettingsExtensions.cs
new file mode 100644
index 0000000000..1fbf579a26
--- /dev/null
+++ b/src/Gemstone/Configuration/AppSettings/AppSettingsExtensions.cs
@@ -0,0 +1,170 @@
+//******************************************************************************************************
+// AppSettingsExtensions.cs - Gbtc
+//
+// Copyright © 2023, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/13/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+
+namespace Gemstone.Configuration.AppSettings
+{
+ ///
+ /// Defines extensions for managing app settings.
+ ///
+ public static class AppSettingsExtensions
+ {
+ private const string InitialValueKey = "__AppSettings:InitialValue";
+ private const string DescriptionKey = "__AppSettings:Description";
+
+ ///
+ /// Adds an for app settings to the given .
+ ///
+ /// The configuration builder.
+ /// The action to build app settings.
+ /// The configuration builder.
+ ///
+ /// This extension provides a simple way to add default values as well as descriptions for app settings
+ /// directly into an application. The source for these is a simple in-memory collection, and additional
+ /// key/value pairs are added so that the initial value and descriptions of these settings can still be
+ /// retrieved even if the settings themselves get overridden by another configuration source.
+ ///
+ public static IConfigurationBuilder AddAppSettings(this IConfigurationBuilder configurationBuilder, Action buildAction)
+ {
+ IAppSettingsBuilder appSettingsBuilder = new AppSettingsBuilder();
+ buildAction(appSettingsBuilder);
+
+ IEnumerable> appSettings = appSettingsBuilder.Build();
+ configurationBuilder.AddInMemoryCollection(appSettings);
+ return configurationBuilder;
+ }
+
+ ///
+ /// Gets the initial value of the app setting with the given name.
+ ///
+ /// The configuration that contains the app setting.
+ /// The name of the app setting.
+ /// The initial value of the app setting.
+ public static string? GetAppSettingInitialValue(this IConfiguration configuration, string name)
+ {
+ string key = ToInitialValueKey(name);
+ return configuration[key];
+ }
+
+ ///
+ /// Gets the description of the app setting with the given name.
+ ///
+ /// The configuration that contains the app setting.
+ /// The name of the app setting.
+ /// The initial value of the app setting.
+ public static string? GetAppSettingDescription(this IConfiguration configuration, string name)
+ {
+ string key = ToDescriptionKey(name);
+ return configuration[key];
+ }
+
+ ///
+ /// Gets the initial value of the given app setting.
+ ///
+ /// The app setting.
+ /// The initial value of the app setting.
+ public static string? GetAppSettingInitialValue(this IConfigurationSection setting) =>
+ setting[InitialValueKey];
+
+ ///
+ /// Gets the description of the given app setting.
+ ///
+ /// The app setting.
+ /// The description of the app setting.
+ public static string? GetAppSettingDescription(this IConfigurationSection setting) =>
+ setting[DescriptionKey];
+
+ private static string ToInitialValueKey(string appSettingName) =>
+ $"{appSettingName}:{InitialValueKey}";
+
+ private static string ToDescriptionKey(string appSettingName) =>
+ $"{appSettingName}:{DescriptionKey}";
+
+ // Implementation of IAppSettingsBuilder that works
+ // with the extension methods defined in this class.
+ private class AppSettingsBuilder : IAppSettingsBuilder
+ {
+ private class AppSetting
+ {
+ public string Name { get; }
+ public string Value { get; }
+ public string Description { get; }
+
+ public AppSetting(string name, string value, string description)
+ {
+ Name = name;
+ Value = value;
+ Description = description;
+ }
+
+ public KeyValuePair ToKeyValuePair() => new(Name, Value);
+
+ public KeyValuePair ToInitialValuePair()
+ {
+ string key = ToInitialValueKey(Name);
+ return new KeyValuePair(key, Value);
+ }
+
+ public KeyValuePair ToDescriptionPair()
+ {
+ string key = ToDescriptionKey(Name);
+ return new KeyValuePair(key, Description);
+ }
+ }
+
+ private Dictionary AppSettingLookup { get; }
+
+ public AppSettingsBuilder() =>
+ AppSettingLookup = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public IAppSettingsBuilder Add(string name, string value, string description)
+ {
+ if (AppSettingLookup.ContainsKey(name))
+ {
+ //throw new ArgumentException($"Unable to add duplicate app setting: {name}", nameof(name));
+ Debug.WriteLine($"Duplicate app setting encountered: {name}\" - this can be normal.");
+ return this;
+ }
+
+ AppSetting appSetting = new(name, value, description);
+ AppSettingLookup.Add(name, appSetting);
+ return this;
+ }
+
+ public IEnumerable> Build()
+ {
+ return AppSettingLookup.Values.SelectMany(appSetting => new[]
+ {
+ appSetting.ToKeyValuePair(),
+ appSetting.ToInitialValuePair(),
+ appSetting.ToDescriptionPair()
+ });
+ }
+ }
+ }
+}
diff --git a/src/Gemstone/Configuration/AppSettings/IAppSettingsBuilder.cs b/src/Gemstone/Configuration/AppSettings/IAppSettingsBuilder.cs
new file mode 100644
index 0000000000..49fceb15eb
--- /dev/null
+++ b/src/Gemstone/Configuration/AppSettings/IAppSettingsBuilder.cs
@@ -0,0 +1,49 @@
+//******************************************************************************************************
+// IAppSettingsBuilder.cs - Gbtc
+//
+// Copyright © 2023, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/13/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System.Collections.Generic;
+
+namespace Gemstone.Configuration.AppSettings
+{
+ ///
+ /// Builder for app settings with descriptions.
+ ///
+ public interface IAppSettingsBuilder
+ {
+ ///
+ /// Adds an app setting to the builder.
+ ///
+ /// The name of the setting.
+ /// The value of the setting.
+ /// A description of the setting.
+ /// The app settings builder.
+ /// is a duplicate of a previously added app setting
+ public IAppSettingsBuilder Add(string name, string value, string description);
+
+ ///
+ /// Converts the app settings into a collection of key/value pairs.
+ ///
+ /// The collection of key/value pairs.
+ public IEnumerable> Build();
+ }
+}
diff --git a/src/Gemstone/Configuration/AppSettings/NamespaceDoc.cs b/src/Gemstone/Configuration/AppSettings/NamespaceDoc.cs
new file mode 100644
index 0000000000..9f631ec651
--- /dev/null
+++ b/src/Gemstone/Configuration/AppSettings/NamespaceDoc.cs
@@ -0,0 +1,36 @@
+//******************************************************************************************************
+// NamespaceDoc.cs - Gbtc
+//
+// Copyright © 2023, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/14/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System.Runtime.CompilerServices;
+
+namespace Gemstone.Configuration.AppSettings
+{
+ ///
+ /// The namespace contains extension functions related
+ /// to the definition of default app settings for Gemstone projects.
+ ///
+ [CompilerGenerated]
+ class NamespaceDoc
+ {
+ }
+}
diff --git a/src/Gemstone/Configuration/IDefineSettings.cs b/src/Gemstone/Configuration/IDefineSettings.cs
new file mode 100644
index 0000000000..6cf3b04aaa
--- /dev/null
+++ b/src/Gemstone/Configuration/IDefineSettings.cs
@@ -0,0 +1,41 @@
+//******************************************************************************************************
+// IDefineSettings.cs - Gbtc
+//
+// Copyright © 2024, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 03/28/2024 - Ritchie Carroll
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+namespace Gemstone.Configuration;
+
+///
+/// Defines as interface that specifies that this object can define settings for a config file.
+///
+public interface IDefineSettings
+{
+ ///
+ /// Establishes default settings for the config file.
+ ///
+ /// Settings instance used to hold configuration.
+ /// The config file settings category under which the settings are defined.
+#if NET
+ static abstract void DefineSettings(Settings settings, string settingsCategory);
+#else
+ static void DefineSettings(Settings settings, string settingsCategory) { }
+#endif
+}
diff --git a/src/Gemstone/Configuration/INIConfigurationExtensions/INIConfigurationExtensions.cs b/src/Gemstone/Configuration/INIConfigurationExtensions/INIConfigurationExtensions.cs
new file mode 100644
index 0000000000..e2ad649d70
--- /dev/null
+++ b/src/Gemstone/Configuration/INIConfigurationExtensions/INIConfigurationExtensions.cs
@@ -0,0 +1,160 @@
+//******************************************************************************************************
+// INIConfigurationExtensions.cs - Gbtc
+//
+// Copyright © 2023, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/14/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gemstone.Configuration.AppSettings;
+using Microsoft.Extensions.Configuration;
+
+namespace Gemstone.Configuration.INIConfigurationExtensions;
+
+///
+/// Defines extensions for setting up configuration defaults for Gemstone projects.
+///
+public static class INIConfigurationExtensions
+{
+ ///
+ /// Generates the contents of an INI file based on the configuration settings.
+ ///
+ /// Source configuration.
+ /// Flag that determines whether the actual value (instead of default only) of each setting should be written to the INI file.
+ /// Flag that determines whether long description lines should be split into multiple lines.
+ /// Generated INI file contents.
+ public static string GenerateINIFileContents(this IConfiguration configuration, bool writeValue, bool splitDescriptionLines)
+ {
+ static IEnumerable Split(string str, int maxLineLength)
+ {
+ string[] lines = str.Split(["\r\n", "\n"], StringSplitOptions.None);
+
+ foreach (string line in lines)
+ {
+ string leftover = line.TrimStart();
+
+ // Lines in the original text that contain
+ // only whitespace will be returned as-is
+ if (leftover.Length == 0)
+ yield return line;
+
+ while (leftover.Length > 0)
+ {
+ char[] chars = leftover
+ .Take(maxLineLength + 1)
+ .Reverse()
+ .SkipWhile(c => !char.IsWhiteSpace(c))
+ .SkipWhile(char.IsWhiteSpace)
+ .Reverse()
+ .ToArray();
+
+ if (!chars.Any())
+ {
+ // Tokens that are longer than the maximum length will
+ // be returned (in their entirety) on their own line;
+ // maxLineLength is just a suggestion
+ chars = leftover
+ .TakeWhile(c => !char.IsWhiteSpace(c))
+ .ToArray();
+ }
+
+ string splitLine = new(chars);
+ leftover = leftover.Substring(splitLine.Length).TrimStart();
+ yield return splitLine;
+ }
+ }
+ }
+
+ static bool HasAppSetting(IConfiguration section) =>
+ section.GetChildren().Any(HasAppSettingDescription);
+
+ static bool HasAppSettingDescription(IConfigurationSection setting) =>
+ setting.GetAppSettingDescription() is not null;
+
+ static string ConvertSettingToINI(IConfigurationSection setting, bool writeValue, bool splitDescriptionLines)
+ {
+ string key = setting.Key;
+ string value = setting.Value ?? "";
+ string initialValue = setting.GetAppSettingInitialValue() ?? "";
+ string description = setting.GetAppSettingDescription() ?? "";
+
+ // Break up long descriptions to be more readable in the INI file
+ IEnumerable descriptionLines = (splitDescriptionLines ?
+ Split(description, 78) :
+ description.Split(["\r\n", "\n"], StringSplitOptions.None))
+ .Select(line => $"; {line}");
+
+ string multilineDescription = string.Join(Environment.NewLine, descriptionLines);
+
+ string[] lines;
+
+ if (writeValue && value != initialValue)
+ {
+ lines =
+ [
+ $"{multilineDescription}",
+ $"{key}={value}"
+ ];
+ }
+ else
+ {
+ lines =
+ [
+ $"{multilineDescription}",
+ $";{key}={initialValue}"
+ ];
+ }
+
+ return string.Join(Environment.NewLine, lines);
+ }
+
+ static string ConvertConfigToINI(IConfiguration config, bool writeValue, bool splitDescriptionLines)
+ {
+ IEnumerable settings = config.GetChildren()
+ .Where(HasAppSettingDescription)
+ .OrderBy(setting => setting.Key)
+ .Select(setting => ConvertSettingToINI(setting, writeValue, splitDescriptionLines));
+
+ string settingSeparator = string.Format("{0}{0}", Environment.NewLine);
+ string settingsText = string.Join(settingSeparator, settings);
+
+ // The root section has no heading
+ if (config is not ConfigurationSection section)
+ return settingsText;
+
+ return string.Join(Environment.NewLine, $"[{section.Key}]", settingsText);
+ }
+
+ // Root MUST go before all other sections, so the order is important:
+ // 1. Sort by section key
+ // 2. Prepend root
+ // 3. Filter out sections without any app settings
+ IEnumerable appSettingsSections = configuration.AsEnumerable()
+ .Select(kvp => configuration.GetSection(kvp.Key))
+ .OrderBy(section => section.Key)
+ .Prepend(configuration)
+ .Where(HasAppSetting)
+ .Select(config => ConvertConfigToINI(config, writeValue, splitDescriptionLines));
+
+ string sectionSeparator = string.Format("{0}{0}", Environment.NewLine);
+ return string.Join(sectionSeparator, appSettingsSections);
+ }
+}
diff --git a/src/Gemstone/Configuration/INIConfigurationExtensions/NamespaceDoc.cs b/src/Gemstone/Configuration/INIConfigurationExtensions/NamespaceDoc.cs
new file mode 100644
index 0000000000..15c3838690
--- /dev/null
+++ b/src/Gemstone/Configuration/INIConfigurationExtensions/NamespaceDoc.cs
@@ -0,0 +1,34 @@
+//******************************************************************************************************
+// NamespaceDoc.cs - Gbtc
+//
+// Copyright © 2024, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 04/02/2024 - Ritchie Carroll
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System.Runtime.CompilerServices;
+
+namespace Gemstone.Configuration.INIConfigurationExtensions;
+
+///
+/// Contains helper and extension methods for INI configuration files.
+///
+[CompilerGenerated]
+class NamespaceDoc
+{
+}
diff --git a/src/Gemstone/Configuration/INIConfigurationHelpers.cs b/src/Gemstone/Configuration/INIConfigurationHelpers.cs
new file mode 100644
index 0000000000..f13ab090eb
--- /dev/null
+++ b/src/Gemstone/Configuration/INIConfigurationHelpers.cs
@@ -0,0 +1,61 @@
+//******************************************************************************************************
+// INIConfigurationHelpers.cs - Gbtc
+//
+// Copyright © 2024, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/14/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System;
+using System.IO;
+
+namespace Gemstone.Configuration;
+
+///
+/// Defines helper functions for working with INI configuration files.
+///
+internal static class INIConfigurationHelpers
+{
+ ///
+ /// Gets file path for INI configuration file.
+ ///
+ /// Target file INI file name.
+ /// INI file path.
+ public static string GetINIFilePath(string fileName)
+ {
+ Environment.SpecialFolder specialFolder = Environment.SpecialFolder.CommonApplicationData;
+ string appDataPath = Environment.GetFolderPath(specialFolder);
+ return Path.Combine(appDataPath, Common.ApplicationName, fileName);
+ }
+
+ ///
+ /// Gets an INI file writer for the specified path.
+ ///
+ /// Path for INI file.
+ /// INI file write at specified path.
+ public static TextWriter GetINIFileWriter(string path)
+ {
+ if (File.Exists(path))
+ return File.CreateText(path);
+
+ string directoryPath = Path.GetDirectoryName(path) ?? string.Empty;
+ Directory.CreateDirectory(directoryPath);
+
+ return File.CreateText(path);
+ }
+}
diff --git a/src/Gemstone/Configuration/IPersistSettings.cs b/src/Gemstone/Configuration/IPersistSettings.cs
new file mode 100644
index 0000000000..42d367f6c0
--- /dev/null
+++ b/src/Gemstone/Configuration/IPersistSettings.cs
@@ -0,0 +1,58 @@
+//******************************************************************************************************
+// IPersistSettings.cs - Gbtc
+//
+// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
+// not use this file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://www.opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 03/21/2007 - Pinal C. Patel
+// Generated original version of source code.
+// 09/16/2008 - Pinal C. Patel
+// Converted code to C#.
+// 09/29/2008 - Pinal C. Patel
+// Reviewed code comments.
+// 09/14/2009 - Stephen C. Wills
+// Added new header and license agreement
+// 12/14/2012 - Starlynn Danyelle Gilliam
+// Modified Header.
+//
+//******************************************************************************************************
+
+namespace Gemstone.Configuration;
+
+///
+/// Defines as interface that specifies that this object can persist settings to a config file.
+///
+public interface IPersistSettings : IDefineSettings
+{
+ ///
+ /// Determines whether the object settings are to be persisted to the config file.
+ ///
+ bool PersistSettings { get; set; }
+
+ ///
+ /// Gets or sets the category name under which the object settings are persisted in the config file.
+ ///
+ string SettingsCategory { get; set; }
+
+ ///
+ /// Saves settings to the config file.
+ ///
+ void SaveSettings();
+
+ ///
+ /// Loads saved settings from the config file.
+ ///
+ void LoadSettings();
+}
diff --git a/src/Gemstone/Configuration/ReadOnly/NamespaceDoc.cs b/src/Gemstone/Configuration/ReadOnly/NamespaceDoc.cs
new file mode 100644
index 0000000000..a36a74dff3
--- /dev/null
+++ b/src/Gemstone/Configuration/ReadOnly/NamespaceDoc.cs
@@ -0,0 +1,39 @@
+//******************************************************************************************************
+// NamespaceDoc.cs - Gbtc
+//
+// Copyright © 2023, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/14/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Configuration;
+
+namespace Gemstone.Configuration.ReadOnly
+{
+ ///
+ /// The namespace contains a wrapper for
+ /// to prevent calls to
+ /// from
+ /// reaching the underlying .
+ ///
+ [CompilerGenerated]
+ class NamespaceDoc
+ {
+ }
+}
diff --git a/src/Gemstone/Configuration/ReadOnly/ReadOnlyConfigurationExtensions.cs b/src/Gemstone/Configuration/ReadOnly/ReadOnlyConfigurationExtensions.cs
new file mode 100644
index 0000000000..19788079ee
--- /dev/null
+++ b/src/Gemstone/Configuration/ReadOnly/ReadOnlyConfigurationExtensions.cs
@@ -0,0 +1,119 @@
+//******************************************************************************************************
+// ReadOnlyConfigurationExtensions.cs - Gbtc
+//
+// Copyright © 2023, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+// Code Modification History:
+// ----------------------------------------------------------------------------------------------------
+// 06/12/2020 - Stephen C. Wills
+// Generated original version of source code.
+//
+//******************************************************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Configuration;
+
+namespace Gemstone.Configuration.ReadOnly
+{
+ ///
+ /// Defines extensions for adding read-only configuration providers.
+ ///
+ public static class ReadOnlyConfigurationExtensions
+ {
+ private class ReferenceEqualityComparer : IEqualityComparer