//****************************************************************************************************** // MetadataExportAdapter.cs - Gbtc // // Copyright © 2018, 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://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/23/2018 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.ComponentModel; using System.Data; using System.IO; using System.Text.RegularExpressions; using System.Threading; using GSF; using GSF.Configuration; using GSF.Data; using GSF.Diagnostics; using GSF.Threading; using GSF.TimeSeries; using GSF.TimeSeries.Adapters; namespace MetadataAdapters { /// /// Defines an adapter that automatically dumps metadata to a file /// [Description("Metadata Export: Automatically exports metadata to a file")] public class MetadataExportAdapter : FacileActionAdapterBase { #region [ Members ] // Constants /// /// Default value for . /// public const string DefaultMetadataTables = "SELECT NodeID, UniqueID, OriginalSource, IsConcentrator, Acronym, Name, AccessID, ParentAcronym, ProtocolName, FramesPerSecond, CompanyAcronym, VendorAcronym, VendorDeviceName, Longitude, Latitude, InterconnectionName, ContactList, Enabled, UpdatedOn FROM DeviceDetail WHERE IsConcentrator = 0;" + "SELECT DeviceAcronym, ID, SignalID, PointTag, SignalReference, SignalAcronym, PhasorSourceIndex, Description, Internal, Enabled, UpdatedOn FROM MeasurementDetail WHERE SignalAcronym <> 'STAT';" + "SELECT ID, DeviceAcronym, Label, Type, Phase, DestinationPhasorID, SourceIndex, UpdatedOn FROM PhasorDetail;" + "SELECT VersionNumber FROM SchemaVersion"; // Fields private LongSynchronizedOperation m_dumpOperation; private DateTime m_lastDump; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public MetadataExportAdapter() { m_dumpOperation = new LongSynchronizedOperation(DumpMetadata, HandleException); } #endregion #region [ Properties ] /// /// Gets or sets semi-colon separated list of SQL select statements used to create data for metadata exchange. /// [ConnectionStringParameter] [DefaultValue(DefaultMetadataTables)] [Description("Semi-colon separated list of SQL select statements used to create data for metadata exchange.")] public string MetadataTables { get; set; } /// /// The file path used when exporting metadata. /// [ConnectionStringParameter] [DefaultValue("")] [Description("Defines the file path used when exporting metadata.")] public string ExportFilePath { get; set; } /// /// Gets or sets based data source available to this . /// public override DataSet DataSource { get { return base.DataSource; } set { base.DataSource = value; ExportMetadata(); } } /// /// Gets the flag indicating if this adapter supports temporal processing. /// public override bool SupportsTemporalProcessing => false; /// /// Gets or sets primary keys of input measurements the expects, if any. /// [EditorBrowsable(EditorBrowsableState.Never)] public new MeasurementKey[] InputMeasurementKeys { get { return base.InputMeasurementKeys; } set { base.InputMeasurementKeys = value; } } /// /// Gets or sets output measurements that the will produce, if any. /// [EditorBrowsable(EditorBrowsableState.Never)] public new IMeasurement[] OutputMeasurements { get { return base.OutputMeasurements; } set { base.OutputMeasurements = value; } } /// /// Gets or sets the frames per second to be used by the . /// [EditorBrowsable(EditorBrowsableState.Never)] public new int FramesPerSecond { get { return base.FramesPerSecond; } set { base.FramesPerSecond = value; } } /// /// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second). /// [EditorBrowsable(EditorBrowsableState.Never)] public new double LagTime { get { return base.LagTime; } set { base.LagTime = value; } } /// /// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second). /// [EditorBrowsable(EditorBrowsableState.Never)] public new double LeadTime { get { return base.LeadTime; } set { base.LeadTime = value; } } #endregion #region [ Methods ] /// /// Initializes . /// public override void Initialize() { base.Initialize(); ConnectionStringParser parser = new ConnectionStringParser(); parser.ParseConnectionString(ConnectionString, this); if (string.IsNullOrWhiteSpace(ExportFilePath)) ExportFilePath = Path.Combine("MetadataExports", $"{Name}.bin"); } /// /// Starts the or restarts it if it is already running. /// public override void Start() { base.Start(); ExportMetadata(); } /// /// Gets a short one-line status of this . /// /// Maximum number of available characters for display. /// A short one-line summary of the current status of this . public override string GetShortStatus(int maxLength) { string shortStatus = "Dumping metadata..."; if (!m_dumpOperation.IsRunning) { string timestampFormat = "HH:mm:ss"; if (DateTime.Now.Subtract(m_lastDump).TotalDays > 1.0D) timestampFormat = "yyyy-MM-dd HH:mm"; string lastDump = m_lastDump.ToString(timestampFormat); shortStatus = $"Last dump: {lastDump}"; } return shortStatus.CenterText(maxLength); } /// /// Initiates the operation to export metadata. /// [AdapterCommand("Initiates the operation to export metadata.")] public void ExportMetadata() { if (Enabled) m_dumpOperation.RunOnceAsync(); } // Executes the metadata export. private void DumpMetadata() { const int MaxFailedAttempts = 5; const int DelayAfterFailure = 2000; int failedAttempts = 0; while (true) { try { string directory = Path.GetDirectoryName(ExportFilePath); if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory); // Open the file to be exported using (FileStream stream = File.Create(ExportFilePath)) { // Get metadata and serialize it to the file DataSet metadata = AcquireMetadata(); metadata.SerializeToStream(stream); m_lastDump = DateTime.Now; break; } } catch (IOException) { // Only throw an IOException // after five failed attempts failedAttempts++; if (failedAttempts >= MaxFailedAttempts) throw; Thread.Sleep(DelayAfterFailure); } } } // Gets the metadata to be exported to a file. private DataSet AcquireMetadata() { using (AdoDataConnection adoDatabase = new AdoDataConnection("systemSettings")) { IDbConnection dbConnection = adoDatabase.Connection; DataSet metadata = new DataSet(); // Initialize active node ID Guid nodeID = Guid.Parse(dbConnection.ExecuteScalar($"SELECT NodeID FROM IaonActionAdapter WHERE ID = {ID}").ToString()); // Copy key metadata tables foreach (string tableExpression in MetadataTables.Split(';')) { if (string.IsNullOrWhiteSpace(tableExpression)) continue; // Query the table or view information from the database DataTable table = dbConnection.RetrieveData(adoDatabase.AdapterType, tableExpression); // Remove any expression from table name Match regexMatch = Regex.Match(tableExpression, @"FROM \w+"); table.TableName = regexMatch.Value.Split(' ')[1]; // Add a copy of the results to the dataset for metadata exchange metadata.Tables.Add(table.Copy()); } return metadata; } } // Handles exceptions encountered by the dump operation. private void HandleException(Exception ex) { OnProcessException(MessageLevel.Error, ex); } #endregion } }