History

Renamed folders and separated GSF.Media and GSF.Net into separate projects.

StephenCWills committed 14 years ago

Commit ac1ef661 parent 45bce55628 files · +733 −461Browse files

Showing the changes to Source/Libraries/GSF.Core/IO/IsamDataFileBase.cs (1 of 628 files). Show all

Unified · Split

Source/Libraries/GSF.Core/IO/IsamDataFileBase.cs+1582−0
@@ -0,0 +1,1582 @@
1//*******************************************************************************************************
2// IsamDataFileBase.cs - Gbtc
3//
4// Tennessee Valley Authority, 2011
5// No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved.
6//
7// This software is made freely available under the TVA Open Source Agreement (see below).
8// Code in this file licensed to GSF under one or more contributor license agreements listed below.
9//
10// Code Modification History:
11// -----------------------------------------------------------------------------------------------------
12// 03/08/2007 - Pinal C. Patel
13// Original version of source code generated.
14// 11/30/2007 - Pinal C. Patel
15// Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property
16// instead of DesignMode property as the former is more accurate than the latter.
17// 09/19/2008 - J. Ritchie Carroll
18// Converted to C#.
19// 10/28/2008 - Pinal C. Patel
20// Edited code comments.
21// 12/04/2008 - J. Ritchie Carroll
22// Modified class an example to use new ISupportBinaryImage.
23// 05/12/2009 - Pinal C. Patel
24// Optimized Read() for better memory management by using "yield return".
25// 05/19/2009 - Pinal C. Patel
26// Implemented the IProvideStatus interface.
27// 07/02/2009 - Pinal C. Patel
28// Modified state alterning properties to reopen the file when changed.
29// 08/10/2009 - Pinal C. Patel
30// Modified Write() to write empty intermediate records that are missing to avoid garbage data
31// for the missing intermediate records when records are being written to disk directly.
32// 09/14/2009 - Stephen C. Wills
33// Added new header and license agreement.
34// 09/16/2009 - Pinal C. Patel
35// Modified Save() to flush buffered data to disk.
36// Removed MinimumRecordCount property - not very useful.
37// 09/17/2009 - Pinal C. Patel
38// Added exception handling to event handlers.
39// 12/04/2009 - Pinal C. Patel
40// Fixed thread synchronization bug in ReadFromDisk().
41// 04/07/2011 - Pinal C. Patel
42// Removed inheritance from Component class to allow for serialization.
43// 11/23/2011 - J. Ritchie Carroll
44// Modified to support buffer optimized ISupportBinaryImage.
45// 12/06/2011 - Pinal C. Patel
46// Updated to instantiate a FileSystemWatcher object that watches for changes to the file content
47// only if needed to avoid a bug introduced in .NET 4.0 that causes memory leak.
48//
49//*******************************************************************************************************
50
51#region [ TVA Open Source Agreement ]
52/*
53
54 THIS OPEN SOURCE AGREEMENT ("AGREEMENT") DEFINES THE RIGHTS OF USE,REPRODUCTION, DISTRIBUTION,
55 MODIFICATION AND REDISTRIBUTION OF CERTAIN COMPUTER SOFTWARE ORIGINALLY RELEASED BY THE
56 TENNESSEE VALLEY AUTHORITY, A CORPORATE AGENCY AND INSTRUMENTALITY OF THE UNITED STATES GOVERNMENT
57 ("GOVERNMENT AGENCY"). GOVERNMENT AGENCY IS AN INTENDED THIRD-PARTY BENEFICIARY OF ALL SUBSEQUENT
58 DISTRIBUTIONS OR REDISTRIBUTIONS OF THE SUBJECT SOFTWARE. ANYONE WHO USES, REPRODUCES, DISTRIBUTES,
59 MODIFIES OR REDISTRIBUTES THE SUBJECT SOFTWARE, AS DEFINED HEREIN, OR ANY PART THEREOF, IS, BY THAT
60 ACTION, ACCEPTING IN FULL THE RESPONSIBILITIES AND OBLIGATIONS CONTAINED IN THIS AGREEMENT.
61
62 Original Software Designation: openPDC
63 Original Software Title: The GSF Open Source Phasor Data Concentrator
64 User Registration Requested. Please Visit https://naspi.tva.com/Registration/
65 Point of Contact for Original Software: J. Ritchie Carroll <mailto:jrcarrol@tva.gov>
66
67 1. DEFINITIONS
68
69 A. "Contributor" means Government Agency, as the developer of the Original Software, and any entity
70 that makes a Modification.
71
72 B. "Covered Patents" mean patent claims licensable by a Contributor that are necessarily infringed by
73 the use or sale of its Modification alone or when combined with the Subject Software.
74
75 C. "Display" means the showing of a copy of the Subject Software, either directly or by means of an
76 image, or any other device.
77
78 D. "Distribution" means conveyance or transfer of the Subject Software, regardless of means, to
79 another.
80
81 E. "Larger Work" means computer software that combines Subject Software, or portions thereof, with
82 software separate from the Subject Software that is not governed by the terms of this Agreement.
83
84 F. "Modification" means any alteration of, including addition to or deletion from, the substance or
85 structure of either the Original Software or Subject Software, and includes derivative works, as that
86 term is defined in the Copyright Statute, 17 USC § 101. However, the act of including Subject Software
87 as part of a Larger Work does not in and of itself constitute a Modification.
88
89 G. "Original Software" means the computer software first released under this Agreement by Government
90 Agency entitled openPDC, including source code, object code and accompanying documentation, if any.
91
92 H. "Recipient" means anyone who acquires the Subject Software under this Agreement, including all
93 Contributors.
94
95 I. "Redistribution" means Distribution of the Subject Software after a Modification has been made.
96
97 J. "Reproduction" means the making of a counterpart, image or copy of the Subject Software.
98
99 K. "Sale" means the exchange of the Subject Software for money or equivalent value.
100
101 L. "Subject Software" means the Original Software, Modifications, or any respective parts thereof.
102
103 M. "Use" means the application or employment of the Subject Software for any purpose.
104
105 2. GRANT OF RIGHTS
106
107 A. Under Non-Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor,
108 with respect to its own contribution to the Subject Software, hereby grants to each Recipient a
109 non-exclusive, world-wide, royalty-free license to engage in the following activities pertaining to
110 the Subject Software:
111
112 1. Use
113
114 2. Distribution
115
116 3. Reproduction
117
118 4. Modification
119
120 5. Redistribution
121
122 6. Display
123
124 B. Under Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor, with
125 respect to its own contribution to the Subject Software, hereby grants to each Recipient under Covered
126 Patents a non-exclusive, world-wide, royalty-free license to engage in the following activities
127 pertaining to the Subject Software:
128
129 1. Use
130
131 2. Distribution
132
133 3. Reproduction
134
135 4. Sale
136
137 5. Offer for Sale
138
139 C. The rights granted under Paragraph B. also apply to the combination of a Contributor's Modification
140 and the Subject Software if, at the time the Modification is added by the Contributor, the addition of
141 such Modification causes the combination to be covered by the Covered Patents. It does not apply to
142 any other combinations that include a Modification.
143
144 D. The rights granted in Paragraphs A. and B. allow the Recipient to sublicense those same rights.
145 Such sublicense must be under the same terms and conditions of this Agreement.
146
147 3. OBLIGATIONS OF RECIPIENT
148
149 A. Distribution or Redistribution of the Subject Software must be made under this Agreement except for
150 additions covered under paragraph 3H.
151
152 1. Whenever a Recipient distributes or redistributes the Subject Software, a copy of this Agreement
153 must be included with each copy of the Subject Software; and
154
155 2. If Recipient distributes or redistributes the Subject Software in any form other than source code,
156 Recipient must also make the source code freely available, and must provide with each copy of the
157 Subject Software information on how to obtain the source code in a reasonable manner on or through a
158 medium customarily used for software exchange.
159
160 B. Each Recipient must ensure that the following copyright notice appears prominently in the Subject
161 Software:
162
163 No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved.
164
165 C. Each Contributor must characterize its alteration of the Subject Software as a Modification and
166 must identify itself as the originator of its Modification in a manner that reasonably allows
167 subsequent Recipients to identify the originator of the Modification. In fulfillment of these
168 requirements, Contributor must include a file (e.g., a change log file) that describes the alterations
169 made and the date of the alterations, identifies Contributor as originator of the alterations, and
170 consents to characterization of the alterations as a Modification, for example, by including a
171 statement that the Modification is derived, directly or indirectly, from Original Software provided by
172 Government Agency. Once consent is granted, it may not thereafter be revoked.
173
174 D. A Contributor may add its own copyright notice to the Subject Software. Once a copyright notice has
175 been added to the Subject Software, a Recipient may not remove it without the express permission of
176 the Contributor who added the notice.
177
178 E. A Recipient may not make any representation in the Subject Software or in any promotional,
179 advertising or other material that may be construed as an endorsement by Government Agency or by any
180 prior Recipient of any product or service provided by Recipient, or that may seek to obtain commercial
181 advantage by the fact of Government Agency's or a prior Recipient's participation in this Agreement.
182
183 F. In an effort to track usage and maintain accurate records of the Subject Software, each Recipient,
184 upon receipt of the Subject Software, is requested to register with Government Agency by visiting the
185 following website: https://naspi.tva.com/Registration/. Recipient's name and personal information
186 shall be used for statistical purposes only. Once a Recipient makes a Modification available, it is
187 requested that the Recipient inform Government Agency at the web site provided above how to access the
188 Modification.
189
190 G. Each Contributor represents that that its Modification does not violate any existing agreements,
191 regulations, statutes or rules, and further that Contributor has sufficient rights to grant the rights
192 conveyed by this Agreement.
193
194 H. A Recipient may choose to offer, and to charge a fee for, warranty, support, indemnity and/or
195 liability obligations to one or more other Recipients of the Subject Software. A Recipient may do so,
196 however, only on its own behalf and not on behalf of Government Agency or any other Recipient. Such a
197 Recipient must make it absolutely clear that any such warranty, support, indemnity and/or liability
198 obligation is offered by that Recipient alone. Further, such Recipient agrees to indemnify Government
199 Agency and every other Recipient for any liability incurred by them as a result of warranty, support,
200 indemnity and/or liability offered by such Recipient.
201
202 I. A Recipient may create a Larger Work by combining Subject Software with separate software not
203 governed by the terms of this agreement and distribute the Larger Work as a single product. In such
204 case, the Recipient must make sure Subject Software, or portions thereof, included in the Larger Work
205 is subject to this Agreement.
206
207 J. Notwithstanding any provisions contained herein, Recipient is hereby put on notice that export of
208 any goods or technical data from the United States may require some form of export license from the
209 U.S. Government. Failure to obtain necessary export licenses may result in criminal liability under
210 U.S. laws. Government Agency neither represents that a license shall not be required nor that, if
211 required, it shall be issued. Nothing granted herein provides any such export license.
212
213 4. DISCLAIMER OF WARRANTIES AND LIABILITIES; WAIVER AND INDEMNIFICATION
214
215 A. No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER
216 EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT
217 SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
218 PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR
219 FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS
220 AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR
221 RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
222 RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND
223 LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT
224 "AS IS."
225
226 B. Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST GOVERNMENT AGENCY, ITS
227 AGENTS, EMPLOYEES, CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE
228 OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM
229 SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE
230 SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS GOVERNMENT AGENCY, ITS AGENTS,
231 EMPLOYEES, CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY
232 LAW. THE FOREGOING RELEASE AND INDEMNIFICATION SHALL APPLY EVEN IF THE LIABILITIES, DEMANDS, DAMAGES,
233 EXPENSES OR LOSSES ARE CAUSED, OCCASIONED, OR CONTRIBUTED TO BY THE NEGLIGENCE, SOLE OR CONCURRENT, OF
234 GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE
235 IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
236
237 5. GENERAL TERMS
238
239 A. Termination: This Agreement and the rights granted hereunder will terminate automatically if a
240 Recipient fails to comply with these terms and conditions, and fails to cure such noncompliance within
241 thirty (30) days of becoming aware of such noncompliance. Upon termination, a Recipient agrees to
242 immediately cease use and distribution of the Subject Software. All sublicenses to the Subject
243 Software properly granted by the breaching Recipient shall survive any such termination of this
244 Agreement.
245
246 B. Severability: If any provision of this Agreement is invalid or unenforceable under applicable law,
247 it shall not affect the validity or enforceability of the remainder of the terms of this Agreement.
248
249 C. Applicable Law: This Agreement shall be subject to United States federal law only for all purposes,
250 including, but not limited to, determining the validity of this Agreement, the meaning of its
251 provisions and the rights, obligations and remedies of the parties.
252
253 D. Entire Understanding: This Agreement constitutes the entire understanding and agreement of the
254 parties relating to release of the Subject Software and may not be superseded, modified or amended
255 except by further written agreement duly executed by the parties.
256
257 E. Binding Authority: By accepting and using the Subject Software under this Agreement, a Recipient
258 affirms its authority to bind the Recipient to all terms and conditions of this Agreement and that
259 Recipient hereby agrees to all terms and conditions herein.
260
261 F. Point of Contact: Any Recipient contact with Government Agency is to be directed to the designated
262 representative as follows: J. Ritchie Carroll <mailto:jrcarrol@tva.gov>.
263
264*/
265#endregion
266
267#region [ Contributor License Agreements ]
268
269//******************************************************************************************************
270//
271// Copyright © 2011, Grid Protection Alliance. All Rights Reserved.
272//
273// The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
274// not use this file except in compliance with the License. You may obtain a copy of the License at:
275//
276// http://www.opensource.org/licenses/eclipse-1.0.php
277//
278// Unless agreed to in writing, the subject software distributed under the License is distributed on an
279// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
280// License for the specific language governing permissions and limitations.
281//
282//******************************************************************************************************
283
284#endregion
285
286using GSF.Configuration;
287using GSF.Parsing;
288using System;
289using System.Collections.Generic;
290using System.Configuration;
291using System.IO;
292using System.Text;
293using System.Threading;
294using System.Xml.Serialization;
295
296namespace GSF.IO
297{
298 /// <summary>
299 /// An abstract class that defines the read/write capabilities for ISAM (Indexed Sequential Access Method) file.
300 /// </summary>
301 /// <typeparam name="T">
302 /// <see cref="Type"/> of the records the file contains. This <see cref="Type"/> must implement the <see cref="ISupportBinaryImage"/> interface.
303 /// </typeparam>
304 /// <remarks>
305 /// <para>
306 /// This ISAM implementation keeps all the records in memory, so it may not be suitable for very large files. Since data is stored
307 /// in memory using a list, the maximum number of possible supported records will be 2,147,483,647 (i.e., Int32.MaxValue).
308 /// </para>
309 /// <para>
310 /// See <a href="http://en.wikipedia.org/wiki/ISAM" target="_blank">http://en.wikipedia.org/wiki/ISAM</a> for more information on ISAM files.
311 /// </para>
312 /// </remarks>
313 /// <example>
314 /// This example shows a sample implementation of <see cref="IsamDataFileBase{T}"/>:
315 /// <code>
316 /// using System;
317 /// using System.Text;
318 /// using GSF;
319 /// using GSF.IO;
320 /// using GSF.Parsing;
321 ///
322 /// class Program
323 /// {
324 /// static void Main(string[] args)
325 /// {
326 /// // Create a few test records.
327 /// TestIsamFileRecord r1 = new TestIsamFileRecord(1);
328 /// r1.Name = "TestRecord1";
329 /// r1.Value = double.MinValue;
330 /// r1.Description = "Test record with minimum double value";
331 /// TestIsamFileRecord r2 = new TestIsamFileRecord(2);
332 /// r2.Name = "TestRecord2";
333 /// r2.Value = double.MaxValue;
334 /// r2.Description = "Test record with maximum double value";
335 ///
336 /// // Open ISAM file.
337 /// TestIsamFile testFile = new TestIsamFile();
338 /// testFile.FileName = "TestIsamFile.dat";
339 /// testFile.Open();
340 ///
341 /// // Write test records.
342 /// testFile.Write(r1.Index, r1);
343 /// testFile.Write(r2.Index, r2);
344 ///
345 /// // Read test records.
346 /// Console.WriteLine(testFile.Read(1));
347 /// Console.WriteLine(testFile.Read(2));
348 ///
349 /// // Close ISAM file.
350 /// testFile.Close();
351 ///
352 /// Console.ReadLine();
353 /// }
354 /// }
355 ///
356 /// class TestIsamFile : IsamDataFileBase&lt;TestIsamFileRecord&gt;
357 /// {
358 /// /// <summary>
359 /// /// Size of a single file record.
360 /// /// </summary>
361 /// protected override int GetRecordSize()
362 /// {
363 /// return TestIsamFileRecord.RecordLength;
364 /// }
365 ///
366 /// /// <summary>
367 /// /// Creates a new empty file record.
368 /// /// </summary>
369 /// protected override TestIsamFileRecord CreateNewRecord(int id)
370 /// {
371 /// return new TestIsamFileRecord(id);
372 /// }
373 /// }
374 ///
375 /// class TestIsamFileRecord : ISupportBinaryImage
376 /// {
377 /// private int m_index;
378 /// private string m_name; // 20 * 1 = 20
379 /// private double m_value; // 1 * 8 = 8
380 /// private string m_description; // 100 * 1 = 100
381 ///
382 /// public const int RecordLength = 128; // Total = 128
383 ///
384 /// public TestIsamFileRecord(int recordIndex)
385 /// {
386 /// m_index = recordIndex;
387 ///
388 /// Name = string.Empty;
389 /// Value = double.NaN;
390 /// Description = string.Empty;
391 /// }
392 ///
393 /// /// <summary>
394 /// /// 1-based index of the record.
395 /// /// </summary>
396 /// public int Index
397 /// {
398 /// get { return m_index; }
399 /// }
400 ///
401 /// /// <summary>
402 /// /// Name of the record.
403 /// /// </summary>
404 /// public string Name
405 /// {
406 /// get { return m_name; }
407 /// set { m_name = value.TruncateRight(20).PadRight(20); }
408 /// }
409 ///
410 /// /// <summary>
411 /// /// Value of the record.
412 /// /// </summary>
413 /// public double Value
414 /// {
415 /// get { return m_value; }
416 /// set { m_value = value; }
417 /// }
418 ///
419 /// /// <summary>
420 /// /// Description of the record.
421 /// /// </summary>
422 /// public string Description
423 /// {
424 /// get { return m_description; }
425 /// set { m_description = value.TruncateRight(100).PadRight(100); }
426 /// }
427 ///
428 /// /// <summary>
429 /// /// Serialized record length.
430 /// /// </summary>
431 /// public int BinaryLength
432 /// {
433 /// get { return RecordLength; }
434 /// }
435 ///
436 /// /// <summary>
437 /// /// Serialized record data.
438 /// /// </summary>
439 /// public byte[] BinaryImage
440 /// {
441 /// get
442 /// {
443 /// // Serialize TestIsamFileRecord into byte array.
444 /// byte[] image = new byte[RecordLength];
445 /// Buffer.BlockCopy(Encoding.ASCII.GetBytes(Name), 0, image, 0, 20);
446 /// Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, image, 20, 8);
447 /// Buffer.BlockCopy(Encoding.ASCII.GetBytes(Description), 0, image, 28, 100);
448 ///
449 /// return image;
450 /// }
451 /// }
452 ///
453 /// /// <summary>
454 /// /// Deserializes the record.
455 /// /// </summary>
456 /// public int Initialize(byte[] binaryImage, int startIndex, int length)
457 /// {
458 /// if (length &gt;= RecordLength)
459 /// {
460 /// // Deserialize byte array into TestIsamFileRecord.
461 /// Name = Encoding.ASCII.GetString(binaryImage, startIndex, 20);
462 /// Value = BitConverter.ToDouble(binaryImage, startIndex + 20);
463 /// Description = Encoding.ASCII.GetString(binaryImage, startIndex + 28, 100);
464 /// }
465 /// else
466 /// throw new InvalidOperationException("Invalid record size, not enough data to deserialize record");
467 ///
468 /// return RecordLength;
469 /// }
470 ///
471 /// /// <summary>
472 /// /// String representation of the record.
473 /// /// </summary>
474 /// public override string ToString()
475 /// {
476 /// return string.Format("Name: {0}, Value: {1}, Description: {2}", Name, Value, Description);
477 /// }
478 /// }
479 /// </code>
480 /// </example>
481 public abstract class IsamDataFileBase<T> : ISupportLifecycle, IProvideStatus, IPersistSettings where T : ISupportBinaryImage
482 {
483 #region [ Members ]
484
485 // Constants
486
487 /// <summary>
488 /// Specifies the default value for the <see cref="FileName"/> property.
489 /// </summary>
490 public const string DefaultFileName = "IsamDataFile.dat";
491
492 /// <summary>
493 /// Specifies the default value for the <see cref="FileAccessMode"/> property.
494 /// </summary>
495 public const FileAccess DefaultFileAccessMode = FileAccess.ReadWrite;
496
497 /// <summary>
498 /// Specifies the default value for the <see cref="AutoSaveInterval"/> property.
499 /// </summary>
500 public const int DefaultAutoSaveInterval = -1;
501
502 /// <summary>
503 /// Specifies the default value for the <see cref="LoadOnOpen"/> property.
504 /// </summary>
505 public const bool DefaultLoadOnOpen = false;
506
507 /// <summary>
508 /// Specifies the default value for the <see cref="SaveOnClose"/> property.
509 /// </summary>
510 public const bool DefaultSaveOnClose = false;
511
512 /// <summary>
513 /// Specifies the default value for the <see cref="ReloadOnModify"/> property.
514 /// </summary>
515 public const bool DefaultReloadOnModify = false;
516
517 /// <summary>
518 /// Specifies the default value for the <see cref="PersistSettings"/> property.
519 /// </summary>
520 public const bool DefaultPersistSettings = false;
521
522 /// <summary>
523 /// Specifies the default value for the <see cref="SettingsCategory"/> property.
524 /// </summary>
525 public const string DefaultSettingsCategory = "IsamDataFile";
526
527 // Events
528
529 /// <summary>
530 /// Occurs when data is being read from disk into memory.
531 /// </summary>
532 public event EventHandler DataLoading;
533
534 /// <summary>
535 /// Occurs when data has been read from disk into memory.
536 /// </summary>
537 public event EventHandler DataLoaded;
538
539 /// <summary>
540 /// Occurs when data is being saved from memory onto disk.
541 /// </summary>
542 public event EventHandler DataSaving;
543
544 /// <summary>
545 /// Occurs when data has been saved from memory onto disk.
546 /// </summary>
547 public event EventHandler DataSaved;
548
549 /// <summary>
550 /// Occurs when file data on the disk is modified.
551 /// </summary>
552 public event EventHandler FileModified;
553
554 /// <summary>
555 /// Occurs when the class has been disposed.
556 /// </summary>
557 public event EventHandler Disposed;
558
559 // Fields
560 private string m_fileName;
561 private FileAccess m_fileAccessMode;
562 private int m_autoSaveInterval;
563 private bool m_loadOnOpen;
564 private bool m_saveOnClose;
565 private bool m_reloadOnModify;
566 private bool m_persistSettings;
567 private string m_settingsCategory;
568 private List<T> m_fileRecords;
569 private byte[] m_recordBuffer;
570 private FileStream m_fileData;
571 private ManualResetEvent m_loadWaitHandle;
572 private ManualResetEvent m_saveWaitHandle;
573 private System.Timers.Timer m_autoSaveTimer;
574 private FileSystemWatcher m_fileWatcher;
575 private bool m_disposed;
576 private bool m_initialized;
577
578 #endregion
579
580 #region [ Constructors ]
581
582 /// <summary>
583 /// Initializes a new instance of the <see cref="IsamDataFileBase{T}"/> class.
584 /// </summary>
585 protected IsamDataFileBase()
586 {
587 m_fileName = DefaultFileName;
588 m_fileAccessMode = DefaultFileAccessMode;
589 m_autoSaveInterval = DefaultAutoSaveInterval;
590 m_loadOnOpen = DefaultLoadOnOpen;
591 m_saveOnClose = DefaultSaveOnClose;
592 m_reloadOnModify = DefaultReloadOnModify;
593 m_persistSettings = DefaultPersistSettings;
594 m_settingsCategory = DefaultSettingsCategory;
595 m_loadWaitHandle = new ManualResetEvent(true);
596 m_saveWaitHandle = new ManualResetEvent(true);
597
598 m_autoSaveTimer = new System.Timers.Timer();
599 m_autoSaveTimer.Elapsed += m_autoSaveTimer_Elapsed;
600 }
601
602 /// <summary>
603 /// Releases the unmanaged resources before the file is reclaimed by <see cref="GC"/>.
604 /// </summary>
605 ~IsamDataFileBase()
606 {
607 Dispose(false);
608 }
609
610 #endregion
611
612 #region [ Properties ]
613
614 /// <summary>
615 /// Gets or sets the name of the file.
616 /// </summary>
617 /// <remarks>
618 /// Changing the <see cref="FileName"/> when the file is open will cause the file to be re-opend.
619 /// </remarks>
620 public string FileName
621 {
622 get
623 {
624 return m_fileName;
625 }
626 set
627 {
628 if (string.IsNullOrEmpty(value))
629 throw new ArgumentNullException("value");
630
631 m_fileName = value;
632 ReOpen();
633 }
634 }
635
636 /// <summary>
637 /// Gets or sets the <see cref="FileAccess"/> value to use when opening the file.
638 /// </summary>
639 public FileAccess FileAccessMode
640 {
641 get
642 {
643 return m_fileAccessMode;
644 }
645 set
646 {
647 m_fileAccessMode = value;
648 ReOpen();
649 }
650 }
651
652 /// <summary>
653 /// Gets or sets the interval in milliseconds at which the records loaded in memory are to be persisted to disk.
654 /// </summary>
655 /// <remarks>
656 /// <see cref="AutoSaveInterval"/> will be effective only if records have been loaded in memory either manually
657 /// by calling the <see cref="Load()"/> method or automatically by settings <see cref="LoadOnOpen"/> to true.
658 /// </remarks>
659 public int AutoSaveInterval
660 {
661 get
662 {
663 return m_autoSaveInterval;
664 }
665 set
666 {
667 m_autoSaveInterval = value;
668 ReOpen();
669 }
670 }
671
672 /// <summary>
673 /// Gets or sets a boolean value that indicates whether records are to be loaded automatically in memory when
674 /// the file is opened.
675 /// </summary>
676 public bool LoadOnOpen
677 {
678 get
679 {
680 return m_loadOnOpen;
681 }
682 set
683 {
684 m_loadOnOpen = value;
685 ReOpen();
686 }
687 }
688
689 /// <summary>
690 /// Gets or sets a boolean value that indicates whether records loaded in memory are to be persisted to disk
691 /// when the file is closed.
692 /// </summary>
693 /// <remarks>
694 /// <see cref="SaveOnClose"/> will be effective only if records have been loaded in memory either manually
695 /// by calling the <see cref="Load()"/> method or automatically by settings <see cref="LoadOnOpen"/> to true.
696 /// </remarks>
697 public bool SaveOnClose
698 {
699 get
700 {
701 return m_saveOnClose;
702 }
703 set
704 {
705 m_saveOnClose = value;
706 }
707 }
708
709 /// <summary>
710 /// Gets or sets a boolean value that indicates whether records loaded in memory are to be re-loaded when the
711 /// file is modified on disk.
712 /// </summary>
713 /// <remarks>
714 /// <see cref="ReloadOnModify"/> will be effective only if records have been loaded in memory either manually
715 /// by calling the <see cref="Load()"/> method or automatically by settings <see cref="LoadOnOpen"/> to true.
716 /// </remarks>
717 public bool ReloadOnModify
718 {
719 get
720 {
721 return m_reloadOnModify;
722 }
723 set
724 {
725 m_reloadOnModify = value;
726 ReOpen();
727 }
728 }
729
730 /// <summary>
731 /// Gets or sets a boolean value that indicates whether the file settings are to be saved to the config file.
732 /// </summary>
733 [XmlIgnore()]
734 public bool PersistSettings
735 {
736 get
737 {
738 return m_persistSettings;
739 }
740 set
741 {
742 m_persistSettings = value;
743 }
744 }
745
746 /// <summary>
747 /// Gets or sets the category under which the file settings are to be saved to the config file if the
748 /// <see cref="PersistSettings"/> property is set to true.
749 /// </summary>
750 /// <exception cref="ArgumentNullException">The value being assigned is null or empty string.</exception>
751 [XmlIgnore()]
752 public string SettingsCategory
753 {
754 get
755 {
756 return m_settingsCategory;
757 }
758 set
759 {
760 if (string.IsNullOrEmpty(value))
761 throw new ArgumentNullException("value");
762
763 m_settingsCategory = value;
764 }
765 }
766
767 /// <summary>
768 /// Gets or sets a boolean value that indicates whether the file is currently enabled.
769 /// </summary>
770 /// <remarks>
771 /// Setting <see cref="Enabled"/> to true will open the file if it is closed, setting
772 /// to false will close the file if it is open.
773 /// </remarks>
774 [XmlIgnore()]
775 public bool Enabled
776 {
777 get
778 {
779 return IsOpen;
780 }
781 set
782 {
783 if (value && !IsOpen)
784 Open();
785 else if (!value && IsOpen)
786 Close();
787 }
788 }
789
790 /// <summary>
791 /// Gets a boolean value that indicates whether the file is open.
792 /// </summary>
793 public bool IsOpen
794 {
795 get
796 {
797 return ((object)m_fileData != null);
798 }
799 }
800
801 /// <summary>
802 /// Gets a boolean value that indicates whether the file data on disk is corrupt.
803 /// </summary>
804 public bool IsCorrupt
805 {
806 get
807 {
808 if (IsOpen)
809 {
810 long fileLength;
811
812 lock (m_fileData)
813 {
814 fileLength = m_fileData.Length;
815 }
816
817 return (fileLength % m_recordBuffer.Length != 0);
818 }
819 else
820 {
821 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
822 }
823 }
824 }
825
826 /// <summary>
827 /// Gets the approximate memory consumption (in KB) of the file.
828 /// </summary>
829 /// <remarks>
830 /// <see cref="MemoryUsage"/> will be zero (0) unless records are loaded in memory.
831 /// </remarks>
832 public long MemoryUsage
833 {
834 get
835 {
836 return RecordsInMemory * m_recordBuffer.Length / 1024;
837 }
838 }
839
840 /// <summary>
841 /// Gets the number of file records on the disk.
842 /// </summary>
843 public int RecordsOnDisk
844 {
845 get
846 {
847 if (IsOpen)
848 {
849 long fileLength;
850
851 lock (m_fileData)
852 {
853 fileLength = m_fileData.Length;
854 }
855
856 return (int)(fileLength / (long)m_recordBuffer.Length);
857 }
858 else
859 {
860 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
861 }
862 }
863 }
864
865 /// <summary>
866 /// Gets the number of file records loaded in memory.
867 /// </summary>
868 public int RecordsInMemory
869 {
870 get
871 {
872 int recordCount = 0;
873
874 if ((object)m_fileRecords != null)
875 {
876 lock (m_fileRecords)
877 {
878 recordCount = m_fileRecords.Count;
879 }
880 }
881
882 return recordCount;
883 }
884 }
885
886 /// <summary>
887 /// Gets the unique identifier of the file.
888 /// </summary>
889 public string Name
890 {
891 get
892 {
893 return m_settingsCategory;
894 }
895 }
896
897 /// <summary>
898 /// Gets the descriptive status of the file.
899 /// </summary>
900 public string Status
901 {
902 get
903 {
904 StringBuilder status = new StringBuilder();
905 status.Append(" File name: ");
906 status.Append(FilePath.TrimFileName(FileName, 30));
907 status.AppendLine();
908 status.Append(" File state: ");
909 status.Append(IsOpen ? "Open" : "Closed");
910 status.AppendLine();
911 status.Append(" File access mode: ");
912 status.Append(FileAccessMode);
913 status.AppendLine();
914 status.Append(" Data validity: ");
915 status.Append(IsCorrupt ? "Invalid" : "Valid");
916 status.AppendLine();
917 status.Append(" Auto-save data: ");
918 if (LoadOnOpen && AutoSaveInterval > 0 && !SaveOnClose)
919 status.AppendFormat("Every {0}ms", AutoSaveInterval);
920 if (LoadOnOpen && AutoSaveInterval > 0 && SaveOnClose)
921 status.AppendFormat("Every {0}ms & File Close", AutoSaveInterval);
922 if (!LoadOnOpen || (LoadOnOpen && AutoSaveInterval < 1 && !SaveOnClose))
923 status.Append("Never");
924 status.AppendLine();
925 status.Append(" Records on disk: ");
926 status.Append(RecordsOnDisk);
927 status.AppendLine();
928 status.Append(" Records in memory: ");
929 status.Append(RecordsInMemory);
930 status.AppendLine();
931 status.Append(" Memory usage (approx.): ");
932 status.AppendFormat("{0} KB", MemoryUsage);
933 status.AppendLine();
934
935 return status.ToString();
936 }
937 }
938
939 /// <summary>
940 /// Gets the underlying <see cref="FileStream"/> of the file.
941 /// </summary>
942 /// <remarks>
943 /// Thread-safety Warning: A lock must be obtained on <see cref="FileData"/> before accessing it.
944 /// </remarks>
945 protected FileStream FileData
946 {
947 get
948 {
949 return m_fileData;
950 }
951 }
952
953 #endregion
954
955 #region [ Methods ]
956
957 #region [ Abstract ]
958
959 /// <summary>
960 /// When overridden in a derived class, gets the size of a record (in bytes).
961 /// </summary>
962 /// <returns>Size of a record in bytes.</returns>
963 protected abstract int GetRecordSize();
964
965 /// <summary>
966 /// When overridden in a derived class, returns a new empty record.
967 /// </summary>
968 /// <param name="recordIndex">1-based index of the new record.</param>
969 /// <returns>New empty record.</returns>
970 protected abstract T CreateNewRecord(int recordIndex);
971
972 #endregion
973
974 /// <summary>
975 /// Releases all the resources used by the file.
976 /// </summary>
977 public void Dispose()
978 {
979 Dispose(true);
980 GC.SuppressFinalize(this);
981 }
982
983 /// <summary>
984 /// Initializes the file.
985 /// </summary>
986 /// <remarks>
987 /// <see cref="Initialize()"/> is to be called by user-code directly only if the file is not consumed through the designer surface of the IDE.
988 /// </remarks>
989 public void Initialize()
990 {
991 if (!m_initialized)
992 {
993 // Load settings from the config file.
994 LoadSettings();
995
996 // Watch for changes to the file content.
997 if (m_reloadOnModify)
998 {
999 // Create watcher only if needed to avoid a bug in .NET 4.0 that causes memory leak.
1000 // http://support.microsoft.com/kb/2628838
1001 m_fileWatcher = new FileSystemWatcher();
1002 m_fileWatcher.Path = FilePath.GetDirectoryName(FilePath.GetAbsolutePath(m_fileName));
1003 m_fileWatcher.Filter = FilePath.GetFileName(m_fileName);
1004 m_fileWatcher.Changed += m_fileWatcher_Changed;
1005 }
1006
1007 m_recordBuffer = new byte[GetRecordSize()]; // Create buffer for reading records.
1008 m_initialized = true; // Initialize only once.
1009 }
1010 }
1011
1012 /// <summary>
1013 /// Saves settings of the file to the config file if the <see cref="PersistSettings"/> property is set to true.
1014 /// </summary>
1015 /// <exception cref="ConfigurationErrorsException"><see cref="SettingsCategory"/> has a value of null or empty string.</exception>
1016 public void SaveSettings()
1017 {
1018 if (m_persistSettings)
1019 {
1020 // Ensure that settings category is specified.
1021 if (string.IsNullOrEmpty(m_settingsCategory))
1022 throw new ConfigurationErrorsException("SettingsCategory property has not been set");
1023
1024 // Save settings under the specified category.
1025 ConfigurationFile config = ConfigurationFile.Current;
1026 CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory];
1027 settings["FileName", true].Update(m_fileName);
1028 settings["FileAccessMode", true].Update(m_fileAccessMode);
1029 settings["AutoSaveInterval", true].Update(m_autoSaveInterval);
1030 settings["LoadOnOpen", true].Update(m_loadOnOpen);
1031 settings["SaveOnClose", true].Update(m_saveOnClose);
1032 settings["ReloadOnModify", true].Update(m_reloadOnModify);
1033 config.Save();
1034 }
1035 }
1036
1037 /// <summary>
1038 /// Loads saved settings of the file from the config file if the <see cref="PersistSettings"/> property is set to true.
1039 /// </summary>
1040 /// <exception cref="ConfigurationErrorsException"><see cref="SettingsCategory"/> has a value of null or empty string.</exception>
1041 public void LoadSettings()
1042 {
1043 if (m_persistSettings)
1044 {
1045 // Ensure that settings category is specified.
1046 if (string.IsNullOrEmpty(m_settingsCategory))
1047 throw new ConfigurationErrorsException("SettingsCategory property has not been set");
1048
1049 // Load settings from the specified category.
1050 ConfigurationFile config = ConfigurationFile.Current;
1051 CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory];
1052 settings.Add("FileName", m_fileName, "Name of the file including its path.");
1053 settings.Add("FileAccessMode", m_fileAccessMode, "Access mode (Read; Write; ReadWrite) to be used when opening the file.");
1054 settings.Add("AutoSaveInterval", m_autoSaveInterval, "Interval in milliseconds at which the file records loaded in memory are to be saved automatically to disk. Use -1 to disable automatic saving.");
1055 settings.Add("LoadOnOpen", m_loadOnOpen, "True if file records are to be loaded in memory when opened; otherwise False.");
1056 settings.Add("SaveOnClose", m_saveOnClose, "True if file records loaded in memory are to be saved to disk when file is closed; otherwise False.");
1057 settings.Add("ReloadOnModify", m_reloadOnModify, "True if file records loaded in memory are to be re-loaded when file is modified on disk; otherwise False.");
1058 FileName = settings["FileName"].ValueAs(m_fileName);
1059 FileAccessMode = settings["FileAccessMode"].ValueAs(m_fileAccessMode);
1060 AutoSaveInterval = settings["AutoSaveInterval"].ValueAs(m_autoSaveInterval);
1061 LoadOnOpen = settings["LoadOnOpen"].ValueAs(m_loadOnOpen);
1062 SaveOnClose = settings["SaveOnClose"].ValueAs(m_saveOnClose);
1063 ReloadOnModify = settings["ReloadOnModify"].ValueAs(m_reloadOnModify);
1064 }
1065 }
1066
1067 /// <summary>
1068 /// Opens the file.
1069 /// </summary>
1070 public void Open()
1071 {
1072 if (!IsOpen)
1073 {
1074 // Initialize if uninitialized.
1075 Initialize();
1076
1077 // Make the file path absolute if it is relative.
1078 m_fileName = FilePath.GetAbsolutePath(m_fileName);
1079
1080 // Create the file directory if it does not exist.
1081 if (!Directory.Exists(FilePath.GetDirectoryName(m_fileName)))
1082 Directory.CreateDirectory(FilePath.GetDirectoryName(m_fileName));
1083
1084 // Open if file exists, or create it if it doesn't.
1085 m_fileData = new FileStream(m_fileName, FileMode.OpenOrCreate, m_fileAccessMode, FileShare.ReadWrite);
1086
1087 // Load records into memory if specified to do so.
1088 if (m_loadOnOpen)
1089 Load();
1090
1091 // Watch for changes to the file content on disk.
1092 if ((object)m_fileWatcher != null)
1093 m_fileWatcher.EnableRaisingEvents = true;
1094
1095 if (m_autoSaveInterval > 0)
1096 {
1097 // Start timer for saving records loaded in memory automatically.
1098 m_autoSaveTimer.Interval = m_autoSaveInterval;
1099 m_autoSaveTimer.Start();
1100 }
1101 }
1102 }
1103
1104 /// <summary>
1105 /// Closes the file.
1106 /// </summary>
1107 public void Close()
1108 {
1109 if (IsOpen)
1110 {
1111 // Stop the timer if it is ticking.
1112 m_autoSaveTimer.Stop();
1113
1114 // Stop monitoring for changes to the file.
1115 if ((object)m_fileWatcher != null)
1116 m_fileWatcher.EnableRaisingEvents = false;
1117
1118 // Save records back to the file if specified.
1119 if (m_saveOnClose)
1120 Save();
1121
1122 // Close the file stream used for file I/O.
1123 if ((object)m_fileData != null)
1124 {
1125 lock (m_fileData)
1126 {
1127 m_fileData.Dispose();
1128 }
1129 }
1130 m_fileData = null;
1131
1132 // Clear the records loaded in memory.
1133 if ((object)m_fileRecords != null)
1134 {
1135 lock (m_fileRecords)
1136 {
1137 m_fileRecords.Clear();
1138 }
1139 }
1140 m_fileRecords = null;
1141 }
1142 }
1143
1144 /// <summary>
1145 /// Loads records from disk into memory.
1146 /// </summary>
1147 public void Load()
1148 {
1149 if (IsOpen)
1150 {
1151 // Waits for any pending request to save records before completing.
1152 m_saveWaitHandle.WaitOne();
1153
1154 // Waits for any prior request to load records before completing.
1155 m_loadWaitHandle.WaitOne();
1156 m_loadWaitHandle.Reset();
1157
1158 try
1159 {
1160 OnDataLoading();
1161
1162 if ((object)m_fileRecords == null)
1163 m_fileRecords = new List<T>();
1164
1165 List<T> records = new List<T>(ReadFromDisk());
1166 lock (m_fileRecords)
1167 {
1168 m_fileRecords.Clear();
1169 m_fileRecords.InsertRange(0, records);
1170 }
1171
1172 OnDataLoaded();
1173 }
1174 finally
1175 {
1176 m_loadWaitHandle.Set();
1177 }
1178 }
1179 else
1180 {
1181 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
1182 }
1183 }
1184
1185 /// <summary>
1186 /// Saves records loaded in memory to disk.
1187 /// </summary>
1188 /// <remarks>
1189 /// <see cref="Save()"/> is equivalent to <see cref="FileStream.Flush()"/> when records are not loaded in memory.
1190 /// </remarks>
1191 public void Save()
1192 {
1193 if (IsOpen)
1194 {
1195 // Waits for any pending request to save records before completing.
1196 m_saveWaitHandle.WaitOne();
1197
1198 // Waits for any prior request to load records before completing.
1199 m_loadWaitHandle.WaitOne();
1200 m_saveWaitHandle.Reset();
1201
1202 try
1203 {
1204 OnDataSaving();
1205
1206 // Saves (persists) records to the file, if present in memory.
1207 if ((object)m_fileRecords != null)
1208 {
1209 lock (m_fileRecords)
1210 {
1211 WriteToDisk(m_fileRecords);
1212 }
1213 }
1214 m_fileData.Flush();
1215 File.SetLastWriteTime(m_fileName, DateTime.Now);
1216
1217 OnDataSaved();
1218 }
1219 finally
1220 {
1221 m_saveWaitHandle.Set();
1222 }
1223 }
1224 else
1225 {
1226 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
1227 }
1228 }
1229
1230 /// <summary>
1231 /// Writes specified records to disk if records were not loaded in memory otherwise updates the records in memory.
1232 /// </summary>
1233 /// <param name="records">Records to be written.</param>
1234 /// <remarks>
1235 /// This operation will causes existing records to be deleted and replaced with the ones specified.
1236 /// </remarks>
1237 public virtual void Write(IEnumerable<T> records)
1238 {
1239 if (IsOpen)
1240 {
1241 if ((object)m_fileRecords == null)
1242 {
1243 WriteToDisk(records);
1244 }
1245 else
1246 {
1247 lock (m_fileRecords)
1248 {
1249 m_fileRecords.Clear();
1250 m_fileRecords.InsertRange(0, records);
1251 }
1252 }
1253 }
1254 else
1255 {
1256 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
1257 }
1258 }
1259
1260 /// <summary>
1261 /// Writes specified record to disk if records were not loaded in memory otherwise updates the record in memory.
1262 /// </summary>
1263 /// <param name="recordIndex">1-based index of the record to be written.</param>
1264 /// <param name="record">Record to be written.</param>
1265 public virtual void Write(int recordIndex, T record)
1266 {
1267 if (IsOpen)
1268 {
1269 if ((object)record != null)
1270 {
1271 int lastRecordIndex = (object)m_fileRecords == null ? RecordsOnDisk : RecordsInMemory;
1272 if (recordIndex > lastRecordIndex + 1)
1273 {
1274 // Write missing intermediate records.
1275 for (int i = lastRecordIndex + 1; i < recordIndex; i++)
1276 {
1277 Write(i, CreateNewRecord(i));
1278 }
1279 }
1280
1281 if ((object)m_fileRecords == null)
1282 {
1283 // Write directly to the file.
1284 WriteToDisk(recordIndex, record);
1285 }
1286 else
1287 {
1288 // Update in-memory record list.
1289 lastRecordIndex = RecordsInMemory;
1290 if (recordIndex == lastRecordIndex + 1)
1291 {
1292 // Add new record.
1293 lock (m_fileRecords)
1294 {
1295 m_fileRecords.Add(record);
1296 }
1297 }
1298 else if (recordIndex <= lastRecordIndex)
1299 {
1300 // Update existing record.
1301 lock (m_fileRecords)
1302 {
1303 m_fileRecords[recordIndex - 1] = record;
1304 }
1305 }
1306 }
1307 }
1308 else
1309 {
1310 throw new ArgumentNullException("record");
1311 }
1312 }
1313 else
1314 {
1315 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
1316 }
1317 }
1318
1319 /// <summary>
1320 /// Reads file records from disk if records were not loaded in memory otherwise returns the records in memory.
1321 /// </summary>
1322 /// <returns>Records of the file.</returns>
1323 public virtual IEnumerable<T> Read()
1324 {
1325 if (IsOpen)
1326 {
1327 if ((object)m_fileRecords == null)
1328 {
1329 // Reads persisted records if no records are in memory.
1330 return ReadFromDisk();
1331 }
1332 else
1333 {
1334 // Reads records in memory.
1335 lock (m_fileRecords)
1336 {
1337 return m_fileRecords;
1338 }
1339 }
1340 }
1341 else
1342 {
1343 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
1344 }
1345 }
1346
1347 /// <summary>
1348 /// Reads specified file record from disk if records were not loaded in memory otherwise returns the record in memory.
1349 /// </summary>
1350 /// <param name="recordIndex">1-based index of the record to be read.</param>
1351 /// <returns>Record with the specified ID if it exists; otherwise null.</returns>
1352 public virtual T Read(int recordIndex)
1353 {
1354 if (IsOpen)
1355 {
1356 T record = default(T);
1357
1358 if (recordIndex > 0)
1359 {
1360 // ID of the requested record is valid.
1361 if ((object)m_fileRecords == null && recordIndex <= RecordsOnDisk)
1362 {
1363 // Reads the requested record exists in the file.
1364 record = ReadFromDisk(recordIndex);
1365 }
1366 else if (((object)m_fileRecords != null) && recordIndex <= RecordsInMemory)
1367 {
1368 // Uses the requested record from memory.
1369 lock (m_fileRecords)
1370 {
1371 record = m_fileRecords[recordIndex - 1];
1372 }
1373 }
1374 }
1375
1376 return record;
1377 }
1378 else
1379 {
1380 throw new InvalidOperationException(string.Format("{0} \"{1}\" is not open", this.GetType().Name, m_fileName));
1381 }
1382 }
1383
1384 /// <summary>
1385 /// Raises the <see cref="FileModified"/> event.
1386 /// </summary>
1387 protected virtual void OnFileModified()
1388 {
1389 if ((object)FileModified != null)
1390 FileModified(this, EventArgs.Empty);
1391 }
1392
1393 /// <summary>
1394 /// Raises the <see cref="DataLoading"/> event.
1395 /// </summary>
1396 protected virtual void OnDataLoading()
1397 {
1398 if ((object)DataLoading != null)
1399 DataLoading(this, EventArgs.Empty);
1400 }
1401
1402 /// <summary>
1403 /// Raises the <see cref="DataLoaded"/> event.
1404 /// </summary>
1405 protected virtual void OnDataLoaded()
1406 {
1407 if ((object)DataLoaded != null)
1408 DataLoaded(this, EventArgs.Empty);
1409 }
1410
1411 /// <summary>
1412 /// Raises the <see cref="DataSaving"/> event.
1413 /// </summary>
1414 protected virtual void OnDataSaving()
1415 {
1416 if ((object)DataSaving != null)
1417 DataSaving(this, EventArgs.Empty);
1418 }
1419
1420 /// <summary>
1421 /// Raises the <see cref="DataSaved"/> event.
1422 /// </summary>
1423 protected virtual void OnDataSaved()
1424 {
1425 if ((object)DataSaved != null)
1426 DataSaved(this, EventArgs.Empty);
1427 }
1428
1429 /// <summary>
1430 /// Releases the unmanaged resources used by the file and optionally releases the managed resources.
1431 /// </summary>
1432 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
1433 protected virtual void Dispose(bool disposing)
1434 {
1435 if (!m_disposed)
1436 {
1437 try
1438 {
1439 // This will be done regardless of whether the object is finalized or disposed.
1440 if (disposing)
1441 {
1442 // This will be done only when the object is disposed by calling Dispose().
1443 Close();
1444 SaveSettings();
1445
1446 if ((object)m_loadWaitHandle != null)
1447 m_loadWaitHandle.Close();
1448
1449 if ((object)m_saveWaitHandle != null)
1450 m_saveWaitHandle.Close();
1451
1452 if ((object)m_autoSaveTimer != null)
1453 {
1454 m_autoSaveTimer.Elapsed -= m_autoSaveTimer_Elapsed;
1455 m_autoSaveTimer.Dispose();
1456 }
1457
1458 if ((object)m_fileWatcher != null)
1459 {
1460 m_fileWatcher.Changed -= m_fileWatcher_Changed;
1461 m_fileWatcher.Dispose();
1462 }
1463 }
1464 }
1465 finally
1466 {
1467 m_disposed = true; // Prevent duplicate dispose.
1468
1469 if (Disposed != null)
1470 Disposed(this, EventArgs.Empty);
1471 }
1472 }
1473 }
1474
1475 /// <summary>
1476 /// Re-opens the file if currently open.
1477 /// </summary>
1478 private void ReOpen()
1479 {
1480 if (IsOpen)
1481 {
1482 Close();
1483 Open();
1484 }
1485 }
1486
1487 /// <summary>
1488 /// Writes records to disk.
1489 /// </summary>
1490 /// <param name="records">Records to be written to disk.</param>
1491 private void WriteToDisk(IEnumerable<T> records)
1492 {
1493 // Write all records to disk.
1494 int index = 0;
1495
1496 foreach (T item in records)
1497 {
1498 WriteToDisk(++index, item);
1499 }
1500
1501 // Discard previously existing records that were not written.
1502 lock (m_fileData)
1503 {
1504 m_fileData.SetLength(index * m_recordBuffer.Length);
1505 }
1506 }
1507
1508 /// <summary>
1509 /// Writes single record to disk.
1510 /// </summary>
1511 /// <param name="recordIndex">1-based index of the record to be written to disk.</param>
1512 /// <param name="record">Record to be written to disk.</param>
1513 private void WriteToDisk(int recordIndex, T record)
1514 {
1515 lock (m_fileData)
1516 {
1517 m_fileData.Seek((recordIndex - 1) * record.BinaryLength, SeekOrigin.Begin);
1518 record.CopyBinaryImageToStream(m_fileData);
1519 }
1520 }
1521
1522 /// <summary>
1523 /// Reads all records from disk.
1524 /// </summary>
1525 /// <returns>Records from disk.</returns>
1526 private IEnumerable<T> ReadFromDisk()
1527 {
1528 for (int i = 1; i <= RecordsOnDisk; i++)
1529 {
1530 yield return ReadFromDisk(i);
1531 }
1532 }
1533
1534 /// <summary>
1535 /// Read single record from disk.
1536 /// </summary>
1537 /// <param name="recordIndex">1-based index of the record to be read.</param>
1538 /// <returns>Record from the disk.</returns>
1539 private T ReadFromDisk(int recordIndex)
1540 {
1541 T newRecord = CreateNewRecord(recordIndex);
1542 lock (m_fileData)
1543 {
1544 m_fileData.Seek((recordIndex - 1) * m_recordBuffer.Length, SeekOrigin.Begin);
1545 m_fileData.Read(m_recordBuffer, 0, m_recordBuffer.Length);
1546 newRecord.ParseBinaryImage(m_recordBuffer, 0, m_recordBuffer.Length);
1547 }
1548
1549 return newRecord;
1550 }
1551
1552 private void m_autoSaveTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
1553 {
1554 try
1555 {
1556 // Automatically save records to disk if loaded in memory.
1557 if (m_autoSaveTimer.Enabled && ((object)m_fileRecords != null) && IsOpen)
1558 Save();
1559 }
1560 catch
1561 {
1562 }
1563 }
1564
1565 private void m_fileWatcher_Changed(object sender, System.IO.FileSystemEventArgs e)
1566 {
1567 try
1568 {
1569 OnFileModified();
1570
1571 // Reload records if they have been loaded in memory and reloading is enabled.
1572 if ((object)m_fileWatcher != null && m_fileWatcher.EnableRaisingEvents && (object)m_fileRecords != null)
1573 Load();
1574 }
1575 catch
1576 {
1577 }
1578 }
1579
1580 #endregion
1581 }
1582}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t