ADTTools/UploadModels/Program.cs (217 lines of code) (raw):
using ADTToolsLibrary;
using Azure;
using Azure.DigitalTwins.Core;
using Azure.Identity;
using CommandLine;
using Ganss.IO;
using DTDLParser;
using DTDLParser.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Core;
namespace UploadModels
{
class Program
{
private readonly Options options;
static async Task Main(string[] args)
{
await Parser.Default.ParseArguments<Options>(args).WithParsedAsync(options => new Program(options).Run());
}
private Program(Options options)
{
this.options = options;
}
private async Task Run()
{
// Expand globs and wildcards for all file specs.
IEnumerable<string> fileNames = GetFileNames(options.FileSpecs);
// Load all the model text.
var modelTexts = new Dictionary<string, string>();
foreach (string fileName in fileNames)
{
modelTexts.Add(fileName, File.ReadAllText(fileName));
Log.Write($"Loaded: {fileName}");
}
Log.Write(string.Empty);
// Check that all model text is valid JSON (for better error reporting).
if (!ParseModelJson(modelTexts))
{
return;
}
// Parse models.
IReadOnlyDictionary<Dtmi, DTEntityInfo> entities = await ParseModelsAsync(modelTexts);
if (entities == null)
{
return;
}
// Get interfaces.
IEnumerable<DTInterfaceInfo> interfaces = from entity in entities.Values
where entity.EntityKind == DTEntityKind.Interface
select (DTInterfaceInfo)entity;
Log.Ok($"Parsed {interfaces.Count()} models successfully.");
Log.Write(string.Empty);
// Order interfaces for upload.
IEnumerable<DTInterfaceInfo> orderedInterfaces = OrderInterfaces(interfaces);
if (options.WhatIf)
{
DisplayOrderedInterfaces(orderedInterfaces);
}
else
{
await UploadOrderedInterfaces(orderedInterfaces);
}
}
private async Task UploadOrderedInterfaces(IEnumerable<DTInterfaceInfo> orderedInterfaces)
{
Log.Write("Uploaded interfaces:");
try
{
TokenCredential credential;
if (options.UseDefaultAzureCredentials)
{
credential = new DefaultAzureCredential();
}
else
{
if (string.IsNullOrEmpty(options.ClientSecret))
{
credential = new InteractiveBrowserCredential(options.TenantId, options.ClientId);
}
else
{
credential = new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret);
}
}
var client = new DigitalTwinsClient(new UriBuilder("https", options.HostName).Uri, credential);
for (int i = 0; i < (orderedInterfaces.Count() / options.BatchSize) + 1; i++)
{
IEnumerable<DTInterfaceInfo> batch = orderedInterfaces.Skip(i * options.BatchSize).Take(options.BatchSize);
Response<DigitalTwinsModelData[]> response = await client.CreateModelsAsync(batch.Select(i => i.GetJsonLdText()));
foreach (DTInterfaceInfo @interface in batch)
{
Log.Ok(@interface.Id.AbsoluteUri);
}
}
}
catch (Exception ex)
{
Log.Error($"Upload failed.");
Log.Error(ex.Message);
}
}
private static void DisplayOrderedInterfaces(IEnumerable<DTInterfaceInfo> orderedInterfaces)
{
Log.Write("Ordered interfaces:");
foreach (DTInterfaceInfo orderedInterface in orderedInterfaces)
{
Log.Write(orderedInterface.Id.AbsoluteUri);
}
}
private static bool ParseModelJson(Dictionary<string, string> modelTexts)
{
var jsonErrors = new Dictionary<string, JsonException>();
foreach (string fileName in modelTexts.Keys)
{
try
{
JsonDocument jsonDoc = JsonDocument.Parse(modelTexts[fileName]);
}
catch (JsonException ex)
{
jsonErrors.Add(fileName, ex);
}
}
if (jsonErrors.Count > 0)
{
Log.Error("Errors parsing models.");
foreach (string fileName in jsonErrors.Keys)
{
Log.Error($"{fileName}: {jsonErrors[fileName].Message}");
}
}
return jsonErrors.Count == 0;
}
private async Task<IReadOnlyDictionary<Dtmi, DTEntityInfo>> ParseModelsAsync(Dictionary<string, string> modelTexts)
{
IReadOnlyDictionary<Dtmi, DTEntityInfo> entities = null;
try
{
var parser = new ModelParser(new ParsingOptions() { AllowUndefinedExtensions = options.AllowUndefinedExtensions });
entities = await parser.ParseAsync(modelTexts.Values.ToAsyncEnumerable());
}
catch (ParsingException ex)
{
Log.Error("Errors parsing models.");
foreach (ParsingError error in ex.Errors)
{
Log.Error(error.Message);
}
}
catch (Exception ex)
{
Log.Error("Errors parsing models.");
Log.Error(ex.Message);
}
return entities;
}
private static IEnumerable<DTInterfaceInfo> OrderInterfaces(IEnumerable<DTInterfaceInfo> interfaces)
{
// This function sorts interfaces from interfaces with no dependencies to interfaces with dependencies.
// Using a depth-first search and post-order processing, we get an ordering from no dependencies to most dependencies.
// Build the set of all referenced interfaces.
HashSet<DTInterfaceInfo> referencedInterfaces = new HashSet<DTInterfaceInfo>(new DTInterfaceInfoEqualityComparer());
foreach (DTInterfaceInfo @interface in interfaces)
{
foreach (DTInterfaceInfo referencedInterface in @interface.Extends)
{
referencedInterfaces.Add(referencedInterface);
}
IEnumerable<DTInterfaceInfo> componentSchemas = from content in @interface.Contents.Values
where content.EntityKind == DTEntityKind.Component
select ((DTComponentInfo)content).Schema;
foreach (DTInterfaceInfo referencedInterface in componentSchemas)
{
referencedInterfaces.Add(referencedInterface);
}
}
// The roots of the trees are all the interfaces that are not referenced.
IEnumerable<DTInterfaceInfo> rootInterfaces = interfaces.Except(referencedInterfaces, new DTInterfaceInfoEqualityComparer());
// For each root, perform depth-first, post-order processing to produce a sorted tree.
OrderedHashSet<DTInterfaceInfo> orderedInterfaces = new OrderedHashSet<DTInterfaceInfo>(new DTInterfaceInfoEqualityComparer());
foreach (DTInterfaceInfo rootInterface in rootInterfaces)
{
OrderedHashSet<DTInterfaceInfo> rootInterfaceOrderedInterfaces = new OrderedHashSet<DTInterfaceInfo>(new DTInterfaceInfoEqualityComparer());
OrderInterface(rootInterfaceOrderedInterfaces, rootInterface);
foreach (DTInterfaceInfo rootInterfaceOrderedInterface in rootInterfaceOrderedInterfaces)
{
if (!orderedInterfaces.Contains(rootInterfaceOrderedInterface))
{
orderedInterfaces.Add(rootInterfaceOrderedInterface);
}
}
}
return orderedInterfaces;
}
private static void OrderInterface(OrderedHashSet<DTInterfaceInfo> orderedInterfaces, DTInterfaceInfo @interface)
{
// Order each extended interface.
foreach (DTInterfaceInfo extendedInterface in @interface.Extends)
{
OrderInterface(orderedInterfaces, extendedInterface);
}
// Order each component schema interface.
IEnumerable<DTInterfaceInfo> componentSchemas = from content in @interface.Contents.Values
where content.EntityKind == DTEntityKind.Component
select ((DTComponentInfo)content).Schema;
foreach (DTInterfaceInfo componentSchemaInterface in componentSchemas)
{
OrderInterface(orderedInterfaces, componentSchemaInterface);
}
// Add this interface to the list of ordered interfaces.
if (!orderedInterfaces.Contains(@interface))
{
orderedInterfaces.Add(@interface);
}
}
private static IEnumerable<string> GetFileNames(IEnumerable<string> fileSpecs)
{
IEnumerable<string> fileNames = Enumerable.Empty<string>();
foreach (string fileSpec in fileSpecs)
{
fileNames = fileNames.Concat(Glob.Expand(fileSpec).Select(fsi => fsi.FullName));
}
return fileNames;
}
}
}