Vanara/PInvoke/Kernel32/InitializationFile.cs

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();
}
}
}