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