Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AGD-1799] Support persisting extension and view extension data #11347

2 changes: 2 additions & 0 deletions src/DynamoCore/DynamoCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ limitations under the License.
<Compile Include="Engine\ShortestQualifiedNameReplacer.cs" />
<Compile Include="Exceptions\CustomNodePackageLoadException.cs" />
<Compile Include="Exceptions\LibraryLoadFailedException.cs" />
<Compile Include="Extensions\ExtensionData.cs" />
<Compile Include="Extensions\IExtensionStorageAccess.cs" />
<Compile Include="Graph\Workspaces\PackageDependencyInfo.cs" />
<Compile Include="Graph\Nodes\NodeOutputData.cs" />
<Compile Include="Graph\Nodes\NodeInputData.cs" />
Expand Down
44 changes: 44 additions & 0 deletions src/DynamoCore/Extensions/ExtensionData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;

namespace Dynamo.Extensions
{
public class ExtensionData
{
/// <summary>
/// Extensions unique id.
/// </summary>
public string ExtensionGuid { get; private set; }
/// <summary>
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved
/// Name of extension.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Extension version.
/// </summary>
public string Version { get; private set; }
/// <summary>
/// Extension specific data.
/// </summary>
public Dictionary<string,string> Data { get; set; }

public ExtensionData(string extensionGuid, string name, string version, Dictionary<string, string> data)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException($"'{nameof(name)}' cannot be null or empty", nameof(name));
}

if (string.IsNullOrEmpty(version))
{
throw new ArgumentException($"'{nameof(version)}' cannot be null or empty", nameof(version));
}

ExtensionGuid = extensionGuid ?? throw new ArgumentNullException(nameof(extensionGuid));
Data = data ?? throw new ArgumentNullException(nameof(data));

Name = name;
Version = version;
}
}
}
22 changes: 22 additions & 0 deletions src/DynamoCore/Extensions/ExtensionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public interface IExtensionSource
public class ExtensionManager: IExtensionManager, ILogSource
{
private readonly List<IExtension> extensions = new List<IExtension>();
private readonly List<IExtensionStorageAccess> storageAccessExtensions = new List<IExtensionStorageAccess>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use a hashset or dictionary of guid to extensions?

private readonly ExtensionLoader extensionLoader = new ExtensionLoader();
private readonly Dictionary<Type, KeyValuePair<string, object>> services = new Dictionary<Type, KeyValuePair<string, object>>();

Expand Down Expand Up @@ -109,6 +110,12 @@ public void Add(IExtension extension)
{
ExtensionAdded(extension);
}

if (extension is IExtensionStorageAccess storageAccess &&
storageAccessExtensions.Find(x => x.UniqueId == extension.UniqueId) is null)
{
storageAccessExtensions.Add(storageAccess);
}
}
else
{
Expand Down Expand Up @@ -139,6 +146,12 @@ public void Remove(IExtension extension)
Log(fullName + " extension cannot be disposed properly: " + ex.Message);
}

if (extension is IExtensionStorageAccess storageAccess &&
storageAccessExtensions.Contains(storageAccess))
{
storageAccessExtensions.Remove(storageAccess);
}

Log(fullName + " extension is removed");
if (ExtensionRemoved != null)
{
Expand All @@ -154,6 +167,14 @@ public IEnumerable<IExtension> Extensions
get { return extensions; }
}

/// <summary>
/// Returns the collection of registered extensions implementing IExtensionStorageAccess
/// </summary>
public IEnumerable<IExtensionStorageAccess> StorageAccessesExtensions
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved
{
get { return storageAccessExtensions; }
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// This event is fired when a new extension is added.
/// </summary>
Expand Down Expand Up @@ -182,6 +203,7 @@ public void Dispose()
Remove(extensions[0]);
}

storageAccessExtensions.Clear();
extensionLoader.MessageLogged -= Log;
}

Expand Down
43 changes: 43 additions & 0 deletions src/DynamoCore/Extensions/IExtensionStorageAccess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dynamo.Graph;

namespace Dynamo.Extensions
{
public interface IExtensionStorageAccess
{
/// <summary>
/// A unique id for this extension instance.
///
/// The id will be equivalent to the extension that implements this interface id.
/// </summary>
string UniqueId { get; }

/// <summary>
/// A name for the Extension.
///
/// The name will be equivalent to the extension that implements this interface name.
/// </summary>
string Name { get; }

/// <summary>
/// Action to be invoked when the workspace is opened. The passed extensionData dictionary is a copy of the <see cref="ExtensionData"/> data dictionary.
/// Modifying the extensionData from this action will not modify the stored <see cref="ExtensionData"/>.
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved
/// To modify the stored <see cref="ExtensionData"/> data dictionary use the <see cref="OnWorkspaceSaving(Dictionary{string, string}, SaveContext)"/>.
/// </summary>
/// <param name="extensionData">A copy of the ExtensionData dictionary</param>
void OnWorkspaceOpen(Dictionary<string, string> extensionData);

/// <summary>
/// Action to be invoked when the workspace has begun its saving process.
/// The passed extensionData dictionary is a direct reference to the stored <see cref="ExtensionData"/> data dictionary, modifications to this dictionary
/// will be reflected in the stored <see cref="ExtensionData"/> data dictionary
/// </summary>
/// <param name="extensionData"></param>
/// <param name="saveContext"></param>
void OnWorkspaceSaving(Dictionary<string, string> extensionData, SaveContext saveContext);
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved
}
}
2 changes: 1 addition & 1 deletion src/DynamoCore/Graph/ModelBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Dynamo.Graph
/// <summary>
/// SaveContext represents several contexts, in which node can be serialized/deserialized.
/// </summary>
public enum SaveContext { File, Copy, Undo, Preset, None };
public enum SaveContext { [Obsolete("Use Save or SaveAs, instead of File")] File, Copy, Undo, Preset, None, Save, SaveAs };
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// This class encapsulates the input parameters that need to be passed into nodes
Expand Down
8 changes: 4 additions & 4 deletions src/DynamoCore/Graph/NodeGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private static IEnumerable<NodeModel> LoadNodesFromXml(XmlDocument xmlDoc, NodeF
elNodes = xmlDoc.GetElementsByTagName("dynElements");
XmlNode elNodesList = elNodes[0];
return from XmlElement elNode in elNodesList.ChildNodes
select LoadNodeFromXml(elNode, SaveContext.File, nodeFactory, resolver);
select LoadNodeFromXml(elNode, SaveContext.Save, nodeFactory, resolver);
}

/// <summary>
Expand Down Expand Up @@ -127,7 +127,7 @@ private static IEnumerable<ConnectorModel> LoadConnectorsFromXml(XmlDocument xml
public static NoteModel LoadNoteFromXml(XmlNode note)
{
var instance = new NoteModel(0, 0, string.Empty, Guid.NewGuid());
instance.Deserialize(note as XmlElement, SaveContext.File);
instance.Deserialize(note as XmlElement, SaveContext.Save);
return instance;
}

Expand All @@ -146,7 +146,7 @@ private static IEnumerable<NoteModel> LoadNotesFromXml(XmlDocument xmlDoc)
internal static AnnotationModel LoadAnnotationFromXml(XmlNode annotation, IEnumerable<NodeModel> nodes, IEnumerable<NoteModel> notes)
{
var instance = new AnnotationModel(nodes,notes);
instance.Deserialize(annotation as XmlElement, SaveContext.File);
instance.Deserialize(annotation as XmlElement, SaveContext.Save);
return instance;
}

Expand Down Expand Up @@ -184,7 +184,7 @@ public static IEnumerable<PresetModel> LoadPresetsFromXml(XmlDocument xmlDoc, IE
private static PresetModel PresetFromXml(XmlElement stateNode, IEnumerable<NodeModel> nodesInNodeGraph)
{
var instance = new PresetModel(nodesInNodeGraph);
instance.Deserialize(stateNode, SaveContext.File);
instance.Deserialize(stateNode, SaveContext.Save);
return instance;
}

Expand Down
7 changes: 5 additions & 2 deletions src/DynamoCore/Graph/Nodes/DummyNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,9 @@ private void SaveNode(XmlDocument xmlDoc, XmlElement nodeElement, SaveContext co
}
}

if (context == SaveContext.File)
if (context == SaveContext.File ||
context == SaveContext.Save ||
context == SaveContext.SaveAs)
{
//When save files, only save the original node's content,
//instead of saving the dummy node.
Expand Down Expand Up @@ -399,7 +401,8 @@ protected override XmlElement CreateElement(XmlDocument xmlDocument, SaveContext
{
XmlElement originalElement = OriginalXmlNodeContent;

if (context == SaveContext.File && originalElement != null)
if ((context == SaveContext.File || context == SaveContext.Save || context == SaveContext.SaveAs) &&
originalElement != null)
{
XmlElement originalNode = xmlDocument.CreateElement(originalElement.Name);
return originalNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public NodeModel CreateNodeFromXml(XmlElement nodeElement, SaveContext context,
}
}

if (context == SaveContext.File && !string.IsNullOrEmpty(assembly))
if ((context == SaveContext.File || context == SaveContext.Save || context == SaveContext.SaveAs) &&
!string.IsNullOrEmpty(assembly))
{
var document = nodeElement.OwnerDocument;
var docPath = Nodes.Utilities.GetDocumentXmlPath(document);
Expand Down
4 changes: 3 additions & 1 deletion src/DynamoCore/Graph/Nodes/NodeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2201,7 +2201,9 @@ protected override void SerializeCore(XmlElement element, SaveContext context)
helper.SetAttribute("nodeState", state.ToString());
}

if (context == SaveContext.File)
if (context == SaveContext.File ||
context == SaveContext.Save ||
context == SaveContext.SaveAs)
OnSave();
}

Expand Down
4 changes: 3 additions & 1 deletion src/DynamoCore/Graph/Nodes/ZeroTouch/DSFunctionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,9 @@ public override void SerializeCore(XmlElement element, SaveContext context)
base.SerializeCore(element, context);
var asmPath = Definition.Assembly ?? "";

if (context == SaveContext.File)
if (context == SaveContext.File ||
context == SaveContext.Save ||
context == SaveContext.SaveAs)
{
// We only make relative paths in a file saving operation.
var docPath = Utilities.GetDocumentXmlPath(element.OwnerDocument);
Expand Down
29 changes: 24 additions & 5 deletions src/DynamoCore/Graph/Workspaces/SerializationConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Dynamo.Configuration;
using Dynamo.Core;
using Dynamo.Engine;
using Dynamo.Extensions;
using Dynamo.Graph.Annotations;
using Dynamo.Graph.Connectors;
using Dynamo.Graph.Nodes;
Expand Down Expand Up @@ -531,6 +532,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
}
}
}


#region Setting Inputs based on view layer info
// TODO: It is currently duplicating the effort with Input Block parsing which should be cleaned up once
Expand Down Expand Up @@ -592,7 +594,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
var info = new WorkspaceInfo(guid.ToString(), name, description, Dynamo.Models.RunType.Automatic);

// IsVisibleInDynamoLibrary and Category should be set explicitly for custom node workspace
if (obj["View"] != null && obj["View"]["Dynamo"] !=null && obj["View"]["Dynamo"]["IsVisibleInDynamoLibrary"] != null)
if (obj["View"] != null && obj["View"]["Dynamo"] != null && obj["View"]["Dynamo"]["IsVisibleInDynamoLibrary"] != null)
{
info.IsVisibleInDynamoLibrary = obj["View"]["Dynamo"]["IsVisibleInDynamoLibrary"].Value<bool>();
}
Expand Down Expand Up @@ -640,22 +642,34 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
WorkspaceModel ws;
if (isCustomNode)
{
ws = new CustomNodeWorkspaceModel(factory, nodes, notes, annotations,
ws = new CustomNodeWorkspaceModel(factory, nodes, notes, annotations,
Enumerable.Empty<PresetModel>(), elementResolver, info);
}
else
{
ws = new HomeWorkspaceModel(guid, engine, scheduler, factory,
loadedTraceData, nodes, notes, annotations,
Enumerable.Empty<PresetModel>(), elementResolver,
loadedTraceData, nodes, notes, annotations,
Enumerable.Empty<PresetModel>(), elementResolver,
info, verboseLogging, isTestMode);
}
};
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved

ws.NodeLibraryDependencies = nodeLibraryDependencies.ToList();
ws.ExtensionData = GetExtensionData(serializer, obj);

return ws;
}

private static List<ExtensionData> GetExtensionData(JsonSerializer serializer, JObject obj)
{
if (!obj.TryGetValue("ExtensionWorkspaceData", StringComparison.OrdinalIgnoreCase, out JToken extensionData))
SHKnudsen marked this conversation as resolved.
Show resolved Hide resolved
return new List<ExtensionData>();
if (!(extensionData is JArray array))
return new List<ExtensionData>();

return array.ToObject<List<ExtensionData>>(serializer);
}


public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
Expand All @@ -668,6 +682,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
public class WorkspaceWriteConverter : JsonConverter
{
private EngineController engine;
private const string EXTENSION_WORKSPACE_DATA = "ExtensionWorkspaceData";

public WorkspaceWriteConverter(EngineController engine = null)
{
Expand Down Expand Up @@ -759,6 +774,10 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
writer.WritePropertyName(WorkspaceReadConverter.NodeLibraryDependenciesPropString);
serializer.Serialize(writer, ws.NodeLibraryDependencies);

// ExtensionData
writer.WritePropertyName(EXTENSION_WORKSPACE_DATA);
serializer.Serialize(writer, ws.ExtensionData);

if (engine != null)
{
// Bindings
Expand Down
Loading