#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved //----------------------------------------------------------------------------- // Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved // Web: https://www.technosoftware.com // // The source code in this file is covered under a dual-license scenario: // - Owner of a purchased license: SCLA 1.0 // - GPL V3: everybody else // // SCLA license terms accompanied with this source code. // See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf // // GNU General Public License as published by the Free Software Foundation; // version 3 of the License are accompanied with this source code. // See https://technosoftware.com/license/GPLv3License.txt // // This source code is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. //----------------------------------------------------------------------------- #endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved #region Using Directives using System; using System.Collections; using System.Runtime.Serialization; #endregion namespace Technosoftware.DaAeHdaClient.Da { /// /// An in-process object used to access subscriptions on OPC Data Access servers. /// [Serializable] public class TsCDaSubscription : ITsCDaSubscription, ISerializable, ICloneable { #region Names Class /// /// A set of names for fields used in serialization. /// private class Names { internal const string State = "State"; internal const string Filters = "Filters"; internal const string Items = "Items"; } #endregion #region Fields private bool disposed_; /// /// The containing server object. /// private TsCDaServer server_; /// /// The remote subscription object. /// internal ITsCDaSubscription Subscription; /// /// The local copy of the subscription state. /// private TsCDaSubscriptionState subscriptionState_ = new TsCDaSubscriptionState(); /// /// The local copy of all subscription items. /// private TsCDaItem[] daItems_; /// /// Whether data callbacks are enabled. /// private bool enabled_ = true; /// /// The local copy of the result filters. /// private int filters_ = (int)TsCDaResultFilter.All | (int)TsCDaResultFilter.ClientHandle; #endregion #region Constructors, Destructor, Initialization /// /// Initializes object with default values. /// public TsCDaSubscription(TsCDaServer server, ITsCDaSubscription subscription) { server_ = server ?? throw new ArgumentNullException(nameof(server)); Subscription = subscription ?? throw new ArgumentNullException(nameof(subscription)); GetResultFilters(); GetState(); } /// /// Constructs a server by de-serializing its OpcUrl from the stream. /// protected TsCDaSubscription(SerializationInfo info, StreamingContext context) { subscriptionState_ = (TsCDaSubscriptionState)info.GetValue(Names.State, typeof(TsCDaSubscriptionState)); filters_ = (int)info.GetValue(Names.Filters, typeof(int)); daItems_ = (TsCDaItem[])info.GetValue(Names.Items, typeof(TsCDaItem[])); } /// /// The finalizer implementation. /// ~TsCDaSubscription() { Dispose(false); } /// /// This must be called explicitly by clients to ensure the remote server is released. /// public virtual void Dispose() { Dispose(true); // Take yourself off the Finalization queue // to prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } /// /// Dispose(bool disposing) executes in two distinct scenarios. /// If disposing equals true, the method has been called directly /// or indirectly by a user's code. Managed and unmanaged resources /// can be disposed. /// If disposing equals false, the method has been called by the /// runtime from inside the finalizer and you should not reference /// other objects. Only unmanaged resources can be disposed. /// /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!disposed_) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { if (Subscription != null) { Subscription.Dispose(); server_ = null; Subscription = null; daItems_ = null; } } // Release unmanaged resources. If disposing is false, // only the following code is executed. } disposed_ = true; } #endregion #region Properties /// /// The server that the subscription is attached to. /// public TsCDaServer Server => server_; /// /// The name assigned to the subscription by the client. /// public string Name => subscriptionState_.Name; /// /// The handle assigned to the subscription by the client. /// public object ClientHandle => subscriptionState_.ClientHandle; /// /// The handle assigned to the subscription by the server. /// public object ServerHandle => subscriptionState_.ServerHandle; /// /// Whether the subscription is active. /// public bool Active => subscriptionState_.Active; /// /// Whether data callbacks are enabled. /// public bool Enabled => enabled_; /// /// The current locale used by the subscription. /// public string Locale => subscriptionState_.Locale; /// /// The current result filters applied by the subscription. /// public int Filters => filters_; /// /// Returns a copy of the current subscription state. /// public TsCDaSubscriptionState State => (TsCDaSubscriptionState)subscriptionState_.Clone(); /// /// The items belonging to the subscription. /// public TsCDaItem[] Items { get { if (daItems_ == null) return new TsCDaItem[0]; var items = new TsCDaItem[daItems_.Length]; for (var ii = 0; ii < daItems_.Length; ii++) items[ii] = (TsCDaItem)daItems_[ii].Clone(); return items; } } #endregion #region Public Methods /// /// Serializes a server into a stream. /// public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(Names.State, subscriptionState_); info.AddValue(Names.Filters, filters_); info.AddValue(Names.Items, daItems_); } /// /// Returns an unconnected copy of the subscription with the same items. /// public virtual object Clone() { // do a memberwise clone. var clone = (TsCDaSubscription)MemberwiseClone(); // place clone in disconnected state. clone.server_ = null; clone.Subscription = null; clone.subscriptionState_ = (TsCDaSubscriptionState)subscriptionState_.Clone(); // clear server handles. clone.subscriptionState_.ServerHandle = null; // always make cloned subscriptions inactive. clone.subscriptionState_.Active = false; // clone items. if (clone.daItems_ != null) { var items = new ArrayList(); Array.ForEach(clone.daItems_, item => items.Add(item.Clone())); clone.daItems_ = (TsCDaItem[])items.ToArray(typeof(TsCDaItem)); } // return clone. return clone; } /// /// Gets default result filters for the server. /// public int GetResultFilters() { filters_ = Subscription.GetResultFilters(); return filters_; } /// /// Sets default result filters for the server. /// public void SetResultFilters(int filters) { Subscription.SetResultFilters(filters); filters_ = filters; } /// /// Returns the current subscription state. /// public TsCDaSubscriptionState GetState() { subscriptionState_ = Subscription.GetState(); return subscriptionState_; } /// /// Updates the current subscription state. /// public TsCDaSubscriptionState ModifyState(int masks, TsCDaSubscriptionState state) { subscriptionState_ = Subscription.ModifyState(masks, state); return subscriptionState_; } /// /// Adds items to the subscription. /// public virtual TsCDaItemResult[] AddItems(TsCDaItem[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (items == null) throw new ArgumentNullException(nameof(items)); // check if there is nothing to do. if (items.Length == 0) { return new TsCDaItemResult[0]; } // add items. var results = Subscription.AddItems(items); if (results == null || results.Length == 0) { throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } // update locale item list. var itemList = new ArrayList(); if (daItems_ != null) itemList.AddRange(daItems_); for (var ii = 0; ii < results.Length; ii++) { // check for failure. if (results[ii].Result.Failed()) { continue; } // create locale copy of the item. // item name, item path and client handle may not be returned by server. var item = new TsCDaItem(results[ii]) { ItemName = items[ii].ItemName, ItemPath = items[ii].ItemPath, ClientHandle = items[ii].ClientHandle }; itemList.Add(item); } // save the new item list. daItems_ = (TsCDaItem[])itemList.ToArray(typeof(TsCDaItem)); // update the local state. GetState(); // return results. return results; } /// /// Modifies items that are already part of the subscription. /// public virtual TsCDaItemResult[] ModifyItems(int masks, TsCDaItem[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (items == null) throw new ArgumentNullException(nameof(items)); // check if there is nothing to do. if (items.Length == 0) { return new TsCDaItemResult[0]; } // modify items. var results = Subscription.ModifyItems(masks, items); if (results == null || results.Length == 0) { throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } // update local item - modify item success means all fields were updated successfully. for (var ii = 0; ii < results.Length; ii++) { // check for failure. if (results[ii].Result.Failed()) { continue; } // search local item list. for (var jj = 0; jj < daItems_.Length; jj++) { if (daItems_[jj].ServerHandle.Equals(items[ii].ServerHandle)) { // update locale copy of the item. // item name, item path and client handle may not be returned by server. var item = new TsCDaItem(results[ii]) { ItemName = daItems_[jj].ItemName, ItemPath = daItems_[jj].ItemPath, ClientHandle = daItems_[jj].ClientHandle }; daItems_[jj] = item; break; } } } // update the local state. GetState(); // return results. return results; } /// /// Removes items from a subscription. /// public virtual OpcItemResult[] RemoveItems(OpcItem[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (items == null) throw new ArgumentNullException(nameof(items)); // check if there is nothing to do. if (items.Length == 0) { return new OpcItemResult[0]; } // remove items from server. var results = Subscription.RemoveItems(items); if (results == null || results.Length == 0) { throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); } // remove items from local list if successful. var itemList = new ArrayList(); foreach (var item in daItems_) { var removed = false; for (var ii = 0; ii < results.Length; ii++) { if (item.ServerHandle.Equals(items[ii].ServerHandle)) { removed = results[ii].Result.Succeeded(); break; } } if (!removed) itemList.Add(item); } // update local list. daItems_ = (TsCDaItem[])itemList.ToArray(typeof(TsCDaItem)); // update the local state. GetState(); // return results. return results; } /// /// Reads a set of subscription items. /// public TsCDaItemValueResult[] Read(TsCDaItem[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); return Subscription.Read(items); } /// /// Writes a set of subscription items. /// public OpcItemResult[] Write(TsCDaItemValue[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); return Subscription.Write(items); } /// /// Begins an asynchronous read operation for a set of items. /// /// The set of items to read (must include the item name). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] Read( TsCDaItem[] items, object requestHandle, TsCDaReadCompleteEventHandler callback, out IOpcRequest request) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); return Subscription.Read(items, requestHandle, callback, out request); } /// /// Begins an asynchronous write operation for a set of items. /// /// The set of item values to write (must include the item name). /// An identifier for the request assigned by the caller. /// A delegate used to receive notifications when the request completes. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public OpcItemResult[] Write( TsCDaItemValue[] items, object requestHandle, TsCDaWriteCompleteEventHandler callback, out IOpcRequest request) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); return Subscription.Write(items, requestHandle, callback, out request); } /// /// Cancels an asynchronous request. /// public void Cancel(IOpcRequest request, TsCDaCancelCompleteEventHandler callback) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); Subscription.Cancel(request, callback); } /// /// Tells the server to send an data change update for all subscription items. /// public void Refresh() { Subscription.Refresh(); } /// /// Causes the server to send a data changed notification for all active items. /// /// An identifier for the request assigned by the caller. /// An object that contains the state of the request (used to cancel the request). /// A set of results containing any errors encountered when the server validated the items. public void Refresh( object requestHandle, out IOpcRequest request) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); Subscription.Refresh(requestHandle, out request); } /// /// Sets whether data change callbacks are enabled. /// public void SetEnabled(bool enabled) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); Subscription.SetEnabled(enabled); enabled_ = enabled; } /// /// Gets whether data change callbacks are enabled. /// public bool GetEnabled() { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); enabled_ = Subscription.GetEnabled(); return enabled_; } #endregion #region ISubscription /// /// An event to receive data change updates. /// public event TsCDaDataChangedEventHandler DataChangedEvent { add => Subscription.DataChangedEvent += value; remove => Subscription.DataChangedEvent -= value; } #endregion } }