//****************************************************************************************************** // FileClient.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: // ---------------------------------------------------------------------------------------------------- // 07/24/2006 - Pinal C. Patel // Original version of source code generated. // 09/06/2006 - J. Ritchie Carroll // Added bypass optimizations for high-speed file data access. // 09/29/2008 - J. Ritchie Carroll // Converted to C#. // 07/08/2009 - J. Ritchie Carroll // Added WaitHandle return value from asynchronous connection. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 06/23/2010 - Stephen C. Wills // Modified to use the absolute file path. // 11/29/2010 - Pinal C. Patel // Corrected the implementation of ConnectAsync() method. // 12/04/2010 - Pinal C. Patel // Removed locking around m_fileClient.Provider since it was unnecessary. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Threading; using System.Timers; using GSF.Configuration; using GSF.IO; using Timer = System.Timers.Timer; namespace GSF.Communication { /// /// Represents a communication client based on . /// /// /// This example shows how to use for writing data to a file: /// /// using System; /// using GSF.Communication; /// /// class Program /// { /// static FileClient s_client; /// /// static void Main(string[] args) /// { /// // Initialize the client. /// s_client = new FileClient(@"File=c:\File.txt"); /// s_client.Initialize(); /// // Register event handlers. /// s_client.ConnectionAttempt += s_client_ConnectionAttempt; /// s_client.ConnectionEstablished += s_client_ConnectionEstablished; /// s_client.ConnectionTerminated += s_client_ConnectionTerminated; /// s_client.SendDataComplete += s_client_SendDataComplete; /// // Connect the client. /// s_client.Connect(); /// /// // Write user input to the file. /// string input; /// while (string.Compare(input = Console.ReadLine(), "Exit", true) != 0) /// { /// s_client.Send(input + "\r\n"); /// } /// /// // Disconnect the client on shutdown. /// s_client.Dispose(); /// } /// /// static void s_client_ConnectionAttempt(object sender, EventArgs e) /// { /// Console.WriteLine("Client is connecting to file."); /// } /// /// static void s_client_ConnectionEstablished(object sender, EventArgs e) /// { /// Console.WriteLine("Client connected to file."); /// } /// /// static void s_client_ConnectionTerminated(object sender, EventArgs e) /// { /// Console.WriteLine("Client disconnected from file."); /// } /// /// static void s_client_SendDataComplete(object sender, EventArgs e) /// { /// Console.WriteLine(string.Format("Sent data - {0}", s_client.TextEncoding.GetString(s_client.Client.SendBuffer))); /// } /// } /// /// This example shows how to use for reading data to a file: /// /// using System; /// using GSF; /// using GSF.Communication; /// /// class Program /// { /// static FileClient s_client; /// /// static void Main(string[] args) /// { /// // Initialize the client. /// s_client = new FileClient(@"File=c:\File.txt"); /// s_client.Initialize(); /// // Register event handlers. /// s_client.ConnectionAttempt += s_client_ConnectionAttempt; /// s_client.ConnectionEstablished += s_client_ConnectionEstablished; /// s_client.ConnectionTerminated += s_client_ConnectionTerminated; /// s_client.ReceiveDataComplete += s_client_ReceiveDataComplete; /// // Connect the client. /// s_client.Connect(); /// /// // Wait for client to read data. /// Console.ReadLine(); /// /// // Disconnect the client on shutdown. /// s_client.Dispose(); /// } /// /// static void s_client_ConnectionAttempt(object sender, EventArgs e) /// { /// Console.WriteLine("Client is connecting to file."); /// } /// /// static void s_client_ConnectionEstablished(object sender, EventArgs e) /// { /// Console.WriteLine("Client connected to file."); /// } /// /// static void s_client_ConnectionTerminated(object sender, EventArgs e) /// { /// Console.WriteLine("Client disconnected from file."); /// } /// /// static void s_client_ReceiveDataComplete(object sender, EventArgs<byte[], int> e) /// { /// Console.WriteLine(string.Format("Received data - {0}", s_client.TextEncoding.GetString(e.Argument1, 0, e.Argument2))); /// } /// } /// /// public class FileClient : ClientBase { #region [ Members ] // Constants /// /// Specifies the default value for the property. /// public const bool DefaultAutoRepeat = false; /// /// Specifies the default value for the property. /// public const bool DefaultReceiveOnDemand = false; /// /// Specifies the default value for the property. /// public const int DefaultReceiveInterval = -1; /// /// Specifies the default value for the property. /// public const long DefaultStartingOffset = 0; /// /// Specifies the default value for the property. /// public const FileMode DefaultFileOpenMode = FileMode.OpenOrCreate; /// /// Specifies the default value for the property. /// public const FileShare DefaultFileShareMode = FileShare.ReadWrite; /// /// Specifies the default value for the property. /// public const FileAccess DefaultFileAccessMode = FileAccess.ReadWrite; /// /// Specifies the default value for the property. /// public const string DefaultConnectionString = "File=DataFile.txt"; // Fields private bool m_autoRepeat; private bool m_receiveOnDemand; private double m_receiveInterval; private long m_startingOffset; private FileMode m_fileOpenMode; private FileShare m_fileShareMode; private FileAccess m_fileAccessMode; private readonly TransportProvider m_fileClient; private Dictionary m_connectData; private readonly Timer m_receiveDataTimer; private ManualResetEvent m_connectionHandle; #if ThreadTracking private ManagedThread m_connectionThread; #else private Thread m_connectionThread; #endif private bool m_disposed; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// public FileClient() : this(DefaultConnectionString) { } /// /// Initializes a new instance of the class. /// /// Connect string of the . See for format. public FileClient(string connectString) : base(TransportProtocol.File, connectString) { m_autoRepeat = DefaultAutoRepeat; m_receiveOnDemand = DefaultReceiveOnDemand; m_receiveInterval = DefaultReceiveInterval; m_startingOffset = DefaultStartingOffset; m_fileOpenMode = DefaultFileOpenMode; m_fileShareMode = DefaultFileShareMode; m_fileAccessMode = DefaultFileAccessMode; m_fileClient = new TransportProvider(); m_receiveDataTimer = new Timer(); m_receiveDataTimer.Elapsed += m_receiveDataTimer_Elapsed; } /// /// Initializes a new instance of the class. /// /// object that contains the . public FileClient(IContainer container) : this() { if (container != null) container.Add(this); } #endregion #region [ Properties ] /// /// Gets or sets a boolean value that indicates whether receiving (reading) of data is to be repeated endlessly. /// /// is enabled when is [Category("Data"), DefaultValue(DefaultAutoRepeat), Description("Indicates whether receiving (reading) of data is to be repeated endlessly.")] public bool AutoRepeat { get { return m_autoRepeat; } set { if (value && m_fileAccessMode == FileAccess.ReadWrite) throw new InvalidOperationException("AutoRepeat cannot be enabled when FileAccessMode is FileAccess.ReadWrite"); m_autoRepeat = value; } } /// /// Gets or sets a boolean value that indicates whether receiving (reading) of data will be initiated manually by calling . /// /// /// will be set to -1 when is enabled. /// [Category("Data"), DefaultValue(DefaultReceiveOnDemand), Description("Indicates whether receiving (reading) of data will be initiated manually by calling ReceiveData().")] public bool ReceiveOnDemand { get { return m_receiveOnDemand; } set { m_receiveOnDemand = value; if (m_receiveOnDemand) // We'll disable receiving data at a set interval if user wants to receive data on demand. m_receiveInterval = -1; } } /// /// Gets or sets the number of milliseconds to pause before receiving (reading) the next available set of data. /// /// /// Set = -1 to receive (read) data continuously without pausing. /// [Category("Data"), DefaultValue(DefaultReceiveInterval), Description("The number of milliseconds to pause before receiving (reading) the next available set of data. Set ReceiveInterval = -1 to receive data continuously without pausing.")] public double ReceiveInterval { get { return m_receiveInterval; } set { if (value < 1) m_receiveInterval = -1; else m_receiveInterval = value; } } /// /// Gets or sets the starting point relative to the beginning of the file from where the data is to be received (read). /// /// The value being assigned is not a positive number. [Category("File"), DefaultValue(DefaultStartingOffset), Description("The starting point relative to the beginning of the file from where the data is to be received (read).")] public long StartingOffset { get { return m_startingOffset; } set { if (value < 0) throw new ArgumentException("Value must be positive"); m_startingOffset = value; } } /// /// Gets or sets the value to be used when opening the file. /// [Category("File"), DefaultValue(DefaultFileOpenMode), Description("The System.IO.FileMode value to be used when opening the file.")] public FileMode FileOpenMode { get { return m_fileOpenMode; } set { m_fileOpenMode = value; } } /// /// Gets or set the value to be used when opening the file. /// [Category("File"), DefaultValue(DefaultFileShareMode), Description("The System.IO.FileShare value to be used when opening the file.")] public FileShare FileShareMode { get { return m_fileShareMode; } set { m_fileShareMode = value; } } /// /// Gets or sets the value to be used when opening the file. /// /// is set to when is enabled. [Category("File"), DefaultValue(DefaultFileAccessMode), Description("The System.IO.FileAccess value to be used when opening the file.")] public FileAccess FileAccessMode { get { return m_fileAccessMode; } set { if (value == FileAccess.ReadWrite && m_autoRepeat) throw new InvalidOperationException("FileAccessMode cannot be set to FileAccess.ReadWrite when AutoRepeat is enabled"); m_fileAccessMode = value; } } /// /// Gets the object for the . /// [Browsable(false)] public FileStream Client { get { return m_fileClient.Provider; } } /// /// Gets the server URI of the . /// [Browsable(false)] public override string ServerUri { get { return string.Format("{0}://{1}", TransportProtocol, m_connectData["file"]).ToLower(); } } #endregion #region [ Methods ] /// /// Reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset. /// /// Destination buffer used to hold copied bytes. /// 0-based starting index into destination to begin writing data. /// The number of bytes to read from current received data buffer and write into . /// The number of bytes read. /// /// This function should only be called from within the event handler. Calling this method outside /// this event will have unexpected results. /// /// No received data buffer has been defined to read. /// is null. /// /// or is less than 0 -or- /// and will exceed length. /// public override int Read(byte[] buffer, int startIndex, int length) { buffer.ValidateParameters(startIndex, length); if ((object)m_fileClient.ReceiveBuffer != null) { int sourceLength = m_fileClient.BytesReceived - ReadIndex; int readBytes = length > sourceLength ? sourceLength : length; Buffer.BlockCopy(m_fileClient.ReceiveBuffer, ReadIndex, buffer, startIndex, readBytes); // Update read index for next call ReadIndex += readBytes; if (ReadIndex >= m_fileClient.BytesReceived) ReadIndex = 0; return readBytes; } throw new InvalidOperationException("No received data buffer has been defined to read."); } /// /// Reads next data buffer from the . /// /// is called when is not connected. /// is called when is disabled. public void ReadNextBuffer() { if (m_receiveOnDemand) { if (CurrentState == ClientState.Connected) ReadData(); else throw new InvalidOperationException("Client is currently not connected"); } else { throw new InvalidOperationException("ReadNextBuffer() cannot be used when ReceiveOnDemand is disabled"); } } /// /// Disconnects the from the . /// public override void Disconnect() { if (CurrentState != ClientState.Disconnected) { m_fileClient.Reset(); m_receiveDataTimer.Stop(); if (m_connectionThread != null) m_connectionThread.Abort(); OnConnectionTerminated(); } } /// /// Connects the to the asynchronously. /// /// Attempt is made to connect the when it is not disconnected. /// for the asynchronous operation. public override WaitHandle ConnectAsync() { m_connectionHandle = (ManualResetEvent)base.ConnectAsync(); m_fileClient.SetReceiveBuffer(ReceiveBufferSize); #if ThreadTracking m_connectionThread = new ManagedThread(OpenFile); m_connectionThread.Name = "GSF.Communication.FileClient.OpenFile()"; #else m_connectionThread = new Thread(OpenFile); m_connectionThread.IsBackground = true; #endif m_connectionThread.Start(); return m_connectionHandle; } /// /// Saves settings to the config file if the property is set to true. /// public override void SaveSettings() { base.SaveSettings(); if (PersistSettings) { // Save settings under the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings["AutoRepeat", true].Update(m_autoRepeat); settings["ReceiveOnDemand", true].Update(m_receiveOnDemand); settings["ReceiveInterval", true].Update(m_receiveInterval); settings["StartingOffset", true].Update(m_startingOffset); settings["FileOpenMode", true].Update(m_fileOpenMode); settings["FileShareMode", true].Update(m_fileShareMode); settings["FileAccessMode", true].Update(m_fileAccessMode); config.Save(); } } /// /// Loads saved settings from the config file if the property is set to true. /// public override void LoadSettings() { base.LoadSettings(); if (PersistSettings) { // Load settings from the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings.Add("AutoRepeat", m_autoRepeat, "True if receiving (reading) of data is to be repeated endlessly, otherwise False."); settings.Add("ReceiveOnDemand", m_receiveOnDemand, "True if receiving (reading) of data will be initiated manually, otherwise False."); settings.Add("ReceiveInterval", m_receiveInterval, "Number of milliseconds to pause before receiving (reading) the next available set of data."); settings.Add("StartingOffset", m_startingOffset, "Starting point relative to the beginning of the file from where the data is to be received (read)."); settings.Add("FileOpenMode", m_fileOpenMode, "Open mode (CreateNew; Create; Open; OpenOrCreate; Truncate; Append) to be used when opening the file."); settings.Add("FileShareMode", m_fileShareMode, "Share mode (None; Read; Write; ReadWrite; Delete; Inheritable) to be used when opening the file."); settings.Add("FileAccessMode", m_fileAccessMode, "Access mode (Read; Write; ReadWrite) to be used when opening the file."); AutoRepeat = settings["AutoRepeat"].ValueAs(m_autoRepeat); ReceiveOnDemand = settings["ReceiveOnDemand"].ValueAs(m_receiveOnDemand); ReceiveInterval = settings["ReceiveInterval"].ValueAs(m_receiveInterval); StartingOffset = settings["StartingOffset"].ValueAs(m_startingOffset); FileOpenMode = settings["FileOpenMode"].ValueAs(m_fileOpenMode); FileShareMode = settings["FileShareMode"].ValueAs(m_fileShareMode); FileAccessMode = settings["FileAccessMode"].ValueAs(m_fileAccessMode); } } /// /// Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (!m_disposed) { try { // This will be done regardless of whether the object is finalized or disposed. if (disposing) { // This will be done only when the object is disposed by calling Dispose(). if (m_connectionHandle != null) m_connectionHandle.Dispose(); if (m_receiveDataTimer != null) { m_receiveDataTimer.Elapsed -= m_receiveDataTimer_Elapsed; m_receiveDataTimer.Dispose(); } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } /// /// Validates the specified . /// /// Connection string to be validated. /// File property is missing. protected override void ValidateConnectionString(string connectionString) { m_connectData = connectionString.ParseKeyValuePairs(); if (!m_connectData.ContainsKey("file")) throw new ArgumentException(string.Format("File property is missing (Example: {0})", DefaultConnectionString)); } /// /// Sends (writes) data to the file asynchronously. /// /// The buffer that contains the binary data to be sent (written). /// The zero-based position in the at which to begin sending (writing) data. /// The number of bytes to be sent (written) from starting at the . /// for the asynchronous operation. protected override WaitHandle SendDataAsync(byte[] data, int offset, int length) { WaitHandle handle; // Send data to the file asynchronously. handle = m_fileClient.Provider.BeginWrite(data, offset, length, SendDataAsyncCallback, null).AsyncWaitHandle; // Notify that the send operation has started. m_fileClient.Statistics.UpdateBytesSent(length); OnSendDataStart(); // Return the async handle that can be used to wait for the async operation to complete. return handle; } /// /// Callback method for asynchronous send operation. /// private void SendDataAsyncCallback(IAsyncResult asyncResult) { try { // Send operation is complete. m_fileClient.Provider.EndWrite(asyncResult); OnSendDataComplete(); } catch (Exception ex) { // Send operation failed to complete. OnSendDataException(ex); } } /// /// Connects to the . /// private void OpenFile() { int connectionAttempts = 0; while (MaxConnectionAttempts == -1 || connectionAttempts < MaxConnectionAttempts) { try { OnConnectionAttempt(); // Open the file. m_fileClient.Provider = new FileStream(FilePath.GetAbsolutePath(m_connectData["file"]), m_fileOpenMode, m_fileAccessMode, m_fileShareMode); // Move to the specified offset. m_fileClient.Provider.Seek(m_startingOffset, SeekOrigin.Begin); m_connectionHandle.Set(); OnConnectionEstablished(); if (!m_receiveOnDemand) { if (m_receiveInterval > 0) { // Start receiving data at interval. m_receiveDataTimer.Interval = m_receiveInterval; m_receiveDataTimer.Start(); } else { // Start receiving data continuously. while (true) { ReadData(); // Read all available data. Thread.Sleep(1000); // Wait for more data to be available. } } } break; // We're done here. } catch (ThreadAbortException) { // Exit gracefully. break; } catch (Exception ex) { // Keep retrying connecting to the file. Thread.Sleep(1000); connectionAttempts++; OnConnectionException(ex); } } } /// /// Receive (reads) data from the . /// private void ReadData() { try { // Process the entire file content while (m_fileClient.Provider.Position < m_fileClient.Provider.Length) { // Retrieve data from the file. m_fileClient.BytesReceived = m_fileClient.Provider.Read(m_fileClient.ReceiveBuffer, 0, m_fileClient.ReceiveBufferSize); m_fileClient.Statistics.UpdateBytesReceived(m_fileClient.BytesReceived); // Notify of the retrieved data. OnReceiveDataComplete(m_fileClient.ReceiveBuffer, m_fileClient.BytesReceived); // Re-read the file if the user wants to repeat when done reading the file. if (m_autoRepeat && m_fileClient.Provider.Position == m_fileClient.Provider.Length) { m_fileClient.Provider.Seek(m_startingOffset, SeekOrigin.Begin); } // Stop processing the file if user has either opted to receive data on demand or receive data at a predefined interval. if (m_receiveOnDemand || m_receiveInterval > 0) { break; } } } catch (ThreadAbortException) { // Exit gracefully. } catch (Exception ex) { // Notify of the exception. if (!(ex is NullReferenceException)) OnReceiveDataException(ex); } } private void m_receiveDataTimer_Elapsed(object sender, ElapsedEventArgs e) { ReadData(); } /// /// Raises the event. /// /// Exception to send to event. protected override void OnConnectionException(Exception ex) { base.Disconnect(); base.OnConnectionException(ex); } #endregion } }