//****************************************************************************************************** // ArchiveDataBlock.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 MIT License (MIT), 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/MIT // // 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: // ---------------------------------------------------------------------------------------------------- // 02/24/2007 - Pinal C. Patel // Generated original version of source code. // 01/23/2008 - Pinal C. Patel // Removed IsForHistoricData and added IsActive to keep track of activity. // 03/31/2008 - Pinal C. Patel // Modified code to use the same FileStream object used by FAT instead to creating a new one. // Removed IDisposable interface implementation and Size property. // 04/20/2009 - Pinal C. Patel // Converted to C#. // 08/05/2009 - Josh L. Patterson // Edited Comments. // 09/15/2009 - Stephen C. Wills // Added new header and license agreement. // 09/23/2009 - Pinal C. Patel // Edited code comments. // Removed the dependency on ArchiveDataPoint. // 10/14/2009 - Pinal C. Patel // Modified Write() to seek only when necessary. // 10/11/2010 - Mihir Brahmbhatt // Updated header and license agreement. // 11/18/2010 - J. Ritchie Carroll // Added a exception handler for reading (exposed via DataReadException event) to make sure // bad data or corruption in an archive file does not stop the read process. // 11/30/2011 - J. Ritchie Carroll // Modified to support buffer optimized ISupportBinaryImage. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using GSF.Parsing; namespace GSF.Historian.Files { /// /// Represents a block of s in an . /// /// /// public class ArchiveDataBlock { #region [ Members ] // Constants /// /// Time in seconds after which the block is considered inactive if no reads or writes were performed. /// private const int InactivityPeriod = 300; // Fields private readonly int m_index; private readonly int m_historianID; private readonly ArchiveFile m_parent; private readonly byte[] m_readBuffer; private long m_writeCursor; private DateTime m_lastActivityTime; /// /// Occurs when an is encountered while reading from the . /// [Category("Data"), Description("Occurs when an Exception is encountered while reading an IDataPoint from the ArchiveDataBlock.")] public event EventHandler> DataReadException; /// /// Occurs when an is encountered while writing to the . /// [Category("Data"), Description("Occurs when an Exception is encountered while writing an IDataPoint to the ArchiveDataBlock.")] public event EventHandler> DataWriteException; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// /// An object. /// 0-based index of the . /// Historian identifier whose is stored in the . /// true to pre-read data to locate write cursor. internal ArchiveDataBlock(ArchiveFile parent, int index, int historianID, bool preRead = true) { m_parent = parent; m_index = index; m_historianID = historianID; m_readBuffer = new byte[ArchiveDataPoint.FixedLength]; m_writeCursor = Location; m_lastActivityTime = DateTime.UtcNow; if (!preRead) return; // Scan through existing data to locate write cursor // ReSharper disable once UnusedVariable foreach (IDataPoint dataPoint in Read()) { } } #endregion #region [ Properties ] /// /// Gets the 0-based index of the . /// public int Index => m_index; /// /// Gets the start location (byte position) of the in the . /// public long Location => m_index * m_parent.DataBlockSize * 1024; /// /// Gets the maximum number of s that can be stored in the . /// public int Capacity => m_parent.DataBlockSize * 1024 / ArchiveDataPoint.FixedLength; /// /// Gets the number of s that have been written to the . /// public int SlotsUsed => (int)((m_writeCursor - Location) / ArchiveDataPoint.FixedLength); /// /// Gets the number of s that can to written to the . /// public int SlotsAvailable => Capacity - SlotsUsed; /// /// Gets a boolean value that indicates whether the is being actively used. /// public bool IsActive => DateTime.UtcNow.Subtract(m_lastActivityTime).TotalSeconds <= InactivityPeriod; #endregion #region [ Methods ] /// /// Reads existing s from the . /// /// Returns s from the . public IEnumerable Read() { ArchiveDataPoint dataPoint; lock (m_parent.FileData) { // We'll start reading from where the data block begins. m_parent.FileData.Seek(Location, SeekOrigin.Begin); for (int i = 0; i < Capacity; i++) { // Read the data in the block. m_lastActivityTime = DateTime.UtcNow; m_parent.FileData.Read(m_readBuffer, 0, m_readBuffer.Length); // Attempt to parse archive data point try { dataPoint = new ArchiveDataPoint(m_historianID, m_readBuffer, 0, m_readBuffer.Length); } catch (Exception ex) { dataPoint = null; OnDataReadException(ex); } if (dataPoint != null && !dataPoint.IsEmpty) { // There is data - use it. m_writeCursor = m_parent.FileData.Position; yield return dataPoint; } else { // Data is empty - stop reading. yield break; } } } } /// /// Writes the to the . /// /// to write. /// Set to true to suppress write exceptions; defaults to false. /// /// true if data point was written; otherwise, false. /// /// /// If is false, the default value, any encountered /// exception will be thrown on the call stack. /// public bool Write(IDataPoint dataPoint, bool suppressExceptions = false) { Exception ex; if (Write(dataPoint, out ex)) return true; if (suppressExceptions) return false; throw ex; } /// /// Writes the to the . /// /// to write. /// Any that may have been encountered while writing. /// /// true if data point was written; otherwise, false. /// public bool Write(IDataPoint dataPoint, out Exception exception) { exception = null; try { TimeTag value = dataPoint.Time; // Do not attempt to write values with a bad timestamp, this will just throw an exception if (value.CompareTo(TimeTag.MinValue) < 0 || value.CompareTo(TimeTag.MaxValue) > 0) { exception = new TimeTagException("Skipping data write for point: Bad time tag, value must between 01/01/1995 and 01/19/2063"); } else { if (SlotsAvailable > 0) { // We have enough space to write the provided point data to the data block. m_lastActivityTime = DateTime.UtcNow; lock (m_parent.FileData) { // Write the data. if (m_writeCursor != m_parent.FileData.Position) m_parent.FileData.Seek(m_writeCursor, SeekOrigin.Begin); dataPoint.CopyBinaryImageToStream(m_parent.FileData); // Update the write cursor. m_writeCursor = m_parent.FileData.Position; // Flush the data if configured. if (!m_parent.CacheWrites) m_parent.FileData.Flush(); } } else { exception = new InvalidOperationException("Skipping data write for point: No slots available for writing new data"); } } } catch (Exception ex) { exception = new InvalidOperationException($"Skipping data write for point: {ex.Message}", ex); } if ((object)exception == null) return true; OnDataWriteException(exception); return false; } /// /// Resets the by overwriting existing s with empty s. /// public void Reset() { m_writeCursor = Location; for (int i = 1; i <= Capacity; i++) Write(new ArchiveDataPoint(m_historianID), true); m_writeCursor = Location; } /// /// Raises the event. /// /// to send to event. protected virtual void OnDataReadException(Exception ex) { DataReadException?.Invoke(this, new EventArgs(ex)); } /// /// Raises the event. /// /// to send to event. protected virtual void OnDataWriteException(Exception ex) { DataWriteException?.Invoke(this, new EventArgs(ex)); } #endregion } }