From ccaa70c1a41af1638802dd0af7ee8b683b1e71ac Mon Sep 17 00:00:00 2001 From: David Sparer Date: Fri, 19 Aug 2016 15:28:12 -0600 Subject: [PATCH] XmlConnectionDeserializer now creates a connectiontreemodel which is turned into a treeview by ConnectionTreeViewBuilder --- .../XmlConnectionsDeserializerTests.cs | 101 +++++++++++++ .../Properties/Resources.Designer.cs | 74 ++++++++++ mRemoteNGTests/Properties/Resources.resx | 124 ++++++++++++++++ mRemoteNGTests/Resources/TestConfCons.xml | 27 ++++ mRemoteNGTests/mRemoteNGTests.csproj | 13 ++ .../Config/Connections/ConnectionsLoader.cs | 16 +- .../Connections/XmlConnectionsDeserializer.cs | 138 ++++++------------ mRemoteV1/UI/ConnectionTreeViewBuilder.cs | 1 + mRemoteV1/UI/Forms/frmMain.cs | 11 +- 9 files changed, 405 insertions(+), 100 deletions(-) create mode 100644 mRemoteNGTests/Config/Connections/XmlConnectionsDeserializerTests.cs create mode 100644 mRemoteNGTests/Properties/Resources.Designer.cs create mode 100644 mRemoteNGTests/Properties/Resources.resx create mode 100644 mRemoteNGTests/Resources/TestConfCons.xml diff --git a/mRemoteNGTests/Config/Connections/XmlConnectionsDeserializerTests.cs b/mRemoteNGTests/Config/Connections/XmlConnectionsDeserializerTests.cs new file mode 100644 index 000000000..07d630e21 --- /dev/null +++ b/mRemoteNGTests/Config/Connections/XmlConnectionsDeserializerTests.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using mRemoteNG.Config.Connections; +using mRemoteNG.Connection; +using mRemoteNG.Container; +using mRemoteNG.Tree; +using mRemoteNGTests.Properties; +using NUnit.Framework; + + +namespace mRemoteNGTests.Config.Connections +{ + public class XmlConnectionsDeserializerTests + { + private XmlConnectionsDeserializer _xmlConnectionsDeserializer; + private ConnectionTreeModel _connectionTreeModel; + + [SetUp] + public void Setup() + { + _xmlConnectionsDeserializer = new XmlConnectionsDeserializer(Resources.TestConfCons); + _connectionTreeModel = _xmlConnectionsDeserializer.Deserialize(); + } + + [TearDown] + public void Teardown() + { + _xmlConnectionsDeserializer = null; + _connectionTreeModel = null; + } + + [Test] + public void DeserializingCreatesRootNode() + { + Assert.That(_connectionTreeModel.RootNodes, Is.Not.Empty); + } + + [Test] + public void RootNodeHasThreeChildren() + { + var connectionRoot = _connectionTreeModel.RootNodes[0]; + Assert.That(connectionRoot.Children.Count, Is.EqualTo(3)); + } + + [Test] + public void RootContainsFolder1() + { + var connectionRoot = _connectionTreeModel.RootNodes[0]; + Assert.That(ContainsNodeNamed("Folder1", connectionRoot.Children), Is.True); + } + + [Test] + public void Folder1ContainsThreeConnections() + { + var connectionRoot = _connectionTreeModel.RootNodes[0]; + var folder1 = GetFolderNamed("Folder1", connectionRoot.Children); + var folder1ConnectionCount = folder1?.Children.Count(node => !(node is ContainerInfo)); + Assert.That(folder1ConnectionCount, Is.EqualTo(3)); + } + + [Test] + public void Folder2ContainsThreeNodes() + { + var connectionRoot = _connectionTreeModel.RootNodes[0]; + var folder2 = GetFolderNamed("Folder2", connectionRoot.Children); + var folder1Count = folder2?.Children.Count(); + Assert.That(folder1Count, Is.EqualTo(3)); + } + + [Test] + public void Folder21HasTwoNodes() + { + var connectionRoot = _connectionTreeModel.RootNodes[0]; + var folder2 = GetFolderNamed("Folder2", connectionRoot.Children); + var folder21 = GetFolderNamed("Folder2.1", folder2.Children); + Assert.That(folder21.Children.Count, Is.EqualTo(2)); + } + + [Test] + public void Folder211HasOneConnection() + { + var connectionRoot = _connectionTreeModel.RootNodes[0]; + var folder2 = GetFolderNamed("Folder2", connectionRoot.Children); + var folder21 = GetFolderNamed("Folder2.1", folder2.Children); + var folder211 = GetFolderNamed("Folder2.1.1", folder21.Children); + var connectionCount = folder211.Children.Count(node => !(node is ContainerInfo)); + Assert.That(connectionCount, Is.EqualTo(1)); + } + + private bool ContainsNodeNamed(string name, IEnumerable list) + { + return list.Any(node => node.Name == name); + } + + private ContainerInfo GetFolderNamed(string name, IEnumerable list) + { + var folder = list.First(node => (node is ContainerInfo && node.Name == name)) as ContainerInfo; + return folder; + } + } +} \ No newline at end of file diff --git a/mRemoteNGTests/Properties/Resources.Designer.cs b/mRemoteNGTests/Properties/Resources.Designer.cs new file mode 100644 index 000000000..fac011306 --- /dev/null +++ b/mRemoteNGTests/Properties/Resources.Designer.cs @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace mRemoteNGTests.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("mRemoteNGTests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<Connections Name="Connections" Export="False" Protected="95syzRuZ4mRxpNkZQzoyX8SDpQXLyMq3GncO8o4SyTBoYvn3TAWgn05ZEU2DrjkM" ConfVersion="2.5"> + /// <Node Name="Folder1" Type="Container" Expanded="True" Descr="" Icon="mRemoteNG" Panel="General" Username="" Domain="" Password="" Hostname="" Protocol="ICA" PuttySession="Default Settings" Port="1494" ConnectToConsole="False" UseCredSsp="True" RenderingEngine="IE" ICAEncryptionStrength="Encr128Bit" RDPAuthenticationLevel=" [rest of string was truncated]";. + /// + internal static string TestConfCons { + get { + return ResourceManager.GetString("TestConfCons", resourceCulture); + } + } + } +} diff --git a/mRemoteNGTests/Properties/Resources.resx b/mRemoteNGTests/Properties/Resources.resx new file mode 100644 index 000000000..2710c157f --- /dev/null +++ b/mRemoteNGTests/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\TestConfCons.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/mRemoteNGTests/Resources/TestConfCons.xml b/mRemoteNGTests/Resources/TestConfCons.xml new file mode 100644 index 000000000..22d6627c0 --- /dev/null +++ b/mRemoteNGTests/Resources/TestConfCons.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index 8da618eb3..a276ae235 100644 --- a/mRemoteNGTests/mRemoteNGTests.csproj +++ b/mRemoteNGTests/mRemoteNGTests.csproj @@ -102,6 +102,7 @@ + @@ -113,6 +114,11 @@ + + True + True + Resources.resx + @@ -139,10 +145,17 @@ + + ResXFileCodeGenerator + Resources.Designer.cs + TestForm.cs + + + diff --git a/mRemoteV1/Config/Connections/ConnectionsLoader.cs b/mRemoteV1/Config/Connections/ConnectionsLoader.cs index a7987a568..cc2b9add8 100644 --- a/mRemoteV1/Config/Connections/ConnectionsLoader.cs +++ b/mRemoteV1/Config/Connections/ConnectionsLoader.cs @@ -1,6 +1,9 @@ +using System.Linq; using System.Windows.Forms; +using mRemoteNG.App; using mRemoteNG.Connection; using mRemoteNG.Container; +using mRemoteNG.UI; using mRemoteNG.UI.Forms; @@ -50,11 +53,16 @@ namespace mRemoteNG.Config.Connections var xmlConnectionsDeserializer = new XmlConnectionsDeserializer(xmlString) { ConnectionList = ConnectionList, - ContainerList = ContainerList, - RootTreeNode = RootTreeNode, + ContainerList = ContainerList }; - xmlConnectionsDeserializer.Deserialize(import); - } + var connectionTreeModel = xmlConnectionsDeserializer.Deserialize(import); + var connectionTreeViewBuilder = new ConnectionTreeViewBuilder(connectionTreeModel); + connectionTreeViewBuilder.Build(); + var treeNodes = new TreeNode[connectionTreeViewBuilder.TreeView.Nodes.Count]; + connectionTreeViewBuilder.TreeView.Nodes.CopyTo(treeNodes, 0); + Windows.treeForm.tvConnections.Nodes.AddRange(treeNodes); + //Windows.treeForm.InitialRefresh(); + } frmMain.Default.AreWeUsingSqlServerForSavingConnections = UseDatabase; frmMain.Default.ConnectionsFileName = ConnectionFileName; diff --git a/mRemoteV1/Config/Connections/XmlConnectionsDeserializer.cs b/mRemoteV1/Config/Connections/XmlConnectionsDeserializer.cs index 793964b9d..58a572bea 100644 --- a/mRemoteV1/Config/Connections/XmlConnectionsDeserializer.cs +++ b/mRemoteV1/Config/Connections/XmlConnectionsDeserializer.cs @@ -4,7 +4,6 @@ using System.Security; using System.Windows.Forms; using System.Xml; using mRemoteNG.App; -using mRemoteNG.App.Info; using mRemoteNG.Connection; using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol.Http; @@ -13,6 +12,7 @@ using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Connection.Protocol.VNC; using mRemoteNG.Container; using mRemoteNG.Messages; +using mRemoteNG.Security; using mRemoteNG.Security.SymmetricEncryption; using mRemoteNG.Tree; using mRemoteNG.Tree.Root; @@ -26,13 +26,12 @@ namespace mRemoteNG.Config.Connections { private XmlDocument _xmlDocument; private double _confVersion; - private SecureString _pW = GeneralAppInfo.EncryptionKey; - private ContainerInfo _previousContainer; - private ConnectionsDecryptor _decryptor = new ConnectionsDecryptor(); + private readonly SecureString _pW = "mR3m".ConvertToSecureString(); + private readonly ConnectionsDecryptor _decryptor = new ConnectionsDecryptor(); //TODO find way to inject data source info private string ConnectionFileName = ""; - public TreeNode RootTreeNode { get; set; } + //public TreeNode RootTreeNode { get; set; } public ConnectionList ConnectionList { get; set; } public ContainerList ContainerList { get; set; } @@ -40,6 +39,8 @@ namespace mRemoteNG.Config.Connections { LoadXmlConnectionData(xml); ValidateConnectionFileVersion(); + ConnectionList = new ConnectionList(); + ContainerList = new ContainerList(); } private void LoadXmlConnectionData(string connections) @@ -78,7 +79,7 @@ namespace mRemoteNG.Config.Connections throw (new Exception($"Incompatible connection file format (file format version {_confVersion}).")); } - public void Deserialize(bool import) + public ConnectionTreeModel Deserialize(bool import = false) { try { @@ -87,6 +88,8 @@ namespace mRemoteNG.Config.Connections // SECTION 2. Initialize the treeview control. var rootInfo = InitializeRootNode(); + var connectionTreeModel = new ConnectionTreeModel(); + connectionTreeModel.AddRootNode(rootInfo); if (_confVersion > 1.3) { @@ -95,34 +98,36 @@ namespace mRemoteNG.Config.Connections { mRemoteNG.Settings.Default.LoadConsFromCustomLocation = false; mRemoteNG.Settings.Default.CustomConsPath = ""; - RootTreeNode.Remove(); - return; + return null; } } if (import && !IsExportFile()) { Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, Language.strCannotImportNormalSessionFile); - return; + return null; } - if (!IsExportFile()) - { - RootTreeNode.ImageIndex = (int)TreeImageType.Root; - RootTreeNode.SelectedImageIndex = (int)TreeImageType.Root; - } + //if (!IsExportFile()) + //{ + // RootTreeNode.ImageIndex = (int)TreeImageType.Root; + // RootTreeNode.SelectedImageIndex = (int)TreeImageType.Root; + //} // SECTION 3. Populate the TreeView with the DOM nodes. - PopulateTreeview(); - RootTreeNode.EnsureVisible(); - Windows.treeForm.InitialRefresh(); - SetSelectedNode(RootTreeNode); + //PopulateTreeview(); + AddNodesFromXmlRecursive(_xmlDocument.DocumentElement, rootInfo); + //RootTreeNode.EnsureVisible(); + //Windows.treeForm.InitialRefresh(); + //SetSelectedNode(RootTreeNode); //open connections from last mremote session - OpenConnectionsFromLastSession(); + //OpenConnectionsFromLastSession(); if (!import) Runtime.IsConnectionsFileLoaded = true; + + return connectionTreeModel; } catch (Exception ex) { @@ -132,42 +137,34 @@ namespace mRemoteNG.Config.Connections } } - private void PopulateTreeview() - { - Windows.treeForm.tvConnections.BeginUpdate(); - AddNodesFromXmlRecursive(_xmlDocument.DocumentElement, RootTreeNode); - RootTreeNode.Expand(); - ExpandPreviouslyOpenedFolders(); - Windows.treeForm.tvConnections.EndUpdate(); - } - - private void AddNodesFromXmlRecursive(XmlNode parentXmlNode, TreeNode parentTreeNode) + private void AddNodesFromXmlRecursive(XmlNode parentXmlNode, ContainerInfo parentContainer) { try { - // Loop through the XML nodes until the leaf is reached. - // Add the nodes to the TreeView during the looping process. - if (parentXmlNode.HasChildNodes) + if (!parentXmlNode.HasChildNodes) return; + foreach (XmlNode xmlNode in parentXmlNode.ChildNodes) { - foreach (XmlNode xmlNode in parentXmlNode.ChildNodes) + var nodeType = ConnectionTreeNode.GetNodeTypeFromString(xmlNode.Attributes?["Type"].Value); + + if (nodeType == TreeNodeType.Connection) { - var treeNode = new TreeNode(xmlNode.Attributes?["Name"].Value); - parentTreeNode.Nodes.Add(treeNode); - var nodeType = ConnectionTreeNode.GetNodeTypeFromString(xmlNode.Attributes?["Type"].Value); - - if (nodeType == TreeNodeType.Connection) - AddConnectionToList(xmlNode, treeNode); - else if (nodeType == TreeNodeType.Container) - AddContainerToList(xmlNode, treeNode); - - AddNodesFromXmlRecursive(xmlNode, treeNode); + var connectionInfo = GetConnectionInfoFromXml(xmlNode); + parentContainer.Add(connectionInfo); + ConnectionList.Add(connectionInfo); + } + else if (nodeType == TreeNodeType.Container) + { + var containerInfo = new ContainerInfo(); + + if (_confVersion >= 0.9) + containerInfo.CopyFrom(GetConnectionInfoFromXml(xmlNode)); + if (_confVersion >= 0.8) + containerInfo.IsExpanded = xmlNode.Attributes?["Expanded"].Value == "True"; + + parentContainer.Add(containerInfo); + ContainerList.Add(containerInfo); + AddNodesFromXmlRecursive(xmlNode, containerInfo); } - } - else - { - var nameAttribute = parentXmlNode.Attributes?["Name"]; - var nodeName = nameAttribute?.Value.Trim(); - parentTreeNode.Text = !string.IsNullOrEmpty(nodeName) ? nodeName : parentXmlNode.Name; } } catch (Exception ex) @@ -177,39 +174,6 @@ namespace mRemoteNG.Config.Connections } } - private void AddConnectionToList(XmlNode xmlNode, TreeNode treeNode) - { - var connectionInfo = GetConnectionInfoFromXml(xmlNode); - connectionInfo.TreeNode = treeNode; - connectionInfo.Parent = _previousContainer; - ConnectionList.Add(connectionInfo); - treeNode.Tag = connectionInfo; - treeNode.ImageIndex = (int)TreeImageType.ConnectionClosed; - treeNode.SelectedImageIndex = (int)TreeImageType.ConnectionClosed; - } - - private void AddContainerToList(XmlNode xmlNode, TreeNode treeNode) - { - var containerInfo = new ContainerInfo(); - - if (_confVersion >= 0.8) - containerInfo.IsExpanded = xmlNode.Attributes?["Expanded"].Value == "True"; - if (_confVersion >= 0.9) - containerInfo.CopyFrom(GetConnectionInfoFromXml(xmlNode)); - - if (treeNode.Parent?.Tag is ContainerInfo) - containerInfo.Parent = (ContainerInfo) treeNode.Parent.Tag; - - containerInfo.TreeNode = treeNode; - containerInfo.Name = xmlNode.Attributes?["Name"].Value; - - _previousContainer = containerInfo; - ContainerList.Add(containerInfo); - treeNode.Tag = containerInfo; - treeNode.ImageIndex = (int) TreeImageType.Container; - treeNode.SelectedImageIndex = (int) TreeImageType.Container; - } - private ConnectionInfo GetConnectionInfoFromXml(XmlNode xxNode) { var connectionInfo = new ConnectionInfo(); @@ -517,18 +481,12 @@ namespace mRemoteNG.Config.Connections private RootNodeInfo InitializeRootNode() { - var rootNodeName = ""; - if (_xmlDocument.DocumentElement.HasAttribute("Name")) - rootNodeName = Convert.ToString(_xmlDocument.DocumentElement.Attributes["Name"].Value.Trim()); - RootTreeNode.Name = !string.IsNullOrEmpty(rootNodeName) ? rootNodeName : _xmlDocument.DocumentElement.Name; - RootTreeNode.Text = RootTreeNode.Name; + var rootNodeName = _xmlDocument.DocumentElement?.Attributes["Name"].Value.Trim(); var rootInfo = new RootNodeInfo(RootNodeType.Connection) { - Name = RootTreeNode.Name, - TreeNode = RootTreeNode + Name = rootNodeName }; - RootTreeNode.Tag = rootInfo; return rootInfo; } diff --git a/mRemoteV1/UI/ConnectionTreeViewBuilder.cs b/mRemoteV1/UI/ConnectionTreeViewBuilder.cs index 007afe884..0051759dc 100644 --- a/mRemoteV1/UI/ConnectionTreeViewBuilder.cs +++ b/mRemoteV1/UI/ConnectionTreeViewBuilder.cs @@ -33,6 +33,7 @@ namespace mRemoteNG.UI private TreeNode InitRootNode(RootNodeInfo rootNodeInfo) { var rootNode = new TreeNode(rootNodeInfo.Name); + rootNode.Expand(); TreeView.Nodes.Add(rootNode); return rootNode; } diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index 6ec5da0f4..2b59b1fd6 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -125,8 +125,6 @@ namespace mRemoteNG.UI.Forms private void frmMain_Load(object sender, EventArgs e) { - - // Create gui config load and save objects var settingsLoader = new SettingsLoader(this); settingsLoader.LoadSettings(); @@ -141,9 +139,6 @@ namespace mRemoteNG.UI.Forms Runtime.MessageCollector = new MessageCollector(Windows.errorsForm); Runtime.WindowList = new WindowList(); - Windows.treePanel.Focus(); - ConnectionTree.TreeView = Windows.treeForm.tvConnections; - if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation && !File.Exists(Runtime.GetStartupConnectionFileName())) { Runtime.NewConnections(Runtime.GetStartupConnectionFileName()); @@ -155,7 +150,11 @@ namespace mRemoteNG.UI.Forms Application.Exit(); return ; } - Config.Putty.Sessions.StartWatcher(); + + Windows.treePanel.Focus(); + ConnectionTree.TreeView = Windows.treeForm.tvConnections; + + Config.Putty.Sessions.StartWatcher(); if (Settings.Default.StartupComponentsCheck) { Windows.Show(WindowType.ComponentsCheck);