using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Netch.Models; using Netch.Utils; using Serilog; using Timer = System.Timers.Timer; namespace Netch.Controllers { public abstract class Guard { private readonly Timer _flushFileStreamTimer = new(300) { AutoReset = true }; private FileStream? _logFileStream; private StreamWriter? _logStreamWriter; private bool _redirectToFile = true; /// /// 日志文件(重定向输出文件) /// protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log"); /// /// 成功启动关键词 /// protected virtual IEnumerable StartedKeywords { get; set; } = new List(); /// /// 启动失败关键词 /// protected virtual IEnumerable StoppedKeywords { get; set; } = new List(); public abstract string Name { get; } /// /// 主程序名 /// public abstract string MainFile { get; protected set; } protected State State { get; set; } = State.Waiting; /// /// 进程是否可以重定向输出 /// protected bool RedirectStd { get; set; } = true; protected bool RedirectToFile { get => RedirectStd && _redirectToFile; set => _redirectToFile = value; } /// /// 进程实例 /// public Process? Instance { get; private set; } /// /// 程序输出的编码, /// protected virtual Encoding? InstanceOutputEncoding { get; } = null; public abstract void Stop(); /// /// 停止进程 /// protected void StopInstance() { try { if (Instance == null || Instance.HasExited) return; Instance.Kill(); Instance.WaitForExit(); } catch (Win32Exception e) { Log.Error(e, "停止 {MainFile} 异常", MainFile); } catch { // ignored } } /// /// 仅初始化 ,不设定事件处理方法 /// /// protected virtual void InitInstance(string argument) { Instance = new Process { StartInfo = { FileName = Path.GetFullPath($"bin\\{MainFile}"), WorkingDirectory = $"{Global.NetchDir}\\bin", Arguments = argument, CreateNoWindow = true, UseShellExecute = !RedirectStd, RedirectStandardOutput = RedirectStd, StandardOutputEncoding = RedirectStd ? InstanceOutputEncoding : null, RedirectStandardError = RedirectStd, StandardErrorEncoding = RedirectStd ? InstanceOutputEncoding : null, WindowStyle = ProcessWindowStyle.Hidden } }; if (!File.Exists(Instance.StartInfo.FileName)) throw new MessageException(i18N.Translate($"bin\\{MainFile} file not found!")); } /// /// 默认行为启动主程序 /// /// 主程序启动参数 /// 进程优先级 /// 是否成功启动 protected void StartInstanceAuto(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal) { State = State.Starting; // 初始化程序 InitInstance(argument); if (RedirectToFile) OpenLogFile(); // 启动程序 Instance!.Start(); if (priority != ProcessPriorityClass.Normal) Instance.PriorityClass = priority; if (RedirectStd) { Task.Run(() => ReadOutput(Instance.StandardOutput)); Task.Run(() => ReadOutput(Instance.StandardError)); if (!StartedKeywords.Any()) { State = State.Started; return; } } else { return; } // 等待启动 for (var i = 0; i < 1000; i++) { Thread.Sleep(10); switch (State) { case State.Started: Task.Run(OnKeywordStarted); return; case State.Stopped: Stop(); CloseLogFile(); OnKeywordStopped(); throw new MessageException($"{Name} 控制器启动失败"); } } Stop(); OnKeywordTimeout(); throw new MessageException($"{Name} 控制器启动超时"); } protected void ReadOutput(TextReader reader) { string? line; while ((line = reader.ReadLine()) != null) { WriteLog(line); OnReadNewLine(line); // State == State.Started if !StartedKeywords.Any() if (State == State.Starting) { if (StartedKeywords.Any(s => line.Contains(s))) State = State.Started; else if (StoppedKeywords.Any(s => line.Contains(s))) State = State.Stopped; } } CloseLogFile(); State = State.Stopped; } /// /// 计时器存储日志 /// /// /// private void FlushFileStreamTimerEvent(object sender, EventArgs e) { try { _logStreamWriter!.Flush(); } catch (Exception ex) { Log.Warning(ex, "写入 {Name} 日志异常", Name); } } #region FileStream private void OpenLogFile() { if (!RedirectToFile) return; _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); _logStreamWriter = new StreamWriter(_logFileStream); _flushFileStreamTimer.Elapsed += FlushFileStreamTimerEvent; _flushFileStreamTimer.Enabled = true; } private void WriteLog(string line) { if (!RedirectToFile) return; _logStreamWriter!.WriteLine(line); } private readonly object _logStreamLock = new(); private void CloseLogFile() { if (!RedirectToFile) return; lock (_logStreamLock) { if (_logFileStream == null) return; _flushFileStreamTimer.Enabled = false; _logStreamWriter?.Close(); _logFileStream?.Close(); _logStreamWriter = _logStreamWriter = null; } } #endregion #region virtual protected virtual void OnReadNewLine(string line) { } protected virtual void OnKeywordStarted() { } protected virtual void OnKeywordStopped() { Misc.Open(LogPath); } protected virtual void OnKeywordTimeout() { } #endregion } }