using FluentTerminal.App.Services.Exceptions; using FluentTerminal.Models; using FluentTerminal.Models.Enums; using FluentTerminal.Models.Requests; using FluentTerminal.Models.Responses; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Threading.Tasks; using Windows.Foundation.Collections; using FluentTerminal.App.Services.Utilities; namespace FluentTerminal.App.Services.Implementation { public class TrayProcessCommunicationService : ITrayProcessCommunicationService { #region Static private static string _userName; private static string _sshConfigDir; private static readonly Dictionary CommandPaths = new Dictionary(); private static readonly Dictionary CommandErrors = new Dictionary(); private static ValueSet CreateMessage(IMessage content) { return new ValueSet { [MessageKeys.Type] = content.Identifier, [MessageKeys.Content] = JsonConvert.SerializeObject(content, PreserveDictionaryKeyCaseContractResolver.SerializerSettings) }; } #endregion Static private readonly ISettingsService _settingsService; private IAppServiceConnection _appServiceConnection; private readonly Dictionary> _terminalOutputHandlers; // ReSharper disable once RedundantDefaultMemberInitializer private byte _nextTerminalId = 0; public event EventHandler TerminalExited; public TrayProcessCommunicationService(ISettingsService settingsService) { _settingsService = settingsService; _terminalOutputHandlers = new Dictionary>(); } public byte GetNextTerminalId() { return _nextTerminalId++; } public async Task GetUserNameAsync() { if (!string.IsNullOrEmpty(_userName)) { // Returning the username from cache return _userName; } var response = await GetResponseAsync(new GetUserNameRequest()).ConfigureAwait(false); if (response.Success) { _userName = response.Value; } return _userName; } public async Task SaveTextFileAsync(string path, string content) { var response = await GetResponseAsync(new SaveTextFileRequest {Path = path, Content = content}) .ConfigureAwait(false); if (!response.Success) { throw new SaveTextFileException(string.IsNullOrEmpty(response.Error) ? "Failed to save the file." : response.Error); } } public async Task ReadTextFileAsync(string path) { var response = await GetResponseAsync(new ReadTextFileRequest { Path = path }).ConfigureAwait(false); if (!response.Success) { throw new ReadTextFileException(string.IsNullOrWhiteSpace(response.Error) ? "Failed to read the file." : response.Error); } return response.Value; } public async Task GetSshConfigDirAsync() { if (!string.IsNullOrEmpty(_sshConfigDir)) { return _sshConfigDir; } var response = await GetResponseAsync(new GetSshConfigFolderRequest { IncludeContent = false }).ConfigureAwait(false); if (response.Success) { _sshConfigDir = response.Path; } return _sshConfigDir; } public async Task GetFilesFromSshConfigDirAsync() { var response = await GetResponseAsync(new GetSshConfigFolderRequest {IncludeContent = true}).ConfigureAwait(false); if (response.Success) { _sshConfigDir = response.Path; return response.Files; } return null; } public async Task CheckFileExistsAsync(string path) { var response = await GetResponseAsync(new CheckFileExistsRequest {Path = path}) .ConfigureAwait(false); return response.Success; } public Task MuteTerminalAsync(bool mute) { return _appServiceConnection.SendMessageAsync(CreateMessage(new MuteTerminalRequest {Mute = mute})); } public void UpdateSettings(ApplicationSettings settings) { _appServiceConnection.SendMessageAsync(CreateMessage(new UpdateSettingsRequest { Settings = settings })); } public async Task CreateTerminalAsync(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType) { var request = new CreateTerminalRequest { Id = id, Size = size, Profile = shellProfile, SessionType = sessionType }; Logger.Instance.Debug("Sending CreateTerminalRequest: {@request}", request); var response = await GetResponseAsync(request).ConfigureAwait(false); Logger.Instance.Debug("Received CreateTerminalResponse: {@response}", response); return response; } public Task PauseTerminalOutputAsync(byte id, bool pause) { return GetResponseAsync( new PauseTerminalOutputRequest {Id = id, Pause = pause}); } public void Initialize(IAppServiceConnection appServiceConnection) { _appServiceConnection = appServiceConnection; _appServiceConnection.MessageReceived += OnMessageReceived; } private void OnMessageReceived(object sender, IDictionary e) { var messageType = (byte)e[MessageKeys.Type]; var messageContent = e[MessageKeys.Content]; switch (messageType) { case Constants.TerminalBufferRequestIdentifier: var terminalId = (byte)e[MessageKeys.TerminalId]; if (_terminalOutputHandlers.ContainsKey(terminalId)) { _terminalOutputHandlers[terminalId].Invoke((byte[])messageContent); } else { Logger.Instance.Error("Received output for unknown terminal Id {id}", terminalId); } break; case (byte) MessageIdentifiers.TerminalExitedRequest: var request = JsonConvert.DeserializeObject((string)messageContent); Logger.Instance.Debug("Received TerminalExitedRequest: {@request}", request); TerminalExited?.Invoke(this, request.ToStatus()); break; default: Logger.Instance.Error("Received unknown message type: {messageType}", messageType); break; } } public Task ResizeTerminalAsync(byte id, TerminalSize size) { return _appServiceConnection.SendMessageAsync(CreateMessage(new ResizeTerminalRequest {TerminalId = id, NewSize = size})); } public void SubscribeForTerminalOutput(byte terminalId, Action callback) { _terminalOutputHandlers[terminalId] = callback; } public void UnsubscribeFromTerminalOutput(byte id) { if (_terminalOutputHandlers.ContainsKey(id)) { _terminalOutputHandlers.Remove(id); } } public Task UpdateToggleWindowKeyBindingsAsync() { var keyBindings = _settingsService.GetCommandKeyBindings()[nameof(Command.ToggleWindow)]; return _appServiceConnection.SendMessageAsync(CreateMessage(new SetToggleWindowKeyBindingsRequest {KeyBindings = keyBindings})); } public Task WriteAsync(byte id, byte[] data) { var message = new ValueSet { [MessageKeys.Type] = Constants.TerminalBufferRequestIdentifier, [MessageKeys.TerminalId] = id, [MessageKeys.Content] = data }; return _appServiceConnection.SendMessageAsync(message); } public Task CloseTerminalAsync(byte terminalId) { var request = new TerminalExitedRequest(terminalId, -1); Logger.Instance.Debug("Sending TerminalExitedRequest: {@request}", request); return _appServiceConnection.SendMessageAsync(CreateMessage(request)); } public async Task GetCommandPathAsync(string command) { if (string.IsNullOrWhiteSpace(command)) { throw new ArgumentException("Input value is null or empty.", nameof(command)); } command = command.Trim(); var commandLower = command.ToLowerInvariant(); if (CommandPaths.TryGetValue(commandLower, out var path)) { return path; } if (CommandErrors.TryGetValue(commandLower, out var error)) { throw new Exception(error); } var response = await GetResponseAsync(new GetCommandPathRequest {Command = command}) .ConfigureAwait(false); if (response.Success) { CommandPaths[commandLower] = response.Value; return response.Value; } CommandErrors[commandLower] = response.Error; throw new Exception(response.Error); } public Task QuitApplicationAsync() { return _appServiceConnection.SendMessageAsync(CreateMessage(new QuitApplicationRequest())); } private async Task GetResponseAsync(IMessage request) { var messageResponse = await _appServiceConnection.SendMessageAsync(CreateMessage(request)).ConfigureAwait(false); return JsonConvert.DeserializeObject((string)messageResponse[MessageKeys.Content]); } } }