//******************************************************************************************************
// ArchiveFileAllocationTable.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/18/2007 - Pinal C. Patel
// Generated original version of code based on DatAWare system specifications by Brian B. Fox, GSF.
// 01/23/2008 - Pinal C. Patel
// Added thread safety to all FindDataBlock() methods.
// Recoded RequestDataBlock() method to include the logic to use previously used partially filled
// data blocks first.
// 03/31/2008 - Pinal C. Patel
// Removed intervaled persisting of FAT since FAT is persisted when new block is requested.
// Recoded RequestDataBlock() method to speed up the block request process based on the block index
// suggestion provided from the state information of the point.
// 07/14/2008 - Pinal C. Patel
// Added overload to GetDataBlock() method that takes a block index.
// 04/21/2009 - Pinal C. Patel
// Converted to C#.
// 09/15/2009 - Stephen C. Wills
// Added new header and license agreement.
// 09/23/2009 - Pinal C. Patel
// Edited code comments.
// 12/03/2009 - Pinal C. Patel
// Modified FindDataBlocks() to accurately find matching data blocks even when the specified
// search timespan is a sub-second range.
// 10/11/2010 - Mihir Brahmbhatt
// Updated header and license agreement.
// 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.IO;
using System.Linq;
using GSF.Interop;
using GSF.Parsing;
using GSF.Units;
namespace GSF.Historian.Files
{
///
/// Represents the File Allocation Table of an .
///
/// .
///
///
public class ArchiveFileAllocationTable : ISupportBinaryImage
{
#region [ Members ]
// Nested Types
///
/// Defines the fixed table region of the .
///
private class FixedTableRegion : ISupportBinaryImage
{
#region [ Members ]
// Constants
///
/// Specifies the number of bytes in the binary image of the .
///
public const int FixedLength = 32;
// Fields
private readonly ArchiveFileAllocationTable m_parent;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the .
///
/// Reference to parent .
public FixedTableRegion(ArchiveFileAllocationTable parent)
{
m_parent = parent;
}
#endregion
#region [ Properties ]
///
/// Gets the length of the .
///
public int BinaryLength
{
get
{
return FixedLength;
}
}
#endregion
#region [ Methods ]
///
/// Initializes by parsing the specified containing a binary image.
///
/// Buffer containing binary image to parse.
/// 0-based starting index in the to start parsing.
/// Valid number of bytes within from .
/// The number of bytes used for initialization in the (i.e., the number of bytes parsed).
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public int ParseBinaryImage(byte[] buffer, int startIndex, int length)
{
buffer.ValidateParameters(startIndex, length);
decimal startTime = (decimal)LittleEndian.ToDouble(buffer, startIndex);
decimal stopTime = (decimal)LittleEndian.ToDouble(buffer, startIndex + 8);
// Validate file time tags
if (startTime < TimeTag.MinValue.Value || startTime > TimeTag.MaxValue.Value)
startTime = TimeTag.MinValue.Value;
if (stopTime < TimeTag.MinValue.Value || stopTime > TimeTag.MaxValue.Value)
stopTime = TimeTag.MinValue.Value;
m_parent.FileStartTime = new TimeTag(startTime);
m_parent.FileEndTime = new TimeTag(stopTime);
m_parent.DataPointsReceived = LittleEndian.ToInt32(buffer, startIndex + 16);
m_parent.DataPointsArchived = LittleEndian.ToInt32(buffer, startIndex + 20);
m_parent.DataBlockSize = LittleEndian.ToInt32(buffer, startIndex + 24);
m_parent.DataBlockCount = LittleEndian.ToInt32(buffer, startIndex + 28);
return FixedLength;
}
///
/// Generates binary image of the and copies it into the given buffer, for bytes.
///
/// Buffer used to hold generated binary image of the source object.
/// 0-based starting index in the to start writing.
/// The number of bytes written to the .
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public int GenerateBinaryImage(byte[] buffer, int startIndex)
{
int length = BinaryLength;
buffer.ValidateParameters(startIndex, length);
Buffer.BlockCopy(LittleEndian.GetBytes((double)m_parent.m_fileStartTime.Value), 0, buffer, startIndex, 8);
Buffer.BlockCopy(LittleEndian.GetBytes((double)m_parent.m_fileEndTime.Value), 0, buffer, startIndex + 8, 8);
Buffer.BlockCopy(LittleEndian.GetBytes(m_parent.m_dataPointsReceived), 0, buffer, startIndex + 16, 4);
Buffer.BlockCopy(LittleEndian.GetBytes(m_parent.m_dataPointsArchived), 0, buffer, startIndex + 20, 4);
Buffer.BlockCopy(LittleEndian.GetBytes(m_parent.m_dataBlockSize), 0, buffer, startIndex + 24, 4);
Buffer.BlockCopy(LittleEndian.GetBytes(m_parent.m_dataBlockCount), 0, buffer, startIndex + 28, 4);
return length;
}
#endregion
}
///
/// Defines the variable table region of the .
///
private class VariableTableRegion : ISupportBinaryImage
{
#region [ Members ]
// Fields
private readonly ArchiveFileAllocationTable m_parent;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the .
///
/// Reference to parent .
public VariableTableRegion(ArchiveFileAllocationTable parent)
{
m_parent = parent;
}
#endregion
#region [ Properties ]
///
/// Gets the length of the .
///
public int BinaryLength
{
get
{
// We add the extra bytes for the array descriptor that are required for reading the file from a VB6 style application.
return (ArrayDescriptorLength + (m_parent.m_dataBlockCount * ArchiveDataBlockPointer.FixedLength));
}
}
#endregion
#region [ Methods ]
///
/// Initializes by parsing the specified containing a binary image.
///
/// Buffer containing binary image to parse.
/// 0-based starting index in the to start parsing.
/// Valid number of bytes within from .
/// The number of bytes used for initialization in the (i.e., the number of bytes parsed).
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public int ParseBinaryImage(byte[] buffer, int startIndex, int length)
{
buffer.ValidateParameters(startIndex, length);
ArchiveDataBlockPointer blockPointer;
// Set parsing cursor beyond old-style visual basic array descriptor
int index = startIndex + ArrayDescriptorLength;
for (int i = 0; i < m_parent.m_dataBlockCount; i++)
{
blockPointer = new ArchiveDataBlockPointer(m_parent.m_parent, i);
index += blockPointer.ParseBinaryImage(buffer, index, length - (index - startIndex));
m_parent.m_dataBlockPointers.Add(blockPointer);
}
return (index - startIndex);
}
///
/// Generates binary image of the and copies it into the given buffer, for bytes.
///
/// Buffer used to hold generated binary image of the source object.
/// 0-based starting index in the to start writing.
/// The number of bytes written to the .
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public int GenerateBinaryImage(byte[] buffer, int startIndex)
{
int length = BinaryLength;
buffer.ValidateParameters(startIndex, length);
VBArrayDescriptor arrayDescriptor = VBArrayDescriptor.OneBasedOneDimensionalArray(m_parent.m_dataBlockCount);
startIndex += arrayDescriptor.GenerateBinaryImage(buffer, startIndex);
lock (m_parent.m_dataBlockPointers)
{
for (int i = 0; i < m_parent.m_dataBlockPointers.Count; i++)
{
startIndex += m_parent.m_dataBlockPointers[i].GenerateBinaryImage(buffer, startIndex);
}
}
return length;
}
#endregion
}
// Constants
private const int ArrayDescriptorLength = 10;
// Fields
private TimeTag m_fileStartTime;
private TimeTag m_fileEndTime;
private int m_dataPointsReceived;
private int m_dataPointsArchived;
private int m_dataBlockSize;
private int m_dataBlockCount;
private readonly List m_dataBlockPointers;
private readonly ArchiveFile m_parent;
private readonly FixedTableRegion m_fixedTableRegion;
private readonly VariableTableRegion m_variableTableRegion;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
/// An object.
internal ArchiveFileAllocationTable(ArchiveFile parent)
{
m_parent = parent;
m_dataBlockPointers = new List();
m_fixedTableRegion = new FixedTableRegion(this);
m_variableTableRegion = new VariableTableRegion(this);
if (m_parent.FileData.Length == 0)
{
// File is brand new.
m_fileStartTime = TimeTag.MinValue;
m_fileEndTime = TimeTag.MinValue;
m_dataBlockSize = m_parent.DataBlockSize;
m_dataBlockCount = ArchiveFile.MaximumDataBlocks(m_parent.FileSize, m_parent.DataBlockSize);
for (int i = 0; i < m_dataBlockCount; i++)
{
m_dataBlockPointers.Add(new ArchiveDataBlockPointer(m_parent, i));
}
}
else
{
// Existing file, read table regions:
// Seek to beginning of fixed table region
m_parent.FileData.Seek(-m_fixedTableRegion.BinaryLength, SeekOrigin.End);
// Parse fixed table region
m_fixedTableRegion.ParseBinaryImageFromStream(m_parent.FileData);
// Seek to beginning of variable table region (above fixed from bottom of file)
m_parent.FileData.Seek(-(m_fixedTableRegion.BinaryLength + m_variableTableRegion.BinaryLength), SeekOrigin.End);
// Parse variable table region
m_variableTableRegion.ParseBinaryImageFromStream(m_parent.FileData);
}
}
#endregion
#region [ Properties ]
///
/// Gets or sets the of the oldest in the .
///
/// The value being assigned is not between 01/01/1995 and 01/19/2063.
public TimeTag FileStartTime
{
get
{
return m_fileStartTime;
}
set
{
if (value < TimeTag.MinValue || value > TimeTag.MaxValue)
throw new ArgumentException("Value must between 01/01/1995 and 01/19/2063");
m_fileStartTime = value;
}
}
///
/// Gets or sets the of the newest in the .
///
/// The value being assigned is not between 01/01/1995 and 01/19/2063.
public TimeTag FileEndTime
{
get
{
return m_fileEndTime;
}
set
{
if (value < TimeTag.MinValue || value > TimeTag.MaxValue)
throw new ArgumentException("Value must between 01/01/1995 and 01/19/2063");
m_fileEndTime = value;
}
}
///
/// Gets or sets the number s received by the for archival.
///
/// The value being assigned is not positive or zero.
public int DataPointsReceived
{
get
{
return m_dataPointsReceived;
}
set
{
if (value < 0)
throw new ArgumentException("Value must be positive or zero");
m_dataPointsReceived = value;
}
}
///
/// Gets or sets the number s archived by the .
///
/// The value being assigned is not positive or zero.
public int DataPointsArchived
{
get
{
return m_dataPointsArchived;
}
set
{
if (value < 0)
throw new ArgumentException("Value must be positive or zero");
m_dataPointsArchived = value;
}
}
///
/// Gets the size (in KB) of a single in the .
///
public int DataBlockSize
{
get
{
return m_dataBlockSize;
}
private set
{
if (value < 1)
throw new ArgumentException("Value must be positive");
m_dataBlockSize = value;
}
}
///
/// Gets the total number of s in the .
///
public int DataBlockCount
{
get
{
return m_dataBlockCount;
}
private set
{
if (value < 1)
throw new ArgumentException("Value must be positive");
m_dataBlockCount = value;
}
}
///
/// Gets the number of used s in the .
///
public int DataBlocksUsed
{
get
{
return m_dataBlockCount - DataBlocksAvailable;
}
}
///
/// Gets the number of unused s in the .
///
public int DataBlocksAvailable
{
get
{
ArchiveDataBlock unusedDataBlock = FindDataBlock(-1);
if ((object)unusedDataBlock != null)
return m_dataBlockCount - unusedDataBlock.Index;
return 0;
}
}
///
/// Gets the s to the s in the .
///
///
/// WARNING: is not thread safe. Synchronized access is required.
///
public IList DataBlockPointers
{
get
{
return m_dataBlockPointers.AsReadOnly();
}
}
///
/// Gets the length of the .
///
public int BinaryLength
{
get
{
return m_variableTableRegion.BinaryLength + m_fixedTableRegion.BinaryLength;
}
}
private long DataLength
{
get
{
return (m_dataBlockCount * (m_dataBlockSize * 1024));
}
}
#endregion
#region [ Methods ]
///
/// Generates binary image of the and copies it into the given buffer, for bytes.
///
/// Buffer used to hold generated binary image of the source object.
/// 0-based starting index in the to start writing.
/// The number of bytes written to the .
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public int GenerateBinaryImage(byte[] buffer, int startIndex)
{
int length = BinaryLength;
buffer.ValidateParameters(startIndex, length);
startIndex += m_variableTableRegion.GenerateBinaryImage(buffer, startIndex);
m_fixedTableRegion.GenerateBinaryImage(buffer, startIndex);
return length;
}
// This method is never used since constructor parses the point allocation table
int ISupportBinaryImage.ParseBinaryImage(byte[] buffer, int startIndex, int length)
{
throw new NotSupportedException();
}
///
/// Saves the data to the .
///
/// Forces an immediate disk flush for save.
public void Save(bool forceFlush = false)
{
// Leave space for data blocks.
lock (m_parent.FileData)
{
if (m_parent.FileData.Length == 0)
m_parent.FileData.SetLength((long)(m_parent.FileSize * SI2.Mega));
m_parent.FileData.Seek(DataLength, SeekOrigin.Begin);
this.CopyBinaryImageToStream(m_parent.FileData);
if (!m_parent.CacheWrites)
m_parent.FileData.Flush();
}
}
///
/// Extends the by one .
///
public void Extend()
{
Extend(1);
}
///
/// Extends the by the specified number of s.
///
/// Number of s to add to the .
public void Extend(int dataBlocksToAdd)
{
// Extend the FAT.
lock (m_dataBlockPointers)
{
for (int i = 1; i <= dataBlocksToAdd; i++)
{
m_dataBlockPointers.Add(new ArchiveDataBlockPointer(m_parent, m_dataBlockPointers.Count));
}
m_dataBlockCount = m_dataBlockPointers.Count;
}
Save();
// Initialize newly added data blocks.
ArchiveDataBlock dataBlock;
for (int i = m_dataBlockCount - dataBlocksToAdd; i < m_dataBlockCount; i++)
{
dataBlock = new ArchiveDataBlock(m_parent, i, -1);
dataBlock.Reset();
}
}
///
/// Returns the first in the for the specified .
///
/// Historian identifier whose is to be retrieved.
/// true to pre-read data to locate write cursor.
/// object if a match is found; otherwise null.
public ArchiveDataBlock FindDataBlock(int historianID, bool preRead = true)
{
ArchiveDataBlockPointer pointer;
lock (m_dataBlockPointers)
{
pointer = m_dataBlockPointers.FirstOrDefault(dataBlockPointer => dataBlockPointer.HistorianID == historianID);
}
if ((object)pointer == null)
return null;
return pointer.GetDataBlock(preRead);
}
///
/// Returns the last in the for the specified .
///
/// Historian identifier.
/// true to pre-read data to locate write cursor.
/// object if a match is found; otherwise null.
public ArchiveDataBlock FindLastDataBlock(int historianID, bool preRead = true)
{
ArchiveDataBlockPointer pointer;
lock (m_dataBlockPointers)
{
pointer = m_dataBlockPointers.LastOrDefault(dataBlockPointer => dataBlockPointer.HistorianID == historianID);
}
if ((object)pointer == null)
return null;
return pointer.GetDataBlock(preRead);
}
///
/// Returns all s in the for the specified .
///
/// Historian identifier.
/// true to pre-read data to locate write cursor.
/// A collection of s.
public List FindDataBlocks(int historianID, bool preRead = true)
{
return FindDataBlocks(historianID, TimeTag.MinValue, preRead);
}
///
/// Returns all s in the for the specified with points later than the specified .
///
/// Historian identifier.
/// Start .
/// true to pre-read data to locate write cursor.
/// A collection of s.
public List FindDataBlocks(int historianID, TimeTag startTime, bool preRead = true)
{
return FindDataBlocks(historianID, startTime, TimeTag.MaxValue, preRead);
}
///
/// Returns all s in the for the specified with s between the specified and .
///
/// Historian identifier.
/// Start .
/// End .
/// true to pre-read data to locate write cursor.
/// A collection of s.
public List FindDataBlocks(int historianID, TimeTag startTime, TimeTag endTime, bool preRead = true)
{
if ((object)startTime == null)
startTime = TimeTag.MaxValue;
if ((object)endTime == null)
endTime = TimeTag.MaxValue;
List blockPointers;
lock (m_dataBlockPointers)
{
// Get all block pointers for given point ID over specified time range
blockPointers = m_dataBlockPointers.FindAll(dataBlockPointer => dataBlockPointer.Matches(historianID, startTime, endTime));
// Look for pointer to data block on the borders of the specified range which may contain data
if (!(startTime == TimeTag.MinValue || endTime == TimeTag.MaxValue))
{
// There are 2 different search criteria for this:
// 1) If matching data block pointers have been found, then find data block pointer before the first matching data block pointer.
// or
// 2) Find the last data block pointer in the time range of TimeTag.MinValue to m_searchEndTime.
TimeTag searchEndTime = endTime;
if (blockPointers.Count > 0)
searchEndTime = new TimeTag(blockPointers.First().StartTime.Value - 1.0M);
ArchiveDataBlockPointer borderMatch = m_dataBlockPointers.LastOrDefault(dataBlockPointer => dataBlockPointer.Matches(historianID, TimeTag.MinValue, searchEndTime));
if ((object)borderMatch != null)
blockPointers.Insert(0, borderMatch);
}
}
// Return list of data blocks for given block pointers
return blockPointers.Select(pointer => pointer.GetDataBlock(preRead)).ToList();
}
///
/// Returns an for writing s for the specified .
///
/// Historian identifier for which the is being requested.
/// of the to be written to the .
/// of the last used for writing s for the .
/// object if available; otherwise null if all s have been allocated.
internal ArchiveDataBlock RequestDataBlock(int historianID, TimeTag dataTime, int blockIndex)
{
ArchiveDataBlock dataBlock = null;
ArchiveDataBlockPointer dataBlockPointer = null;
if (blockIndex >= 0 && blockIndex < m_dataBlockCount)
{
// Valid data block index is specified, so retrieve the corresponding data block.
lock (m_dataBlockPointers)
{
dataBlockPointer = m_dataBlockPointers[blockIndex];
}
dataBlock = dataBlockPointer.DataBlock;
if (!dataBlockPointer.IsAllocated && dataBlock.SlotsUsed > 0)
{
// Clear existing data from the data block since it is unallocated.
dataBlock.Reset();
}
else if (dataBlockPointer.IsAllocated && (dataBlockPointer.HistorianID != historianID || (dataBlockPointer.HistorianID == historianID && dataBlock.SlotsAvailable == 0)))
{
// Search for a new data block since the suggested data block cannot be used.
blockIndex = -1;
}
}
if (blockIndex < 0)
{
// Negative data block index is specified indicating a search must be performed for a data block.
dataBlock = FindLastDataBlock(historianID);
if ((object)dataBlock != null && dataBlock.SlotsAvailable == 0)
{
// Previously used data block is full.
dataBlock = null;
}
if ((object)dataBlock == null)
{
// Look for the first unallocated data block.
dataBlock = FindDataBlock(-1);
if ((object)dataBlock == null)
{
// Extend the file for historic writes only.
if (m_parent.FileType == ArchiveFileType.Historic)
{
Extend();
dataBlock = m_dataBlockPointers[m_dataBlockPointers.Count - 1].DataBlock;
}
}
else
{
// Reset the unallocated data block if there is data in it.
if (dataBlock.SlotsUsed > 0)
dataBlock.Reset();
}
}
// Get the pointer to the data block so that its information can be updated if necessary.
if ((object)dataBlock == null)
{
dataBlockPointer = null;
}
else
{
lock (m_dataBlockPointers)
{
dataBlockPointer = m_dataBlockPointers[dataBlock.Index];
}
}
}
if ((object)dataBlockPointer != null && !dataBlockPointer.IsAllocated)
{
// Mark the data block as allocated.
dataBlockPointer.HistorianID = historianID;
dataBlockPointer.StartTime = dataTime;
// Set the file start time if not set.
if (m_fileStartTime == TimeTag.MinValue)
m_fileStartTime = dataTime;
// Persist data block information to disk.
lock (m_parent.FileData)
{
// We'll write information about the just allocated data block to the file.
m_parent.FileData.Seek(DataLength + ArrayDescriptorLength + (dataBlockPointer.Index * ArchiveDataBlockPointer.FixedLength), SeekOrigin.Begin);
dataBlockPointer.CopyBinaryImageToStream(m_parent.FileData);
// We'll also write the fixed part of the FAT data that resides at the end.
m_parent.FileData.Seek(-m_fixedTableRegion.BinaryLength, SeekOrigin.End);
// Copy generated binary image to stream
m_fixedTableRegion.CopyBinaryImageToStream(m_parent.FileData);
if (!m_parent.CacheWrites)
m_parent.FileData.Flush();
}
// Re-fetch the data block with updated information after allocation.
dataBlock = dataBlockPointer.DataBlock;
}
return dataBlock;
}
#endregion
}
}