






I'm sure quite a few of you would have come across a situation when you have to read certain user defined variable field values from an ini (or text) file based on which your application will have to do something. An example could be a windows service which takes the server name/port to which it has to connect to as soon as it is started or the database server name to which a service has to connect to. Or for example a service which keeps listening on a serial port and which takes the various parameters to connect to serial port from an ini file. In such cases, where your application requires to read from an ini file, the following code will help you take care of any change (add/edit/delete etc) that occurs to the ini file. It also demonstrates how to read section/field name/values from ini file. The comments are elaborate and so I assume the code should be a cakewalk to understand...
Here is the base class to represent the ini file...
1 // Copyright © 2009 Yours truly. All rights reserved.
2
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Globalization;
8 using System.Text;
9
10 namespace MyIniFileReader
11 {
12 //**************************************************************************
13 /// <summary>
14 /// Encapsulates a Windows INI file, which is a text file containing
15 /// key=value pairs organized into sections denoted by strings enclosed in
16 /// square brackets (e.g., [Section]).
17 /// </summary>
18 /// <remarks>
19 /// See the MSDN documentation of the GetProfileString() function
20 /// (http://msdn.microsoft.com/en-us/library/ms724366.aspx) for information
21 /// on the format of the file this class supports.
22 /// <para>To read an INI file, construct a new instance and call the
23 /// <see cref="Load"/> method. Then, set the <see cref="CurrentSection"/>
24 /// and obtain values by calling one of the Read*() methods (e.g.
25 /// <see cref="ReadString(string)"/>.</para>
26 /// <para>This class only supports reading from an INI file.
27 /// TODO: Add the capability to write an INI file.</para>
28 /// <para>The registry mapping capability of the GetProfileString() function
29 /// is not supported by this implementation.</para>
30 /// <para>$Id$</para>
31 /// <author>Author: Yours truly</author>
32 /// </remarks>
33 /// <threadsafety static="true" instance="false"/>
34 //**************************************************************************
35 public class MyBaseIniFile
36 {
37 /// <summary>The delimiters separating key from value.</summary>
38 private static readonly char[] DELIM_KEY_VALUE = new char[] { '=' };
39 /// <summary>Holds the <see cref="Sections"/>.</summary>
40 private readonly Dictionary<string, IniSection> m_sections =
41 new Dictionary<string, IniSection>();
42 /// <summary>Holds the <see cref="CurrentSection"/>.</summary>
43 private string m_currentSection = String.Empty;
44
45
46 //**********************************************************************
47 /// <summary>
48 /// Construct a new instance with no sections and no data.
49 /// </summary>
50 public MyBaseIniFile()
51 {
52 }
53
54
55 //**********************************************************************
56 /// <summary>
57 /// Gets or sets the section from which values are read, and to which
58 /// values are written.
59 /// </summary>
60 /// <value>Defaults to <see cref="String.Empty">the empty string</see>.
61 /// Will never be <see langword="null"/>.</value>
62 /// <remarks>
63 /// When set to an empty string, two distinct locations are identified:
64 /// The first is the area preceding the start of the first section. The
65 /// second is the empty name section. This section's name is an empty
66 /// string (i.e., "[]" or the "" section).
67 /// </remarks>
68 /// <exception cref="ArgumentNullException">An attempt was made to set
69 /// this property to <see langword="null"/>.</exception>
70 public string CurrentSection
71 {
72 get { return m_currentSection; }
73
74 set
75 {
76 if (value == null) throw new ArgumentNullException("value");
77 m_currentSection = value;
78 }
79 }
80
81 //**********************************************************************
82 /// <summary>
83 /// Gets a map of objects containing the key=value pairs for each
84 /// section, keyed by the section name.
85 /// </summary>
86 protected IDictionary<string, IniSection> Sections
87 {
88 get { return m_sections; }
89 }
90
91
92 //**********************************************************************
93 /// <summary>
94 /// Reads the information from the indicated INI file into the current
95 /// instance.
96 /// </summary>
97 /// <remarks>
98 /// This method infers the encoding from the bytes present at the
99 /// beginning of the file. It automatically recognizes UTF-8, UTF-16 LE
100 /// and UTF-16 BE if the file starts with the appropriate preamble
101 /// (a.k.a., byte order marks). If it does not recognize the preamble,
102 /// it assumes the encoding is UTF-8.
103 /// <para>When the loading process encounters a duplicate section, it
104 /// merges its contents with the existing definitions. Any duplicates
105 /// (i.e., keys with the same name) are overwritten. Duplicates
106 /// encountered later in the file overwrite previous definitions.</para>
107 /// <para>If the loading process encounters key=value pairs above the
108 /// first section, it adds them to a section named "" (empty string).
109 /// This is also referred to as "[]", the "" section and the empty name
110 /// section. If there is an explicit "" section defined, any key=value
111 /// pairs defined there are handled as though they are defined in a
112 /// duplicate section.</para>
113 /// </remarks>
114 /// <param name="path">The path to the INI file. If a relative path is
115 /// specified, it is assumed to be relative to the current working
116 /// directory of the executing process.</param>
117 /// <returns>
118 /// <see langword="true"/> if the indicated file was found and loaded;
119 /// <see langword="false"/> if the indicated file cannot be found.
120 /// </returns>
121 /// <exception cref="ArgumentNullException"><paramref name="path"/> is
122 /// <see langword="null"/>.</exception>
123 /// <exception cref="ArgumentException"><paramref name="path"/> is an
124 /// empty string.</exception>
125 /// <exception cref="IOException"><paramref name="path"/> includes an
126 /// incorrect or invalid syntax for file name, directory name, or volume
127 /// label.</exception>
128 public virtual bool Load(string path)
129 {
130 if (path == null) throw new ArgumentNullException("path");
131
132 IDictionary<string, IniSection> sections = Sections;
133 sections.Clear();
134
135 bool result;
136 try
137 {
138 ReadIniFile(path, sections);
139 result = true;
140 }
141 catch (DirectoryNotFoundException)
142 {
143 result = false;
144 }
145 catch (FileNotFoundException)
146 {
147 result = false;
148 }
149
150 return (result);
151 }
152
153 //**********************************************************************
154 /// <overloads>
155 /// Save the information contained in the current instance to the
156 /// indicated file.
157 /// </overloads>
158 /// <summary>
159 /// Save the information contained in the current instance to the
160 /// indicated file using UTF-8 encoding.
161 /// </summary>
162 /// <param name="path">The path to the INI file.</param>
163 /// <exception cref="NotImplementedException">This method is not yet
164 /// implemented.</exception>
165 public virtual void Save(string path)
166 {
167 Save(path, Encoding.UTF8);
168 }
169
170 //**********************************************************************
171 /// <summary>
172 /// Save the information contained in the current instance to the
173 /// indicated file using the specified encoding.
174 /// </summary>
175 /// <param name="path">The path to the INI file.</param>
176 /// <param name="encoding">The desired encoding.</param>
177 /// <exception cref="NotImplementedException">This method is not yet
178 /// implemented.</exception>
179 public virtual void Save(string path, Encoding encoding)
180 {
181 throw new NotImplementedException();
182 }
183
184 //**********************************************************************
185 /// <summary>
186 /// Retrieves a list of strings identifying the sections contained
187 /// within the current instance.
188 /// </summary>
189 /// <returns>
190 /// A list of zero or more section names.
191 /// </returns>
192 public virtual IList<string> ListSections()
193 {
194 IDictionary<string, IniSection> sections = Sections;
195
196 List<string> list = new List<string>(sections.Count);
197 foreach (string name in sections.Keys)
198 {
199 list.Add(name);
200 }
201
202 return (list);
203 }
204
205 //**********************************************************************
206 /// <summary>
207 /// Retrieves a list of strings identifying the keys defined by the
208 /// <see cref="CurrentSection">current section</see> contained within
209 /// the current instance.
210 /// </summary>
211 /// <returns>
212 /// A list of zero or more key names.
213 /// </returns>
214 public virtual IList<string> ListKeys()
215 {
216 IniSection section = GetCurrentSection();
217 IList<string> keys;
218 if (section != null)
219 {
220 IDictionary<string, string> contents = section.Contents;
221 keys = new List<string>(contents.Count);
222 foreach (string key in contents.Keys)
223 {
224 keys.Add(key);
225 }
226 }
227 else
228 {
229 keys = new string[0];
230 }
231
232 return (keys);
233 }
234
235 //**********************************************************************
236 /// <summary>
237 /// Deletes all data contained within the current instance and sets the
238 /// <see cref="CurrentSection">current section</see> to an empty string.
239 /// </summary>
240 public virtual void Clear()
241 {
242 Sections.Clear();
243 CurrentSection = String.Empty;
244 // Not caught: ArgumentNullException: Can't happen as coded.
245 }
246
247 //**********************************************************************
248 /// <overloads>
249 /// Retrieves the value associated with the given key defined in the
250 /// <see cref="CurrentSection">current section</see> of this instance.
251 /// </overloads>
252 /// <summary>
253 /// Retrieves the value associated with the given key, or returns
254 /// <see langword="null"/> if the key (or <see cref="CurrentSection"/>)
255 /// could not be found.
256 /// </summary>
257 /// <param name="key">The key. An empty string is valid.</param>
258 /// <returns>
259 /// The value associated with the given key in the current section, or
260 /// <see langword="null"/> if the key or section does not exist in the
261 /// current instance.
262 /// </returns>
263 /// <exception cref="ArgumentNullException">The given key was
264 /// null.</exception>
265 public virtual string ReadString(string key)
266 {
267 if (key == null) throw new ArgumentNullException("key");
268
269 IniSection section = GetCurrentSection();
270 if ((section != null) && section.Contents.ContainsKey(key))
271 {
272 return (section.Contents[key]);
273 }
274 else
275 {
276 return (null);
277 }
278 }
279
280 //**********************************************************************
281 /// <summary>
282 /// Retrieves the value associated with the given key, or returns the
283 /// provided default if the key (or <see cref="CurrentSection"/>) could
284 /// not be found.
285 /// </summary>
286 /// <param name="key">The key. An empty string is valid.</param>
287 /// <param name="dflt">The value to return if the <paramref name="key"/>
288 /// (or <see cref="CurrentSection"/>) could not be found.</param>
289 /// <returns>
290 /// The value associated with the given key in the current section, or
291 /// <paramref name="dflt"/> if the key or section does not exist in the
292 /// current instance.
293 /// </returns>
294 /// <exception cref="ArgumentNullException">The given key was
295 /// null.</exception>
296 public virtual string ReadString(string key, string dflt)
297 {
298 string val = ReadString(key);
299 if (val == null) val = dflt;
300 return (val);
301 }
302
303 //**********************************************************************
304 /// <overloads>
305 /// Retrieves the value associated with the given key defined in the
306 /// <see cref="CurrentSection">current section</see> of this instance
307 /// and parses it into a 32-bit integer.
308 /// </overloads>
309 /// <summary>
310 /// Retrieves the int value associated with the given key, or throws an
311 /// ArgumentException if the key (or <see cref="CurrentSection"/>)
312 /// could not be found.
313 /// </summary>
314 /// <param name="key">The key. An empty string is valid.</param>
315 /// <returns>
316 /// The int value associated with the given key in the current section.
317 /// </returns>
318 /// <exception cref="ArgumentNullException">The given key was
319 /// null.</exception>
320 /// <exception cref="ArgumentException">The given key (or
321 /// <see cref="CurrentSection"/>) could not be found.</exception>
322 /// <exception cref="FormatException">The value associated with the
323 /// given key (and <see cref="CurrentSection"/>) was not in a format
324 /// that could be parsed into a 32-bit integer.</exception>
325 /// <exception cref="OverflowException">The value associated with the
326 /// given key (and <see cref="CurrentSection"/>) was too big or too
327 /// small to fit in a 32-bit integer.</exception>
328 public virtual int ReadInt(string key)
329 {
330 MyTuple<bool, int> result = ReadIntImpl(key);
331 if (!result.First)
332 {
333 throw new ArgumentException(
334 String.Format(CultureInfo.InvariantCulture,
335 "Section '{0}' or key '{1}' was not found",
336 CurrentSection, key), "key");
337 }
338
339 return (result.Second);
340 }
341
342 //**********************************************************************
343 /// <summary>
344 /// Retrieves the int value associated with the given key, or returns
345 /// the provided default if the key (or <see cref="CurrentSection"/>)
346 /// could not be found or if the value could not be parsed into a 32-bit
347 /// integer.
348 /// </summary>
349 /// <param name="key">The key. An empty string is valid.</param>
350 /// <param name="dflt">The value to return if the <paramref name="key"/>
351 /// (or <see cref="CurrentSection"/>) could not be found, or the value
352 /// could not be parsed into a 32-bit integer.</param>
353 /// <returns>
354 /// The int value associated with the given key in the current section
355 /// or <paramref name="dflt"/> if the key or section does not exist in
356 /// the current instance, or if the value could not be parsed into a
357 /// 32-bit integer.</returns>
358 /// <exception cref="ArgumentNullException">The given key was
359 /// null.</exception>
360 public virtual int ReadInt(string key, int dflt)
361 {
362 int val;
363 try
364 {
365 MyTuple<bool, int> result = ReadIntImpl(key);
366 if (result.First) val = result.Second;
367 else val = dflt;
368 }
369 catch (FormatException)
370 {
371 val = dflt;
372 }
373 catch (OverflowException)
374 {
375 val = dflt;
376 }
377
378 return (val);
379 }
380
381
382 //**********************************************************************
383 /// <summary>
384 /// Attempts to read the value associated with the given key and, if
385 /// successful, parses it into a 32-bit integer.
386 /// </summary>
387 /// <param name="key">The key. An empty string is valid.</param>
388 /// <returns>
389 /// An <see cref="MyTuple{F,S}"/> structure indicating the result of the
390 /// operation in its <see cref="MyTuple{F,S}.First"/> property and the
391 /// parsed integer in its <see cref="MyTuple{F,S}.Second"/> property.
392 /// <para><see cref="MyTuple{F,S}.First"/> is <see langword="true"/> if
393 /// the <paramref name="key"/> was found in the section indicated by the
394 /// <see cref="CurrentSection"/> property. In this case, the <see cref=
395 /// "MyTuple{F,S}.Second"/> property contains the result of parsing the
396 /// string into a 32-bit integer. If the <paramref name="key"/> cannot
397 /// be found, <see cref="MyTuple{F,S}.First"/> will be <see langword=
398 /// "false"/> and <see cref="MyTuple{F,S}.Second"/> will be 0.</para>
399 /// </returns>
400 /// <exception cref="ArgumentNullException">The given key was
401 /// <see langword="null"/>.</exception>
402 /// <exception cref="FormatException">The value associated with the
403 /// given key (and <see cref="CurrentSection"/>) was not in a format
404 /// that could be parsed into a 32-bit integer.</exception>
405 /// <exception cref="OverflowException">The value associated with the
406 /// given key (and <see cref="CurrentSection"/>) was too big or too
407 /// small to fit in a 32-bit integer.</exception>
408 protected MyTuple<bool, int> ReadIntImpl(string key)
409 {
410 MyTuple<bool, int> result;
411
412 string valStr = ReadString(key);
413 if (valStr != null)
414 {
415 // TODO: Use CultureInfo.InvariantCulture?
416 int parsed = Int32.Parse(valStr, CultureInfo.CurrentCulture);
417 result = new MyTuple<bool, int>(true, parsed);
418 }
419 else
420 {
421 result = new MyTuple<bool, int>(false, 0);
422 }
423
424 return (result);
425 }
426
427
428 //**********************************************************************
429 /// <summary>
430 /// Opens, reads and parses the contents of the indicated file, adding
431 /// the parsed sections to the provided dictionary.
432 /// </summary>
433 /// <param name="path">The path to the file to read and parse. If a
434 /// relative path is specified, it is assumed to be relative to the
435 /// current working directory of the executing process.</param>
436 /// <param name="sections">A dictionary of objects containing key=value
437 /// pairs defined within a section, keyed by the section name.</param>
438 /// <exception cref="ArgumentNullException"><paramref name="path"/> is
439 /// <see langword="null"/>.</exception>
440 /// <exception cref="ArgumentException"><paramref name="path"/> is an
441 /// empty string.</exception>
442 /// <exception cref="DirectoryNotFoundException">The specified path is
443 /// invalid, such as being on an unmapped drive.</exception>
444 /// <exception cref="FileNotFoundException">The file identified by
445 /// <paramref name="path"/> cannot be found.</exception>
446 /// <exception cref="IOException"><paramref name="path"/> includes an
447 /// incorrect or invalid syntax for file name, directory name, or volume
448 /// label.</exception>
449 private static void ReadIniFile
450 (
451 string path,
452 IDictionary<string, IniSection> sections
453 )
454 {
455 TextReader reader = new StreamReader(path, true);
456 using (reader)
457 {
458 IniSection iniSection = new IniSection();
459 iniSection.Name = String.Empty;
460
461 string line = reader.ReadLine();
462 while (line != null)
463 {
464 string trimmed = line.Trim();
465 int len = trimmed.Length;
466 if (len <= 0)
467 {
468 // Blank lines are expected and should be skipped.
469 }
470 else if (trimmed[0] == ';')
471 {
472 // This is a comment line and should be skipped.
473 }
474 else if ((trimmed[0] == '[') && (trimmed[len - 1] == ']'))
475 {
476 // Add previous section.
477 sections[iniSection.Name] = iniSection;
478 // Get the name of the section just encountered.
479 string name = trimmed.Substring(1, (len - 2)).Trim();
480 // Look for an existing section. Append to it if found.
481 if (!sections.TryGetValue(name, out iniSection))
482 {
483 // Start new section.
484 iniSection = new IniSection();
485 iniSection.Name = name;
486 }
487 }
488 else
489 {
490 string[] split = trimmed.Split(DELIM_KEY_VALUE, 2);
491 if (split.Length == 2)
492 {
493 string key = split[0].Trim();
494 if (key.Length > 0)
495 {
496 string val = split[1].Trim();
497 iniSection.Contents[key] = val;
498 }
499 }
500 }
501
502 line = reader.ReadLine();
503 }
504
505 // Add the last section.
506 sections[iniSection.Name] = iniSection;
507 }
508
509 // Not caught: ArgumentOutOfRangeException: Can't happen as coded.
510 // Not caught: IndexOutOfRangeException: Can't happen as coded.
511
512 } // end ReadIniFile
513
514 //**********************************************************************
515 /// <summary>
516 /// Retrieves a reference to the object containing the contents of the
517 /// section identified by <see cref="CurrentSection"/>.
518 /// </summary>
519 /// <returns>
520 /// The section or <see langword="null"/> if a matching section could
521 /// not be found.
522 /// </returns>
523 private IniSection GetCurrentSection()
524 {
525 IniSection iniSection;
526 if (!Sections.TryGetValue(CurrentSection, out iniSection))
527 {
528 iniSection = null;
529 }
530
531 return (iniSection);
532 }
533
534
535 #region Nested classes
536
537 //**********************************************************************
538 /// <summary>
539 /// Encapsulates an INI file section, which basically identifies a
540 /// namespace for defining key=value pairs.
541 /// </summary>
542 /// <remarks>
543 /// Instances of this class are used by the enclosing class to contain
544 /// the key=value pairs defined within a section of an INI file.
545 /// <para>$Id$</para>
546 /// <author>Author: Yours truly</author>
547 /// </remarks>
548 //**********************************************************************
549 protected class IniSection
550 {
551 /// <summary>Stores the <see cref="Name"/>.</summary>
552 private string m_name = String.Empty;
553 /// <summary>Holds the <see cref="Contents"/>.</summary>
554 private readonly Dictionary<string, string> m_contents =
555 new Dictionary<string, string>();
556
557 //******************************************************************
558 /// <summary>
559 /// Gets or sets the section name.
560 /// </summary>
561 /// <value>Defaults to <see cref="String.Empty">the empty
562 /// string</see>. Will never be <see langword="null"/>.</value>
563 /// <remarks>
564 /// This is the string in between the square brackets. For example,
565 /// a section identified using "[SectionName]" is named
566 /// "SectionName".
567 /// </remarks>
568 /// <exception cref="ArgumentNullException">An attempt was made to
569 /// set this property to <see langword="null"/>.</exception>
570 public string Name
571 {
572 get { return m_name; }
573
574 set
575 {
576 if (value == null) throw new ArgumentNullException("value");
577 m_name = value;
578 }
579 }
580
581 //******************************************************************
582 /// <summary>
583 /// Gets a dictionary of string values keyed by string names,
584 /// identifying the key=value pairs defined within the INI file
585 /// section represented by the current instance.
586 /// </summary>
587 /// <value>This property will always return the same object. It
588 /// will never return <see langword="null"/>.</value>
589 public IDictionary<string, string> Contents
590 {
591 get { return m_contents; }
592 }
593 }
594
595 #endregion
596
597 } // end class MyBaseIniFile
598
599 } // end namespace MyIniFileReader
The derived class MyIniFile makes use of the base class to actually implement a number of required stuffs ....
1 // Copyright © 2009 Yours truly. All rights reserved.
2
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Text;
8 using System.Threading;
9
10 namespace MyIniFileReader
11 {
12 //**************************************************************************
13 /// <summary>
14 /// Encapsulates a Windows INI file.
15 /// </summary>
16 /// <seealso cref="MyIniFile"/>
17 /// <remarks>
18 /// INI files always reside in the application User subdirectory.
19 /// They can optionally monitor themselves and report changes.
20 /// <para>
21 /// By default, self-monitoring is off (false). The caller should always
22 /// invoke <see cref="Load()"/> to ensure the first read occurs.
23 /// <example>
24 /// Usage example:
25 /// this.m_IniFile = new MyIniFile("MyIniFile.ini");
26 /// this.m_IniFile.StateChanged += this.IniFileHandler;
27 /// this.m_IniFile.Load();
28 /// this.m_IniFile.Monitor = true;
29 /// </example>
30 /// </para>
31 /// <para>$Id$</para>
32 /// <author>Author: Yours truly</author>
33 /// </remarks>
34 /// <threadsafety static="true" instance="false"/>
35 //**************************************************************************
36 public class MyIniFile : MyBaseIniFile, IDisposable
37 {
38 /// <summary>The built-in file watcher.</summary>
39 private readonly FileSystemWatcher m_configWatcher;
40
41 /// <summary>The handler for file watcher events.</summary>
42 private readonly FileSystemEventHandler WatcherEventHandler;
43
44 private readonly RenamedEventHandler RenameHandler;
45
46 //**********************************************************************
47 /// <summary>
48 /// An event is raised whenever the instance's state changes.
49 /// </summary>
50 /// <remarks>
51 /// This will happen after the actual file has been changed and new
52 /// values have been read into this object.
53 /// </remarks>
54 [field: NonSerialized]
55 public event EventHandler<MyIniEventArgs> StateChanged;
56
57 //**********************************************************************
58 /// <summary>
59 /// Constructor.
60 /// </summary>
61 /// <seealso cref="Load()"/>
62 /// <remarks>
63 /// The default is to set <see cref="Monitor"/> to <c>false</c>.
64 /// This means that by default, the <c>MyIniFile</c> does NOT do
65 /// self-monitoring. However, it also means that the caller has time to
66 /// hook into the <see cref="StateChanged"/> event and then initiate
67 /// self-monitoring by setting <c>Monitor = true</c>.
68 /// <para>
69 /// Without self-monitoring, the caller must explicitly call
70 /// <see cref="Load()"/>.
71 /// </para>
72 /// </remarks>
73 /// <param name="directoryPath">The directory path which contains the
74 /// file with the specified <paramref name="fileName"/>.</param>
75 /// <param name="fileName">The basename of the file.</param>
76 public MyIniFile(string directoryPath, string fileName)
77 {
78 this.m_configWatcher = new FileSystemWatcher();
79
80 // Sets the folder being watched
81 this.m_configWatcher.Path = directoryPath;
82
83 // Watch for changes in LastAccess and LastWrite times, and
84 // the renaming of files or directories.
85 this.m_configWatcher.NotifyFilter = NotifyFilters.LastAccess |
86 NotifyFilters.LastWrite | NotifyFilters.FileName |
87 NotifyFilters.DirectoryName;
88
89 // Sets the configuration file to be watched.
90 this.m_configWatcher.Filter = fileName;
91 // Register a handler that gets called if the
92 // FileSystemWatcher needs to report an error.
93 this.m_configWatcher.Error += new ErrorEventHandler(HandleError);
94
95 // Add event handler.
96 this.WatcherEventHandler =
97 new FileSystemEventHandler(this.HandleWatcherEvent);
98 this.RenameHandler =
99 new RenamedEventHandler(this.HandleWatcherEvent);
100
101 } // end MyIniFile
102
103 //**********************************************************************
104 /// <summary>
105 /// The INI file basename.
106 /// </summary>
107 /// <seealso cref="Path"/>
108 /// <seealso cref="FullPathName"/>
109 public string Name
110 {
111 get { return this.m_configWatcher.Filter; }
112 }
113
114 //**********************************************************************
115 /// <summary>
116 /// The path to the folder containing the INI file.
117 /// </summary>
118 /// <seealso cref="Name"/>
119 /// <seealso cref="FullPathName"/>
120 public string Path
121 {
122 get { return this.m_configWatcher.Path; }
123 }
124
125 //**********************************************************************
126 /// <summary>
127 /// The full pathname of the INI file.
128 /// </summary>
129 /// <seealso cref="Name"/>
130 /// <seealso cref="Path"/>
131 public string FullPathName
132 {
133 get { return this.Path + @"\" + this.Name; }
134 }
135
136 //**********************************************************************
137 /// <summary>
138 /// Gets or sets this object's ability to monitor the INI file for changes.
139 /// </summary>
140 /// <remarks>
141 /// If <c>true</c> the <see cref="StateChanged"/> event will be raised
142 /// by this object whenever there is a change to the INI file.
143 /// </remarks>
144 /// <value>
145 /// If <c>true</c> the file at <see cref="FullPathName"/> will be monitored.
146 /// </value>
147 public bool Monitor
148 {
149 get { return this.m_configWatcher.EnableRaisingEvents; }
150 set
151 {
152 if (value)
153 {
154 this.m_configWatcher.Changed += this.WatcherEventHandler;
155 this.m_configWatcher.Created += this.WatcherEventHandler;
156 this.m_configWatcher.Deleted += this.WatcherEventHandler;
157 this.m_configWatcher.Renamed += this.RenameHandler;
158 }
159 else
160 {
161 this.m_configWatcher.Changed -= this.WatcherEventHandler;
162 this.m_configWatcher.Created -= this.WatcherEventHandler;
163 this.m_configWatcher.Deleted -= this.WatcherEventHandler;
164 this.m_configWatcher.Renamed -= this.RenameHandler;
165 }
166 this.m_configWatcher.EnableRaisingEvents = value;
167 }
168 }
169
170 //**********************************************************************
171 /// <summary>
172 /// Reads the information from the INI file at
173 /// <see cref="FullPathName"/> into this instance.
174 /// </summary>
175 /// <remarks>
176 /// This is the preferred function overload for callers to use.
177 /// </remarks>
178 /// <returns>
179 /// <see langword="true"/> if the indicated file was loaded;
180 /// <see langword="false"/> if the indicated file cannot be loaded.
181 /// </returns>
182 /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is
183 /// <see langword="null"/> or empty.</exception>
184 /// <exception cref="ArgumentException"><paramref name="fileName"/> is an
185 /// invalid string.</exception>
186 /// <exception cref="IOException"><paramref name="fileName"/> includes an
187 /// incorrect or invalid syntax for file name, directory name, or volume
188 /// label, or possibly, if the file is opened for editing.
189 /// </exception>
190 public bool Load()
191 {
192 return this.Load(this.Name);
193 } // end Load()
194
195 //**********************************************************************
196 /// <summary>
197 /// Reads the information from the indicated INI file into this instance.
198 /// </summary>
199 /// <seealso cref="Load()"/>
200 /// <remarks>
201 /// The <paramref name="fileName"/> must match the <c>fileName</c> parameter
202 /// originally passed to the constructor of this object, (which is also
203 /// the same value that <see cref="Name"/> returns).
204 /// </remarks>
205 /// <param name="fileName">The basename of the INI file.</param>
206 /// <returns>
207 /// <see langword="true"/> if the indicated file was loaded;
208 /// <see langword="false"/> if the indicated file cannot be loaded.
209 /// </returns>
210 /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is
211 /// <see langword="null"/> or empty.</exception>
212 /// <exception cref="ArgumentException"><paramref name="fileName"/> is an
213 /// invalid string.</exception>
214 /// <exception cref="IOException"><paramref name="fileName"/> includes an
215 /// incorrect or invalid syntax for file name, directory name, or volume
216 /// label, or possibly, if the file is opened for editing.
217 /// </exception>
218 public override bool Load(string fileName)
219 {
220 if (String.IsNullOrEmpty(fileName))
221 {
222 throw new ArgumentNullException("Invalid INI path.");
223 }
224 if (!fileName.Equals(this.Name))
225 {
226 // Though, we do change the name of the file as it is renamed.
227 throw
228 new ArgumentException("Load and Constructor params must match!");
229 }
230
231 IDictionary<string, IniSection> saveSections = null;
232 if (this.Monitor)
233 {
234 IDictionary<string, IniSection> tmpSections = base.Sections;
235 if (null != tmpSections && 0 < tmpSections.Count)
236 {
237 saveSections =
238 MyIniFile.CopyIniSections(tmpSections);
239 }
240 }
241
242 IOException excp = null;
243 bool flag = false;
244 int tries = 0;
245 while (!flag && 3 > tries)
246 {
247 try
248 {
249 flag = base.Load(this.FullPathName);
250 if (!flag)
251 {
252 tries = 3; // no need to retry.
253 }
254 }
255 // DEVNOTE: If the user has the file open for editing, we may
256 // get an IOException after the save. But if the file is then
257 // closed or made available soon thereafter, we might succeed
258 // if we have another go at reading it.
259 catch (IOException e)
260 {
261 excp = e;
262 Thread.Sleep(2000);
263 }
264 tries++;
265 }
266 if (!flag)
267 {
268 if (null != excp)
269 {
270 // Logging??
271 }
272 else
273 {
274 // Logging??
275 }
276 }
277
278
279 if (flag && null != saveSections)
280 {
281 IDictionary<string, IDictionary<string, object>> oldSections =
282 MyIniFile.ConvertIniSections(saveSections);
283 IDictionary<string, IDictionary<string, object>> newSections =
284 MyIniFile.ConvertIniSections(this.Sections);
285 MyIniEventArgs ea =
286 new MyIniEventArgs(oldSections, newSections);
287 // DEVNOTE: During debugging, it was observed that the File
288 // Watcher could raise apparently redundant events.
289 if (0 < ea.Changes.Count ||
290 0 < ea.Deletions.Count ||
291 0 < ea.Additions.Count)
292 {
293 this.OnStateChanged(ea);
294 }
295 }
296
297 return flag;
298 } // end Load
299
300 //**********************************************************************
301 /// <summary>
302 /// Get a dictionary of name-value pairs in the given
303 /// <paramref name="sectionName"/>.
304 /// </summary>
305 /// <param name="sectionName">The name of the INI section.</param>
306 /// <returns>An <see cref="IDictionary{TKey, TValue}"/> of name-value
307 /// pairs.</returns>
308 public IDictionary<string, object> GetSection(string sectionName)
309 {
310 IDictionary<string, object> section = null;
311
312 IniSection iniSection = null;
313 bool flag = this.Sections.TryGetValue(sectionName, out iniSection);
314 if (flag && null != iniSection)
315 {
316 section = ConvertOneIniSection(iniSection);
317 }
318
319 return section;
320 } // end GetSection
321
322 //**********************************************************************
323 /// <summary>
324 /// Releases all resources used by the current instance.
325 /// </summary>
326 public void Dispose()
327 {
328 this.Dispose(true);
329 GC.SuppressFinalize(this);
330 } // end Dispose
331
332 //**********************************************************************
333 /// <summary>
334 /// Releases the unmanaged resources used by this Component and
335 /// optionally releases the managed resources.
336 /// </summary>
337 /// <param name="isDisposing"><see langword="true"/> to release both
338 /// managed and unmanaged resources; <see langword="false"/> to release
339 /// only unmanaged resources.</param>
340 protected virtual void Dispose(bool isDisposing)
341 {
342 if (isDisposing)
343 {
344 this.Monitor = false;
345 this.m_configWatcher.Dispose();
346 }
347 } // end Dispose
348
349 //**********************************************************************
350 /// <summary>
351 /// Create a shallow copy of <paramref name="source"/>.
352 /// </summary>
353 /// <param name="source"><c>IDictionary{string, IniSection}</c></param>
354 /// <returns><c>IDictionary{string, IniSection}</c></returns>
355 static protected IDictionary<string, IniSection> CopyIniSections
356 (
357 IDictionary<string, IniSection> source
358 )
359 {
360 IDictionary<string, IniSection> copy =
361 new Dictionary<string, IniSection>(source);
362
363 return copy;
364 } // end CopySections
365
366 //**********************************************************************
367 /// <summary>
368 /// Convert an <c>IDictionary{string, IniSection}</c> to an
369 /// <c>IDictionary{string, IDictionary{string, object}</c>.
370 /// </summary>
371 /// <param name="source"><c>IDictionary{string, IniSection}</c></param>
372 /// <returns><c>IDictionary{string, IDictionary{string, object}</c></returns>
373 static protected IDictionary<string, IDictionary<string, object>> ConvertIniSections
374 (
375 IDictionary<string, IniSection> source
376 )
377 {
378 IDictionary<string, IDictionary<string, object>> conversion =
379 new Dictionary<string, IDictionary<string, object>>(source.Count);
380
381 foreach (IniSection iniSection in source.Values)
382 {
383 IDictionary<string, object> section =
384 ConvertOneIniSection(iniSection);
385 conversion.Add(iniSection.Name, section);
386 }
387
388 return conversion;
389 } // end ConvertIniSections
390
391 //**********************************************************************
392 /// <summary>
393 /// Convert the given <see cref="MyBaseIniFile.IniSection"/> to an ordinary
394 /// generic <see cref="IDictionary{TKey, TValue}"/>.
395 /// </summary>
396 /// <param name="iniSection">An internal class type representing an
397 /// INI file section.</param>
398 /// <returns>An <see cref="IDictionary{TKey, TValue}"/> of name-value
399 /// pairs.</returns>
400 static protected IDictionary<string, object> ConvertOneIniSection
401 (
402 IniSection iniSection
403 )
404 {
405 Debug.Assert(null != iniSection, "Invalid IniSection!");
406
407 IDictionary<string, object> section =
408 new Dictionary<string, object>(iniSection.Contents.Count);
409 foreach (KeyValuePair<string, string> keyValue in iniSection.Contents)
410 {
411 section.Add(keyValue.Key, keyValue.Value);
412 }
413 return section;
414 } // end ConvertOneIniSection
415
416 //**********************************************************************
417 /// <summary>
418 /// Raise the <see cref="StateChanged"/> event.
419 /// </summary>
420 /// <param name="ea">The <c>EventArgs</c> for this event.</param>
421 protected void OnStateChanged(MyIniEventArgs ea)
422 {
423 EventHandler<MyIniEventArgs> tmp = this.StateChanged;
424 if (null != tmp)
425 {
426 tmp(this, ea);
427 }
428 } // end OnStateChanged
429
430 ///********************************************************************
431 /// <summary>
432 /// Handle the change event from the file watcher.
433 /// </summary>
434 /// <param name="source">The <see cref="FileSystemWatcher"/> that raised
435 /// the event.</param>
436 /// <param name="e">Contains the event data.</param>
437 private void HandleWatcherEvent
438 (
439 object source,
440 FileSystemEventArgs e
441 )
442 {
443 WatcherChangeTypes wct = e.ChangeType;
444 switch (wct)
445 {
446 case WatcherChangeTypes.Changed:
447 this.Load(e.Name);
448 Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());
449 break;
450 // TODO: These other events are not getting picked up for
451 // reasons unknown.
452 case WatcherChangeTypes.Created:
453 this.Load(e.Name);
454 Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());
455 break;
456 case WatcherChangeTypes.Deleted:
457 Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());
458 break;
459 case WatcherChangeTypes.Renamed:
460 // If you want, you can save the renamed filename/path in
461 // some application config file so next time you know which
462 // file to pick. That way one can get rid of the
463 // parameterized constructor.
464 this.m_configWatcher.Filter = e.Name;
465 this.Load(e.Name);
466 Console.WriteLine("File {0} {1}", e.FullPath, wct.ToString());
467 break;
468 default:
469 Debug.Fail("ChangeType?");
470 break;
471 }
472 } //end HandleWatcherEvent
473
474 // *********************************************************************
475 /// <summary>
476 /// This method is called when the FileSystemWatcher detects an error.
477 /// </summary>
478 /// <param name="source">The <see cref="FileSystemWatcher"/> that raised
479 /// the event.</param>
480 /// <param name="e">Contains the error event data.</param>
481 private void HandleError(object source, ErrorEventArgs e)
482 {
483 // Show that an error has been detected.
484 Console.WriteLine("The FileSystemWatcher has detected an error");
485 // Give more information if the error is due to an internal buffer overflow.
486 if (e.GetException().GetType() == typeof(InternalBufferOverflowException))
487 {
488 // This can happen if Windows is reporting many file system events quickly
489 // and internal buffer of the FileSystemWatcher is not large enough to handle this
490 // rate of events. The InternalBufferOverflowException error informs the application
491 // that some of the file system events are being lost.
492 Console.WriteLine(("The file system watcher experienced an internal buffer overflow: " +
493 e.GetException().Message));
494 }
495 }
496 } // end class MyIniFile
497 } // end namespace MyIniFileReader
I have made use of the EventArgs class to have our own custom event arguments to listen to various changes in ini file...This class is MyIniEventArgs.
1 // Copyright © 2009 Yours truly. All rights reserved.
2
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6
7
8 namespace MyIniFileReader
9 {
10 //**************************************************************************
11 /// <summary>
12 /// Event data for <see cref="MyIniFile.StateChanged"/> event.
13 /// </summary>
14 /// <remarks>
15 /// <see cref="MyIniFile"/>s monitor themselves and raise the
16 /// <c>StateChanged</c> with this <c>MyComIniEventArgs</c> object.
17 /// <para>
18 /// This <c>EventArgs</c> object contains the information as to what
19 /// changed as well as the new and old values.
20 /// </para>
21 /// <para>$Id$</para>
22 /// <author>Author: Yours truly</author>
23 /// </remarks>
24 /// <threadsafety static="true" instance="false"/>
25 //**************************************************************************
26 public class MyIniEventArgs : EventArgs
27 {
28 private readonly IDictionary<string, IDictionary<string, object>> m_additions;
29 private readonly IDictionary<string, IDictionary<string, object>> m_deletions;
30 private readonly IDictionary<string, IDictionary<string, MyTuple<object, object>>> m_changes;
31
32 //**********************************************************************
33 /// <summary>
34 /// Initializes an instance of <see cref="MyIniEventArgs"/>.
35 /// </summary>
36 /// <param name="oldSections">The collection of old key-value pairs.</param>
37 /// <param name="newSections">The collection of new key-value pairs.</param>
38 public MyIniEventArgs
39 (
40 IDictionary<string, IDictionary<string, object>> oldSections,
41 IDictionary<string, IDictionary<string, object>> newSections
42 )
43 {
44 this.m_additions =
45 new Dictionary<string, IDictionary<string, object>>();
46 this.m_deletions =
47 new Dictionary<string, IDictionary<string, object>>();
48 this.m_changes =
49 new Dictionary<string, IDictionary<string, MyTuple<object, object>>>();
50
51 // Iterate over the oldSections, checking if they are present in newSections.
52 // If not present, copy a missing oldSection in its entirety to Deletions.
53 foreach (KeyValuePair<string, IDictionary<string, object>> section in oldSections)
54 {
55 if (!newSections.ContainsKey(section.Key))
56 {
57 this.m_deletions.Add(section);
58 }
59 }
60
61 // Iterate over the newSections, checking if they are present in oldSections.
62 // If not present, copy the missing newSection in its entirety to Additions.
63 foreach (KeyValuePair<string, IDictionary<string, object>> section in newSections)
64 {
65 if (!oldSections.ContainsKey(section.Key))
66 {
67 this.m_additions.Add(section);
68 }
69 }
70
71 // Iterate over newSections that were present in oldSections, and evaluate
72 // the KeyValuePairs for additions, deletions or changes.
73 foreach (KeyValuePair<string, IDictionary<string, object>> section in newSections)
74 {
75 if (oldSections.ContainsKey(section.Key))
76 {
77 this.EvaluateSection(section, oldSections[section.Key]);
78 }
79 }
80 } // end ctor
81
82 //**********************************************************************
83 /// <summary>
84 /// Gets a dictionary of all sections that have been newly added, each
85 /// with their own dictionary of the newly-added key-value pairs.
86 /// </summary>
87 public IDictionary<string, IDictionary<string, object>> Additions
88 {
89 get { return this.m_additions; }
90 }
91
92 //**********************************************************************
93 /// <summary>
94 /// The entries appearing in here were formerly existing entries that
95 /// are no longer found.
96 /// </summary>
97 public IDictionary<string, IDictionary<string, object>> Deletions
98 {
99 get { return this.m_deletions; }
100 }
101
102 //**********************************************************************
103 /// <summary>
104 /// The entries appearing in here were all existing entries that got
105 /// modified.
106 /// </summary>
107 public IDictionary<string, IDictionary<string, MyTuple<object, object>>> Changes
108 {
109 get { return this.m_changes; }
110 }
111
112 //**********************************************************************
113 /// <summary>
114 /// Determine which entries changed, which were deleted and which were
115 /// added.
116 /// </summary>
117 /// <param name="section">The INI file section.</param>
118 /// <param name="oldValues">A dictionary of old values.</param>
119 private void EvaluateSection
120 (
121 KeyValuePair<string, IDictionary<string, object>> section,
122 IDictionary<string, object> oldValues
123 )
124 {
125 foreach (KeyValuePair<string, object> oldValue in oldValues)
126 {
127 if (!section.Value.ContainsKey(oldValue.Key))
128 {
129 this.AddToDeletions(section.Key, oldValue);
130 }
131 }
132
133 foreach (KeyValuePair<string, object> value in section.Value)
134 {
135 if (oldValues.ContainsKey(value.Key))
136 {
137 object obj = oldValues[value.Key];
138 if (!obj.Equals(value.Value))
139 {
140 MyTuple<object, object> oldAndNew =
141 new MyTuple<object, object>(obj, value.Value);
142 this.AddToChanges(section.Key, value.Key, oldAndNew);
143 }
144 }
145 else
146 {
147 this.AddToAdditions(section.Key, value);
148 }
149 }
150 } // end EvaluateSection
151
152 // *********************************************************************
153 /// <summary>
154 /// Adds the changed values to the list of values.
155 /// </summary>
156 /// <param name="sectionName">Name of section containing the changed
157 /// values.</param>
158 /// <param name="entryName">The name of key(entry) for which the value
159 /// is changed.</param>
160 /// <param name="value">The changed value.</param>
161 private void AddToChanges
162 (
163 string sectionName,
164 string entryName,
165 MyTuple<object, object> value
166 )
167 {
168 if (!Changes.ContainsKey(sectionName))
169 {
170 IDictionary<string, MyTuple<object, object>> tmpSection =
171 new Dictionary<string, MyTuple<object, object>>();
172 this.m_changes.Add(sectionName, tmpSection);
173 }
174
175 IDictionary<string, MyTuple<object, object>> section =
176 Changes[sectionName];
177
178 Debug.Assert(!section.ContainsKey(entryName),
179 "ERROR: Change for this value has already been posted!");
180
181 section.Add(entryName, value);
182 } // end AddToChanges
183
184 //**********************************************************************
185 /// <summary>
186 /// Adds a list of additions for the new values.
187 /// </summary>
188 /// <param name="sectionName">The name of section for which additions
189 /// are required.</param>
190 /// <param name="keyValuePair">The key value pair to be added.</param>
191 private void AddToAdditions
192 (
193 string sectionName,
194 KeyValuePair<string, object> keyValuePair
195 )
196 {
197 if (!Additions.ContainsKey(sectionName))
198 {
199 IDictionary<string, object> tmpSection =
200 new Dictionary<string, object>();
201 this.m_additions.Add(sectionName, tmpSection);
202 }
203
204 IDictionary<string, object> section =
205 Additions[sectionName];
206
207 Debug.Assert(!section.ContainsKey(keyValuePair.Key),
208 "ERROR: Addition for this value has already been posted!");
209
210 section.Add(keyValuePair);
211 } // end AddToAdditions
212
213 //**********************************************************************
214 /// <summary>
215 /// Adds the <paramref name="keyValuePair"/> for the specified
216 /// <see cref="sectionName"/> to the list of values to be deleted.
217 /// </summary>
218 /// <param name="sectionName">The name of section for which additions
219 /// are required.</param>
220 /// <param name="keyValuePair">The key value pair to be added.</param>
221 private void AddToDeletions
222 (
223 string sectionName,
224 KeyValuePair<string, object> keyValuePair
225 )
226 {
227 if (!Deletions.ContainsKey(sectionName))
228 {
229 IDictionary<string, object> tmpSection =
230 new Dictionary<string, object>();
231 this.m_deletions.Add(sectionName, tmpSection);
232 }
233
234 IDictionary<string, object> section =
235 Deletions[sectionName];
236
237 Debug.Assert(!section.ContainsKey(keyValuePair.Key),
238 "ERROR: Deletion for this value has already been posted!");
239
240 section.Add(keyValuePair);
241 } // end AddToDeletions
242
243 } // end class MyIniEventArgs
244 } // end namespace MyIniFileReader
Throughout the code I have used MyTuple class from an earlier post.
I hope this helps someone ...
Continue >>