//****************************************************************************************************** // 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 } }