using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.Configuration
{
/// Provides access to an initialization (.ini) file and values.
public class InitializationFile
{
/// Initializes a new instance of the class.
///
/// 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.
///
public InitializationFile(string fullName)
{
FullName = fullName;
Sections = new InitializationFileSections(this);
}
private InitializationFile()
{
}
/// Gets the name of the initialization file.
/// The name of the initialization file.
public string FullName { get; }
/// Gets the sections of the initialization file.
/// The sections.
public InitializationFileSections Sections { get; }
/// Provides access to the key/value pairs within an initialization file's section.
///
public class InitializationFileSection : IDictionary
{
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;
}
/// Gets the number of elements contained in the .
public int Count => KeyNames.Length;
/// Gets an containing the keys of the .
public ICollection Keys => KeyNames;
/// Gets the name of the section.
/// The section name.
public string Name { get; }
/// Gets an containing the values in the .
public ICollection Values => GetSection().Select(kv => kv.Value).ToList();
/// Gets a value indicating whether the is read-only.
bool ICollection>.IsReadOnly => false;
private string[] KeyNames => GetPrivateProfileString(Name, null, file.FullName);
/// Gets or sets the with the specified key.
/// The .
/// The key.
///
/// key not found or unable to return key value - key
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);
}
/// Adds an element with the provided key and value to the .
/// The object to use as the key of the element to add.
/// The object to use as the value of the element to add.
public void Add(string key, string value) => Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileString(Name, key, value, file.FullName));
/// Adds an item to the .
/// The object to add to the .
public void Add(KeyValuePair item) => Add(item.Key, item.Value);
/// Adds the range.
/// The items.
public void AddRange(IEnumerable> items)
{
foreach (KeyValuePair item in items)
Add(item.Key, item.Value);
}
/// Removes all items from the .
public void Clear() => Win32Error.ThrowLastErrorIfFalse(WritePrivateProfileSection(Name, new string[0], file.FullName));
/// Determines whether this instance contains the object.
/// The object to locate in the .
///
/// if is found in the ; otherwise, .
///
public bool Contains(KeyValuePair item) => TryGetValue(item.Key, out var value) && value == item.Value;
/// Determines whether the contains an element with the specified key.
/// The key to locate in the .
///
/// if the contains an element with the key; otherwise, .
///
public bool ContainsKey(string key) => TryGetValue(key, out _);
///
/// Copies the elements of the to an , starting at a particular index.
///
///
/// The one-dimensional that is the destination of the elements copied from .
/// The must have zero-based indexing.
///
/// The zero-based index in at which copying begins.
public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
KeyValuePair[] s = GetSection();
Array.Copy(s, 0, array, arrayIndex, s.Length);
}
/// Returns an enumerator that iterates through the collection.
/// An enumerator that can be used to iterate through the collection.
public IEnumerator> GetEnumerator() => GetSection().Cast>().GetEnumerator();
/// Removes the element with the specified key from the .
/// The key of the element to remove.
///
/// if the element is successfully removed; otherwise, . This method also returns
/// if was not found in the original .
///
public bool Remove(string key) => WritePrivateProfileString(Name, key, null, file.FullName);
/// Removes the first occurrence of a specific object from the .
/// The object to remove from the .
///
/// if was successfully removed from the ; otherwise,
/// . This method also returns if is not found in the
/// original .
///
public bool Remove(KeyValuePair item) => Contains(item) && Remove(item.Key);
/// Sets the value of an element with the provided key.
/// The key of the element to set.
/// The value of the element to set.
public void SetValue(string key, string value) => Add(key, value);
/// Sets the value of an element with the provided key.
///
/// If is or , the value is written as text using
/// WritePrivateProfileString. If any other type, the value is written using WritePrivateProfileStruct resulting
/// in a byte array string.
///
/// The key of the element to set.
/// The value of the element to set.
public void SetValue(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));
}
/// Gets the value associated with the specified key.
///
/// If is or , the text value is retrieved using
/// GetPrivateProfileInt. If any other type, the byte array value is read into the structure using GetPrivateProfileStruct.
///
/// The key whose value to get.
///
/// 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 parameter. This parameter is passed uninitialized.
///
///
/// if the object that implements contains an element with the
/// specified key; otherwise, .
///
public bool TryGetValue(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);
}
/// Gets the value associated with the specified key.
/// The key whose value to get.
///
/// 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 parameter. This parameter is passed uninitialized.
///
///
/// if the object that implements contains an element with the
/// specified key; otherwise, .
///
/// key
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;
}
/// Returns an enumerator that iterates through a collection.
/// An object that can be used to iterate through the collection.
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private KeyValuePair[] GetSection() => Array.ConvertAll(GetPrivateProfileSection(Name, file.FullName), Parse);
private KeyValuePair 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(value.Substring(0, eq), value.Length > eq ? value.Substring(eq + 1) : string.Empty);
}
}
/// Provides a collection of sections within an initialization file.
///
public class InitializationFileSections : ICollection
{
private readonly InitializationFile file;
/// Initializes a new instance of the class.
/// The private profile file.
public InitializationFileSections(InitializationFile privateProfileFile) => file = privateProfileFile;
/// Gets the number of elements contained in the .
public int Count => SectionNames.Length;
/// Gets a value indicating whether the is read-only.
public bool IsReadOnly => false;
private string[] SectionNames => GetPrivateProfileSectionNames(file.FullName);
/// Gets the with the specified section name.
/// The .
/// Name of the section.
///
///
public InitializationFileSection this[string sectionName] => new InitializationFileSection(file, sectionName);
/// Adds the specified section name.
/// Name of the section.
///
/// Duplicate name. - sectionName
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;
}
/// Removes all items from the .
public void Clear()
{
foreach (var s in SectionNames)
Remove(s);
}
/// Determines whether this instance contains the object.
/// Name of the section.
/// if [contains] [the specified section name]; otherwise, .
public bool Contains(string sectionName) => SectionNames.Contains(sectionName, StringComparer.InvariantCultureIgnoreCase);
///
/// Copies the elements of the to an , starting at a particular index.
///
///
/// The one-dimensional that is the destination of the elements copied from .
/// The must have zero-based indexing.
///
/// The zero-based index in at which copying begins.
public void CopyTo(InitializationFileSection[] array, int arrayIndex) => Array.Copy(Array.ConvertAll(SectionNames, n => new InitializationFileSection(file, n)), 0, array, arrayIndex, Count);
/// Returns an enumerator that iterates through the collection.
/// An enumerator that can be used to iterate through the collection.
public IEnumerator GetEnumerator() => SectionNames.Select(n => new InitializationFileSection(file, n)).GetEnumerator();
/// Removes the specified section name.
/// Name of the section.
///
public bool Remove(string sectionName) => WritePrivateProfileString(sectionName, null, null, file.FullName);
/// Removes the first occurrence of a specific object from the .
/// The object to remove from the .
///
/// if was successfully removed from the ; otherwise,
/// . This method also returns if is not found in the
/// original .
///
public bool Remove(InitializationFileSection item) => Remove(item.Name);
/// Adds an item to the .
/// The object to add to the .
void ICollection.Add(InitializationFileSection item) => Add(item.Name);
/// Determines whether this instance contains the object.
/// The object to locate in the .
///
/// if is found in the ; otherwise, .
///
bool ICollection.Contains(InitializationFileSection item) => Contains(item.Name);
/// Returns an enumerator that iterates through a collection.
/// An object that can be used to iterate through the collection.
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
}