using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Net; using System.Net.NetworkInformation; using Pluto.MSL.Api; using System.IO; using Yaulw.Assembly; namespace MSLConnect { [ComVisible(true), Guid("3F04286D-86CF-4C30-85E2-9FF7C0DA9DEB")] public interface IMSLMobileConnect { bool MobileAboutDialog_GotCalled(string SharedConnectionDataSource, string RegisteredName, string PracticeName, string DataBaseNameOrDataSetName, out string UserApiKey, out string UserPin); bool ProductSetupCompleted_PriorUserLogin(string SharedConnectionDataSource, string RegisteredName); bool PracticeName_ChangeOccured(string SharedConnectionDataSource, string NewPracticeName, string DataBaseNameOrDataSetName); bool IsServerReachableFromTheInternet(string SharedConnectionDataSource); string CheckConnectivity(string SharedConnectionDataSource, string RegisteredName, string PracticeName, string DataBaseNameOrDataSetName); } [ComVisible(true), ClassInterface(ClassInterfaceType.None)] [Guid("803E5D8F-FD6D-4CC8-9DD7-0A2322C565AE")] public class MSLMobileConnect : IMSLMobileConnect { // Hard-coded in * Internal channel always uses this port internal const int INTERNAL_CHANNEL_PORT = 1945; // Use this host to check, if we are on the McKesson Network internal const string MCK_HOST_TO_CHECK_ON_MCK_NETWORK = "ndh1fs01.mckesson.com"; #region Logging /// /// FileWriter Object /// //private Yaulw.File.LoggerQuick _log = null; /// /// Is this DLL running on McKesson Network /// private bool IsRunningOnMcKessonNetwork = false; private bool DidWeCheckForMcKessonNetwork = false; /// /// Setup the logger upon HasConnectivity Calls, this way it won't be done in the constructor /// with Medisoft stuff, you just never know /// //private void SetupLoggingIfNotSetup() //{ // if (!DidWeCheckForMcKessonNetwork) // { // IsRunningOnMcKessonNetwork = Yaulw.Net.IPHostHelper.CanResolveHost(MCK_HOST_TO_CHECK_ON_MCK_NETWORK,true); // DidWeCheckForMcKessonNetwork = true; // } // if (_log == null) // { // string path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\MSLConnect\\"; // _log = new Yaulw.File.LoggerQuick("MSLConnect", IsRunningOnMcKessonNetwork, path); // } //} #endregion #region Constructor /// /// Construct the beautiful /// public MSLMobileConnect() { } #endregion #region Connectivity /// /// Check to make sure we can communicate with the Mobile Service. /// Check the Server's ip & port for connectivity as well as that this /// computer has connectivity. /// This way we won't throw any connectivity errors, which is good. /// /// /// In order for this call to work port INTERNAL_CHANNEL_PORT outgoing must be open, /// Medisoft, Lytec unified Installer will be responsible for making sure Lytec.exe and MAPA.exe /// can open up port INTERNAL_CHANNEL_PORT by adding an exception to the windows firewall /// netsh firewall add allowedprogram \"[Path]\[Program.exe]\" \"[Name]\" ENABLE ALL /// /// private bool HasConnectivity(string SharedConnectionDataSource) { //SetupLoggingIfNotSetup(); //_log.Info("HasConnectivity called"); //_log.Debug("With the following SharedConnectionString {0}", SharedConnectionDataSource); // Retrieve the IP from the DataSource and set it in the API string host = Yaulw.Net.IPHostHelper.GetServerNameFromAConnectionString(SharedConnectionDataSource); IPAddress ip = Yaulw.Net.IPHostHelper.GetIpForHost(host); //_log.Info("Determined the following Host:{0} and IP:{1}", host, ip.ToString()); // Test if we can connect bool bCanConnect = Yaulw.Net.IPHostHelper.HasConnectivity(SharedConnectionDataSource, INTERNAL_CHANNEL_PORT); if (bCanConnect) { API.SetNetworkSettings(ip.ToString(), INTERNAL_CHANNEL_PORT); //_log.Info("Network Connectivity to Pluto.MSL Service was established on IP:{0} and Internal Port:{1}", ip, INTERNAL_CHANNEL_PORT); } else { //_log.Error("Network Connectivity to Pluto.MSL Service could not be established to IP:{0} and Internal Port:{1} HasConnection={2}", ip, INTERNAL_CHANNEL_PORT, Yaulw.Net.IPHostHelper.HasConnection()); } return bCanConnect; } #endregion #region IMSLMobileConnect /// /// Called by Lytec/Medisoft when the mobile About Dialog is called to retrieve the UserApiKey and Pin /// /// /// /// /// /// public bool MobileAboutDialog_GotCalled(string SharedConnectionDataSource, string RegisteredName, string PracticeName,string DataBaseNameOrDataSetName, out string UserApiKey, out string UserPin) { /* Ensure no nulls */ if(SharedConnectionDataSource == null) SharedConnectionDataSource = String.Empty; if(RegisteredName == null) RegisteredName = String.Empty; if(PracticeName == null) PracticeName = String.Empty; if (DataBaseNameOrDataSetName == null) DataBaseNameOrDataSetName = String.Empty; //SetupLoggingIfNotSetup(); //_log.Info("MobileAboutDialog_GotCalled Called"); //_log.Debug("With the following SharedConnectionDataSource:{0},RegisteredName:{1},PracticeName:{2},DataBaseOrDataSetName:{3}", SharedConnectionDataSource, RegisteredName, PracticeName, DataBaseNameOrDataSetName); UserApiKey = String.Empty; UserPin = String.Empty; try { if (HasConnectivity(SharedConnectionDataSource)) { bool bSuccess = API.MobileAboutDialog_GotCalled(SharedConnectionDataSource, RegisteredName, PracticeName, DataBaseNameOrDataSetName, out UserApiKey, out UserPin); //if(bSuccess) //_log.Info("MobileAboutDialog_GotCalled returns true"); //else //_log.Info("MobileAboutDialog_GotCalled returns false"); return bSuccess; } } catch (Exception e) { //_log.Error("Exception Thrown {0}", e.Message); } //_log.Info("MobileAboutDialog_GotCalled returns false"); return false; } /// /// Called by Lytec/Medisoft once product setup is complete (product is registered and /// connected to a database), but before the user logs in. This will setup the Mobile service /// on the server. /// /// /// /// public bool ProductSetupCompleted_PriorUserLogin(string SharedConnectionDataSource, string RegisteredName) { /* Ensure no nulls */ if (SharedConnectionDataSource == null) SharedConnectionDataSource = String.Empty; if (RegisteredName == null) RegisteredName = String.Empty; //SetupLoggingIfNotSetup(); //_log.Info("ProductSetupCompleted_PriorUserLogin Called"); //_log.Debug("With the following SharedConnectionDataSource:{0},RegisteredName:{1}", SharedConnectionDataSource, RegisteredName); try { if (HasConnectivity(SharedConnectionDataSource)) { bool bSuccess = API.ProductSetupCompleted_PriorUserLogin(SharedConnectionDataSource, RegisteredName); if (bSuccess) // _log.Info("ProductSetupCompleted_PriorUserLogin returns true"); //else // _log.Info("ProductSetupCompleted_PriorUserLogin returns false"); return bSuccess; } } catch (Exception e) { //_log.Error("Exception Thrown {0}", e.Message); } // _log.Info("ProductSetupCompleted_PriorUserLogin returns false"); return false; } /// /// Called by Lytec/Medisoft after a Practice Name Change occurred on their end to let /// the Mobile solution know and update it's information /// /// /// /// public bool PracticeName_ChangeOccured(string SharedConnectionDataSource, string NewPracticeName, string DataBaseNameOrDataSetName) { /* Ensure no nulls */ if (SharedConnectionDataSource == null) SharedConnectionDataSource = String.Empty; if (NewPracticeName == null) NewPracticeName = String.Empty; if (DataBaseNameOrDataSetName == null) DataBaseNameOrDataSetName = String.Empty; //SetupLoggingIfNotSetup(); //_log.Info("PracticeName_ChangeOccured Called"); //_log.Debug("With the following SharedConnectionDataSource:{0},NewPracticeName:{1},DataBaseOrDataSetName:{2}", SharedConnectionDataSource, NewPracticeName, DataBaseNameOrDataSetName); try { if (HasConnectivity(SharedConnectionDataSource)) { bool bSuccess = API.PracticeName_ChangeOccured(SharedConnectionDataSource, NewPracticeName, DataBaseNameOrDataSetName); //if (bSuccess) // _log.Info("PracticeName_ChangeOccured returns true"); //else // _log.Info("PracticeName_ChangeOccured returns false"); return bSuccess; } } catch (Exception e) { // _log.Error("Exception Thrown {0}", e.Message); } //_log.Info("PracticeName_ChangeOccured returns false"); return false; } /// /// Is the Server Reachable from the internet? if not, show error icon or give message /// /// /// public bool IsServerReachableFromTheInternet(string SharedConnectionDataSource) { /* Ensure no nulls */ if (SharedConnectionDataSource == null) SharedConnectionDataSource = String.Empty; //SetupLoggingIfNotSetup(); //_log.Info("IsServerReachableFromTheInternet Called"); //_log.Debug("With the following SharedConnectionDataSource:{0}", SharedConnectionDataSource); try { if (HasConnectivity(SharedConnectionDataSource)) { bool bSuccess = API.IsServerReachableFromTheInternet(SharedConnectionDataSource); if (bSuccess) // _log.Info("IsServerReachableFromTheInternet returns true"); //else // _log.Info("IsServerReachableFromTheInternet returns false"); return bSuccess; } } catch (Exception e) { //_log.Error("Exception Thrown {0}", e.Message); } //_log.Info("IsServerReachableFromTheInternet returns false"); return false; } /// /// Checks for CheckConnectivity /// private enum Checks { Skipped, Passed, Failed } /// /// Check Connectivity Function simplifies the above calls by summarizing issues that occur when /// connecting the Mobile Device /// /// /// /// /// public string CheckConnectivity(string SharedConnectionDataSource, string RegisteredName, string PracticeName, string DataBaseNameOrDataSetName) { /* Ensure no nulls */ if (SharedConnectionDataSource == null) SharedConnectionDataSource = String.Empty; if (RegisteredName == null) RegisteredName = String.Empty; if (PracticeName == null) PracticeName = String.Empty; if (DataBaseNameOrDataSetName == null) DataBaseNameOrDataSetName = String.Empty; // SetupLoggingIfNotSetup(); // _log.Info("CheckConnectivity Called"); //_log.Debug("With the following SharedConnectionDataSource:{0},RegisteredName:{1},PracticeName:{2},DataBaseOrDataSetName:{3}", SharedConnectionDataSource, RegisteredName, PracticeName, DataBaseNameOrDataSetName); StringBuilder sb = new StringBuilder(); sb.Append("Checking connectivity to 'Mobile Service': {0}\n"); sb.Append("Checking outgoing connectivity: {1}\n"); sb.Append("Checking incoming connectivity: {2}\n"); // 3 Checks Checks check1 = Checks.Skipped; Checks check2 = Checks.Skipped; Checks check3 = Checks.Skipped; // Check 1 if (ProductSetupCompleted_PriorUserLogin(SharedConnectionDataSource, RegisteredName)) { check1 = Checks.Passed; // Check 2 string strUserApiKey; string strApiPin; if (MobileAboutDialog_GotCalled(SharedConnectionDataSource, RegisteredName, PracticeName, DataBaseNameOrDataSetName, out strUserApiKey, out strApiPin)) { check2 = Checks.Passed; // Check 3 if (IsServerReachableFromTheInternet(SharedConnectionDataSource)) { check3 = Checks.Passed; } else { check3 = Checks.Failed; } } else { check2 = Checks.Failed; } // * Newly Added * Once Check 1 passes we know we can connect to the service, // so might as well try to update the service, if needed, to make sure they are running the latest // however let's do it here, at the end, after performing all of our checks // ~Let's not do this, let them try to connect with their device (this was/is either too clever or // too silly) after re-consideration, i think it is best to skip //UpdateServiceIfNeeded(SharedConnectionDataSource); } else { check1 = Checks.Failed; } string retVal = String.Format(sb.ToString(), check1.ToString(), check2.ToString(), check3.ToString()); //_log.Info("CheckConnectivity returns string:{0}", retVal); return retVal; } #endregion #region Pluto AddOns /// /// Try to Update the Service, if needed /// /// /// public void UpdateServiceIfNeeded(string SharedConnectionDataSource) { //SetupLoggingIfNotSetup(); //_log.Info("UpdateServiceIfNeeded"); //_log.Debug("With the following SharedConnectionDataSource:{0}", SharedConnectionDataSource); try { if (HasConnectivity(SharedConnectionDataSource)) API.UpdateServiceIfNeeded(); } catch (Exception e) { //_log.Error("Exception Thrown {0}", e.Message); } } #endregion } }