using System; using System.IO; using System.Threading; using System.Windows.Forms; using mRemoteNG.App; using mRemoteNG.App.Info; using mRemoteNG.Config; using mRemoteNG.Config.Connections; using mRemoteNG.Config.Connections.Multiuser; using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.Putty; using mRemoteNG.Config.Serializers.MsSql; using mRemoteNG.Connection.Protocol; using mRemoteNG.Messages; using mRemoteNG.Security; using mRemoteNG.Tools; using mRemoteNG.Tree; using mRemoteNG.Tree.Root; using mRemoteNG.UI; namespace mRemoteNG.Connection { public class ConnectionsService { private static readonly object SaveLock = new object(); private readonly PuttySessionsManager _puttySessionsManager; private readonly IDataProvider _localConnectionPropertiesDataProvider; private readonly LocalConnectionPropertiesXmlSerializer _localConnectionPropertiesSerializer; private bool _batchingSaves = false; private bool _saveRequested = false; private bool _saveAsyncRequested = false; public bool IsConnectionsFileLoaded { get; set; } public bool UsingDatabase { get; private set; } public string ConnectionFileName { get; private set; } public RemoteConnectionsSyncronizer RemoteConnectionsSyncronizer { get; set; } public DateTime LastSqlUpdate { get; set; } public ConnectionTreeModel ConnectionTreeModel { get; private set; } public ConnectionsService(PuttySessionsManager puttySessionsManager) { if (puttySessionsManager == null) throw new ArgumentNullException(nameof(puttySessionsManager)); _puttySessionsManager = puttySessionsManager; var path = SettingsFileInfo.SettingsPath; _localConnectionPropertiesDataProvider = new FileDataProvider(Path.Combine(path, "LocalConnectionProperties.xml")); _localConnectionPropertiesSerializer = new LocalConnectionPropertiesXmlSerializer(); } public void NewConnectionsFile(string filename) { try { filename.ThrowIfNullOrEmpty(nameof(filename)); var newConnectionsModel = new ConnectionTreeModel(); newConnectionsModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection)); SaveConnections(newConnectionsModel, false, new SaveFilter(), filename, true); LoadConnections(false, false, filename); } catch (Exception ex) { Runtime.MessageCollector.AddExceptionMessage(Language.strCouldNotCreateNewConnectionsFile, ex); } } public ConnectionInfo CreateQuickConnect(string connectionString, ProtocolType protocol) { try { var uri = new Uri("dummyscheme" + Uri.SchemeDelimiter + connectionString); if (string.IsNullOrEmpty(uri.Host)) return null; var newConnectionInfo = new ConnectionInfo(); newConnectionInfo.CopyFrom(DefaultConnectionInfo.Instance); newConnectionInfo.Name = Settings.Default.IdentifyQuickConnectTabs ? string.Format(Language.strQuick, uri.Host) : uri.Host; newConnectionInfo.Protocol = protocol; newConnectionInfo.Hostname = uri.Host; if (uri.Port == -1) { newConnectionInfo.SetDefaultPort(); } else { newConnectionInfo.Port = uri.Port; } if (string.IsNullOrEmpty(newConnectionInfo.Panel)) newConnectionInfo.Panel = Language.strGeneral; newConnectionInfo.IsQuickConnect = true; return newConnectionInfo; } catch (Exception ex) { Runtime.MessageCollector.AddExceptionMessage(Language.strQuickConnectFailed, ex); return null; } } /// /// Load connections from a source. is ignored if /// is true. /// /// /// /// public void LoadConnections(bool useDatabase, bool import, string connectionFileName) { var oldConnectionTreeModel = ConnectionTreeModel; var oldIsUsingDatabaseValue = UsingDatabase; var connectionLoader = useDatabase ? (IConnectionsLoader)new SqlConnectionsLoader(_localConnectionPropertiesSerializer, _localConnectionPropertiesDataProvider) : new XmlConnectionsLoader(connectionFileName); var newConnectionTreeModel = connectionLoader.Load(); if (useDatabase) LastSqlUpdate = DateTime.Now; if (newConnectionTreeModel == null) { DialogFactory.ShowLoadConnectionsFailedDialog(connectionFileName, "Decrypting connection file failed", IsConnectionsFileLoaded); return; } IsConnectionsFileLoaded = true; ConnectionFileName = connectionFileName; UsingDatabase = useDatabase; if (!import) { _puttySessionsManager.AddSessions(); newConnectionTreeModel.RootNodes.AddRange(_puttySessionsManager.RootPuttySessionsNodes); } ConnectionTreeModel = newConnectionTreeModel; UpdateCustomConsPathSetting(connectionFileName); RaiseConnectionsLoadedEvent(oldConnectionTreeModel, newConnectionTreeModel, oldIsUsingDatabaseValue, useDatabase, connectionFileName); Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Connections loaded using {connectionLoader.GetType().Name}"); } /// /// When turned on, calls to or /// will not immediately execute. /// Instead, they will be deferred until /// is called. /// public void BeginBatchingSaves() { _batchingSaves = true; } /// /// Immediately executes a single or /// if one has been requested /// since calling . /// public void EndBatchingSaves() { _batchingSaves = false; if (_saveAsyncRequested) SaveConnectionsAsync(); else if(_saveRequested) SaveConnections(); } /// /// All calls to or /// will be deferred until the returned is disposed. /// Once disposed, this will immediately executes a single /// or if one has been requested. /// Place this call in a 'using' block to represent a batched saving context. /// /// public DisposableAction BatchedSavingContext() { return new DisposableAction(BeginBatchingSaves, EndBatchingSaves); } /// /// Saves the currently loaded with /// no . /// public void SaveConnections() { SaveConnections(ConnectionTreeModel, UsingDatabase, new SaveFilter(), ConnectionFileName); } /// /// Saves the given . /// If is true, is ignored /// /// /// /// /// /// Bypasses safety checks that prevent saving if a connection file isn't loaded. /// /// Optional. The name of the property that triggered /// this save. /// public void SaveConnections( ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName, bool forceSave = false, string propertyNameTrigger = "") { if (connectionTreeModel == null) return; if (!forceSave && !IsConnectionsFileLoaded) return; if (_batchingSaves) { _saveRequested = true; return; } try { Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, "Saving connections..."); RemoteConnectionsSyncronizer?.Disable(); var previouslyUsingDatabase = UsingDatabase; var saver = useDatabase ? (ISaver)new SqlConnectionsSaver(saveFilter, _localConnectionPropertiesSerializer, _localConnectionPropertiesDataProvider) : new XmlConnectionsSaver(connectionFileName, saveFilter); saver.Save(connectionTreeModel, propertyNameTrigger); if (UsingDatabase) LastSqlUpdate = DateTime.Now; UsingDatabase = useDatabase; ConnectionFileName = connectionFileName; RaiseConnectionsSavedEvent(connectionTreeModel, previouslyUsingDatabase, UsingDatabase, connectionFileName); Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, "Successfully saved connections"); } catch (Exception ex) { Runtime.MessageCollector?.AddExceptionMessage(string.Format(Language.strConnectionsFileCouldNotSaveAs, connectionFileName), ex, logOnly:false); } finally { RemoteConnectionsSyncronizer?.Enable(); } } /// /// Save the currently loaded connections asynchronously /// /// /// Optional. The name of the property that triggered /// this save. /// public void SaveConnectionsAsync(string propertyNameTrigger = "") { if (_batchingSaves) { _saveAsyncRequested = true; return; } var t = new Thread(() => { lock (SaveLock) { SaveConnections( ConnectionTreeModel, UsingDatabase, new SaveFilter(), ConnectionFileName, propertyNameTrigger: propertyNameTrigger); } }); t.SetApartmentState(ApartmentState.STA); t.Start(); } public string GetStartupConnectionFileName() { return Settings.Default.LoadConsFromCustomLocation == false ? GetDefaultStartupConnectionFileName() : Settings.Default.CustomConsPath; } public string GetDefaultStartupConnectionFileName() { return Runtime.IsPortableEdition ? GetDefaultStartupConnectionFileNamePortableEdition() : GetDefaultStartupConnectionFileNameNormalEdition(); } private void UpdateCustomConsPathSetting(string filename) { if (filename == GetDefaultStartupConnectionFileName()) { Settings.Default.LoadConsFromCustomLocation = false; } else { Settings.Default.LoadConsFromCustomLocation = true; Settings.Default.CustomConsPath = filename; } } private string GetDefaultStartupConnectionFileNameNormalEdition() { var appDataPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Application.ProductName, ConnectionsFileInfo.DefaultConnectionsFile); return File.Exists(appDataPath) ? appDataPath : GetDefaultStartupConnectionFileNamePortableEdition(); } private string GetDefaultStartupConnectionFileNamePortableEdition() { return Path.Combine(ConnectionsFileInfo.DefaultConnectionsPath, ConnectionsFileInfo.DefaultConnectionsFile); } #region Events public event EventHandler ConnectionsLoaded; public event EventHandler ConnectionsSaved; private void RaiseConnectionsLoadedEvent(Optional previousTreeModel, ConnectionTreeModel newTreeModel, bool previousSourceWasDatabase, bool newSourceIsDatabase, string newSourcePath) { ConnectionsLoaded?.Invoke(this, new ConnectionsLoadedEventArgs( previousTreeModel, newTreeModel, previousSourceWasDatabase, newSourceIsDatabase, newSourcePath)); } private void RaiseConnectionsSavedEvent(ConnectionTreeModel modelThatWasSaved, bool previouslyUsingDatabase, bool usingDatabase, string connectionFileName) { ConnectionsSaved?.Invoke(this, new ConnectionsSavedEventArgs(modelThatWasSaved, previouslyUsingDatabase, usingDatabase, connectionFileName)); } #endregion } }