using Google.Protobuf; using Newtonsoft.Json; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Tango.Core; using Tango.Core.DI; using Tango.Core.ExtensionMethods; using Tango.FSE.Procedures; using Tango.Logging; using Tango.PMR; using Tango.PMR.Common; using Tango.Settings; using Tango.StubsUtils.Shared; using Tango.Transport; using Tango.Transport.Adapters; using Tango.Transport.Transporters; namespace Tango.StubsUtils.Service { public class StubsService : ExtendedObject { private static int transporterCount = 1; private const string PIPE_NAME = "Tango_Stubs_Server"; private NamedPipeServerStream _server; private StreamReader _reader; private StreamWriter _writer; private BinaryFormatter _formatter; private bool _initialized; private List _stubsTypes; private Dictionary _stubsLookup; private Thread _communicationThread; #region Events public event EventHandler CommunicationFailed; #endregion #region Properties private ITransporter _transporter; public ITransporter Transporter { get { return _transporter; } private set { _transporter = value; RaisePropertyChangedAuto(); } } private bool _isStarted; public bool IsStarted { get { return _isStarted; } private set { _isStarted = value; RaisePropertyChangedAuto(); } } private bool _isConnected; public bool IsConnected { get { return _isConnected; } set { _isConnected = value; RaisePropertyChangedAuto(); } } private bool _enableLogs; public bool EnableLogs { get { return _enableLogs; } set { _enableLogs = value; RaisePropertyChangedAuto(); } } #endregion #region Constructors public StubsService() { } #endregion #region Start/Stop private void Initialize() { if (!_initialized) { _stubsTypes = new List(); _stubsLookup = new Dictionary(); foreach (var type in typeof(MessageFactory).Assembly.GetTypes().Where(x => x.Namespace != null && x.Namespace.Contains("Stubs") && (x.Name.Contains("Request") || x.Name.Contains("Response")) && !x.Name.Contains("Reflection")).ToList()) { _stubsTypes.Add(type); } _server = new NamedPipeServerStream(PIPE_NAME); _reader = new StreamReader(_server); _writer = new StreamWriter(_server); _formatter = new BinaryFormatter(); _communicationThread = new Thread(CommunicationMethod); _communicationThread.IsBackground = true; _communicationThread.Start(); _initialized = true; } } public Task Start() { return Task.Factory.StartNew(() => { if (!IsStarted) { try { LogManager.Log("Starting stubs service..."); IsStarted = true; Initialize(); LogManager.Log("Starting IPC service..."); } catch (Exception ex) { LogManager.Log(ex, "Error starting stubs service."); } } }); } public Task Stop() { return Task.Factory.StartNew(() => { if (IsStarted) { IsStarted = false; } }); } #endregion #region Connect/Disconnect public async Task Connect(String comPort) { if (!IsStarted) throw new InvalidOperationException("Cannot connect the transporter before the service has started."); if (!IsConnected) { Transporter = new BasicTransporter(new UsbTransportAdapter(comPort)); Transporter.FailsWithAdapter = true; Transporter.ComponentName = $"Transporter {transporterCount++}"; Transporter.UseKeepAlive = false; Transporter.StateChanged += Transporter_StateChanged; await Transporter.Connect(); IsConnected = true; } } public async Task Disconnect() { if (IsConnected) { await Transporter.Disconnect(); IsConnected = false; } } private void Transporter_StateChanged(object sender, TransportComponentState state) { if (state == TransportComponentState.Failed) { IsConnected = false; CommunicationFailed?.Invoke(this, new EventArgs()); } } #endregion #region Communication private void CommunicationMethod() { while (IsStarted) { try { _server.WaitForConnection(); var request = _reader.ReadLine(); if (EnableLogs) LogManager.Log($"Stub package received: '{request}'..."); var package = new StubPackageRequestDTO(); package.Arguments = request.Split('^'); if (package.Arguments.Length == 0) { throw new InvalidOperationException("Zero arguments provided."); } if (package.Arguments[0] == "procedure") { ProcessProcedureProject(package); try { var response = new StubPackageResponseDTO() { Status = StubPackageResponseStatus.OK, Message = $"Completed." }; _writer.WriteLine(response.ToString()); _writer.Flush(); } catch { } } else { var response = ProcessStubPackage(package); _writer.Write(response.ToString()); _writer.Flush(); } } catch (Exception ex) { LogManager.Log(ex, "Error processing stub package."); try { var response = new StubPackageResponseDTO() { Status = StubPackageResponseStatus.Error, Message = $"Error: {ex.GetFirstIfAggregate().Message}" }; _writer.WriteLine(response.ToString()); _writer.Flush(); } catch { } } finally { _server.Disconnect(); } } } #endregion #region Process Procedure public void ProcessProcedureProject(StubPackageRequestDTO package) { List arguments = package.Arguments.Skip(1).ToList(); String projectPath = arguments[0]; arguments = arguments.Skip(1).ToList(); String json = File.ReadAllText(projectPath); ProcedureProject project = ProcedureProject.FromJson(json); var mainScript = project.Scripts.FirstOrDefault(x => x.IsEntryPoint); int index = 0; foreach (var match in Regex.Matches(mainScript.Code, "public const [S|s]tring.+;").OfType()) { if (index < arguments.Count) { String defLine = match.Value; String replace = Regex.Replace(defLine, "(?<=\").*(?=\")", arguments[index]); mainScript.Code = mainScript.Code.Replace(defLine, replace); index++; } } TangoIOC.Default.ThrowOnRequestedTypeNotFound = false; StubsServiceProcedureContext context = new StubsServiceProcedureContext(project, Transporter, new StubsServiceProcedureLogger((message) => { _writer.WriteLine(message); })); var session = project.Run(context).Result; var obj = session.WaitForCompletion().Result; return; } #endregion #region Process Package private StubPackageResponseDTO ProcessStubPackage(StubPackageRequestDTO package) { StubPackageResponseDTO response = new StubPackageResponseDTO(); if (Transporter == null || Transporter.State != TransportComponentState.Connected) { throw new InvalidOperationException("Machine is disconnected"); } if (EnableLogs) LogManager.Log("Processing package..."); String stubName = package.Arguments[0]; List arguments = package.Arguments.Skip(1).ToList(); StubReflection stubReflection = GetStubReflection(stubName); MessageContainer requestContainer = new MessageContainer(); requestContainer.Token = Guid.NewGuid().ToString(); requestContainer.Type = stubReflection.MessageType; IMessage request = Activator.CreateInstance(stubReflection.Type) as IMessage; for (int i = 0; i < arguments.Count; i++) { String argument = arguments[i]; if (i >= stubReflection.Properties.Count) { throw new ArgumentException($"Argument '{argument}' index is out of range for stub '{stubReflection.Type.Name}'."); } PropertyInfo prop = stubReflection.Properties[i]; if (prop.PropertyType == typeof(UInt32)) { prop.SetValue(request, UInt32.Parse(argument)); } else if (prop.PropertyType == typeof(bool)) { prop.SetValue(request, bool.Parse(argument)); } else if (typeof(IList).IsAssignableFrom(prop.PropertyType)) { IList arr = prop.GetValue(request) as IList; foreach (var item in argument.Split(',')) { object converted = Convert.ChangeType(item, prop.PropertyType.GetGenericArguments()[0]); arr.Add(converted); } } else { object converted = Convert.ChangeType(argument, prop.PropertyType); prop.SetValue(request, converted); } } if (EnableLogs) LogManager.Log($"Request stub constructed:\n{request.ToJsonString()}"); requestContainer.Data = request.ToByteString(); var responseContainer = Transporter.SendRequest(requestContainer, new TransportRequestConfig() { ThreadingMode = TransportThreadingMode.ThreadPool }).Result; var stubResponseReflection = GetStubReflection(responseContainer.Type.ToOriginalName()); IMessage stubResponse = stubResponseReflection.Parser.ParseFrom(responseContainer.Data); String responseMessage = String.Empty; foreach (var prop in stubResponseReflection.Properties) { responseMessage += $"{prop.Name}: {prop.GetValue(stubResponse).ToStringSafe()}\n"; } if (EnableLogs) { String responseJson = stubResponse.ToJsonString(); LogManager.Log($"Stub package response:\n{responseJson}"); } response.Status = StubPackageResponseStatus.OK; response.Message = responseMessage; return response; } #endregion #region Helper Methods private StubReflection GetStubReflection(String stubName) { if (_stubsLookup.ContainsKey(stubName)) { return _stubsLookup[stubName]; } var stubReflection = StubReflection.FromStubName(stubName, _stubsTypes); _stubsLookup[stubName] = stubReflection; return stubReflection; } #endregion } }