#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 Technosoftware.DaAeHdaClient.Hda;
using Technosoftware.OpcRcw.Hda;
using Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Hda
{
///
/// An in-process wrapper an OPC HDA browser object.
///
internal class Browser : ITsCHdaBrowser
{
//======================================================================
// Construction
///
/// Initializes the object with the specifed COM server.
///
internal Browser(Server server, IOPCHDA_Browser browser, TsCHdaBrowseFilter[] filters, OpcResult[] results)
{
if (browser == null) throw new ArgumentNullException(nameof(browser));
// save the server object that created the browser.
m_server = server;
// save the COM server (released in Dispose()).
m_browser = browser;
// save only the filters that were accepted.
if (filters != null)
{
var validFilters = new ArrayList();
for (var ii = 0; ii < filters.Length; ii++)
{
if (results[ii].Succeeded())
{
validFilters.Add(filters[ii]);
}
}
m_filters = new TsCHdaBrowseFilterCollection(validFilters);
}
}
#region IDisposable Members
///
/// This must be called explicitly by clients to ensure the COM server is released.
///
public virtual void Dispose()
{
lock (this)
{
m_server = null;
Utilities.Interop.ReleaseServer(m_browser);
m_browser = null;
}
}
#endregion
//======================================================================
// Filters
///
/// Returns the set of attribute filters used by the browser.
///
public TsCHdaBrowseFilterCollection Filters
{
get
{
lock (this)
{
return (TsCHdaBrowseFilterCollection)m_filters.Clone();
}
}
}
//======================================================================
// Browse
///
/// Browses the server's address space at the specified branch.
///
/// The item id of the branch to search.
/// The set of elements that meet the filter criteria.
public TsCHdaBrowseElement[] Browse(OpcItem itemID)
{
IOpcBrowsePosition position;
var elements = Browse(itemID, 0, out position);
if (position != null)
{
position.Dispose();
}
return elements;
}
///
/// Begins a browsing the server's address space at the specified branch.
///
/// The item id of the branch to search.
/// The maximum number of elements to return.
/// The position object used to continue a browse operation.
/// The set of elements that meet the filter criteria.
public TsCHdaBrowseElement[] Browse(OpcItem itemID, int maxElements, out IOpcBrowsePosition position)
{
position = null;
// interpret invalid values as 'no limit'.
if (maxElements <= 0)
{
maxElements = int.MaxValue;
}
lock (this)
{
var branchPath = (itemID != null && itemID.ItemName != null)?itemID.ItemName:"";
// move to the correct position in the server's address space.
try
{
m_browser.ChangeBrowsePosition(OPCHDA_BROWSEDIRECTION.OPCHDA_BROWSE_DIRECT, branchPath);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCHDA_Browser.ChangeBrowsePosition", e);
}
// browse for branches
var enumerator = GetEnumerator(true);
var elements = FetchElements(enumerator, maxElements, true);
// check if max element count reached.
if (elements.Count >= maxElements)
{
position = new BrowsePosition(branchPath, enumerator, false);
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release enumerator.
enumerator.Dispose();
// browse for items
enumerator = GetEnumerator(false);
var items = FetchElements(enumerator, maxElements-elements.Count, false);
if (items != null)
{
elements.AddRange(items);
}
// check if max element count reached.
if (elements.Count >= maxElements)
{
position = new BrowsePosition(branchPath, enumerator, true);
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release enumerator.
enumerator.Dispose();
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
}
//======================================================================
// BrowseNext
///
/// Continues browsing the server's address space at the specified position.
///
/// The maximum number of elements to return.
/// The position object used to continue a browse operation.
/// The set of elements that meet the filter criteria.
public TsCHdaBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position)
{
// check arguments.
if (position == null || position.GetType() != typeof(BrowsePosition))
{
throw new ArgumentException("Not a valid browse position object.", nameof(position));
}
// interpret invalid values as 'no limit'.
if (maxElements <= 0)
{
maxElements = int.MaxValue;
}
lock (this)
{
var pos = (BrowsePosition)position;
var elements = new ArrayList();
if (!pos.FetchingItems)
{
elements = FetchElements(pos.Enumerator, maxElements, true);
// check if max element count reached.
if (elements.Count >= maxElements)
{
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release enumerator.
pos.Enumerator.Dispose();
pos.Enumerator = null;
pos.FetchingItems = true;
// move to the correct position in the server's address space.
try
{
m_browser.ChangeBrowsePosition(OPCHDA_BROWSEDIRECTION.OPCHDA_BROWSE_DIRECT, pos.BranchPath);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCHDA_Browser.ChangeBrowsePosition", e);
}
// create enumerator for items.
pos.Enumerator = GetEnumerator(false);
}
// fetch next set of items.
var items = FetchElements(pos.Enumerator, maxElements-elements.Count, false);
if (items != null)
{
elements.AddRange(items);
}
// check if max element count reached.
if (elements.Count >= maxElements)
{
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
// release position object.
position.Dispose();
position = null;
// return elements.
return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement));
}
}
#region Private Methods
///
/// Creates an enumerator for the elements contained with the current branch.
///
private EnumString GetEnumerator(bool isBranch)
{
try
{
var browseType = (isBranch)?OPCHDA_BROWSETYPE.OPCHDA_BRANCH:OPCHDA_BROWSETYPE.OPCHDA_LEAF;
IEnumString pEnumerator = null;
m_browser.GetEnum(browseType, out pEnumerator);
return new EnumString(pEnumerator);
}
catch (Exception e)
{
throw Utilities.Interop.CreateException("IOPCHDA_Browser.GetEnum", e);
}
}
///
/// Fetches the element names and item ids for each element.
///
private ArrayList FetchElements(EnumString enumerator, int maxElements, bool isBranch)
{
var elements = new ArrayList();
while (elements.Count < maxElements)
{
// fetch next batch of element names.
var count = BLOCK_SIZE;
if (elements.Count + count > maxElements)
{
count = maxElements - elements.Count;
}
var names = enumerator.Next(count);
// check if no more elements found.
if (names == null || names.Length == 0)
{
break;
}
// create new element objects.
foreach (var name in names)
{
var element = new TsCHdaBrowseElement();
element.Name = name;
// lookup item id for element.
try
{
string itemID = null;
m_browser.GetItemID(name, out itemID);
element.ItemName = itemID;
element.ItemPath = null;
element.HasChildren = isBranch;
}
catch
{
// ignore errors.
}
elements.Add(element);
}
}
// validate items - this is necessary to set the IsItem flag correctly.
var results = m_server.ValidateItems((OpcItem[])elements.ToArray(typeof(OpcItem)));
if (results != null)
{
for (var ii = 0; ii < results.Length; ii++)
{
if (results[ii].Result.Succeeded())
{
((TsCHdaBrowseElement)elements[ii]).IsItem = true;
}
}
}
// return results.
return elements;
}
#endregion
#region Private Members
private Server m_server = null;
private IOPCHDA_Browser m_browser = null;
private TsCHdaBrowseFilterCollection m_filters = new TsCHdaBrowseFilterCollection();
private const int BLOCK_SIZE = 10;
#endregion
}
///
/// Stores the state of a browse operation that was halted.
///
internal class BrowsePosition : TsCHdaBrowsePosition
{
///
/// Initializes a the object with the browse operation state information.
///
/// The item id of branch used in the browse operation.
/// The enumerator used for the browse operation.
/// Whether the enumerator is return branches or items.
internal BrowsePosition(string branchPath, EnumString enumerator, bool fetchingItems)
{
m_branchPath = branchPath;
m_enumerator = enumerator;
m_fetchingItems = fetchingItems;
}
///
/// The item id of the branch being browsed.
///
internal string BranchPath
{
get => m_branchPath;
set => m_branchPath = value;
}
///
/// The enumerator that was in use when the browse halted.
///
internal EnumString Enumerator
{
get => m_enumerator;
set => m_enumerator = value;
}
///
/// Whether the browse halted while fetching items.
///
internal bool FetchingItems
{
get => m_fetchingItems;
set => m_fetchingItems = value;
}
#region IDisposable Members
///
/// Releases any unmanaged resources held by the object.
///
public override void Dispose()
{
if (m_enumerator != null)
{
m_enumerator.Dispose();
m_enumerator = null;
}
base.Dispose();
}
#endregion
#region Private Members
private string m_branchPath = null;
private EnumString m_enumerator = null;
private bool m_fetchingItems = false;
#endregion
}
}