//******************************************************************************************************
// ConfigurationFrame.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:
// ----------------------------------------------------------------------------------------------------
// 05/05/2009 - J. Ritchie Carroll
// Generated original version of source code.
// 09/15/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/20/2011 - J. Ritchie Carroll
// Updated configuration caching algorithm to create multiple backup configurations.
// 12/17/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Soap;
using GSF.Collections;
using GSF.Configuration;
using GSF.IO;
using GSF.IO.Checksums;
using GSF.Threading;
namespace GSF.PhasorProtocols.Anonymous
{
///
/// Represents a protocol independent implementation of a that can be sent or received.
///
[Serializable]
public class ConfigurationFrame : ConfigurationFrameBase
{
#region [ Constructors ]
///
/// Creates a new from specified parameters.
///
/// The ID code of this .
/// The exact timestamp, in , of the data represented by this .
/// The defined frame rate of this .
public ConfigurationFrame(ushort idCode, Ticks timestamp, ushort frameRate)
: base(idCode, new ConfigurationCellCollection(), timestamp, frameRate)
{
}
///
/// Creates a new from serialization parameters.
///
/// The with populated with data.
/// The source for this deserialization.
protected ConfigurationFrame(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
#region [ Properties ]
///
/// Gets reference to the for this .
///
public new ConfigurationCellCollection Cells => base.Cells as ConfigurationCellCollection;
///
/// Gets or sets name of this configuration frame as assigned or useful in an end-use context.
///
public virtual string Name { get; set; }
#endregion
#region [ Methods ]
///
/// Calculates checksum of given .
///
/// Buffer image over which to calculate checksum.
/// Start index into to calculate checksum.
/// Length of data within to calculate checksum.
/// Checksum over specified portion of .
protected override ushort CalculateChecksum(byte[] buffer, int offset, int length)
{
// Just returning calculated CRC-CCITT over given buffer as a default CRC
return buffer.CrcCCITTChecksum(offset, length);
}
#endregion
#region [ Static ]
// Static Fields
private static readonly ProcessQueue, string>> s_configurationCacheQueue;
private static string s_configurationCachePath;
private static int s_configurationBackups;
// Static Constructor
static ConfigurationFrame()
{
s_configurationBackups = -1;
s_configurationCacheQueue = ProcessQueue, string>>.CreateRealTimeQueue(CacheConfigurationFile);
s_configurationCacheQueue.SynchronizedOperationType = SynchronizedOperationType.LongBackground;
s_configurationCacheQueue.Start();
}
// Static Properties
///
/// Gets the path for storing serialized phasor protocol configurations.
///
public static string ConfigurationCachePath
{
get
{
// This property will not change during system life-cycle so we cache if for future use
if (string.IsNullOrEmpty(s_configurationCachePath))
{
// Define default configuration cache directory relative to path of host application
s_configurationCachePath = string.Format("{0}{1}ConfigurationCache{1}", FilePath.GetAbsolutePath(""), Path.DirectorySeparatorChar);
// Make sure configuration cache path setting exists within system settings section of config file
ConfigurationFile configFile = ConfigurationFile.Current;
CategorizedSettingsElementCollection systemSettings = configFile.Settings["systemSettings"];
systemSettings.Add("ConfigurationCachePath", s_configurationCachePath, "Defines the path used to cache serialized phasor protocol configurations");
// Retrieve configuration cache directory as defined in the config file
s_configurationCachePath = FilePath.AddPathSuffix(systemSettings["ConfigurationCachePath"].Value);
// Make sure configuration cache directory exists
if (!Directory.Exists(s_configurationCachePath))
Directory.CreateDirectory(s_configurationCachePath);
}
return s_configurationCachePath;
}
}
///
/// Gets total number of configuration backups to keep when storing serialized phasor protocol configurations.
///
public static int ConfigurationBackups
{
get
{
if (s_configurationBackups == -1)
{
const int DefaultConfigurationBackups = 5;
// Make sure configuration backups setting exists within system settings section of config file
ConfigurationFile configFile = ConfigurationFile.Current;
CategorizedSettingsElementCollection systemSettings = configFile.Settings["systemSettings"];
systemSettings.Add("ConfigurationBackups", DefaultConfigurationBackups, "Defines the total number of older backup configurations to maintain.");
// Retrieve configuration backups value as defined in the config file
s_configurationBackups = systemSettings["ConfigurationBackups"].ValueAs(DefaultConfigurationBackups);
}
return s_configurationBackups;
}
}
// Static Methods
///
/// Serializes configuration frame to cache folder on an independent thread for later use (if needed).
///
/// Reference to .
/// delegate to handle process exceptions.
/// representing the configuration name.
public static void Cache(IConfigurationFrame configurationFrame, Action exceptionHandler, string configurationName)
{
Tuple, string> cacheState = new(configurationFrame, exceptionHandler, configurationName);
s_configurationCacheQueue.Add(cacheState);
}
// Cache configuration file
private static void CacheConfigurationFile(Tuple, string> args)
{
if (args is null)
return;
FileStream configFile = null;
IConfigurationFrame configurationFrame = args.Item1;
Action exceptionHandler = args.Item2;
string configurationName = args.Item3;
string configurationCacheFileName = GetConfigurationCacheFileName(configurationName);
try
{
// Create multiple backup configurations, if requested
for (int i = ConfigurationBackups; i > 0; i--)
{
string origConfigFile = $"{configurationCacheFileName}.backup{(i == 1 ? "" : (i - 1).ToString())}";
if (File.Exists(origConfigFile))
{
string nextConfigFile = $"{configurationCacheFileName}.backup{i}";
if (File.Exists(nextConfigFile))
File.Delete(nextConfigFile);
File.Move(origConfigFile, nextConfigFile);
}
}
}
catch (Exception ex)
{
exceptionHandler(new InvalidOperationException($"Failed to create extra backup serialized configuration frames due to exception: {ex.Message}"));
}
try
{
if (ConfigurationBackups > 0)
{
// Back up current configuration file, if any
if (File.Exists(configurationCacheFileName))
{
string backupConfigFile = $"{configurationCacheFileName}.backup";
if (File.Exists(backupConfigFile))
File.Delete(backupConfigFile);
File.Move(configurationCacheFileName, backupConfigFile);
}
}
}
catch (Exception ex)
{
exceptionHandler(new InvalidOperationException($"Failed to backup last serialized configuration frame due to exception: {ex.Message}"));
}
try
{
// Serialize configuration frame to a file
SoapFormatter xmlSerializer = new()
{
AssemblyFormat = FormatterAssemblyStyle.Simple,
TypeFormat = FormatterTypeStyle.TypesWhenNeeded
};
configFile = File.Create(configurationCacheFileName);
xmlSerializer.Serialize(configFile, configurationFrame);
}
catch (Exception ex)
{
exceptionHandler(new InvalidOperationException($"Failed to serialize configuration frame: {ex.Message}", ex));
}
finally
{
configFile?.Close();
}
}
///
/// Gets the file name with path of the specified .
///
/// Name of the configuration to get file name for.
/// File name with path of the specified .
public static string GetConfigurationCacheFileName(string configurationName)
{
return $"{ConfigurationCachePath}{configurationName.ReplaceCharacters('_', c => Path.GetInvalidFileNameChars().Contains(c))}.configuration.xml";
}
///
/// Deletes the cached configuration, if defined.
///
/// Name of the configuration to delete.
public static void DeleteCachedConfiguration(string configurationName)
{
string configFileName = GetConfigurationCacheFileName(configurationName);
if (File.Exists(configFileName))
File.Delete(configFileName);
}
///
/// Deserializes cached configuration, if available.
///
/// Name of the configuration to get file name for.
/// Set to True retrieve from cache, False to retrieve from specified file name in
/// Cached configuration frame, or null if not available.
public static IConfigurationFrame GetCachedConfiguration(string configurationName, bool fromCache)
{
IConfigurationFrame configFrame = null;
string configFileName = fromCache ? GetConfigurationCacheFileName(configurationName) : configurationName;
if (File.Exists(configFileName))
configFrame = Common.DeserializeConfigurationFrame(configFileName);
return configFrame;
}
#endregion
}
}