using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Vinno.IUS.Common.Log; using Vinno.IUS.Common.Network.Leaf; using Vinno.IUS.Common.Network.Tcp; using Vinno.IUS.Common.Network.Transfer; using Vinno.IUS.Common.Utilities; using Vinno.vCloud.Common.FIS.AfterSales; using Vinno.vCloud.Common.FIS.FLYINSONOLogin; using Vinno.vCloud.Common.FIS.LiveVideos; using Vinno.vCloud.Common.FIS.Remedicals; using Vinno.vCloud.Common.Storage; using Vinno.vCloud.FIS.CrossPlatform.Common; using Vinno.vCloud.FIS.CrossPlatform.Common.Enum; using Vinno.vCloud.FIS.CrossPlatform.Common.LiveVideo; using Vinno.vCloud.Protocol.Infrastructures; using Vinno.vCloud.Protocol.Messages; using Vinno.vCloud.Protocol.Messages.Client; using Vinno.vCloud.Protocol.Messages.Client.AfterSales; using Vinno.vCloud.Protocol.Messages.Client.AssignTermianl; using Vinno.vCloud.Protocol.Messages.Client.AssignTerminal; using Vinno.vCloud.Protocol.Messages.Client.Live; using Vinno.vCloud.Protocol.Messages.Client.Storage; using Vinno.vCloud.Protocol.Messages.Login; using Vinno.vCloud.Protocol.Messages.Terminal; using Vinno.vCloud.Protocol.Messages.Terminal.Login; namespace Vinno.vCloud.Common.FIS { internal class vCloudTerminal : IvCloudTerminal { private readonly Dictionary<TerminalFeatureType, object> _features = new Dictionary<TerminalFeatureType, object>(); private readonly ConnectionInfo _connectionInfo; private readonly string _deviceName; private readonly string _password; private readonly bool _isUserDefined; private readonly LoginSource _loginSource; private readonly string _url; private readonly int _usScreenWidth; private readonly int _usScreenHeight; private readonly int _connectionCheckCycle; private readonly int _heartRateCycle; private readonly bool _isUseOldTerminalSdk; private string _clientId; private TerminalStatus _status; private ClientLeaf _leaf; private ConnectionChecker _connectionChecker; private HeartRateKeeper _heartRateKeeper; private bool _disposed; private string _deviceCode; private string _sessionId; private int _reconnectCounter; /// <summary> /// Gets the global remedical working folder. /// </summary> internal static string WorkingFolder { get; private set; } /// <summary> /// Raised when the status is changed. /// </summary> public event EventHandler StatusChanged; /// <summary> /// Gets the unique id. /// </summary> public string UniqueId { get; private set; } /// <inheritdoc /> /// <summary> /// Gets the status of the Terminal /// </summary> /// <remarks> /// </remarks> public TerminalStatus Status { get => _status; private set { if (_status != value) { _status = value; CommonParameter.Instance.DeviceStatus = (EnumDeviceStatus)_status; OnStatusChanged(); } } } public string DeviceCode => _deviceCode; public string Url => _url; public vCloudTerminal(ConnectionInfo connectionInfo, bool isUsedOldSdk = false) { _isUseOldTerminalSdk = isUsedOldSdk; _usScreenHeight = connectionInfo.USScreenHeight; _usScreenWidth = connectionInfo.USScreenWidth; _connectionInfo = connectionInfo; _deviceName = connectionInfo.Account.Name; _password = connectionInfo.Account.Password; _isUserDefined = connectionInfo.Account.IsUserDefined; _url = connectionInfo.ServerUrl; _loginSource = CommonParameter.Instance.IsSonopost ? LoginSource.WindowSonopost : LoginSource.UltrasoundMachine; _clientId = IdHelper.Generate<string>(); //Set the default value 10 seconds. _connectionCheckCycle = 10; //Set the default value 3 minutes. _heartRateCycle = 3; _status = TerminalStatus.Offline; switch (CrossPlatformHelper.Instance.Platform) { case EnumPlatform.Windows: if (CommonParameter.Instance.IsSonopost) { WorkingFolder = connectionInfo.FISFolder; } else { if (Directory.Exists("D:")) { WorkingFolder = @"D:\FISRepository"; } else { WorkingFolder = connectionInfo.FISFolder; } } break; case EnumPlatform.Linux: DirectoryHelper.CreateDirectory(@"/userdata"); if (Directory.Exists(@"/userdata")) { WorkingFolder = @"/userdata/FISRepository"; } else { WorkingFolder = connectionInfo.FISFolder; } break; default: WorkingFolder = connectionInfo.FISFolder; break; } DirectoryHelper.CreateDirectory(WorkingFolder); if (!_isUseOldTerminalSdk) { CreateLeaf(); CreateConnectionChecker(); if (!connectionInfo.IsOnlyUsedToRegister) { CPCombineSmartPushConfiguration.Instance.Initialize(connectionInfo.FISFolder); UploadHelper.GetAuthorization = GetAuthorization; } } } /// <inheritdoc /> /// <summary> /// Get the feature instance by feature enum. /// </summary> /// <typeparam name="T">The interface of the feature</typeparam> /// <param name="featureType">The feature type</param> /// <returns>The instance of the feature</returns> /// <remarks> /// If the feature doesn't exist, this method will return null. /// </remarks> public T GetFeature<T>(TerminalFeatureType featureType) where T : IFeature { if (_features.ContainsKey(featureType)) { var feature = _features[featureType]; if (feature is T t) { return t; } } return default(T); } /// <summary> /// Connect to server. /// </summary> internal void Connect(string uniqueId = "", string terminalId = "") { try { if (!_isUseOldTerminalSdk) { var loginResult = NewLogin(); if (!_isUserDefined && loginResult.Status == LoginStatus.WrongAccount) { if (string.IsNullOrEmpty(_connectionInfo.Organization)) { Status = TerminalStatus.OrganizationMissing; return; } if (RegisterAccount()) { NewLogin(); } else { Status = TerminalStatus.LoginFailed; } } } else { UniqueId = uniqueId; _deviceCode = terminalId; ReleaseFeatures(); UpdateFeatures(_connectionInfo.EnabledFeatures); Status = TerminalStatus.Logon; } } catch (Exception e) { Logger.WriteLineError($"Terminal {_deviceName} login url:{_url} failed {e}"); Status = TerminalStatus.LoginFailed; } } public bool Register() { return RegisterAccount(); } /// <summary> /// Register Account /// </summary> /// <returns></returns> private bool RegisterAccount() { using (var request = MessagePool.GetMessage<AddTerminalAccountRequest>()) { request.TerminalName = _deviceName; request.TerminalModel = _connectionInfo.DeviceMode; request.TerminalPassword = _password; request.OrganizationName = _connectionInfo.Organization; request.IsLive = true; var addTerminalAccountServerResult = _leaf?.Send(request); var addTerminalAccountResult = ResultMessage.Convert(addTerminalAccountServerResult); if (addTerminalAccountResult == CCR.OK) { Logger.WriteLineInfo($"VCloudTerminal Register Account Success,OrganizaitonName is {_connectionInfo?.Organization}"); return true; } else { Logger.WriteLineError($"VCloudTerminal Register Account Fail,the result is not CCR.OK,OrganizaitonName is {_connectionInfo?.Organization}"); return false; } } } public void Disconnect() { if (!_isUseOldTerminalSdk) { var status = TerminalStatus.Logoff; try { SendStopPushStreamResult(); using (var request = MessagePool.GetMessage<NewTerminalLogoffRequest>()) { request.Name = _deviceName; request.Password = _password; request.Source = _loginSource; request.IsUserDefinedAccount = _isUserDefined; var result = _leaf?.Send(request); var logoffResult = LogoffResult.Convert(result); switch (logoffResult.Status) { case LogoffStatus.Success: status = TerminalStatus.Logoff; break; case LogoffStatus.WrongAccount: status = TerminalStatus.WrongAccount; break; case LogoffStatus.WrongPassword: status = TerminalStatus.WrongPassword; break; } } } catch (Exception e) { Logger.WriteLineError($"Disconnect terminal {_deviceName} error {e}"); } finally { Release(); FLYINSONOUserManager.Instance.Clear(); Status = status; } } else { try { Release(); FLYINSONOUserManager.Instance.Clear(); Status = TerminalStatus.Logoff; } catch (Exception e) { Logger.WriteLineError($"Disconnect terminal with old Sdk {_deviceName} error {e}"); } } } public IEnumerable<string> GetOrganizations(string keyWord) { using (var request = MessagePool.GetMessage<FindOrganizationByKeyWordRequest>()) { request.KeyWord = keyWord; var result = _leaf?.Send(request); var findOrganizationInfosResult = FindOrganizationInfosResult.Convert(result); if (findOrganizationInfosResult?.OrganizationInfos != null) { return findOrganizationInfosResult.OrganizationInfos.Select(i => i.Name); } } return new List<string>(); } public bool IsTerminalOrganizationMissging() { return Status == TerminalStatus.OrganizationMissing; } public bool UpdatelTerminalOrganizationName(string organizationName, bool createNewOrganization) { using (var request = MessagePool.GetMessage<UpdateTerminalOrganizationNameRequest>()) { request.TerminalName = _deviceName; request.OrganizationName = organizationName; request.IsCreateNewOrganization = createNewOrganization; var result = _leaf?.Send(request); var updateTerminalResult = ResultMessage.Convert(result); if (updateTerminalResult == CCR.OK) { Logger.WriteLineInfo($"vCloudTerminal UpdatelTerminalOrganizationName Success,TerminalName:{_deviceName},OrganizationName:{organizationName},CreateNewOrganization:{createNewOrganization}"); return true; } else { Logger.WriteLineError($"vCloudTerminal UpdatelTerminalOrganizationName Failed,TerminalName:{_deviceName},OrganizationName:{organizationName},CreateNewOrganization:{createNewOrganization}"); return false; //update organization name fail } } } public string GetOrganizationName() { var organizationName = string.Empty; try { using (var request = MessagePool.GetMessage<FindTerminalByNameRequest>()) { request.TerminalName = _deviceName; var result = _leaf?.Send(request); if (result != null) { var findTerminalByNameResult = FindTerminalByNameResult.Convert(result); if (findTerminalByNameResult != null) { organizationName = findTerminalByNameResult.Termianl?.OrganizationName; } } } } catch (Exception ex) { Logger.WriteLineError($"Vcloud terminal get organization name by terminal failed:{ex}"); } return organizationName; } /// <summary> /// Update enabled feature types. /// </summary> /// <param name="enabledFeatureTypes">The enabled feature types.</param> public void UpdateFeatures(IEnumerable<TerminalFeatureType> enabledFeatureTypes) { CommonParameter.Instance.IsFeatureReleased = false; if (!_isUseOldTerminalSdk) { var removedFeatures = _features.Keys.Where(f => !enabledFeatureTypes.Contains(f)).ToList(); foreach (var feature in removedFeatures) { var disposable = _features[feature] as IDisposable; disposable?.Dispose(); _features.Remove(feature); } foreach (var enabledFeature in enabledFeatureTypes) { if (!_features.ContainsKey(enabledFeature)) { var feature = GetFeature(enabledFeature); _features.Add(enabledFeature, feature); } } } else { var removedFeatures = _features.Keys.Where(f => !enabledFeatureTypes.Contains(f)).ToList(); foreach (var feature in removedFeatures) { if (feature == TerminalFeatureType.Consultation) { var disposable = _features[feature] as IDisposable; disposable?.Dispose(); _features.Remove(feature); } } foreach (var enabledFeature in enabledFeatureTypes) { if (enabledFeature == TerminalFeatureType.Consultation) { if (!_features.ContainsKey(enabledFeature)) { var feature = GetFeature(enabledFeature); _features.Add(enabledFeature, feature); } } } } } public void Dispose() { if (!_disposed) { Release(); _disposed = true; } GC.SuppressFinalize(this); } private void SendStopPushStreamResult() { if (!string.IsNullOrWhiteSpace(_deviceCode)) { using (var request = MessagePool.GetMessage<StopPushStreamResult>()) { request.TerminalId = _deviceCode; var result = _leaf?.Send(request); var ok = ResultMessage.Convert(result); if (ok == null || ok.ResultCode != 0) throw new InvalidOperationException("Send reply to server error."); } } } private string GetAuthorization(string fileName) { try { var authorization = string.Empty; var getAuthorizationRequest = new GetAuthorizationRequest(); getAuthorizationRequest.FileName = fileName; var storageResult = _leaf?.Send(getAuthorizationRequest); var getAuthorizationResult = GetAuthorizationResult.Convert(storageResult); if (getAuthorizationResult != null) { authorization = getAuthorizationResult.Authorization; } return authorization; } catch (Exception e) { throw new Exception($"get authentication form server error:{e}"); } } private NewTerminalLoginResult4 NewLogin() { using (var request = MessagePool.GetMessage<NewTerminalLoginRequest4>()) { request.Name = _deviceName; request.Password = _password; request.SupportLive = true; request.ClientId = _clientId; request.IsUserDefinedAccount = _isUserDefined; request.Source = _loginSource; request.TerminalVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); var result = _leaf?.Send(request); var loginResult = NewTerminalLoginResult4.Convert(result); if (loginResult != null) { UniqueId = loginResult.TerminalUniqueId; ////TODO(FISJuly) //StorageType = loginResult.StorageType; HandleLoginResult(loginResult.Status, loginResult.AccountId, loginResult.AccountSessionId); } else { throw new InvalidOperationException("Login failed."); } return loginResult; } } private void HandleLoginResult(LoginStatus loginStatus, string accountId, string accountSessionId) { if (loginStatus == LoginStatus.Success) { Status = TerminalStatus.Logoning; _sessionId = accountSessionId; _deviceCode = accountId; CreateHeartRateKeeper(); ReleaseFeatures(); UpdateFeatures(_connectionInfo.EnabledFeatures); UpdateTerminalInfo(); _reconnectCounter = 0; Status = TerminalStatus.Logon; Logger.WriteLineInfo($"{_deviceName} Login url:{_url} successed, UniqueId:{UniqueId}"); } else if (loginStatus == LoginStatus.WrongPassword) { Status = TerminalStatus.WrongPassword; } else if (_isUserDefined && loginStatus == LoginStatus.WrongAccount) { Status = TerminalStatus.WrongAccount; } else { Status = TerminalStatus.LoginFailed; Logger.WriteLineWarn($"Login to server error:{loginStatus}"); } } private void UpdateTerminalInfo() { try { Logger.WriteLineInfo($"begin ChangeTerminalDeviceInfoRequest SerialNumber:{_connectionInfo.SerialNumber} ,USMode:{_connectionInfo.DeviceMode}, USCPU:{_connectionInfo.USCPU}, USOS:{_connectionInfo.USOS}, USSoftware:{_connectionInfo.SoftwareVersion}, "); if (CommonParameter.Instance.IsSonopost) { using (var request = MessagePool.GetMessage<ChangeTerminalDeviceInfoRequest1>()) { request.TerminalId = _deviceCode; request.SerialNumber = _connectionInfo.SerialNumber; request.TerminalMode = _connectionInfo.DeviceMode; request.TerminalCPU = _connectionInfo.USCPU; request.TerminalOS = ""; request.SoftwareVersion = _connectionInfo.SoftwareVersion; request.IsSonopost = true; request.Capability = _deviceName; request.SonopostVerson = SonopostVersonEnum.Sonopost2; var result = _leaf?.Send(request); var changeTerminalDeviceInfoResult = ChangeTerminalDeviceInfoResult.Convert(result); if (changeTerminalDeviceInfoResult != null && changeTerminalDeviceInfoResult.IsSuccess) { Logger.WriteLineInfo($"ChangeTerminalDeviceInfoRequest success"); } else { Logger.WriteLineError($"ChangeTerminalDeviceInfoRequest failed: {changeTerminalDeviceInfoResult?.Msg}"); } } } else { using (var request = MessagePool.GetMessage<ChangeTerminalDeviceInfoRequest>()) { request.TerminalId = _deviceCode; request.SerialNumber = _connectionInfo.SerialNumber; request.TerminalMode = _connectionInfo.DeviceMode; request.TerminalCPU = _connectionInfo.USCPU; request.TerminalOS = _connectionInfo.USOS; request.SoftwareVersion = _connectionInfo.SoftwareVersion; request.IsSonopost = false; request.Capability = _deviceName; var result = _leaf?.Send(request); var changeTerminalDeviceInfoResult = ChangeTerminalDeviceInfoResult.Convert(result); if (changeTerminalDeviceInfoResult != null && changeTerminalDeviceInfoResult.IsSuccess) { Logger.WriteLineInfo($"ChangeTerminalDeviceInfoRequest success"); } else { Logger.WriteLineError($"ChangeTerminalDeviceInfoRequest failed: {changeTerminalDeviceInfoResult?.Msg}"); } } } } catch (Exception ex) { Logger.WriteLineError($"ChangeTerminalDeviceInfoRequest error: {ex}"); } } private void OnStatusChanged() { StatusChanged?.Invoke(this, EventArgs.Empty); } ////TODO(July):支里是否是超声机已在其他机器登录的原因,代码得看看 private void CreateLeaf() { _leaf = new ClientLeaf(new LeafIdContext(), LeafMode.Dual, new TcpCreator(_url), "Terminal connection:"); _leaf.MessageArrived += OnMessageArrived; if (!_leaf.Online) { _leaf.Close(); Status = TerminalStatus.Offline; } else { _leaf.RegisterSetAccountDataMessageFunc(SetAccountDataToMessage); Status = TerminalStatus.Online; } } private void DisposeLeaf() { try { if (_leaf != null) { _leaf.MessageArrived -= OnMessageArrived; _leaf.Close(); _leaf = null; } } catch (Exception ex) { Logger.WriteLineError($"VCloudTerminal DisposeLeaf Error:{ex}"); } } private void CreateHeartRateKeeper() { if (_heartRateKeeper == null) { _heartRateKeeper = new HeartRateKeeper(_sessionId, _leaf, _heartRateCycle); _heartRateKeeper.Start(); } } private void DisposeHeartRateKeeper() { try { if (_heartRateKeeper != null) { _heartRateKeeper.Stop(); _heartRateKeeper = null; } } catch (Exception ex) { Logger.WriteLineError($"VCloudTerminal DisposeHeartRateKeeper Error:{ex}"); } } private void CreateConnectionChecker() { if (_connectionChecker == null) { _connectionChecker = new ConnectionChecker(_leaf, _connectionCheckCycle); _connectionChecker.Offlined += ConnectionCheckerOnOfflined; _connectionChecker.Start(); } } private void DisposeConnectionChecker() { try { if (_connectionChecker != null) { _connectionChecker.Offlined -= ConnectionCheckerOnOfflined; _connectionChecker.Stop(); _connectionChecker = null; } } catch (Exception ex) { Logger.WriteLineError($"VCloudTerminal DisposeConnectionChecker Error:{ex}"); } } private void OnMessageArrived(object sender, Message e) { var connectionReplacedNotification = ConnectionReplacedNotification.Convert(e); if (connectionReplacedNotification != null) { HandleConnectionReplacedNotification(connectionReplacedNotification); } var connectNotification = ConnectNotification.Convert(e); if (connectNotification != null) { HandleConnectNotification(connectNotification); } } private void HandleConnectionReplacedNotification(ConnectionReplacedNotification connectionReplacedNotification) { Dispose(); Status = TerminalStatus.ReplacedNotification; } private void HandleConnectNotification(ConnectNotification connectNotification) { try { var afterSales = GetFeature<IAfterSales>(TerminalFeatureType.AfterSales); if (afterSales != null) { afterSales.UpdateAfterSalesInfo(connectNotification.UserId, connectNotification.UserName); using (var processReslut = MessagePool.GetMessage<ProcessReslut>()) { processReslut.TargetId = connectNotification.UserId; processReslut.RunTaskType = TaskType.Connect; processReslut.RunTaskStatus = TaskStatus.Finished; processReslut.Detail = DetailType.Connected; processReslut.Percent = 1; processReslut.Source = ProcessSource.FromTerminal; processReslut.FileToken = string.Empty; _leaf?.Send(processReslut); } } else { using (var processReslut = MessagePool.GetMessage<ProcessReslut>()) { processReslut.TargetId = connectNotification.UserId; processReslut.RunTaskType = TaskType.Connect; processReslut.RunTaskStatus = TaskStatus.Finished; processReslut.Detail = DetailType.ConnectedDeny; processReslut.Percent = 0; processReslut.Source = ProcessSource.FromTerminal; processReslut.FileToken = string.Empty; _leaf?.Send(processReslut); } } } catch (Exception ex) { Logger.WriteLineError($"HandleConnectNotification error:{ex}"); } } private Message SetAccountDataToMessage(Message message) { if (message is ClientRequestMessage clientRequestMessage) { clientRequestMessage.AccountData = GetAccountDataMessage() as ClientAccountMessage; return clientRequestMessage; } return message; } private Message GetAccountDataMessage() { var accountData = MessagePool.GetMessage<ClientAccountMessage>(); accountData.AccountId = _deviceCode ?? string.Empty; accountData.AccountName = _deviceName ?? string.Empty; accountData.SessionId = _sessionId ?? string.Empty; accountData.Source = _loginSource; return accountData; } private void ConnectionCheckerOnOfflined(object sender, EventArgs e) { Release(); Status = TerminalStatus.Offline; //No need to reconnect after instance release if (_disposed) return; //Reconnect if (_reconnectCounter < 5) { if (!string.IsNullOrEmpty(_deviceName) && !string.IsNullOrEmpty(_password)) { CreateLeaf(); CreateConnectionChecker(); if (Status == TerminalStatus.Online) { Status = TerminalStatus.Reconnecting; Connect(); _reconnectCounter++; } } } else { Dispose(); } } private void Release() { _deviceCode = string.Empty; DisposeConnectionChecker(); DisposeHeartRateKeeper(); ReleaseFeatures(); DisposeLeaf(); } private void ReleaseFeatures() { CommonParameter.Instance.IsFeatureReleased = true; foreach (var type in _features.Keys.ToList()) { var feature = _features[type]; var disposable = feature as IDisposable; disposable?.Dispose(); _features.Remove(type); } } private IFeature GetFeature(TerminalFeatureType terminalFeatureType) { switch (terminalFeatureType) { case TerminalFeatureType.Remedical: return new Remedical(_deviceCode, _leaf); case TerminalFeatureType.LiveVideo: if (CommonParameter.Instance.IsSonopost) { return new LiveVideoForSonopost(_deviceCode, _deviceName, _leaf); } else { return new LiveVideo(_deviceCode, _deviceName, _leaf, _usScreenWidth, _usScreenHeight); } case TerminalFeatureType.AfterSales: return new AfterSales.AfterSales(_deviceCode, _leaf); case TerminalFeatureType.Upgrade: return new Upgraders.Upgraders(_leaf); case TerminalFeatureType.Teaching: return new Teaching.Teaching(_deviceCode, _leaf); case TerminalFeatureType.Consultation: return new Consultation.Consultation(_deviceCode, _deviceName, _url, LoginSource.PersonalComputer, UniqueId); case TerminalFeatureType.Log: return new Log.Log(_deviceCode, _leaf); default: return null; } } } }