mirror of https://github.com/dahall/Vanara.git
340 lines
17 KiB
C#
340 lines
17 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Vanara.PInvoke;
|
|
using static Vanara.PInvoke.Kernel32;
|
|
|
|
namespace Vanara.Configuration
|
|
{
|
|
/// <summary>Provides access to an initialization (.ini) file and values.</summary>
|
|
public class InitializationFile
|
|
{
|
|
/// <summary>Initializes a new instance of the <see cref="InitializationFile"/> class.</summary>
|
|
/// <param name="fullName">
|
|
/// The name of the initialization file. If this parameter does not contain a full path for the file, the function searches the
|
|
/// Windows directory for the file. If the file does not exist and lpFileName does not contain a full path, the function creates the
|
|
/// file in the Windows directory.
|
|
/// </param>
|
|
public InitializationFile(string fullName)
|
|
{
|
|
FullName = fullName;
|
|
Sections = new InitializationFileSections(this);
|
|
}
|
|
|
|
private InitializationFile()
|
|
{
|
|
}
|
|
|
|
/// <summary>Gets the name of the initialization file.</summary>
|
|
/// <value>The name of the initialization file.</value>
|
|
public string FullName { get; }
|
|
|
|
/// <summary>Gets the sections of the initialization file.</summary>
|
|
/// <value>The sections.</value>
|
|
public InitializationFileSections Sections { get; }
|
|
|
|
/// <summary>Provides access to the key/value pairs within an initialization file's section.</summary>
|
|
/// <seealso cref="IDictionary{TKey, TValue}"/>
|
|
public class InitializationFileSection : IDictionary<string, string>
|
|
{
|
|
private static readonly uint AutoChSz = (uint)Extensions.StringHelper.GetCharSize();
|
|
private static readonly string UnqVal = Guid.NewGuid().ToString();
|
|
|
|
private readonly InitializationFile file;
|
|
|
|
internal InitializationFileSection(InitializationFile privateProfileFile, string name)
|
|
{
|
|
file = privateProfileFile;
|
|
Name = name;
|
|
}
|
|
|
|
/// <summary>Gets the number of elements contained in the <see cref="ICollection{T}"/>.</summary>
|
|
public int Count => KeyNames.Length;
|
|
|
|
/// <summary>Gets an <see cref="ICollection{T}"/> containing the keys of the <see cref="IDictionary{TKey, TValue}"/>.</summary>
|
|
public ICollection<string> Keys => KeyNames;
|
|
|
|
/// <summary>Gets the name of the section.</summary>
|
|
/// <value>The section name.</value>
|
|
public string Name { get; }
|
|
|
|
/// <summary>Gets an <see cref="ICollection{T}"/> containing the values in the <see cref="IDictionary{TKey, TValue}"/>.</summary>
|
|
public ICollection<string> Values => GetSection().Select(kv => kv.Value).ToList();
|
|
|
|
/// <summary>Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.</summary>
|
|
bool ICollection<KeyValuePair<string, string>>.IsReadOnly => false;
|
|
|
|
private string[] KeyNames => GetPrivateProfileString(Name, null, file.FullName);
|
|
|
|
/// <summary>Gets or sets the <see cref="string"/> with the specified key.</summary>
|
|
/// <value>The <see cref="string"/>.</value>
|
|
/// <param name="key">The key.</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="System.ArgumentException">key not found or unable to return key value - key</exception>
|
|
public string this[string key]
|
|
{
|
|
get => TryGetValue(key, out var value) ? value : throw new ArgumentException("key not found or unable to return key value", nameof(key));
|
|
set => Add(key, value);
|
|
}
|
|
|
|
/// <summary>Adds an element with the provided key and value to the <see cref="IDictionary{TKey, TValue}"/>.</summary>
|
|
/// <param name="key">The object to use as the key of the element to add.</param>
|
|
/// <param name="value">The object to use as the value of the element to add.</param>
|
|
public void Add(string key, string value) => Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileString(Name, key, value, file.FullName));
|
|
|
|
/// <summary>Adds an item to the <see cref="ICollection{T}"/>.</summary>
|
|
/// <param name="item">The object to add to the <see cref="ICollection{T}"/>.</param>
|
|
public void Add(KeyValuePair<string, string> item) => Add(item.Key, item.Value);
|
|
|
|
/// <summary>Adds the range.</summary>
|
|
/// <param name="items">The items.</param>
|
|
public void AddRange(IEnumerable<KeyValuePair<string, string>> items)
|
|
{
|
|
foreach (KeyValuePair<string, string> item in items)
|
|
Add(item.Key, item.Value);
|
|
}
|
|
|
|
/// <summary>Removes all items from the <see cref="ICollection{T}"/>.</summary>
|
|
public void Clear() => Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileSection(Name, new string[0], file.FullName));
|
|
|
|
/// <summary>Determines whether this instance contains the object.</summary>
|
|
/// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, <see langword="false"/>.
|
|
/// </returns>
|
|
public bool Contains(KeyValuePair<string, string> item) => TryGetValue(item.Key, out var value) && value == item.Value;
|
|
|
|
/// <summary>Determines whether the <see cref="IDictionary{TKey, TValue}"/> contains an element with the specified key.</summary>
|
|
/// <param name="key">The key to locate in the <see cref="IDictionary{TKey, TValue}"/>.</param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if the <see cref="IDictionary{TKey, TValue}"/> contains an element with the key; otherwise, <see langword="false"/>.
|
|
/// </returns>
|
|
public bool ContainsKey(string key) => TryGetValue(key, out _);
|
|
|
|
/// <summary>
|
|
/// Copies the elements of the <see cref="ICollection{T}"/> to an <see cref="Array"/>, starting at a particular <see
|
|
/// cref="Array"/> index.
|
|
/// </summary>
|
|
/// <param name="array">
|
|
/// The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ICollection{T}"/>.
|
|
/// The <see cref="Array"/> must have zero-based indexing.
|
|
/// </param>
|
|
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
|
|
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
|
|
{
|
|
KeyValuePair<string, string>[] s = GetSection();
|
|
Array.Copy(s, 0, array, arrayIndex, s.Length);
|
|
}
|
|
|
|
/// <summary>Returns an enumerator that iterates through the collection.</summary>
|
|
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
|
|
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => GetSection().Cast<KeyValuePair<string, string>>().GetEnumerator();
|
|
|
|
/// <summary>Removes the element with the specified key from the <see cref="IDictionary{TKey, TValue}"/>.</summary>
|
|
/// <param name="key">The key of the element to remove.</param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if the element is successfully removed; otherwise, <see langword="false"/>. This method also returns
|
|
/// <see langword="false"/> if <paramref name="key"/> was not found in the original <see cref="IDictionary{TKey, TValue}"/>.
|
|
/// </returns>
|
|
public bool Remove(string key) => WritePrivateProfileString(Name, key, null, file.FullName);
|
|
|
|
/// <summary>Removes the first occurrence of a specific object from the <see cref="ICollection{T}"/>.</summary>
|
|
/// <param name="item">The object to remove from the <see cref="ICollection{T}"/>.</param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if <paramref name="item"/> was successfully removed from the <see cref="ICollection{T}"/>; otherwise,
|
|
/// <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item"/> is not found in the
|
|
/// original <see cref="ICollection{T}"/>.
|
|
/// </returns>
|
|
public bool Remove(KeyValuePair<string, string> item) => Contains(item) && Remove(item.Key);
|
|
|
|
/// <summary>Sets the value of an element with the provided key.</summary>
|
|
/// <param name="key">The key of the element to set.</param>
|
|
/// <param name="value">The value of the element to set.</param>
|
|
public void SetValue(string key, string value) => Add(key, value);
|
|
|
|
/// <summary>Sets the value of an element with the provided key.</summary>
|
|
/// <typeparam name="T">
|
|
/// If <typeparamref name="T"/> is <see cref="uint"/> or <see cref="int"/>, the value is written as text using
|
|
/// <c>WritePrivateProfileString</c>. If any other type, the value is written using <c>WritePrivateProfileStruct</c> resulting
|
|
/// in a byte array string.
|
|
/// </typeparam>
|
|
/// <param name="key">The key of the element to set.</param>
|
|
/// <param name="value">The value of the element to set.</param>
|
|
public void SetValue<T>(string key, in T value) where T : unmanaged
|
|
{
|
|
if (value is int || value is uint)
|
|
Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileString(Name, key, value.ToString(), file.FullName));
|
|
else
|
|
Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileStruct(Name, key, value, file.FullName));
|
|
}
|
|
|
|
/// <summary>Gets the value associated with the specified key.</summary>
|
|
/// <typeparam name="T">
|
|
/// If <typeparamref name="T"/> is <see cref="uint"/> or <see cref="int"/>, the text value is retrieved using
|
|
/// <c>GetPrivateProfileInt</c>. If any other type, the byte array value is read into the structure using <c>GetPrivateProfileStruct</c>.
|
|
/// </typeparam>
|
|
/// <param name="key">The key whose value to get.</param>
|
|
/// <param name="value">
|
|
/// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for
|
|
/// the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if the object that implements <see cref="IDictionary{TKey, TValue}"/> contains an element with the
|
|
/// specified key; otherwise, <see langword="false"/>.
|
|
/// </returns>
|
|
public bool TryGetValue<T>(string key, out T value) where T : unmanaged
|
|
{
|
|
value = default;
|
|
if (value is int || value is uint)
|
|
{
|
|
if (TryGetValue(key, out var s) && int.TryParse(s, out var i))
|
|
{
|
|
value = (T)Convert.ChangeType(i, typeof(T));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return TryGetValue(key, out _) && GetPrivateProfileStruct(Name, key, out value, file.FullName);
|
|
}
|
|
|
|
/// <summary>Gets the value associated with the specified key.</summary>
|
|
/// <param name="key">The key whose value to get.</param>
|
|
/// <param name="value">
|
|
/// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for
|
|
/// the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if the object that implements <see cref="IDictionary{TKey, TValue}"/> contains an element with the
|
|
/// specified key; otherwise, <see langword="false"/>.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentNullException">key</exception>
|
|
public bool TryGetValue(string key, out string value)
|
|
{
|
|
if (key is null) throw new ArgumentNullException(nameof(key));
|
|
var chars = 1024U;
|
|
using var mem = new SafeHeapBlock(chars * AutoChSz);
|
|
while (true)
|
|
{
|
|
var ret = GetPrivateProfileString(Name, key, UnqVal, mem, chars, file.FullName);
|
|
if (ret == UnqVal.Length && mem.ToString(-1) == UnqVal) break;
|
|
if (ret != chars - 1) { value = mem.ToString(-1); return true; }
|
|
if (chars == short.MaxValue) break;
|
|
mem.Size = (chars = Math.Min(chars * 2, (uint)short.MaxValue)) * AutoChSz;
|
|
}
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
|
/// <returns>An <see cref="System.Collections.IEnumerator"/> object that can be used to iterate through the collection.</returns>
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
private KeyValuePair<string, string>[] GetSection() => Array.ConvertAll(GetPrivateProfileSection(Name, file.FullName), Parse);
|
|
|
|
private KeyValuePair<string, string> Parse(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));
|
|
var eq = value.IndexOf('=');
|
|
if (eq < 0) throw new ArgumentException("Invalid key/value pair value.");
|
|
return new KeyValuePair<string, string>(value.Substring(0, eq), value.Length > eq ? value.Substring(eq + 1) : string.Empty);
|
|
}
|
|
}
|
|
|
|
/// <summary>Provides a collection of sections within an initialization file.</summary>
|
|
/// <seealso cref="ICollection{T}"/>
|
|
public class InitializationFileSections : ICollection<InitializationFileSection>
|
|
{
|
|
private readonly InitializationFile file;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="InitializationFileSections"/> class.</summary>
|
|
/// <param name="privateProfileFile">The private profile file.</param>
|
|
public InitializationFileSections(InitializationFile privateProfileFile) => file = privateProfileFile;
|
|
|
|
/// <summary>Gets the number of elements contained in the <see cref="ICollection{T}"/>.</summary>
|
|
public int Count => SectionNames.Length;
|
|
|
|
/// <summary>Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.</summary>
|
|
public bool IsReadOnly => false;
|
|
|
|
private string[] SectionNames => GetPrivateProfileSectionNames(file.FullName);
|
|
|
|
/// <summary>Gets the <see cref="InitializationFileSection"/> with the specified section name.</summary>
|
|
/// <value>The <see cref="InitializationFileSection"/>.</value>
|
|
/// <param name="sectionName">Name of the section.</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="System.Collections.Generic.KeyNotFoundException"></exception>
|
|
public InitializationFileSection this[string sectionName] => new InitializationFileSection(file, sectionName);
|
|
|
|
/// <summary>Adds the specified section name.</summary>
|
|
/// <param name="sectionName">Name of the section.</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="System.ArgumentException">Duplicate name. - sectionName</exception>
|
|
public InitializationFileSection Add(string sectionName)
|
|
{
|
|
if (Contains(sectionName))
|
|
throw new ArgumentException("Duplicate name.", nameof(sectionName));
|
|
var ret = new InitializationFileSection(file, sectionName);
|
|
Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileSection(sectionName, new string[0], file.FullName));
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>Removes all items from the <see cref="ICollection{T}"/>.</summary>
|
|
public void Clear()
|
|
{
|
|
foreach (var s in SectionNames)
|
|
Remove(s);
|
|
}
|
|
|
|
/// <summary>Determines whether this instance contains the object.</summary>
|
|
/// <param name="sectionName">Name of the section.</param>
|
|
/// <returns><see langword="true"/> if [contains] [the specified section name]; otherwise, <see langword="false"/>.</returns>
|
|
public bool Contains(string sectionName) => SectionNames.Contains(sectionName, StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Copies the elements of the <see cref="ICollection{T}"/> to an <see cref="Array"/>, starting at a particular <see
|
|
/// cref="Array"/> index.
|
|
/// </summary>
|
|
/// <param name="array">
|
|
/// The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ICollection{T}"/>.
|
|
/// The <see cref="Array"/> must have zero-based indexing.
|
|
/// </param>
|
|
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
|
|
public void CopyTo(InitializationFileSection[] array, int arrayIndex) => Array.Copy(Array.ConvertAll(SectionNames, n => new InitializationFileSection(file, n)), 0, array, arrayIndex, Count);
|
|
|
|
/// <summary>Returns an enumerator that iterates through the collection.</summary>
|
|
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
|
|
public IEnumerator<InitializationFileSection> GetEnumerator() => SectionNames.Select(n => new InitializationFileSection(file, n)).GetEnumerator();
|
|
|
|
/// <summary>Removes the specified section name.</summary>
|
|
/// <param name="sectionName">Name of the section.</param>
|
|
/// <returns></returns>
|
|
public bool Remove(string sectionName) => WritePrivateProfileString(sectionName, null, null, file.FullName);
|
|
|
|
/// <summary>Removes the first occurrence of a specific object from the <see cref="ICollection{T}"/>.</summary>
|
|
/// <param name="item">The object to remove from the <see cref="ICollection{T}"/>.</param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if <paramref name="item"/> was successfully removed from the <see cref="ICollection{T}"/>; otherwise,
|
|
/// <see langword="false"/>. This method also returns <see langword="false"/> if <paramref name="item"/> is not found in the
|
|
/// original <see cref="ICollection{T}"/>.
|
|
/// </returns>
|
|
public bool Remove(InitializationFileSection item) => Remove(item.Name);
|
|
|
|
/// <summary>Adds an item to the <see cref="ICollection{T}"/>.</summary>
|
|
/// <param name="item">The object to add to the <see cref="ICollection{T}"/>.</param>
|
|
void ICollection<InitializationFileSection>.Add(InitializationFileSection item) => Add(item.Name);
|
|
|
|
/// <summary>Determines whether this instance contains the object.</summary>
|
|
/// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, <see langword="false"/>.
|
|
/// </returns>
|
|
bool ICollection<InitializationFileSection>.Contains(InitializationFileSection item) => Contains(item.Name);
|
|
|
|
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
|
/// <returns>An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.</returns>
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
}
|
|
}
|
|
}
|