using Common; using Common.AppSettings; using Common.Helpers; using Common.Messaging; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using MultiTerm.Core.Types; using MultiTerm.Protocols; using MultiTerm.Protocols.Types; using System.Text; namespace MultiTerm.Core.ViewModel; public abstract partial class TerminalViewModel : ObservableObject, ITerminalViewModel { private const string defaultReceiveNewlineSeparatorAppSettingsKey = "DefaultReceiveNewlineSeparator"; private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator"; private readonly IAppSettingsProvider appSettings; private readonly IMessenger messenger; private readonly IContext context; public abstract string Title { get; } public abstract TerminalViewType ViewType { get; } public ProtocolType ProtocolType { get; set; } /// /// Holds communication data, meaning data that was sent to or received over the communication protocol. /// [ObservableProperty] private CommunicationDataViewModel? communicationData; /// /// Newline Separator Type that is selected for receival data of this Terminal. Defaults to none. /// [ObservableProperty] private NewlineSeparatorType receiveNewlineSeparatorType = NewlineSeparatorType.None; /// /// Newline Separator Type that is selected for sent out data of this Terminal. Defaults to none. /// [ObservableProperty] private NewlineSeparatorType sendNewlineSeparatorType = NewlineSeparatorType.None; private ICommunicationProtocol? communicationProtocol; public ICommunicationProtocol? CommunicationProtocol { get { return this.communicationProtocol; } set { this.communicationProtocol = value; // register communication protocol in the Communication Data View Model this.CommunicationData = new CommunicationDataViewModel(this.communicationProtocol, this.context); // initializes default newline separators, requires communicationData to be not null this.InitializeNewlineSeparatorsFromAppSettings(); // initialize both newline Separators. initializes with null if they are not available. this.CommunicationData.ConfigureNewlineSeparators(this.ReceiveNewlineSeparatorType, this.SendNewlineSeparatorType); } } private IProtocolSettingsViewModel? protocolSettings; public IProtocolSettingsViewModel? ProtocolSettings { get { return this.protocolSettings; } set { this.protocolSettings = value; // register event handler for connection request from viewmodel if(value != null) { this.protocolSettings!.ConnectRequested += OnViewModelRequestedConnect; this.protocolSettings!.DisconnectRequested += OnViewModelRequestedDisconnect; } } } public TerminalViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context) { this.appSettings = appSettings; this.messenger = messenger; this.context = context; } /// /// Sends the given string to this instances , UTF-16 encoded. /// Appends configured to the end of string. /// /// data string protected void SendToCommunicationProtocol(string data) { // guard communication protocol null if(this.CommunicationProtocol == null) { throw new NullReferenceException($"'{nameof(SendToCommunicationProtocol)}()' was called but {nameof(CommunicationProtocol)} is null."); } // inform user and quit if communication protocol is not connected if (this.CommunicationProtocol.IsConnected == false) { this.messenger.Send(new ProtocolNotConnectedMessage("Cannot send message")); return; } // add newline sequence to end of data string newlineSequence = this.SendNewlineSeparatorType switch { NewlineSeparatorType.None => String.Empty, NewlineSeparatorType.CR => "\r", NewlineSeparatorType.LF => "\n", NewlineSeparatorType.CR_LF => "\r\n", _ => throw new NotImplementedException($"'{nameof(SendToCommunicationProtocol)}()' " + $"does not implement handling for {nameof(NewlineSeparatorType)} {this.SendNewlineSeparatorType}.") }; string modifiedData = data + newlineSequence; // extract unicode byte array var byteArray = Encoding.Unicode.GetBytes(modifiedData); // send this.CommunicationProtocol.SendBytes(byteArray); } #region Connect/Disconnect private void OnViewModelRequestedConnect(object? sender, ConnectionRequestEventArgs e) { // guard uninitialized CommunicationProtocol if (CommunicationProtocol == null) { throw new Exception($"To call '{nameof(OnViewModelRequestedConnect)}()', CommunicationProtocol must not be null!"); } e.Success = this.CommunicationProtocol.Connect(e.Settings); } private void OnViewModelRequestedDisconnect(object? sender, EventArgs e) { // guard uninitialized CommunicationProtocol if (CommunicationProtocol == null) { throw new Exception($"To call '{nameof(OnViewModelRequestedConnect)}()', CommunicationProtocol must not be null!"); } this.CommunicationProtocol.Disconnect(); } #endregion #region Closing handling public event EventHandler? ClosingEvent; /// /// Method to override if any closing actions are required. /// Closing can be cancelled using the return value. /// /// true if closing is allowed. false if it shall be cancelled. protected virtual bool ClosingActions() { return true; } [RelayCommand] public void CloseRequest() { if (this.ClosingActions() == true) { ClosingEvent?.Invoke(this, EventArgs.Empty); } } #endregion #region Newline Separator handling /// /// Gets the default settings for both newline separators from the AppSettings Provider. /// Writes them to the local variables and . /// In case there are no default settings, it does not overwrite the local variables. /// In case the Communication Data is null, nothing is done. /// private void InitializeNewlineSeparatorsFromAppSettings() { // guard null objects if(this.CommunicationData == null) { return; } // get newline separators from persistent settings, if not available catch exceptions this.appSettings.TryReadSetting(defaultReceiveNewlineSeparatorAppSettingsKey, out string settingValueReceiveNLSep); try { this.ReceiveNewlineSeparatorType = EnumHelpers.ParseEnum(settingValueReceiveNLSep); } catch { } this.appSettings.TryReadSetting(defaultSendNewlineSeparatorAppSettingsKey, out string settingValueSendNLSep); try { this.SendNewlineSeparatorType = EnumHelpers.ParseEnum(settingValueSendNLSep); } catch { } } partial void OnReceiveNewlineSeparatorTypeChanged(NewlineSeparatorType value) { // triggers rearranging the received data with the new NewlineSeparatorType this.CommunicationData?.ConfigureNewlineSeparators(value, null); } partial void OnSendNewlineSeparatorTypeChanged(NewlineSeparatorType value) { // triggers rearranging the sent data with the new NewlineSeparatorType this.CommunicationData?.ConfigureNewlineSeparators(null, value); } #endregion }