1134 lines
48 KiB
C#
1134 lines
48 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using DTS.Common.DAS.Concepts.DAS.Channel;
|
|
using DTS.Common.Exceptions;
|
|
using DTS.Common.Utilities;
|
|
using DTS.Common.Utilities.DotNetProgrammingConstructs;
|
|
using DTS.Common.Utilities.IO.MemoryMap;
|
|
|
|
namespace DTS.Serialization.SliceRaw
|
|
{
|
|
public partial class File
|
|
{
|
|
/// <summary>
|
|
/// Representation of a timestamp. Changes made to this representation
|
|
/// are immediately manifest in the associated timestamp file.
|
|
/// </summary>
|
|
public partial class PersistentDasTimestamp
|
|
: ExceptionalList<short>,
|
|
ILargeDataAware,
|
|
IDasTimestampHeader,
|
|
IDisposable
|
|
{
|
|
/// <summary>
|
|
/// The window into this persistent timestamp's soul.
|
|
/// </summary>
|
|
protected FileMapViewArray ViewArray
|
|
{
|
|
get => _ViewArray.Value;
|
|
set => _ViewArray.Value = value;
|
|
}
|
|
private readonly Property<FileMapViewArray> _ViewArray
|
|
= new Property<FileMapViewArray>(
|
|
typeof(PersistentDasTimestamp).Namespace + ".File.PersistentDasTimestamp.ViewArray",
|
|
null,
|
|
false
|
|
);
|
|
|
|
/// <summary>
|
|
/// Get <see cref="bool"/> value indicating whether or not this class has been
|
|
/// preinitialized from an existing serialization.
|
|
/// </summary>
|
|
protected bool IsInitializedFromFile
|
|
{
|
|
get => _IsInitializedFromFile.Value;
|
|
|
|
private set => _IsInitializedFromFile.Value = value;
|
|
}
|
|
private readonly Property<bool> _IsInitializedFromFile
|
|
= new Property<bool>(
|
|
typeof(PersistentDasTimestamp).Namespace + ".File.PersistentDasTimestamp.IsInitializedFromFile",
|
|
false,
|
|
false
|
|
);
|
|
|
|
private readonly object LockObject;
|
|
|
|
/// <summary>
|
|
/// Initialize an instance of this class.
|
|
/// </summary>
|
|
///
|
|
/// <param name="filename">
|
|
/// The <see cref="string"/> filename of the associated timestamp data file.
|
|
/// </param>
|
|
///
|
|
/// <param name="dasHeader">
|
|
/// The timestamp header to be written into the associated timestamp data file.
|
|
/// </param>
|
|
///
|
|
public PersistentDasTimestamp(string filename, BinaryDasTimestampHeader dasHeader, bool overwriteIfExists)
|
|
{
|
|
LockObject = new object();
|
|
try
|
|
{
|
|
IsInitializedFromFile = System.IO.File.Exists(Filename = filename);
|
|
|
|
IsMemoryMapped = false;
|
|
|
|
if (IsInitializedFromFile && !overwriteIfExists) return;
|
|
if (null == dasHeader)
|
|
throw new ArgumentNullException("das header is required to generate new file but none was provided");
|
|
|
|
lock (LockObject)
|
|
{
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.OpenOrCreate)))
|
|
{
|
|
var fields = Enum.GetValues(typeof(Field)).Cast<Field>().ToArray();
|
|
// convert to byte[]
|
|
|
|
foreach (var field in fields)
|
|
{
|
|
switch (field)
|
|
{
|
|
case Field.BeginningOfData:
|
|
break;
|
|
case Field.BeginningOfFile:
|
|
break;
|
|
case Field.Crc32:
|
|
fileWriter.Write(dasHeader.Crc32); // public UInt32
|
|
break;
|
|
case Field.HeaderVersionNumber:
|
|
fileWriter.Write(this.dasHeader.HeaderVersionNumber = BinaryDasTimestampHeader.CurrentVersionNumber); _HeaderVersionNumberInitialized = true;
|
|
break;
|
|
case Field.MagicKey:
|
|
fileWriter.Write(this.dasHeader.MagicKey = BinaryDasTimestampHeader.RequiredMagicKey); _MagicKeyInitialized = true;
|
|
break;
|
|
case Field.NumberOfBitsPerSample:
|
|
fileWriter.Write(this.dasHeader.NumberOfBitsPerSample = dasHeader.NumberOfBitsPerSample); _NumberOfBitsPerSampleInitialized = true;
|
|
break;
|
|
case Field.NumberOfSamples:
|
|
fileWriter.Write(this.dasHeader.NumberOfSamples = dasHeader.NumberOfSamples); _NumberOfSamplesInitialized = true;
|
|
break;
|
|
case Field.OffsetOfSampleDataStart:
|
|
fileWriter.Write(this.dasHeader.OffsetOfSampleDataStart = dasHeader.OffsetOfSampleDataStart); _OffsetOfSampleDataStartInitialized = true;
|
|
break;
|
|
default:
|
|
throw new NotSupportedException("SliceRaw::File::PersistentDasTimestamp(filename, dasHeader, overwriteIfExists) field not supported: " + field.ToString());
|
|
}
|
|
}
|
|
}
|
|
if (0 == dasHeader.OffsetOfSampleDataStart)
|
|
{
|
|
//inconcievable. header data exists at the beginning. recalculate.
|
|
dasHeader.OffsetOfSampleDataStart = (ulong)GetFileOffsetOf(Field.BeginningOfData);
|
|
OffsetOfSampleDataStart = dasHeader.OffsetOfSampleDataStart;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem constructing " + GetType().FullName, ex);
|
|
}
|
|
}
|
|
|
|
// xxx Make this a recursive function, with the terminating condition being a call with the first case, which
|
|
// returns the offset of "start of file".
|
|
//
|
|
|
|
public enum Field
|
|
{
|
|
BeginningOfFile, // UInt32
|
|
MagicKey, // public UInt32
|
|
HeaderVersionNumber, // public UInt32
|
|
OffsetOfSampleDataStart, // public UInt64
|
|
NumberOfSamples, // public UInt64
|
|
NumberOfBitsPerSample, // publid UInt32
|
|
Crc32, // public UInt32
|
|
BeginningOfData
|
|
|
|
}
|
|
private int GetSizeOfV1(Field field)
|
|
{
|
|
switch (field)
|
|
{
|
|
case Field.BeginningOfData:
|
|
return 0;
|
|
case Field.BeginningOfFile:
|
|
return sizeof(uint);
|
|
case Field.Crc32:
|
|
return sizeof(uint);
|
|
case Field.HeaderVersionNumber:
|
|
return sizeof(uint);
|
|
case Field.MagicKey:
|
|
return sizeof(uint);
|
|
case Field.NumberOfSamples:
|
|
return sizeof(ulong);
|
|
case Field.NumberOfBitsPerSample:
|
|
return sizeof(uint);
|
|
case Field.OffsetOfSampleDataStart:
|
|
return sizeof(ulong);
|
|
}
|
|
return 0;
|
|
}
|
|
private int GetFileOffsetOfV1(Field field)
|
|
{
|
|
var previousField = (Field)((int)field - 1);
|
|
var offset = GetFileOffsetOf(previousField) + GetSizeOfV1(previousField);
|
|
return offset;
|
|
}
|
|
/// <summary>
|
|
/// if field is one of the first 3, the offset doesn't change
|
|
/// however for the rest we need to know the header version number and then we
|
|
/// can get the file offset
|
|
/// </summary>
|
|
/// <param name="field"></param>
|
|
/// <returns></returns>
|
|
public int GetFileOffsetOf(Field field)
|
|
{
|
|
try
|
|
{
|
|
var offset = 0;
|
|
|
|
//if we are header version number or less, the fields don't change position ...
|
|
if (field == Field.HeaderVersionNumber || field == Field.MagicKey || field == Field.BeginningOfFile)
|
|
{
|
|
switch (field)
|
|
{
|
|
case Field.BeginningOfFile:
|
|
offset = 0;
|
|
break;
|
|
case Field.MagicKey:
|
|
offset = GetFileOffsetOf(Field.BeginningOfFile);
|
|
break;
|
|
case Field.HeaderVersionNumber:
|
|
offset = GetFileOffsetOf(Field.MagicKey) + sizeof(uint);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (HeaderVersionNumber)
|
|
{
|
|
case 0x01:
|
|
return GetFileOffsetOfV1(field);
|
|
}
|
|
}
|
|
return offset;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting file offset for property", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get <see cref="bool"/> value indicating whether or not all necessary properties of this
|
|
/// class have been initialized to make this object useable.
|
|
/// </summary>
|
|
public bool IsInitialized
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
return _HeaderVersionNumberInitialized
|
|
&& _MagicKeyInitialized
|
|
&& _NumberOfSamplesInitialized
|
|
&& _NumberOfBitsPerSampleInitialized
|
|
&& _OffsetOfSampleDataStartInitialized;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem determining initialization status", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public bool IsDataArraySized => NumberOfSamples < int.MaxValue;
|
|
|
|
/// <summary>
|
|
/// Object to cache header property accesses.
|
|
/// </summary>
|
|
public BinaryDasTimestampHeader dasHeader = new BinaryDasTimestampHeader();
|
|
|
|
/// <summary>
|
|
/// "Magic Key" relatively-UID <see cref="UInt32"/> for our binary file type.
|
|
/// </summary>
|
|
public uint MagicKey
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!IsInitialized && !IsInitializedFromFile)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
if (!_MagicKeyInitialized)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
long valueOffset = GetFileOffsetOf(Field.MagicKey);
|
|
using (var fileReader = new BinaryReader(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
var prevPosition = fileReader.BaseStream.Position;
|
|
fileReader.BaseStream.Position = valueOffset;
|
|
dasHeader.MagicKey = fileReader.ReadUInt32();
|
|
fileReader.BaseStream.Position = prevPosition;
|
|
}
|
|
}
|
|
}
|
|
return dasHeader.MagicKey;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".MagicKey property value", ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
var offset = GetFileOffsetOf(Field.MagicKey);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
fileWriter.Write(dasHeader.MagicKey = value);
|
|
}
|
|
}
|
|
|
|
_MagicKeyInitialized = true;
|
|
|
|
// If at this point the persistent timestamp object has been initialized, go ahead and
|
|
// keep the CRC current.
|
|
if (IsInitialized)
|
|
Crc32 = dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".MagicKey property value", ex);
|
|
}
|
|
}
|
|
}
|
|
private bool _MagicKeyInitialized = false;
|
|
/// <summary>
|
|
/// <see cref="UInt32"/> header version number for our binary file type.
|
|
/// </summary>
|
|
public uint HeaderVersionNumber
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!IsInitialized && !IsInitializedFromFile)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
if (!_HeaderVersionNumberInitialized)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
long valueOffset = GetFileOffsetOf(Field.HeaderVersionNumber);
|
|
using (var fileReader = new BinaryReader(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
var prevPosition = fileReader.BaseStream.Position;
|
|
fileReader.BaseStream.Position = valueOffset;
|
|
dasHeader.HeaderVersionNumber = fileReader.ReadUInt32();
|
|
fileReader.BaseStream.Position = prevPosition;
|
|
}
|
|
}
|
|
}
|
|
return dasHeader.HeaderVersionNumber;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".HeaderVersionNumber property value", ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
var offset = GetFileOffsetOf(Field.HeaderVersionNumber);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
fileWriter.Write(dasHeader.HeaderVersionNumber = value);
|
|
}
|
|
}
|
|
|
|
_HeaderVersionNumberInitialized = true;
|
|
|
|
// If at this point the persistent timestamp object has been initialized, go ahead and
|
|
// keep the CRC current.
|
|
if (IsInitialized)
|
|
Crc32 = dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".HeaderVersionNumber property value", ex);
|
|
}
|
|
}
|
|
}
|
|
private bool _HeaderVersionNumberInitialized = false;
|
|
/// <summary>
|
|
/// Get/set offset of sample data start value.
|
|
/// </summary>
|
|
public ulong OffsetOfSampleDataStart
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!IsInitialized && !IsInitializedFromFile)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
if (!_OffsetOfSampleDataStartInitialized)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
long valueOffset = GetFileOffsetOf(Field.OffsetOfSampleDataStart);
|
|
using (var fileReader = new BinaryReader(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
var prevPosition = fileReader.BaseStream.Position;
|
|
fileReader.BaseStream.Position = valueOffset;
|
|
dasHeader.OffsetOfSampleDataStart = fileReader.ReadUInt64();
|
|
fileReader.BaseStream.Position = prevPosition;
|
|
_OffsetOfSampleDataStartInitialized = true;
|
|
}
|
|
}
|
|
}
|
|
return dasHeader.OffsetOfSampleDataStart;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".OffsetOfSampleDataStart property value", ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
var offset = GetFileOffsetOf(Field.OffsetOfSampleDataStart);
|
|
//APILogger.Log("writing 9 ", Filename);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
fileWriter.Write(dasHeader.OffsetOfSampleDataStart = value);
|
|
}
|
|
}
|
|
|
|
_OffsetOfSampleDataStartInitialized = true;
|
|
|
|
// If at this point the persistent timestamp object has been initialized, go ahead and
|
|
// keep the CRC current.
|
|
if (IsInitialized)
|
|
Crc32 = dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".OffsetOfSampleDataStart property value", ex);
|
|
}
|
|
}
|
|
}
|
|
private bool _OffsetOfSampleDataStartInitialized = false;
|
|
|
|
/// <summary>
|
|
/// Get/set number of samples.
|
|
/// </summary>
|
|
public ulong NumberOfSamples
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!IsInitialized && !IsInitializedFromFile)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
if (!_NumberOfSamplesInitialized)
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
long valueOffset = GetFileOffsetOf(Field.NumberOfSamples);
|
|
|
|
using (var fileReader = new BinaryReader(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
var prevPosition = fileReader.BaseStream.Position;
|
|
fileReader.BaseStream.Position = valueOffset;
|
|
dasHeader.NumberOfSamples = fileReader.ReadUInt64();
|
|
fileReader.BaseStream.Position = prevPosition;
|
|
_NumberOfSamplesInitialized = true;
|
|
}
|
|
}
|
|
}
|
|
return dasHeader.NumberOfSamples;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".NumberOfSamples property value", ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
var offset = GetFileOffsetOf(Field.NumberOfSamples);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
fileWriter.Write(dasHeader.NumberOfSamples = value);
|
|
}
|
|
}
|
|
|
|
_NumberOfSamplesInitialized = true;
|
|
|
|
// If at this point the persistent timestamp object has been initialized, go ahead and
|
|
// keep the CRC current.
|
|
if (IsInitialized)
|
|
Crc32 = dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".NumberOfSamples property value", ex);
|
|
}
|
|
}
|
|
}
|
|
private bool _NumberOfSamplesInitialized = false;
|
|
|
|
/// <summary>
|
|
/// Get/set number of bits per sample.
|
|
/// </summary>
|
|
public uint NumberOfBitsPerSample
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!IsInitialized && !IsInitializedFromFile)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
if (!_NumberOfBitsPerSampleInitialized)
|
|
{
|
|
//APILogger.Log("reading 11 ", Filename);
|
|
lock (LockObject)
|
|
{
|
|
long valueOffset = GetFileOffsetOf(Field.NumberOfBitsPerSample);
|
|
using (var fileReader = new BinaryReader(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
var prevPosition = fileReader.BaseStream.Position;
|
|
fileReader.BaseStream.Position = valueOffset;
|
|
dasHeader.NumberOfBitsPerSample = fileReader.ReadUInt32();
|
|
fileReader.BaseStream.Position = prevPosition;
|
|
_NumberOfBitsPerSampleInitialized = true;
|
|
}
|
|
}
|
|
}
|
|
return dasHeader.NumberOfBitsPerSample;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".NumberOfBitsPerSample property value", ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
var offset = GetFileOffsetOf(Field.NumberOfBitsPerSample);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
fileWriter.Write(dasHeader.NumberOfBitsPerSample = value);
|
|
}
|
|
}
|
|
|
|
_NumberOfBitsPerSampleInitialized = true;
|
|
|
|
// If at this point the persistent timestamp object has been initialized, go ahead and
|
|
// keep the CRC current.
|
|
if (IsInitialized)
|
|
Crc32 = dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".NumberOfBitsPerSample property value", ex);
|
|
}
|
|
}
|
|
}
|
|
private bool _NumberOfBitsPerSampleInitialized = false;
|
|
|
|
/// <summary>
|
|
/// <see cref="int"/> size of view into memory-mapped file.
|
|
/// </summary>
|
|
protected int ViewSize
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private bool _IsViewSizeInitialized = false;
|
|
|
|
|
|
/// <summary>
|
|
/// <see cref="bool"/> to indicate whether or not a memory mapping has already been
|
|
/// established for this object.
|
|
/// </summary>
|
|
protected bool IsMemoryMapped
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the <see cref="long"/> length of this persistent timestamp's data.
|
|
/// </summary>
|
|
public long Length
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (NumberOfSamples > long.MaxValue)
|
|
throw new ApplicationException("ulong overflowed while being cast to long");
|
|
return (long)NumberOfSamples;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem determining total amount of timestamp data", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the <see cref="int"/> data count of this persistent timestamp's data.
|
|
/// </summary>
|
|
public new int Count
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (Length > int.MaxValue)
|
|
throw new ApplicationException("long overflowed while being cast to int");
|
|
return (int)Length;
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting timestamp data count", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override long-indexing here. Return a sample of the underlying array.
|
|
/// </summary>
|
|
///
|
|
/// <param name="i">
|
|
/// The <see cref="long"/> index of the sample being affected.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// The <see cref="short"/> sample at the specified index.
|
|
/// </returns>
|
|
///
|
|
public short this[long i]
|
|
{
|
|
get
|
|
{
|
|
try { return this[(ulong)i]; }
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting long-indexable value at index " + i.ToString(), ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try { this[(ulong)i] = value; }
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting long-indexable value at index " + i.ToString(), ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UnSet()
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
_Data.Value = new short[0];
|
|
_Data.UnInitialize();
|
|
}
|
|
}
|
|
// Override indexing operator here, return a sample of the underlying array.
|
|
public short this[ulong i]
|
|
{
|
|
get
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
try
|
|
{
|
|
var offsetSampleDataStart = OffsetOfSampleDataStart;
|
|
if (!_IsViewSizeInitialized)
|
|
{
|
|
//
|
|
// Memory map array object map view paging math seems to be a bit
|
|
// screwed. If you don't make the page exactly the same size as the
|
|
// system memory allocation granularity, it will start returning trash
|
|
// data after the first page fault.
|
|
//
|
|
const int StandardPageSize = 65536;
|
|
if (NumberOfSamples + offsetSampleDataStart > StandardPageSize)
|
|
{
|
|
ViewSize = StandardPageSize;
|
|
}
|
|
else
|
|
{ //
|
|
// If the map size exceeds the file size, we'll get strange COM error
|
|
// codes.
|
|
//
|
|
var numberOfDataBytes = NumberOfSamples * 2;
|
|
if (numberOfDataBytes + offsetSampleDataStart > int.MaxValue)
|
|
throw new Exception("overflowed view size variable (max value: " + int.MaxValue.ToString() + ") when downcasting number of bytes in test data (" + numberOfDataBytes.ToString() + ")");
|
|
ViewSize = (int)(numberOfDataBytes + offsetSampleDataStart);
|
|
}
|
|
|
|
_IsViewSizeInitialized = true;
|
|
}
|
|
|
|
|
|
// MMBAD: Possible inefficiency. Perhaps either for "debug" build, or find some other way
|
|
// to verify this outside of the array access.
|
|
//
|
|
//if ( !IsInitialized && !IsInitializedFromFile )
|
|
// throw new NotInitializedException( "object initialization has not completed" );
|
|
//else if ( !IsMemoryMapped )
|
|
if (!IsMemoryMapped)
|
|
{
|
|
var fileInfo = new FileInfo(Filename);
|
|
// Make a new mapViewArray for the file with proper offset and
|
|
// readonly privilages to protect sample integrity.
|
|
ViewArray = new FileMapViewArray(Path.GetFullPath(Filename),
|
|
MapAccess.FileMapRead,
|
|
MapProtection.PageReadOnly,
|
|
offsetSampleDataStart,
|
|
fileInfo.Length,
|
|
ViewSize);
|
|
IsMemoryMapped = true;
|
|
}
|
|
|
|
//http://manuscript.dts.local/f/cases/16463/HDF-export-runs-very-slowly
|
|
long osdsLong = (long)(offsetSampleDataStart + 2 * i);
|
|
return (short)((ViewArray[osdsLong] & 0xFFFF) + ((ViewArray[osdsLong + 1L] & 0xFFFF) << 8));
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting short value at index " + i.ToString(), ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
throw new NotSupportedException("SliceRaw::File::PersistentDasTimestamp::[] Sample Data is READ_ONLY.");
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting short value at index " + i.ToString(), ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Some older files will need this format to be invoked to fix slight formatting problems.
|
|
/// </summary>
|
|
public void RecalculateDataOffset()
|
|
{
|
|
try
|
|
{ //
|
|
// Older SliceWARE data files have incorrect values for the offset of sample data start,
|
|
// and number of data samples. Adjust them or we may run into trouble later.
|
|
//
|
|
if (0 == OffsetOfSampleDataStart)
|
|
{
|
|
var realDataStart = GetFileOffsetOf(Field.BeginningOfData);
|
|
OffsetOfSampleDataStart = (ulong)realDataStart;
|
|
NumberOfSamples -= (ulong)realDataStart;
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem recalculating data offset", ex);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get/set the filename of the associated timestamp data file.
|
|
/// </summary>
|
|
public string Filename
|
|
{
|
|
get => _Filename.Value;
|
|
private set => _Filename.Value = value;
|
|
}
|
|
private readonly Property<string> _Filename
|
|
= new Property<string>(
|
|
typeof(PersistentDasTimestamp).Namespace + ".File.PreAllocatedChannel.Filename",
|
|
null,
|
|
false
|
|
);
|
|
|
|
public void ReplaceData(short[] newData)
|
|
{
|
|
if (newData.Length != _Data.Value.Length)
|
|
{
|
|
return;
|
|
}
|
|
_Data.Value = newData;
|
|
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
var offset = GetFileOffsetOf(Field.BeginningOfData);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
foreach (var dataShort in newData)
|
|
{
|
|
fileWriter.Write(dataShort);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If at this point the persistent timestamp object has been initialized, go ahead and
|
|
// keep the CRC current.
|
|
if (IsInitialized)
|
|
Crc32 = dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem replacing data", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/set <see cref="short"/> data array value.
|
|
/// </summary>
|
|
public short[] Data
|
|
{
|
|
get
|
|
{
|
|
var lastDataSpot = -1;
|
|
try
|
|
{
|
|
if (!IsInitialized && !IsInitializedFromFile)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
if (!IsDataArraySized)
|
|
throw new SliceRaw.File.PersistentChannel.DataTooBigForArrayException("Data size (" + Count.ToString() + ") is too large to be safely handled within the application");
|
|
if (!_Data.IsInitialized)
|
|
{ //
|
|
// If we haven't handed out one of these yet, make one up and then hang onto it
|
|
// to use for subsequent requests to we don't have clones all over the place.
|
|
//
|
|
var dataArray = new short[Count];
|
|
for (var i = 0; i < dataArray.Length; i++)
|
|
{
|
|
lastDataSpot = i;
|
|
dataArray[i] = this[i];
|
|
}
|
|
|
|
_Data.Value = dataArray;
|
|
}
|
|
return _Data.Value;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
string msg = string.Empty;
|
|
if (lastDataSpot > 0)
|
|
{
|
|
throw new OutOfDataException($"Problem accessing sample {lastDataSpot} - {GetType().FullName} {ex}", lastDataSpot);
|
|
}
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".Data property value", ex);
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
try
|
|
{
|
|
_Data.Value = value;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".Data property value", ex);
|
|
}
|
|
}
|
|
}
|
|
private readonly Property<short[]> _Data
|
|
= new Property<short[]>(
|
|
typeof(PersistentDasTimestamp).Namespace + ".File.PreAllocatedChannel.Data",
|
|
null,
|
|
false
|
|
);
|
|
|
|
/// <summary>
|
|
/// Get/set binary writer to be used by data append session.
|
|
/// </summary>
|
|
private BinaryWriter AppendSessionWriter
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare this object for repeated calls to AppendSessionData.
|
|
/// </summary>
|
|
public void BeginAppendSession()
|
|
{
|
|
try
|
|
{
|
|
AppendSessionWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open));
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem beginning data append session", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Secure object from repeated calls to AppendSessionData.
|
|
/// </summary>
|
|
public void EndAppendSession()
|
|
{
|
|
if (null == AppendSessionWriter) return; //don't repeat
|
|
try
|
|
{
|
|
AppendSessionWriter.Close();
|
|
AppendSessionWriter.Dispose();
|
|
AppendSessionWriter = null;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem ending data append session", ex);
|
|
}
|
|
}
|
|
|
|
public int MaxAppendToWrite { get; set; } = 4;
|
|
|
|
/// <summary>
|
|
/// the timestamp data is striped over channel shorts
|
|
/// </summary>
|
|
List<short[]> stampData = new List<short[]>();
|
|
|
|
/// <summary>
|
|
/// Append data to this das timestamp. Must be bracketed by BeginAppendSession and
|
|
/// EndAppendSession method invocations.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">
|
|
/// The <see cref="short"/> striped data to be appended to this das timestamp.
|
|
/// </param>
|
|
///
|
|
public void AppendSessionData(short[] data)
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
stampData.Add(data);
|
|
|
|
if (MaxAppendToWrite == stampData.Count)
|
|
{
|
|
//got all of our striped data, write this chunk
|
|
AppendSessionWriter.Seek(0, SeekOrigin.End);
|
|
var maxLength = stampData.Select(d => d.Length).Max();
|
|
for (var j = 0; j < maxLength; j++)
|
|
{
|
|
for (var i = 0; i < stampData.Count; i++)
|
|
AppendSessionWriter.Write(j < stampData[i].Length ? stampData[i][j] : (short)0);
|
|
}
|
|
stampData.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem appending data to persistent timestamp representation", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append data to this persistent timestamp. To be used for one-time applications. If we're streaming
|
|
/// data to this object, AppendSessionData is more efficient.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">
|
|
/// The <see cref="short"/> data to be appended to this persistent timestamp.
|
|
/// </param>
|
|
///
|
|
public void DisabledAppendData(short[] data)
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(0, SeekOrigin.End);
|
|
for (var i = 0; i < data.Length; i++)
|
|
fileWriter.Write(data[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem appending data to persistent timestamp representation", ex);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get the CRC for the current state of the header.
|
|
/// </summary>
|
|
public uint Crc32
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!IsInitialized)
|
|
throw new SliceRaw.File.PersistentChannel.NotInitializedException("object initialization has not completed");
|
|
return dasHeader.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem getting " + GetType().FullName + ".Crc32 property value", ex);
|
|
}
|
|
}
|
|
|
|
private set
|
|
{
|
|
try
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
{
|
|
var offset = GetFileOffsetOf(Field.Crc32);
|
|
using (var fileWriter = new BinaryWriter(new FileStream(Filename, FileMode.Open)))
|
|
{
|
|
fileWriter.Seek(offset, SeekOrigin.Begin);
|
|
fileWriter.Write(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem setting " + GetType().FullName + ".Crc32 property", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populate the persistent CRC property with the CRC of the current state of the timestamp header.
|
|
/// </summary>
|
|
public void StampCrc()
|
|
{
|
|
try
|
|
{ //
|
|
// Calling the "get" accessor for all properties will ensure that they
|
|
// are populated, which is important if the CRC is going to be running
|
|
// over them.
|
|
//
|
|
var header = new BinaryDasTimestampHeader();
|
|
|
|
header.MagicKey = MagicKey;
|
|
header.HeaderVersionNumber = HeaderVersionNumber;
|
|
header.OffsetOfSampleDataStart = OffsetOfSampleDataStart;
|
|
header.NumberOfSamples = NumberOfSamples;
|
|
|
|
Crc32 = header.Crc32;
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem stamping persistent timestamp CRC", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose of this object's Disposables.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
lock (LockObject)
|
|
{
|
|
try
|
|
{
|
|
if (_ViewArray.IsInitialized)
|
|
{
|
|
IsMemoryMapped = false; //Is this right?
|
|
}
|
|
try
|
|
{
|
|
ViewArray?.Dispose();
|
|
}
|
|
catch (System.Exception)
|
|
{
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
// If the ViewArray was not yet initialized, that's OK. Eat the exception
|
|
if (null == ex.InnerException || ex.InnerException.GetType() != typeof(Property<FileMapViewArray>.NotInitializedException))
|
|
{
|
|
throw new Exception("encountered problem disposing of " + GetType().FullName, ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|