1363 lines · 49.0 KB · C#
RawDownload
//******************************************************************************************************
//  IsamDataFileBase.cs - Gbtc
//
//  Copyright © 2012, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/eclipse-1.0.php
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  03/08/2007 - Pinal C. Patel
//       Original version of source code generated.
//  11/30/2007 - Pinal C. Patel
//       Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property
//       instead of DesignMode property as the former is more accurate than the latter.
//  09/19/2008 - J. Ritchie Carroll
//       Converted to C#.
//  10/28/2008 - Pinal C. Patel
//       Edited code comments.
//  12/04/2008 - J. Ritchie Carroll
//       Modified class an example to use new ISupportBinaryImage.
//  05/12/2009 - Pinal C. Patel
//       Optimized Read() for better memory management by using "yield return".
//  05/19/2009 - Pinal C. Patel
//       Implemented the IProvideStatus interface.
//  07/02/2009 - Pinal C. Patel
//       Modified state alterning properties to reopen the file when changed.
//  08/10/2009 - Pinal C. Patel
//       Modified Write() to write empty intermediate records that are missing to avoid garbage data 
//       for the missing intermediate records when records are being written to disk directly.
//  09/14/2009 - Stephen C. Wills
//       Added new header and license agreement.
//  09/16/2009 - Pinal C. Patel
//       Modified Save() to flush buffered data to disk.
//       Removed MinimumRecordCount property - not very useful.
//  09/17/2009 - Pinal C. Patel
//       Added exception handling to event handlers.
//  12/04/2009 - Pinal C. Patel
//       Fixed thread synchronization bug in ReadFromDisk().
//  04/07/2011 - Pinal C. Patel
//       Removed inheritance from Component class to allow for serialization.
//  11/23/2011 - J. Ritchie Carroll
//       Modified to support buffer optimized ISupportBinaryImage.
//  12/06/2011 - Pinal C. Patel
//       Updated to instantiate a FileSystemWatcher object that watches for changes to the file content 
//       only  if needed to avoid a bug introduced in .NET 4.0 that causes memory leak.
//  12/14/2012 - Starlynn Danyelle Gilliam
//       Modified Header.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Text;
using System.Threading;
using System.Timers;
using System.Xml.Serialization;
using GSF.Configuration;
using GSF.Parsing;
using Timer = System.Timers.Timer;

namespace GSF.IO
{
    /// <summary>
    /// An abstract class that defines the read/write capabilities for ISAM (Indexed Sequential Access Method) file.
    /// </summary>
    /// <typeparam name="T">
    /// <see cref="Type"/> of the records the file contains. This <see cref="Type"/> must implement the <see cref="ISupportBinaryImage"/> interface.
    /// </typeparam>
    /// <remarks>
    /// <para>
    /// This ISAM implementation keeps all the records in memory, so it may not be suitable for very large files. Since data is stored
    /// in memory using a list, the maximum number of possible supported records will be 2,147,483,647 (i.e., Int32.MaxValue).
    /// </para>
    /// <para>
    /// See <a href="http://en.wikipedia.org/wiki/ISAM" target="_blank">http://en.wikipedia.org/wiki/ISAM</a> for more information on ISAM files.
    /// </para>
    /// </remarks>
    /// <example>
    /// This example shows a sample implementation of <see cref="IsamDataFileBase{T}"/>:
    /// <code>
    /// using System;
    /// using System.Text;
    /// using GSF;
    /// using GSF.IO;
    /// using GSF.Parsing;
    /// 
    /// class Program
    /// {
    ///     static void Main(string[] args)
    ///     {
    ///         // Create a few test records.
    ///         TestIsamFileRecord r1 = new TestIsamFileRecord(1);
    ///         r1.Name = "TestRecord1";
    ///         r1.Value = double.MinValue;
    ///         r1.Description = "Test record with minimum double value";
    ///         TestIsamFileRecord r2 = new TestIsamFileRecord(2);
    ///         r2.Name = "TestRecord2";
    ///         r2.Value = double.MaxValue;
    ///         r2.Description = "Test record with maximum double value";
    /// 
    ///         // Open ISAM file.
    ///         TestIsamFile testFile = new TestIsamFile();
    ///         testFile.FileName = "TestIsamFile.dat";
    ///         testFile.Open();
    /// 
    ///         // Write test records.
    ///         testFile.Write(r1.Index, r1);
    ///         testFile.Write(r2.Index, r2);
    /// 
    ///         // Read test records.
    ///         Console.WriteLine(testFile.Read(1));
    ///         Console.WriteLine(testFile.Read(2));
    /// 
    ///         // Close ISAM file.
    ///         testFile.Close();
    /// 
    ///         Console.ReadLine();
    ///     }
    /// }
    /// 
    /// class TestIsamFile : IsamDataFileBase&lt;TestIsamFileRecord&gt;
    /// {
    ///     /// <summary>
    ///     /// Size of a single file record.
    ///     /// </summary>
    ///     protected override int GetRecordSize()
    ///     {
    ///         return TestIsamFileRecord.RecordLength;
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Creates a new empty file record.
    ///     /// </summary>
    ///     protected override TestIsamFileRecord CreateNewRecord(int id)
    ///     {
    ///         return new TestIsamFileRecord(id);
    ///     }
    /// }
    /// 
    /// class TestIsamFileRecord : ISupportBinaryImage
    /// {
    ///     private int m_index;
    ///     private string m_name;                  // 20  * 1 =  20
    ///     private double m_value;                 // 1   * 8 =   8
    ///     private string m_description;           // 100 * 1 = 100
    ///     
    ///     public const int RecordLength = 128;    // Total   = 128
    /// 
    ///     public TestIsamFileRecord(int recordIndex)
    ///     {
    ///         m_index = recordIndex;
    ///         
    ///         Name = string.Empty;
    ///         Value = double.NaN;
    ///         Description = string.Empty;
    ///     }
    /// 
    ///     /// <summary>
    ///     /// 1-based index of the record.
    ///     /// </summary>
    ///     public int Index
    ///     {
    ///         get { return m_index; }
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Name of the record.
    ///     /// </summary>
    ///     public string Name
    ///     {
    ///         get { return m_name; }
    ///         set { m_name = value.TruncateRight(20).PadRight(20); }
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Value of the record.
    ///     /// </summary>
    ///     public double Value
    ///     {
    ///         get { return m_value; }
    ///         set { m_value = value; }
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Description of the record.
    ///     /// </summary>
    ///     public string Description
    ///     {
    ///         get { return m_description; }
    ///         set { m_description = value.TruncateRight(100).PadRight(100); }
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Serialized record length.
    ///     /// </summary>
    ///     public int BinaryLength
    ///     {
    ///         get { return RecordLength; }
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Serialized record data.
    ///     /// </summary>
    ///     public byte[] BinaryImage
    ///     {
    ///         get
    ///         {
    ///             // Serialize TestIsamFileRecord into byte array.
    ///             byte[] image = new byte[RecordLength];
    ///             Buffer.BlockCopy(Encoding.ASCII.GetBytes(Name), 0, image, 0, 20);
    ///             Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, image, 20, 8);
    ///             Buffer.BlockCopy(Encoding.ASCII.GetBytes(Description), 0, image, 28, 100);
    /// 
    ///             return image;
    ///         }
    ///     }
    /// 
    ///     /// <summary>
    ///     /// Deserializes the record.
    ///     /// </summary>
    ///     public int Initialize(byte[] binaryImage, int startIndex, int length)
    ///     {
    ///         if (length &gt;= RecordLength)
    ///         {
    ///             // Deserialize byte array into TestIsamFileRecord.
    ///             Name = Encoding.ASCII.GetString(binaryImage, startIndex, 20);
    ///             Value = BitConverter.ToDouble(binaryImage, startIndex + 20);
    ///             Description = Encoding.ASCII.GetString(binaryImage, startIndex + 28, 100);
    ///         }
    ///         else
    ///             throw new InvalidOperationException("Invalid record size, not enough data to deserialize record"); 
    /// 
    ///         return RecordLength;
    ///     }
    /// 
    ///     /// <summary>
    ///     /// String representation of the record.
    ///     /// </summary>
    ///     public override string ToString()
    ///     {
    ///         return string.Format("Name: {0}, Value: {1}, Description: {2}", Name, Value, Description);
    ///     }
    /// }
    /// </code>
    /// </example>
    public abstract class IsamDataFileBase<T> : ISupportLifecycle, IProvideStatus, IPersistSettings where T : ISupportBinaryImage
    {
        #region [ Members ]

        // Constants

        /// <summary>
        /// Specifies the default value for the <see cref="FileName"/> property.
        /// </summary>
        public const string DefaultFileName = "IsamDataFile.dat";

        /// <summary>
        /// Specifies the default value for the <see cref="FileAccessMode"/> property.
        /// </summary>
        public const FileAccess DefaultFileAccessMode = FileAccess.ReadWrite;

        /// <summary>
        /// Specifies the default value for the <see cref="AutoSaveInterval"/> property.
        /// </summary>
        public const int DefaultAutoSaveInterval = -1;

        /// <summary>
        /// Specifies the default value for the <see cref="LoadOnOpen"/> property.
        /// </summary>
        public const bool DefaultLoadOnOpen = false;

        /// <summary>
        /// Specifies the default value for the <see cref="SaveOnClose"/> property.
        /// </summary>
        public const bool DefaultSaveOnClose = false;

        /// <summary>
        /// Specifies the default value for the <see cref="ReloadOnModify"/> property.
        /// </summary>
        public const bool DefaultReloadOnModify = false;

        /// <summary>
        /// Specifies the default value for the <see cref="PersistSettings"/> property.
        /// </summary>
        public const bool DefaultPersistSettings = false;

        /// <summary>
        /// Specifies the default value for the <see cref="SettingsCategory"/> property.
        /// </summary>
        public const string DefaultSettingsCategory = "IsamDataFile";

        // Events

        /// <summary>
        /// Occurs when data is being read from disk into memory.
        /// </summary>
        public event EventHandler DataLoading;

        /// <summary>
        /// Occurs when data has been read from disk into memory.
        /// </summary>
        public event EventHandler DataLoaded;

        /// <summary>
        /// Occurs when data is being saved from memory onto disk.
        /// </summary>
        public event EventHandler DataSaving;

        /// <summary>
        /// Occurs when data has been saved from memory onto disk.
        /// </summary>
        public event EventHandler DataSaved;

        /// <summary>
        /// Occurs when file data on the disk is modified.
        /// </summary>
        public event EventHandler FileModified;

        /// <summary>
        /// Occurs when the class has been disposed.
        /// </summary>
        public event EventHandler Disposed;

        // Fields
        private string m_fileName;
        private FileAccess m_fileAccessMode;
        private int m_autoSaveInterval;
        private bool m_loadOnOpen;
        private bool m_saveOnClose;
        private bool m_reloadOnModify;
        private bool m_persistSettings;
        private string m_settingsCategory;
        private List<T> m_fileRecords;
        private byte[] m_recordBuffer;
        private FileStream m_fileData;
        private readonly ManualResetEvent m_loadWaitHandle;
        private readonly ManualResetEvent m_saveWaitHandle;
        private readonly Timer m_autoSaveTimer;
        private FileSystemWatcher m_fileWatcher;
        private bool m_disposed;
        private bool m_initialized;

        #endregion

        #region [ Constructors ]

        /// <summary>
        /// Initializes a new instance of the <see cref="IsamDataFileBase{T}"/> class.
        /// </summary>
        protected IsamDataFileBase()
        {
            m_fileName = DefaultFileName;
            m_fileAccessMode = DefaultFileAccessMode;
            m_autoSaveInterval = DefaultAutoSaveInterval;
            m_loadOnOpen = DefaultLoadOnOpen;
            m_saveOnClose = DefaultSaveOnClose;
            m_reloadOnModify = DefaultReloadOnModify;
            m_persistSettings = DefaultPersistSettings;
            m_settingsCategory = DefaultSettingsCategory;
            m_loadWaitHandle = new ManualResetEvent(true);
            m_saveWaitHandle = new ManualResetEvent(true);

            m_autoSaveTimer = new Timer();
            m_autoSaveTimer.Elapsed += m_autoSaveTimer_Elapsed;
        }

        /// <summary>
        /// Releases the unmanaged resources before the file is reclaimed by <see cref="GC"/>.
        /// </summary>
        ~IsamDataFileBase()
        {
            Dispose(false);
        }

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets or sets the name of the file.
        /// </summary>
        /// <remarks>
        /// Changing the <see cref="FileName"/> when the file is open will cause the file to be re-opend.
        /// </remarks>
        public string FileName
        {
            get
            {
                return m_fileName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ArgumentNullException("value");

                m_fileName = value;
                ReOpen();
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="FileAccess"/> value to use when opening the file.
        /// </summary>
        public FileAccess FileAccessMode
        {
            get
            {
                return m_fileAccessMode;
            }
            set
            {
                m_fileAccessMode = value;
                ReOpen();
            }
        }

        /// <summary>
        /// Gets or sets the interval in milliseconds at which the records loaded in memory are to be persisted to disk.
        /// </summary>
        /// <remarks>
        /// <see cref="AutoSaveInterval"/> will be effective only if records have been loaded in memory either manually 
        /// by calling the <see cref="Load()"/> method or automatically by settings <see cref="LoadOnOpen"/> to true.
        /// </remarks>
        public int AutoSaveInterval
        {
            get
            {
                return m_autoSaveInterval;
            }
            set
            {
                m_autoSaveInterval = value;
                ReOpen();
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether records are to be loaded automatically in memory when 
        /// the file is opened.
        /// </summary>
        public bool LoadOnOpen
        {
            get
            {
                return m_loadOnOpen;
            }
            set
            {
                m_loadOnOpen = value;
                ReOpen();
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether records loaded in memory are to be persisted to disk 
        /// when the file is closed.
        /// </summary>
        /// <remarks>
        /// <see cref="SaveOnClose"/> will be effective only if records have been loaded in memory either manually 
        /// by calling the <see cref="Load()"/> method or automatically by settings <see cref="LoadOnOpen"/> to true.
        /// </remarks>
        public bool SaveOnClose
        {
            get
            {
                return m_saveOnClose;
            }
            set
            {
                m_saveOnClose = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether records loaded in memory are to be re-loaded when the 
        /// file is modified on disk.
        /// </summary>
        /// <remarks>
        /// <see cref="ReloadOnModify"/> will be effective only if records have been loaded in memory either manually 
        /// by calling the <see cref="Load()"/> method or automatically by settings <see cref="LoadOnOpen"/> to true.
        /// </remarks>
        public bool ReloadOnModify
        {
            get
            {
                return m_reloadOnModify;
            }
            set
            {

Lines 1–500 of 1363.

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