From f0cd05c3fdb4723c12d848f0691f0e3148fdcbef Mon Sep 17 00:00:00 2001 From: jesusalvino <96534278+jesusalvino@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:45:26 -0500 Subject: [PATCH] DYN-6311 checksum generation (#14542) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add the Checksum property * Naming and Test file * Checksum function of a graph and storage in the preferences for later comparisons * Checksum generation - Rrefactoring * Assignation of the checksum properties refactored --------- Co-authored-by: Jesus Alfredo Alviño Co-authored-by: Aaron (Qilong) <173288704@qq.com> --- .../Configuration/GraphChecksumItem.cs | 17 +++ src/DynamoCore/Configuration/IPreferences.cs | 6 + .../Configuration/PreferenceSettings.cs | 6 + .../Graph/Workspaces/WorkspaceModel.cs | 2 +- .../ViewModels/Core/DynamoViewModel.cs | 43 ++++++- .../ViewModels/Core/WorkspaceViewModel.cs | 113 +++++++++++++++++- .../Configuration/PreferenceSettingsTests.cs | 12 ++ test/settings/DynamoSettings-NewSettings.xml | 6 + 8 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 src/DynamoCore/Configuration/GraphChecksumItem.cs diff --git a/src/DynamoCore/Configuration/GraphChecksumItem.cs b/src/DynamoCore/Configuration/GraphChecksumItem.cs new file mode 100644 index 00000000000..a00b4538800 --- /dev/null +++ b/src/DynamoCore/Configuration/GraphChecksumItem.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.ObjectModel; +using Dynamo.Core; +using Dynamo.Properties; + +namespace Dynamo.Configuration +{ + /// + /// Represents the stringified version of the nodes connections from a graph + /// + public class GraphChecksumItem + { + public string GraphId { get; set; } + + public string Checksum { get; set; } + } +} diff --git a/src/DynamoCore/Configuration/IPreferences.cs b/src/DynamoCore/Configuration/IPreferences.cs index e3d79c34c9a..7798668f18b 100644 --- a/src/DynamoCore/Configuration/IPreferences.cs +++ b/src/DynamoCore/Configuration/IPreferences.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Dynamo.Configuration; using Dynamo.Graph.Connectors; namespace Dynamo.Interfaces @@ -146,6 +147,11 @@ public interface IPreferences /// Background preview name /// Active state to set void SetIsBackgroundPreviewActive(string name, bool value); + + /// + /// Return a list of GraphChecksumItems + /// + List GraphChecksumItemsList { get; set; } } /// diff --git a/src/DynamoCore/Configuration/PreferenceSettings.cs b/src/DynamoCore/Configuration/PreferenceSettings.cs index cf035afe617..17bb1b7078e 100644 --- a/src/DynamoCore/Configuration/PreferenceSettings.cs +++ b/src/DynamoCore/Configuration/PreferenceSettings.cs @@ -458,6 +458,11 @@ public bool DisableTrustWarnings /// private List trustedLocations { get; set; } = new List(); + /// + /// Return a list of GraphChecksumItems + /// + public List GraphChecksumItemsList { get; set; } + // This function is used to deserialize the trusted locations manually // so that the TrustedLocation propertie's setter does not need to be public. private List DeserializeTrustedLocations(XmlNode preferenceSettingsElement) @@ -915,6 +920,7 @@ public PreferenceSettings() EnableDynamoPlayerRenamedWatchAsOutput = false; DynamoPlayerFolderGroups = new List(); backupLocation = string.Empty; + GraphChecksumItemsList = new List(); } /// diff --git a/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs b/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs index b31a1025553..99ffc2c06c2 100644 --- a/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs +++ b/src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs @@ -2013,7 +2013,7 @@ internal bool IsValidForFDX { get { - return !HasErrors && !HasNoneGeometryRelatedWarnings; + return Nodes.Count() > 1 && !HasErrors && !HasNoneGeometryRelatedWarnings; } } diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index 8ccc4f74f39..5985e801a57 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -2090,15 +2090,52 @@ internal bool CanSaveAs(object parameters) return (parameters != null); } + /// + /// Indicates if the graph has been changed substantially bearing in mind the connections of its nodes and store the checksum value of the graph in the preferences to later comparison + /// + /// + private bool HasSubstantialCheckSum() + { + bool substantialChecksum = false; + string graphId = Model.CurrentWorkspace.Guid.ToString(); + + GraphChecksumItem checksumItem = PreferenceSettings.GraphChecksumItemsList.Where(i => i.GraphId == graphId).FirstOrDefault(); + if (checksumItem != null) + { + if (checksumItem.Checksum != currentWorkspaceViewModel.Checksum) + { + PreferenceSettings.GraphChecksumItemsList.Remove(checksumItem); + PreferenceSettings.GraphChecksumItemsList.Add(new GraphChecksumItem() { GraphId = graphId, Checksum = currentWorkspaceViewModel.Checksum }); + substantialChecksum = true; + } + } + else + { + PreferenceSettings.GraphChecksumItemsList.Add(new GraphChecksumItem() { GraphId = graphId, Checksum = currentWorkspaceViewModel.Checksum }); + substantialChecksum = true; + } + return substantialChecksum; + } + private void InternalSaveAs(string path, SaveContext saveContext, bool isBackup = false) { try { Model.Logger.Log(String.Format(Properties.Resources.SavingInProgress, path)); CurrentSpaceViewModel.Save(path, isBackup, Model.EngineController, saveContext); - if (!isBackup) AddToRecentFiles(path); - if (currentWorkspaceViewModel?.IsHomeSpace ?? true) - Model.Logger.Log("The Workspace is valid for FDX : " + (HomeSpace.HasRunWithoutCrash && Model.CurrentWorkspace.IsValidForFDX).ToString()); + + if (!isBackup) + { + AddToRecentFiles(path); + + if ((currentWorkspaceViewModel?.IsHomeSpace ?? true) && HomeSpace.HasRunWithoutCrash && Model.CurrentWorkspace.IsValidForFDX && currentWorkspaceViewModel.Checksum != string.Empty) + { + Model.Logger.Log("The Workspace is valid for FDX"); + Model.Logger.Log("The Workspace id is : " + currentWorkspaceViewModel.Model.Guid.ToString()); + Model.Logger.Log("The Workspace checksum is : " + currentWorkspaceViewModel.Checksum); + Model.Logger.Log("The Workspace has Substantial checksum, so is ready to send to FDX : " + HasSubstantialCheckSum().ToString()); + } + } } catch (Exception ex) { diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs index 0850e474490..aa64e8920fe 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs @@ -357,6 +357,106 @@ public bool IsHomeSpace get { return Model == DynamoViewModel.HomeSpace; } } + /// + /// Returns the Json representation of the current graph + /// + [JsonIgnore] + internal JObject JsonRepresentation { get; set; } + + /// + /// Returns the stringified representation of the connected nodes + /// + [JsonIgnore] + public string Checksum + { + get + { + List nodeInfoConnections = new List(); + JObject jsonWorkspace = JsonRepresentation; + var nodes = jsonWorkspace["Nodes"]; + + List nodeIds = new List(); + foreach (JObject node in nodes) + { + var nodeProperties = node.Children(); + JProperty id = nodeProperties.FirstOrDefault(x => x.Name == "Id"); + nodeIds.Add(id.Value.ToString()); + } + + nodeIds.Sort(); + + foreach (string nodeId in nodeIds) + { + List outputIds = new List(); + var node = jsonWorkspace["Nodes"].Where(t => t.Value("Id") == nodeId).Select(t => t).FirstOrDefault(); + var outputsProperty = node.Children().FirstOrDefault(x => x.Name == "Outputs"); + var outputs = (JArray)outputsProperty.Value; + int outputIndex = 1; + + foreach (JObject output in outputs) + { + var outputProperties = output.Children(); + JProperty outputId = outputProperties.FirstOrDefault(x => x.Name == "Id"); + outputIds.Add(outputId.Value.ToString()); + + var connectorsProperty = jsonWorkspace["Connectors"].Where(t => t.Value("Start") == outputId.Value.ToString()); + + foreach (var connector in connectorsProperty) + { + var connectorProperties = connector.Children(); + JProperty endProperty = connectorProperties.FirstOrDefault(x => x.Name == "End"); + string inputId = (String)endProperty.Value; + + var outputConnectedNode = GetNodeByInputId(inputId, jsonWorkspace); + nodeInfoConnections.Add(nodeId + "|[" + outputIndex.ToString() + "|" + outputConnectedNode.Item1 + "|" + outputConnectedNode.Item2.ToString() + "]"); + } + outputIndex++; + } + } + return nodeInfoConnections.Count > 0 ? string.Join(",", nodeInfoConnections) : string.Empty; + } + } + + Tuple GetNodeByInputId(string inputId, JObject jsonWorkspace) + { + var nodes = jsonWorkspace["Nodes"]; + + string nodeId = string.Empty; + int connectedInputIndex = 1; + bool foundNode = false; + + foreach (var node in nodes) + { + if (!foundNode) + { + nodeId = string.Empty; + connectedInputIndex = 1; + + var nodeProperties = node.Children(); + JProperty nodeProperty = nodeProperties.FirstOrDefault(x => x.Name == "Id"); + nodeId = (String)nodeProperty.Value; + + JProperty nodeInputs = nodeProperties.FirstOrDefault(x => x.Name == "Inputs"); + var inputs = (JArray)nodeInputs.Value; + + foreach (JObject input in inputs) + { + var inputProperties = input.Children(); + JProperty connectedNodeInputId = inputProperties.FirstOrDefault(x => x.Name == "Id"); + + if ((String)connectedNodeInputId.Value == inputId) + { + foundNode = true; + break; + } + connectedInputIndex++; + } + } + } + + return new Tuple(nodeId, connectedInputIndex); + } + [JsonIgnore] public bool HasUnsavedChanges { @@ -652,19 +752,26 @@ internal void Save(string filePath, bool isBackup = false, EngineController engi } } } - - saveContent = GuidUtility.UpdateWorkspaceGUIDs(jo.ToString()); + saveContent = GuidUtility.UpdateWorkspaceGUIDs(jo.ToString()); } else { saveContent = jo.ToString(); - } + } + File.WriteAllText(filePath, saveContent); // Handle Workspace or CustomNodeWorkspace related non-serialization internal logic // Only for actual save, update file path and recent file list + // The assignation of the JsonRepresentation and Guid is only for the checksum flow, it will grab info only from .dyn files if (!isBackup) { + if (Path.GetExtension(filePath).Equals(".dyn")) + { + JsonRepresentation = JObject.Parse(saveContent); + DynamoViewModel.Workspaces[0].Model.Guid = new Guid(JsonRepresentation.Properties().First(p => p.Name == "Uuid").Value.ToString()); + } + Model.FileName = filePath; Model.OnSaved(); } diff --git a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs index c710f6e602e..c35151802dd 100644 --- a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs +++ b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs @@ -358,6 +358,18 @@ PreferencesComparison comparePrefenceSettings(PreferenceSettings defaultSettings propertiesWithDifferentValue.Add(destinationPi.Name); } } + else if (destinationPi.PropertyType == typeof(List)) + { + if (((List)sourcePi.GetValue(newGeneralSettings, null)).Count == + ((List)destinationPi.GetValue(defaultSettings, null)).Count) + { + propertiesWithSameValue.Add(destinationPi.Name); + } + else + { + propertiesWithDifferentValue.Add(destinationPi.Name); + } + } else { if (newValue?.ToString() == oldValue?.ToString()) diff --git a/test/settings/DynamoSettings-NewSettings.xml b/test/settings/DynamoSettings-NewSettings.xml index 686957ac2d4..1699decee44 100644 --- a/test/settings/DynamoSettings-NewSettings.xml +++ b/test/settings/DynamoSettings-NewSettings.xml @@ -46,6 +46,12 @@ C:\ProgramData\Dynamo\Dynamo Core\2.16\packages true + + + a6f6ab04-752c-4b17-8360-2e563bea7c23 + 588c8174ea314ee3945243a19457796f|[1|f9ec27ad6b584c0babf7055a787df6ad|1] + + C:\ E:\