using System; using System.Collections.Generic; using System.IO; using System.IO.Abstractions; using System.Linq; using System.Net; using System.Net.Sockets; using BO.ProcessServer.Business.StatisticCounters; using BO.ProcessServer.Business.Workers; using BO.ProcessServer.Business.Workers.Contexts; using BO.ProcessServer.Options; using Connector.PS; using Quadarax.Foundation.Core.IO; using Quadarax.Foundation.Core.Logging; using Quadarax.Foundation.Core.Object; namespace BO.ProcessServer.Business { internal class Server : DisposableObject { #region *** Private fields *** private Configuration _configuration; private IFileSystem _fileSystem; private IList _processes; protected ILog Log { get; } protected ILog LogLifecycle { get; } protected Statistics Statistics { get; } private Pool _pool; private PullDocumentsWorker _wkPull; private RetentionWorker _wkRetention; private ProcessingDocumentsWorker _wkProcessing; private IConnectionPS _connection; #endregion #region *** Public properties *** public bool IsRunning { get; private set; } #endregion #region *** Constructors *** public Server(Configuration serverConfiguration, IFileSystem fileSystem, ILogger logger) { _configuration = serverConfiguration ?? throw new ArgumentNullException(nameof(serverConfiguration)); _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); if (logger == null) throw new ArgumentNullException(nameof(logger)); Log = logger.GetLogger(typeof(Server)); LogLifecycle = logger.GetLogger("Lifecycle"); Statistics = new Statistics(); ValidateConfiguration(); _processes = new List(); _connection = new Connector(_configuration, _fileSystem); _pool = new Pool(_configuration, _fileSystem, logger); _wkPull = new PullDocumentsWorker(_connection, _configuration.System.PoolPullInterval, _fileSystem, Statistics, _pool, logger); _wkRetention = new RetentionWorker(new CfgCtx(_configuration, Statistics, _pool)); _wkProcessing = new ProcessingDocumentsWorker(_configuration, _processes, _connection, _fileSystem, Statistics, _pool, logger); } #endregion #region *** Public operations *** public void Start() { if (IsRunning) { LogWarn($"Instance '{_configuration.System.Identification}' already running. Start skipped."); return; } LogDebug($"Starting instance '{_configuration.System.Identification}' ..."); _wkPull.Start(); _wkRetention.Start(); _wkProcessing.Start(); Statistics.GetCounter(Statistics.CNT_PS_ONLINE_DUR).Start(); IsRunning = true; LogInfo( $"Instance '{_configuration.System.Identification}' STARTED."); LogLifecycle.Log(LogSeverityEnum.Info, $"Instance '{_configuration.System.Identification}' STARTED."); } public void Stop() { if (!IsRunning) { LogWarn($"Instance '{_configuration.System.Identification}' already stopped. Stop skipped."); return; } LogDebug($"Stopping instance '{_configuration.System.Identification}' ..."); StopAll(); Statistics.GetCounter(Statistics.CNT_PS_ONLINE_DUR).Stop(); IsRunning = false; LogInfo( $"Instance '{_configuration.System.Identification}' STOPPED."); LogLifecycle.Log(LogSeverityEnum.Info, $"Instance '{_configuration.System.Identification}' STOPPED."); LogLifecycle.Log(LogSeverityEnum.Info, Statistics.ToString()); } #endregion #region *** Overrides *** protected override void OnDisposing() { if (IsRunning) StopAll(true); _wkPull?.Dispose(); _wkRetention?.Dispose(); _wkProcessing?.Dispose(); _connection?.Dispose(); _pool?.Dispose(); LogDebug($"Instance '{_configuration.System.Identification}' disposed."); _configuration = null; _fileSystem = null; _wkPull=null; _wkRetention=null; _wkProcessing = null; _connection=null; _pool = null; } #endregion #region *** Private Operations *** public void LogDebug(string message) { LogRaw(LogSeverityEnum.Debug, message); } public void LogInfo(string message) { LogRaw(LogSeverityEnum.Info, message); } public void LogWarn(string message) { LogRaw(LogSeverityEnum.Warn, message); } public void LogRaw(LogSeverityEnum severity, string message) { Log.Log(severity, message); } private void ValidateConfiguration() { if (string.IsNullOrEmpty(_configuration.System.Identification)) throw new ArgumentNullException(nameof(_configuration.System.Identification), "Mandatory process server identification."); if (string.IsNullOrEmpty(_configuration.System.PreferredInterface)) _configuration.System.PreferredInterface = "auto"; var host = Dns.GetHostEntry(Dns.GetHostName()); if (_configuration.System.PreferredInterface.Trim().ToLower() == "auto") { _configuration.System.PreferredInterface = host.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork || x.AddressFamily == AddressFamily.InterNetworkV6).OrderBy(x=>x.AddressFamily).FirstOrDefault() ?.ToString(); } if (!host.AddressList.Any(x => (x.AddressFamily == AddressFamily.InterNetwork || x.AddressFamily == AddressFamily.InterNetworkV6) && x.ToString().ToLower() == _configuration.System.PreferredInterface.Trim().ToLower())) throw new ArgumentNullException(nameof(_configuration.System.PreferredInterface), "Mandatory preferred interface (or auto) for server identification."); // Pool directories structure EnsureDirectory(_configuration.System.PoolPathBase); EnsureDirectory(_fileSystem.Path.Combine(_configuration.System.PoolPathBase, _configuration.System.PoolPathDone)); EnsureDirectory(_fileSystem.Path.Combine(_configuration.System.PoolPathBase, _configuration.System.PoolPathFail)); EnsureDirectory(_fileSystem.Path.Combine(_configuration.System.PoolPathBase, _configuration.System.PoolPathPending)); EnsureDirectory(_fileSystem.Path.Combine(_configuration.System.PoolPathBase, _configuration.System.PoolPathWorking)); // Scenario structure EnsureDirectory(_configuration.System.ScenarioPathBase); if (_fileSystem.Directory.GetFiles(TranslatePath(_configuration.System.ScenarioPathBase), "*.*").Length == 0) LogWarn($"Scenario directory '{TranslatePath(_configuration.System.ScenarioPathBase)}' is empty."); LogInfo("Configuration validated."); } private string EnsureDirectory(string path) { var dir = FileUtils.EnsurePathEndsWithDirSeparator(_fileSystem, TranslatePath(path)); if (!_fileSystem.Directory.Exists(dir)) { _fileSystem.Directory.CreateDirectory(dir); LogDebug($"Directory '{dir}' created."); } if (!_fileSystem.Directory.Exists(dir)) throw new FileNotFoundException($"Directory '{dir}' not found!"); return dir; } private string TranslatePath(string path) { if (string.IsNullOrEmpty(path)) return path; return path.Replace("{CD}", _fileSystem.Directory.GetCurrentDirectory()); } private void StopAll(bool silently = false) { _wkPull.Stop(silently); _wkRetention.Stop(silently); _wkProcessing.Stop(silently); foreach (var process in _processes) process.Stop(silently); foreach (var process in _processes) process.Dispose(); _processes.Clear(); } #endregion } }