using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
/// The Files script global (design 2026-09-28): READ-ONLY access to files under the site
/// node's configured ScadaBridge:SiteRuntime:ScriptFileAccess:AllowedRoots — e.g. a
/// recipe file whose path a MES/Delmia call handed to the machine. Off by default. Every
/// refusal or failure throws ; reads are bounded by the
/// script's own execution deadline as well as the caller's token.
///
/// foreach (var line in await Files.ReadLinesAsync(path)) { … }
///
///
public sealed class FilesAccessor
{
private readonly IScriptFileReader? _reader;
private readonly CancellationToken _scriptToken;
/// Initializes a new instance.
/// The host reader; null when the node has none wired (then every call throws Disabled).
/// The running script's execution-timeout token.
public FilesAccessor(IScriptFileReader? reader, CancellationToken scriptToken)
{
_reader = reader;
_scriptToken = scriptToken;
}
/// Reads a whole text file (BOM-detected, UTF-8 by default).
/// Absolute path under an allowed root.
/// Cancels the read.
/// The file's text.
public async Task ReadTextAsync(string path, CancellationToken cancellationToken = default)
{
using var linked = CancellationTokenSource.CreateLinkedTokenSource(_scriptToken, cancellationToken);
return await Reader.ReadTextAsync(path, linked.Token).ConfigureAwait(false);
}
///
/// Reads a text file as lines (\r\n, \n or \r); a trailing line break does
/// not produce a final empty line.
///
/// Absolute path under an allowed root.
/// Cancels the read.
/// The file's lines.
public async Task> ReadLinesAsync(string path, CancellationToken cancellationToken = default)
{
var text = await ReadTextAsync(path, cancellationToken).ConfigureAwait(false);
return SplitLines(text);
}
/// True when an existing file is at the path; false when nothing is there. Still refuses paths outside the allowed roots.
/// Absolute path under an allowed root.
/// Cancels the check.
/// Whether the file exists.
public async Task ExistsAsync(string path, CancellationToken cancellationToken = default)
{
using var linked = CancellationTokenSource.CreateLinkedTokenSource(_scriptToken, cancellationToken);
return await Reader.ExistsAsync(path, linked.Token).ConfigureAwait(false);
}
private IScriptFileReader Reader => _reader
?? throw new ScriptFileAccessException(ScriptFileAccessFailure.Disabled,
"Script file access is not available on this node.");
internal static IReadOnlyList SplitLines(string text)
{
if (text.Length == 0)
return Array.Empty();
var lines = text.Split(["\r\n", "\n", "\r"], StringSplitOptions.None).ToList();
if (lines.Count > 0 && lines[^1].Length == 0)
lines.RemoveAt(lines.Count - 1);
return lines;
}
}