commit 148d99e1d1189483a12178a050ce9a12390e35ff Author: iminet <27138272+iminet@users.noreply.github.com> Date: Mon May 2 20:13:35 2022 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..934db39 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/desktop.ini \ No newline at end of file diff --git a/STA.INIFile.cs b/STA.INIFile.cs new file mode 100644 index 0000000..52f1f18 --- /dev/null +++ b/STA.INIFile.cs @@ -0,0 +1,533 @@ +// ******************************* +// *** INIFile class V2.1 *** +// ******************************* +// *** (C)2009-2013 S.T.A. snc *** +// ******************************* +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace STA.Settings +{ + + internal class INIFile + { + +#region "Declarations" + + // *** Lock for thread-safe access to file and local cache *** + private object m_Lock = new object(); + + // *** File name *** + private string m_FileName = null; + internal string FileName + { + get + { + return m_FileName; + } + } + + // *** Lazy loading flag *** + private bool m_Lazy = false; + + // *** Automatic flushing flag *** + private bool m_AutoFlush = false; + + // *** Local cache *** + private Dictionary> m_Sections = new Dictionary>(); + private Dictionary> m_Modified = new Dictionary>(); + + // *** Local cache modified flag *** + private bool m_CacheModified = false; + +#endregion + +#region "Methods" + + // *** Constructor *** + public INIFile(string FileName) + { + Initialize(FileName, false, false); + } + + public INIFile(string FileName, bool Lazy, bool AutoFlush) + { + Initialize(FileName, Lazy, AutoFlush); + } + + // *** Initialization *** + private void Initialize(string FileName, bool Lazy, bool AutoFlush) + { + m_FileName = FileName; + m_Lazy = Lazy; + m_AutoFlush = AutoFlush; + if (!m_Lazy) Refresh(); + } + + // *** Parse section name *** + private string ParseSectionName(string Line) + { + if (!Line.StartsWith("[")) return null; + if (!Line.EndsWith("]")) return null; + if (Line.Length < 3) return null; + return Line.Substring(1, Line.Length - 2); + } + + // *** Parse key+value pair *** + private bool ParseKeyValuePair(string Line, ref string Key, ref string Value) + { + // *** Check for key+value pair *** + int i; + if ((i = Line.IndexOf('=')) <= 0) return false; + + int j = Line.Length - i - 1; + Key = Line.Substring(0, i).Trim(); + if (Key.Length <= 0) return false; + + Value = (j > 0) ? (Line.Substring(i + 1, j).Trim()) : (""); + return true; + } + + // *** Read file contents into local cache *** + internal void Refresh() + { + lock (m_Lock) + { + StreamReader sr = null; + try + { + // *** Clear local cache *** + m_Sections.Clear(); + m_Modified.Clear(); + + // *** Open the INI file *** + try + { + sr = new StreamReader(m_FileName); + } + catch (FileNotFoundException) + { + return; + } + + // *** Read up the file content *** + Dictionary CurrentSection = null; + string s; + string SectionName; + string Key = null; + string Value = null; + while ((s = sr.ReadLine()) != null) + { + s = s.Trim(); + + // *** Check for section names *** + SectionName = ParseSectionName(s); + if (SectionName != null) + { + // *** Only first occurrence of a section is loaded *** + if (m_Sections.ContainsKey(SectionName)) + { + CurrentSection = null; + } + else + { + CurrentSection = new Dictionary(); + m_Sections.Add(SectionName, CurrentSection); + } + } + else if (CurrentSection != null) + { + // *** Check for key+value pair *** + if (ParseKeyValuePair(s, ref Key, ref Value)) + { + // *** Only first occurrence of a key is loaded *** + if (!CurrentSection.ContainsKey(Key)) + { + CurrentSection.Add(Key, Value); + } + } + } + } + } + finally + { + // *** Cleanup: close file *** + if (sr != null) sr.Close(); + sr = null; + } + } + } + + // *** Flush local cache content *** + internal void Flush() + { + lock (m_Lock) + { + PerformFlush(); + } + } + + private void PerformFlush() + { + // *** If local cache was not modified, exit *** + if (!m_CacheModified) return; + m_CacheModified = false; + + // *** Check if original file exists *** + bool OriginalFileExists = File.Exists(m_FileName); + + // *** Get temporary file name *** + string TmpFileName = Path.ChangeExtension(m_FileName, "$n$"); + + // *** Copy content of original file to temporary file, replace modified values *** + StreamWriter sw = null; + + // *** Create the temporary file *** + sw = new StreamWriter(TmpFileName); + + try + { + Dictionary CurrentSection = null; + if (OriginalFileExists) + { + StreamReader sr = null; + try + { + // *** Open the original file *** + sr = new StreamReader(m_FileName); + + // *** Read the file original content, replace changes with local cache values *** + string s; + string SectionName; + string Key = null; + string Value = null; + bool Unmodified; + bool Reading = true; + while (Reading) + { + s = sr.ReadLine(); + Reading = (s != null); + + // *** Check for end of file *** + if (Reading) + { + Unmodified = true; + s = s.Trim(); + SectionName = ParseSectionName(s); + } + else + { + Unmodified = false; + SectionName = null; + } + + // *** Check for section names *** + if ((SectionName != null) || (!Reading)) + { + if (CurrentSection != null) + { + // *** Write all remaining modified values before leaving a section **** + if (CurrentSection.Count > 0) + { + foreach (string fkey in CurrentSection.Keys) + { + if (CurrentSection.TryGetValue(fkey, out Value)) + { + sw.Write(fkey); + sw.Write('='); + sw.WriteLine(Value); + } + } + sw.WriteLine(); + CurrentSection.Clear(); + } + } + + if (Reading) + { + // *** Check if current section is in local modified cache *** + if (!m_Modified.TryGetValue(SectionName, out CurrentSection)) + { + CurrentSection = null; + } + } + } + else if (CurrentSection != null) + { + // *** Check for key+value pair *** + if (ParseKeyValuePair(s, ref Key, ref Value)) + { + if (CurrentSection.TryGetValue(Key, out Value)) + { + // *** Write modified value to temporary file *** + Unmodified = false; + CurrentSection.Remove(Key); + + sw.Write(Key); + sw.Write('='); + sw.WriteLine(Value); + } + } + } + + // *** Write unmodified lines from the original file *** + if (Unmodified) + { + sw.WriteLine(s); + } + } + + // *** Close the original file *** + sr.Close(); + sr = null; + } + finally + { + // *** Cleanup: close files *** + if (sr != null) sr.Close(); + sr = null; + } + } + + // *** Cycle on all remaining modified values *** + foreach (KeyValuePair> SectionPair in m_Modified) + { + CurrentSection = SectionPair.Value; + if (CurrentSection.Count > 0) + { + sw.WriteLine(); + + // *** Write the section name *** + sw.Write('['); + sw.Write(SectionPair.Key); + sw.WriteLine(']'); + + // *** Cycle on all key+value pairs in the section *** + foreach (KeyValuePair ValuePair in CurrentSection) + { + // *** Write the key+value pair *** + sw.Write(ValuePair.Key); + sw.Write('='); + sw.WriteLine(ValuePair.Value); + } + CurrentSection.Clear(); + } + } + m_Modified.Clear(); + + // *** Close the temporary file *** + sw.Close(); + sw = null; + + // *** Rename the temporary file *** + File.Copy(TmpFileName, m_FileName, true); + + // *** Delete the temporary file *** + File.Delete(TmpFileName); + } + finally + { + // *** Cleanup: close files *** + if (sw != null) sw.Close(); + sw = null; + } + } + + // *** Read a value from local cache *** + internal string GetValue(string SectionName, string Key, string DefaultValue) + { + // *** Lazy loading *** + if (m_Lazy) + { + m_Lazy = false; + Refresh(); + } + + lock (m_Lock) + { + // *** Check if the section exists *** + Dictionary Section; + if (!m_Sections.TryGetValue(SectionName, out Section)) return DefaultValue; + + // *** Check if the key exists *** + string Value; + if (!Section.TryGetValue(Key, out Value)) return DefaultValue; + + // *** Return the found value *** + return Value; + } + } + + // *** Insert or modify a value in local cache *** + internal void SetValue(string SectionName, string Key, string Value) + { + // *** Lazy loading *** + if (m_Lazy) + { + m_Lazy = false; + Refresh(); + } + + lock (m_Lock) + { + // *** Flag local cache modification *** + m_CacheModified = true; + + // *** Check if the section exists *** + Dictionary Section; + if (!m_Sections.TryGetValue(SectionName, out Section)) + { + // *** If it doesn't, add it *** + Section = new Dictionary(); + m_Sections.Add(SectionName,Section); + } + + // *** Modify the value *** + if (Section.ContainsKey(Key)) Section.Remove(Key); + Section.Add(Key, Value); + + // *** Add the modified value to local modified values cache *** + if (!m_Modified.TryGetValue(SectionName, out Section)) + { + Section = new Dictionary(); + m_Modified.Add(SectionName, Section); + } + + if (Section.ContainsKey(Key)) Section.Remove(Key); + Section.Add(Key, Value); + + // *** Automatic flushing : immediately write any modification to the file *** + if (m_AutoFlush) PerformFlush(); + } + } + + // *** Encode byte array *** + private string EncodeByteArray(byte[] Value) + { + if (Value == null) return null; + + StringBuilder sb = new StringBuilder(); + foreach (byte b in Value) + { + string hex = Convert.ToString(b, 16); + int l = hex.Length; + if (l > 2) + { + sb.Append(hex.Substring(l - 2, 2)); + } + else + { + if (l < 2) sb.Append("0"); + sb.Append(hex); + } + } + return sb.ToString(); + } + + // *** Decode byte array *** + private byte[] DecodeByteArray(string Value) + { + if (Value == null) return null; + + int l = Value.Length; + if (l < 2) return new byte[] { }; + + l /= 2; + byte[] Result = new byte[l]; + for (int i = 0; i < l; i++) Result[i] = Convert.ToByte(Value.Substring(i * 2, 2), 16); + return Result; + } + + // *** Getters for various types *** + internal bool GetValue(string SectionName, string Key, bool DefaultValue) + { + string StringValue = GetValue(SectionName, Key, DefaultValue.ToString(System.Globalization.CultureInfo.InvariantCulture)); + int Value; + if (int.TryParse(StringValue, out Value)) return (Value != 0); + return DefaultValue; + } + + internal int GetValue(string SectionName, string Key, int DefaultValue) + { + string StringValue = GetValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); + int Value; + if (int.TryParse(StringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out Value)) return Value; + return DefaultValue; + } + + internal long GetValue(string SectionName, string Key, long DefaultValue) + { + string StringValue = GetValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); + long Value; + if (long.TryParse(StringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out Value)) return Value; + return DefaultValue; + } + + internal double GetValue(string SectionName, string Key, double DefaultValue) + { + string StringValue = GetValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); + double Value; + if (double.TryParse(StringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out Value)) return Value; + return DefaultValue; + } + + internal byte[] GetValue(string SectionName, string Key, byte[] DefaultValue) + { + string StringValue = GetValue(SectionName, Key, EncodeByteArray(DefaultValue)); + try + { + return DecodeByteArray(StringValue); + } + catch (FormatException) + { + return DefaultValue; + } + } + + internal DateTime GetValue(string SectionName, string Key, DateTime DefaultValue) + { + string StringValue = GetValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); + DateTime Value; + if (DateTime.TryParse(StringValue, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AssumeLocal, out Value)) return Value; + return DefaultValue; + } + + // *** Setters for various types *** + internal void SetValue(string SectionName, string Key, bool Value) + { + SetValue(SectionName, Key, (Value) ? ("1") : ("0")); + } + + internal void SetValue(string SectionName, string Key, int Value) + { + SetValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); + } + + internal void SetValue(string SectionName, string Key, long Value) + { + SetValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); + } + + internal void SetValue(string SectionName, string Key, double Value) + { + SetValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); + } + + internal void SetValue(string SectionName, string Key, byte[] Value) + { + SetValue(SectionName, Key, EncodeByteArray(Value)); + } + + internal void SetValue(string SectionName, string Key, DateTime Value) + { + SetValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); + } + +#endregion + + } + +}