//******************************************************************************************************
// ArchiveReader.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:
// ----------------------------------------------------------------------------------------------------
// 09/29/2012 - J. Ritchie Carroll
// Generated original version of source code.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Timers;
using GSF.IO;
using Timer = System.Timers.Timer;
namespace GSF.Historian.Files
{
///
/// Opens a local set of historian files for reading with active archive file being monitored for roll-over.
///
///
/// This class is expected to be used as an out-of-process instance of the that
/// will properly open archive files as read-only and yield to roll-over processing.
///
public class ArchiveReader : IDisposable
{
#region [ Members ]
// Events
///
/// Occurs when to a new is started.
///
[Category("Rollover"),
Description("Occurs when Rollover to a new ArchiveFile is started.")]
public event EventHandler RolloverStart;
///
/// Occurs when to a new is complete.
///
[Category("Rollover"),
Description("Occurs when Rollover to a new ArchiveFile is complete.")]
public event EventHandler RolloverComplete;
///
/// Occurs when the process of building historic list is started.
///
[Category("File"),
Description("Occurs when the process of building historic ArchiveFile list is started.")]
public event EventHandler HistoricFileListBuildStart;
///
/// Occurs when the process of building historic list is complete.
///
[Category("File"),
Description("Occurs when the process of building historic ArchiveFile list is complete.")]
public event EventHandler HistoricFileListBuildComplete;
///
/// Occurs when an is encountered in historic list building process.
///
[Category("File"),
Description("Occurs when an Exception is encountered in historic ArchiveFile list building process.")]
public event EventHandler> HistoricFileListBuildException;
///
/// Occurs when the historic list is updated to reflect addition or deletion of historic s.
///
[Category("File"),
Description("Occurs when the historic ArchiveFile list is updated to reflect addition or deletion of historic ArchiveFiles.")]
public event EventHandler HistoricFileListUpdated;
///
/// Occurs when an is encountered while reading from the current or historic .
///
[Category("Data"),
Description("Occurs when an Exception is encountered while reading IDataPoint from the current or historic ArchiveFile.")]
public event EventHandler> DataReadException;
// Fields
private ArchiveFile m_archiveFile;
private Timer m_rolloverWatcher;
private readonly object m_watcherLock;
private bool m_rolloverInProgress;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new .
///
public ArchiveReader()
{
m_watcherLock = new object();
m_rolloverWatcher = new Timer();
m_rolloverWatcher.Interval = 1000;
m_rolloverWatcher.Elapsed += m_rolloverWatcher_Elapsed;
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~ArchiveReader()
{
Dispose(false);
}
#endregion
#region [ Properties ]
///
/// Gets the name of the .
///
public string FileName
{
get
{
if ((object)m_archiveFile == null)
return null;
return m_archiveFile.FileName;
}
}
///
/// Gets the path to the offload location used by the .
///
public string ArchiveOffloadLocation
{
get
{
if ((object)m_archiveFile == null)
return null;
return m_archiveFile.ArchiveOffloadLocation;
}
}
///
/// Gets the used by the .
///
public StateFile StateFile
{
get
{
if ((object)m_archiveFile == null)
return null;
return m_archiveFile.StateFile;
}
}
///
/// Gets the used by the .
///
public MetadataFile MetadataFile
{
get
{
if ((object)m_archiveFile == null)
return null;
return m_archiveFile.MetadataFile;
}
}
///
/// Gets the used by the .
///
public IntercomFile IntercomFile
{
get
{
if ((object)m_archiveFile == null)
return null;
return m_archiveFile.IntercomFile;
}
}
///
/// Gets the descriptive status of the internal .
///
public string Status
{
get
{
if ((object)m_archiveFile == null)
return "";
return m_archiveFile.Status;
}
}
#endregion
#region [ Methods ]
///
/// Releases all the resources used by the object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if ((object)m_rolloverWatcher != null)
{
m_rolloverWatcher.Stop();
m_rolloverWatcher.Dispose();
}
m_rolloverWatcher = null;
CloseArchiveFile();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
}
}
}
///
/// Opens the for use.
///
/// Archive .D archive file name.
/// Path to archive offload location.
public void Open(string fileName, string offloadLocation = "")
{
CloseArchiveFile();
m_archiveFile = OpenArchiveFile(fileName, offloadLocation.ToNonNullString());
}
///
/// Reads all s from the for the specified .
///
/// Historian identifier for which s are to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, bool timeSorted = true)
{
return ReadData(historianID, TimeTag.MinValue, timeSorted);
}
///
/// Reads all s from the for the specified .
///
/// Historian identifiers for which s are to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, bool timeSorted = true)
{
return ReadData(historianIDs, TimeTag.MinValue, timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifier for which s are to be retrieved.
/// representation of the start time (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, string startTime, bool timeSorted = true)
{
return ReadData(historianID, startTime, TimeTag.MinValue.ToString(), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifiers for which s are to be retrieved.
/// representation of the start time (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, string startTime, bool timeSorted = true)
{
return ReadData(historianIDs, startTime, TimeTag.MinValue.ToString(), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifier for which s are to be retrieved.
/// representation of the start time (in UTC) for the s to be retrieved.
/// representation of the end time (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, string startTime, string endTime, bool timeSorted = true)
{
return ReadData(historianID, TimeTag.Parse(startTime), TimeTag.Parse(endTime), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifiers for which s are to be retrieved.
/// representation of the start time (in UTC) for the s to be retrieved.
/// representation of the end time (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, string startTime, string endTime, bool timeSorted = true)
{
return ReadData(historianIDs, TimeTag.Parse(startTime), TimeTag.Parse(endTime), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifier for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, DateTime startTime, bool timeSorted = true)
{
return ReadData(historianID, startTime, TimeTag.MinValue.ToDateTime(), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifiers for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, DateTime startTime, bool timeSorted = true)
{
return ReadData(historianIDs, startTime, TimeTag.MinValue.ToDateTime(), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifier for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// End (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, DateTime startTime, DateTime endTime, bool timeSorted = true)
{
return ReadData(historianID, new TimeTag(startTime), new TimeTag(endTime), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifiers for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// End (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, DateTime startTime, DateTime endTime, bool timeSorted = true)
{
return ReadData(historianIDs, new TimeTag(startTime), new TimeTag(endTime), timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifier for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, TimeTag startTime, bool timeSorted = true)
{
return ReadData(historianID, startTime, TimeTag.MaxValue, timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifiers for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, TimeTag startTime, bool timeSorted = true)
{
return ReadData(historianIDs, startTime, TimeTag.MaxValue, timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifier for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// End (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(int historianID, TimeTag startTime, TimeTag endTime, bool timeSorted = true)
{
return ReadData(new[] { historianID }, startTime, endTime, timeSorted);
}
///
/// Reads s from the .
///
/// Historian identifiers for which s are to be retrieved.
/// Start (in UTC) for the s to be retrieved.
/// End (in UTC) for the s to be retrieved.
/// Indicates whether the data retrieved from the archive should be time sorted.
/// object containing zero or more s.
public IEnumerable ReadData(IEnumerable historianIDs, TimeTag startTime, TimeTag endTime, bool timeSorted = true)
{
if ((object)m_archiveFile == null)
throw new InvalidOperationException("Archive file is not open, cannot read data.");
return m_archiveFile.ReadData(historianIDs, startTime, endTime, timeSorted);
}
// Opens an archive file as read-only and returns the ArchiveFile object.
private ArchiveFile OpenArchiveFile(string fileName, string offloadLocation)
{
const string metadataFileName = "{0}{1}_dbase.dat";
const string stateFileName = "{0}{1}_startup.dat";
const string intercomFileName = "{0}scratch.dat";
string archiveLocation = FilePath.GetDirectoryName(fileName);
string archiveName = FilePath.GetFileName(fileName);
string instance = archiveName.Substring(0, archiveName.LastIndexOf("_archive", StringComparison.OrdinalIgnoreCase));
ArchiveFile file = new ArchiveFile
{
FileName = fileName,
FileAccessMode = FileAccess.Read,
MonitorNewArchiveFiles = true,
PersistSettings = false,
ArchiveOffloadLocation = offloadLocation,
StateFile = new StateFile
{
FileAccessMode = FileAccess.Read,
FileName = string.Format(stateFileName, archiveLocation, instance)
},
IntercomFile = new IntercomFile
{
FileAccessMode = FileAccess.Read,
FileName = string.Format(intercomFileName, archiveLocation)
},
MetadataFile = new MetadataFile
{
FileAccessMode = FileAccess.Read,
FileName = string.Format(metadataFileName, archiveLocation, instance),
LoadOnOpen = true
}
};
file.DataReadException += file_DataReadException;
file.HistoricFileListBuildComplete += file_HistoricFileListBuildComplete;
file.HistoricFileListBuildException += file_HistoricFileListBuildException;
file.HistoricFileListBuildStart += file_HistoricFileListBuildStart;
file.HistoricFileListUpdated += file_HistoricFileListUpdated;
file.RolloverComplete += file_RolloverComplete;
file.RolloverStart += file_RolloverStart;
// Initialize the archive file (starts file watchers)
file.Initialize();
// Open the active archive file
file.Open();
// Start the roll-over watch timer
m_rolloverWatcher.Start();
return file;
}
private void CloseArchiveFile()
{
if ((object)m_archiveFile != null)
{
m_archiveFile.DataReadException -= file_DataReadException;
m_archiveFile.HistoricFileListBuildComplete -= file_HistoricFileListBuildComplete;
m_archiveFile.HistoricFileListBuildException -= file_HistoricFileListBuildException;
m_archiveFile.HistoricFileListBuildStart -= file_HistoricFileListBuildStart;
m_archiveFile.HistoricFileListUpdated -= file_HistoricFileListUpdated;
m_archiveFile.RolloverComplete -= file_RolloverComplete;
m_archiveFile.RolloverStart -= file_RolloverStart;
m_archiveFile.Dispose();
}
m_archiveFile = null;
}
// Monitors for roll-over notifications
private void m_rolloverWatcher_Elapsed(object sender, ElapsedEventArgs e)
{
// Don't start another rollover activity if one is already in progress...
if (Monitor.TryEnter(m_watcherLock))
{
try
{
// Read the inter-process communications file for changes in roll-over state
if ((object)m_archiveFile != null && (object)m_archiveFile.IntercomFile != null && m_archiveFile.IntercomFile.IsOpen)
{
m_archiveFile.IntercomFile.Load();
IntercomRecord record = m_archiveFile.IntercomFile.Read(1);
if ((object)record != null)
{
// Pause processing
if (record.RolloverInProgress)
{
if (!m_rolloverInProgress)
{
m_rolloverInProgress = true;
// Notify internal archive file components about the pending rollover
m_archiveFile.RolloverWaitHandle.Reset();
// Raise roll-over start event (sets m_rolloverInProgress flag in ArchiveFile)
m_archiveFile.OnRolloverStart();
// Wait for pending reads to yield
m_archiveFile.WaitForReadersRelease();
}
// Close the active archive file stream so it can be rolled-over
if (m_archiveFile.IsOpen)
m_archiveFile.CloseStream();
}
// Resume processing
if (!record.RolloverInProgress)
{
// Open new active archive file stream
if (!m_archiveFile.IsOpen)
m_archiveFile.OpenStream();
if (m_rolloverInProgress)
{
m_rolloverInProgress = false;
// Raise roll-over complete event (resets m_rolloverInProgress flag in ArchiveFile)
m_archiveFile.OnRolloverComplete();
// Notify waiting internal archive components that rollover is complete
m_archiveFile.RolloverWaitHandle.Set();
}
}
}
}
}
catch (Exception ex)
{
OnDataReadException(new InvalidOperationException("Exception encountered during roll-over processing: " + ex.Message, ex));
}
finally
{
Monitor.Exit(m_watcherLock);
}
}
}
///
/// Raises the event.
///
protected internal virtual void OnRolloverStart() => RolloverStart?.Invoke(this, EventArgs.Empty);
///
/// Raises the event.
///
protected internal virtual void OnRolloverComplete() => RolloverComplete?.Invoke(this, EventArgs.Empty);
///
/// Raises the event.
///
protected virtual void OnHistoricFileListBuildStart() => HistoricFileListBuildStart?.Invoke(this, EventArgs.Empty);
///
/// Raises the event.
///
protected virtual void OnHistoricFileListBuildComplete() => HistoricFileListBuildComplete?.Invoke(this, EventArgs.Empty);
///
/// Raise the event.
///
/// to send to event.
protected virtual void OnHistoricFileListBuildException(Exception ex) => HistoricFileListBuildException?.Invoke(this, new EventArgs(ex));
///
/// Raises the event.
///
protected virtual void OnHistoricFileListUpdated() => HistoricFileListUpdated?.Invoke(this, EventArgs.Empty);
///
/// Raises the event.
///
/// to send to event.
protected virtual void OnDataReadException(Exception ex) => DataReadException?.Invoke(this, new EventArgs(ex));
private void file_RolloverStart(object sender, EventArgs e) => OnRolloverStart();
private void file_RolloverComplete(object sender, EventArgs e) => OnRolloverComplete();
private void file_HistoricFileListUpdated(object sender, EventArgs e) => OnHistoricFileListUpdated();
private void file_HistoricFileListBuildStart(object sender, EventArgs e) => OnHistoricFileListBuildStart();
private void file_HistoricFileListBuildException(object sender, EventArgs e) => OnHistoricFileListBuildException(e.Argument);
private void file_HistoricFileListBuildComplete(object sender, EventArgs e) => OnHistoricFileListBuildComplete();
private void file_DataReadException(object sender, EventArgs e) => OnDataReadException(e.Argument);
#endregion
}
}